Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cac874601f | |||
| b223a69884 |
@@ -142,6 +142,37 @@ SDD 屬於架構決策,必須人確認。CC 不可以自行在 `docs/3-specs/`
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第六類:租戶字串來源(Arcrun#108/#105 同族)
|
||||||
|
|
||||||
|
### 6.1 靜態租戶字串不得用於資料面過濾
|
||||||
|
**知識資料面的 `owner_id`(三元組/entries/records/藏書地圖/工作流 KV)必須與寫入端同源。**
|
||||||
|
寫入端只有一個真相源=使用者 `~/.arcrun/config.yaml` 的 `api_key`(=實例 namespace,
|
||||||
|
CLI push/小幫手上傳/MCP 都用它)。讀取端拿另一份手抄的環境變數預設值 → 全被過濾掉。
|
||||||
|
|
||||||
|
實害:`portalTenant(env) = env.CONSOLE_TENANT || "leo"` 讓 leo 的 **1854 條三元組被過濾成 0 個庫**
|
||||||
|
(#108);前一天 `ownerNamespace(env) = env.MCP_OWNER_NAMESPACE || "leo"` 是同一句話(#105)。
|
||||||
|
|
||||||
|
**規則**:
|
||||||
|
1. `cypher-executor/src/lib/tenant.ts` 是租戶字串的**唯一產地**。
|
||||||
|
`CONSOLE_TENANT` / `ARCRUN_NAMESPACE` 只能在該檔被讀取。
|
||||||
|
2. 知識資料面用 `knowledgeOwner(env)`(回 `TenantId`),過濾一律經
|
||||||
|
`ownerQuery()` / `ownerField()`——它們只吃 `TenantId`,`tsc` 就擋掉「隨手一個 string」。
|
||||||
|
3. 帳號層用 `accountTenant(env)`(回 `string`,**刻意不是 TenantId**):帳號子 namespace
|
||||||
|
`{tenant}::portal` 與 cypher 自己寫的設定用它,型別上不可能流進知識資料面。
|
||||||
|
4. 身分解析路徑上**不准有字面預設值**。解析不到 → 丟 `TenantUnresolvedError`,
|
||||||
|
誠實回「讀不到」(不是「你沒有」,#100 同一條)。
|
||||||
|
|
||||||
|
**機械強制**(規則存在但沒機制驗證=它會再犯第三次):
|
||||||
|
- 出貨閘:`scripts/build-worker-artifacts.mjs` 編 tier2 成品前先掃,違規 → **編不出成品**。
|
||||||
|
- 本機自查:`cd cypher-executor && npm run check:tenant`(`npm test` 也會先跑它)。
|
||||||
|
- 規則本體:`cypher-executor/scripts/tenant-source-rules.mjs`(純函式);
|
||||||
|
閘自己的測試:`cypher-executor/tests/tenant-gate.test.ts`(壞例子會擋+合法寫法零誤攔)。
|
||||||
|
|
||||||
|
> 尚未接上 PreToolUse hook(`.claude/hooks/` 為受保護檔案,需人類加入)。
|
||||||
|
> 要加的話:檢查器已備妥 `--stdin <相對路徑>` 模式,可在寫入前擋。
|
||||||
|
|
||||||
## Hook Block 訊息格式
|
## Hook Block 訊息格式
|
||||||
|
|
||||||
當 hook 擋住一個操作時,訊息格式統一為:
|
當 hook 擋住一個操作時,訊息格式統一為:
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
var __defProp = Object.defineProperty;
|
var __defProp = Object.defineProperty;
|
||||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||||
var __esm = (fn, res, err2) => function __init() {
|
var __esm = (fn, res) => function __init() {
|
||||||
if (err2) throw err2[0];
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
||||||
try {
|
|
||||||
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
||||||
} catch (e) {
|
|
||||||
throw err2 = [e], e;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
var __export = (target, all) => {
|
var __export = (target, all) => {
|
||||||
for (var name in all)
|
for (var name in all)
|
||||||
@@ -107,7 +102,7 @@ function applyBaseRuntimeOptions(runtime, options) {
|
|||||||
function applyModuleEvalRuntimeOptions(runtime, options) {
|
function applyModuleEvalRuntimeOptions(runtime, options) {
|
||||||
options.moduleLoader && runtime.setModuleLoader(options.moduleLoader), options.shouldInterrupt && runtime.setInterruptHandler(options.shouldInterrupt), options.memoryLimitBytes !== void 0 && runtime.setMemoryLimit(options.memoryLimitBytes), options.maxStackSizeBytes !== void 0 && runtime.setMaxStackSize(options.maxStackSizeBytes);
|
options.moduleLoader && runtime.setModuleLoader(options.moduleLoader), options.shouldInterrupt && runtime.setInterruptHandler(options.shouldInterrupt), options.memoryLimitBytes !== void 0 && runtime.setMemoryLimit(options.memoryLimitBytes), options.maxStackSizeBytes !== void 0 && runtime.setMaxStackSize(options.maxStackSizeBytes);
|
||||||
}
|
}
|
||||||
var __defProp2, __export2, QTS_DEBUG, errors_exports, QuickJSUnwrapError, QuickJSWrongOwner, QuickJSUseAfterFree, QuickJSNotImplemented, QuickJSAsyncifyError, QuickJSAsyncifySuspended, QuickJSMemoryLeakDetected, QuickJSEmscriptenModuleError, QuickJSUnknownIntrinsic, QuickJSPromisePending, QuickJSEmptyGetOwnPropertyNames, AwaitYield, UsingDisposable, SymbolDispose, prototypeAsAny, Lifetime, StaticLifetime, WeakLifetime, Scope, AbstractDisposableResult, DisposableSuccess, DisposableFail, DisposableResult, QuickJSDeferredPromise, ModuleMemory, DefaultIntrinsics, QuickJSIterator, ContextMemory, QuickJSContext, QuickJSRuntime, QuickJSEmscriptenModuleCallbacks, QuickJSModuleCallbacks, QuickJSWASMModule;
|
var __defProp2, __export2, QTS_DEBUG, errors_exports, QuickJSUnwrapError, QuickJSWrongOwner, QuickJSUseAfterFree, QuickJSNotImplemented, QuickJSAsyncifyError, QuickJSAsyncifySuspended, QuickJSMemoryLeakDetected, QuickJSEmscriptenModuleError, QuickJSUnknownIntrinsic, QuickJSPromisePending, QuickJSEmptyGetOwnPropertyNames, AwaitYield, UsingDisposable, SymbolDispose, prototypeAsAny, Lifetime, StaticLifetime, WeakLifetime, Scope, AbstractDisposableResult, DisposableSuccess, DisposableFail, DisposableResult, QuickJSDeferredPromise, ModuleMemory, UnstableSymbol, DefaultIntrinsics, QuickJSIterator, ContextMemory, QuickJSContext, QuickJSRuntime, QuickJSEmscriptenModuleCallbacks, QuickJSModuleCallbacks, QuickJSWASMModule;
|
||||||
var init_chunk_JTKJZQYV = __esm({
|
var init_chunk_JTKJZQYV = __esm({
|
||||||
"registry/components/code/node_modules/quickjs-emscripten-core/dist/chunk-JTKJZQYV.mjs"() {
|
"registry/components/code/node_modules/quickjs-emscripten-core/dist/chunk-JTKJZQYV.mjs"() {
|
||||||
init_dist();
|
init_dist();
|
||||||
@@ -195,7 +190,7 @@ var init_chunk_JTKJZQYV = __esm({
|
|||||||
return this.dispose();
|
return this.dispose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
SymbolDispose = Symbol.dispose ?? /* @__PURE__ */ Symbol.for("Symbol.dispose");
|
SymbolDispose = Symbol.dispose ?? Symbol.for("Symbol.dispose");
|
||||||
prototypeAsAny = UsingDisposable.prototype;
|
prototypeAsAny = UsingDisposable.prototype;
|
||||||
prototypeAsAny[SymbolDispose] || (prototypeAsAny[SymbolDispose] = function() {
|
prototypeAsAny[SymbolDispose] || (prototypeAsAny[SymbolDispose] = function() {
|
||||||
return this.dispose();
|
return this.dispose();
|
||||||
@@ -414,6 +409,7 @@ Lifetime used`) : new QuickJSUseAfterFree("Lifetime not alive");
|
|||||||
return this.module._free(ptr), str;
|
return this.module._free(ptr), str;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
UnstableSymbol = Symbol("Unstable");
|
||||||
DefaultIntrinsics = Object.freeze({ BaseObjects: true, Date: true, Eval: true, StringNormalize: true, RegExp: true, JSON: true, Proxy: true, MapSet: true, TypedArrays: true, Promise: true });
|
DefaultIntrinsics = Object.freeze({ BaseObjects: true, Date: true, Eval: true, StringNormalize: true, RegExp: true, JSON: true, Proxy: true, MapSet: true, TypedArrays: true, Promise: true });
|
||||||
QuickJSIterator = class extends UsingDisposable {
|
QuickJSIterator = class extends UsingDisposable {
|
||||||
constructor(handle, context) {
|
constructor(handle, context) {
|
||||||
@@ -777,7 +773,7 @@ ${cause.stack}Host: ${hostStack}`), Object.assign(exception, rest), exception;
|
|||||||
}
|
}
|
||||||
return result.value;
|
return result.value;
|
||||||
}
|
}
|
||||||
[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
|
[Symbol.for("nodejs.util.inspect.custom")]() {
|
||||||
return this.alive ? `${this.constructor.name} { ctx: ${this.ctx.value} rt: ${this.rt.value} }` : `${this.constructor.name} { disposed }`;
|
return this.alive ? `${this.constructor.name} { ctx: ${this.ctx.value} rt: ${this.rt.value} }` : `${this.constructor.name} { disposed }`;
|
||||||
}
|
}
|
||||||
getFunction(fn_id) {
|
getFunction(fn_id) {
|
||||||
@@ -913,7 +909,7 @@ ${cause.stack}Host: ${hostStack}`), Object.assign(exception, rest), exception;
|
|||||||
debugLog(...msg) {
|
debugLog(...msg) {
|
||||||
this._debugMode && console.log("quickjs-emscripten:", ...msg);
|
this._debugMode && console.log("quickjs-emscripten:", ...msg);
|
||||||
}
|
}
|
||||||
[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
|
[Symbol.for("nodejs.util.inspect.custom")]() {
|
||||||
return this.alive ? `${this.constructor.name} { rt: ${this.rt.value} }` : `${this.constructor.name} { disposed }`;
|
return this.alive ? `${this.constructor.name} { rt: ${this.rt.value} }` : `${this.constructor.name} { disposed }`;
|
||||||
}
|
}
|
||||||
getSystemContext() {
|
getSystemContext() {
|
||||||
@@ -1343,10 +1339,10 @@ async function QuickJSRaw(moduleArg = {}) {
|
|||||||
x ? (0 === h && (h = ra()), g[m] = x(e[m])) : g[m] = e[m];
|
x ? (0 === h && (h = ra()), g[m] = x(e[m])) : g[m] = e[m];
|
||||||
}
|
}
|
||||||
b = a(...g);
|
b = a(...g);
|
||||||
return b = (function(k) {
|
return b = function(k) {
|
||||||
0 !== h && sa(h);
|
0 !== h && sa(h);
|
||||||
return "string" === d ? R(k) : "boolean" === d ? !!k : k;
|
return "string" === d ? R(k) : "boolean" === d ? !!k : k;
|
||||||
})(b);
|
}(b);
|
||||||
};
|
};
|
||||||
c.wasmMemory ? r = c.wasmMemory : r = new WebAssembly.Memory({ initial: (c.INITIAL_MEMORY || 16777216) / 65536, maximum: 32768 });
|
c.wasmMemory ? r = c.wasmMemory : r = new WebAssembly.Memory({ initial: (c.INITIAL_MEMORY || 16777216) / 65536, maximum: 32768 });
|
||||||
K();
|
K();
|
||||||
@@ -1468,7 +1464,7 @@ async function QuickJSRaw(moduleArg = {}) {
|
|||||||
}, t: function(a, d) {
|
}, t: function(a, d) {
|
||||||
c.callbacks.freeHostRef(void 0, a, d);
|
c.callbacks.freeHostRef(void 0, a, d);
|
||||||
} }, Z;
|
} }, Z;
|
||||||
Z = await (async function() {
|
Z = await async function() {
|
||||||
function a(b) {
|
function a(b) {
|
||||||
b = Z = b.exports;
|
b = Z = b.exports;
|
||||||
c._malloc = b.v;
|
c._malloc = b.v;
|
||||||
@@ -1555,7 +1551,7 @@ async function QuickJSRaw(moduleArg = {}) {
|
|||||||
});
|
});
|
||||||
M ??= c.locateFile ? c.locateFile ? c.locateFile("emscripten-module.wasm", u) : u + "emscripten-module.wasm" : new URL("emscripten-module.wasm", import.meta.url).href;
|
M ??= c.locateFile ? c.locateFile ? c.locateFile("emscripten-module.wasm", u) : u + "emscripten-module.wasm" : new URL("emscripten-module.wasm", import.meta.url).href;
|
||||||
return a((await ea(d)).instance);
|
return a((await ea(d)).instance);
|
||||||
})();
|
}();
|
||||||
(function() {
|
(function() {
|
||||||
function a() {
|
function a() {
|
||||||
c.calledRun = true;
|
c.calledRun = true;
|
||||||
@@ -3038,7 +3034,7 @@ var Hono = class _Hono {
|
|||||||
var emptyParam = [];
|
var emptyParam = [];
|
||||||
function match(method, path) {
|
function match(method, path) {
|
||||||
const matchers = this.buildAllMatchers();
|
const matchers = this.buildAllMatchers();
|
||||||
const match2 = ((method2, path2) => {
|
const match2 = (method2, path2) => {
|
||||||
const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
|
const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
|
||||||
const staticMatch = matcher[2][path2];
|
const staticMatch = matcher[2][path2];
|
||||||
if (staticMatch) {
|
if (staticMatch) {
|
||||||
@@ -3050,7 +3046,7 @@ function match(method, path) {
|
|||||||
}
|
}
|
||||||
const index = match3.indexOf("", 1);
|
const index = match3.indexOf("", 1);
|
||||||
return [matcher[1][index], match3];
|
return [matcher[1][index], match3];
|
||||||
});
|
};
|
||||||
this.match = match2;
|
this.match = match2;
|
||||||
return match2(method, path);
|
return match2(method, path);
|
||||||
}
|
}
|
||||||
@@ -4012,7 +4008,7 @@ app.post("/", async (c) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
var index_default = app;
|
var code_default = app;
|
||||||
export {
|
export {
|
||||||
index_default as default
|
code_default as default
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
var __defProp = Object.defineProperty;
|
var __defProp = Object.defineProperty;
|
||||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||||
var __esm = (fn, res, err) => function __init() {
|
var __esm = (fn, res) => function __init() {
|
||||||
if (err) throw err[0];
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
||||||
try {
|
|
||||||
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
||||||
} catch (e) {
|
|
||||||
throw err = [e], e;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
var __export = (target, all) => {
|
var __export = (target, all) => {
|
||||||
for (var name in all)
|
for (var name in all)
|
||||||
@@ -1506,7 +1501,7 @@ var init_hono_base = __esm({
|
|||||||
// cypher-executor/node_modules/.pnpm/hono@4.12.10/node_modules/hono/dist/router/reg-exp-router/matcher.js
|
// cypher-executor/node_modules/.pnpm/hono@4.12.10/node_modules/hono/dist/router/reg-exp-router/matcher.js
|
||||||
function match(method, path) {
|
function match(method, path) {
|
||||||
const matchers = this.buildAllMatchers();
|
const matchers = this.buildAllMatchers();
|
||||||
const match2 = ((method2, path2) => {
|
const match2 = (method2, path2) => {
|
||||||
const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
|
const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
|
||||||
const staticMatch = matcher[2][path2];
|
const staticMatch = matcher[2][path2];
|
||||||
if (staticMatch) {
|
if (staticMatch) {
|
||||||
@@ -1518,7 +1513,7 @@ function match(method, path) {
|
|||||||
}
|
}
|
||||||
const index = match3.indexOf("", 1);
|
const index = match3.indexOf("", 1);
|
||||||
return [matcher[1][index], match3];
|
return [matcher[1][index], match3];
|
||||||
});
|
};
|
||||||
this.match = match2;
|
this.match = match2;
|
||||||
return match2(method, path);
|
return match2(method, path);
|
||||||
}
|
}
|
||||||
@@ -7438,7 +7433,7 @@ var init_lib = __esm({
|
|||||||
...processCreateParams(params)
|
...processCreateParams(params)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
BRAND = /* @__PURE__ */ Symbol("zod_brand");
|
BRAND = Symbol("zod_brand");
|
||||||
ZodBranded = class extends ZodType {
|
ZodBranded = class extends ZodType {
|
||||||
_parse(input) {
|
_parse(input) {
|
||||||
const { ctx } = this._processInputParams(input);
|
const { ctx } = this._processInputParams(input);
|
||||||
@@ -7612,14 +7607,14 @@ var init_lib = __esm({
|
|||||||
onumber = () => numberType().optional();
|
onumber = () => numberType().optional();
|
||||||
oboolean = () => booleanType().optional();
|
oboolean = () => booleanType().optional();
|
||||||
coerce = {
|
coerce = {
|
||||||
string: ((arg) => ZodString.create({ ...arg, coerce: true })),
|
string: (arg) => ZodString.create({ ...arg, coerce: true }),
|
||||||
number: ((arg) => ZodNumber.create({ ...arg, coerce: true })),
|
number: (arg) => ZodNumber.create({ ...arg, coerce: true }),
|
||||||
boolean: ((arg) => ZodBoolean.create({
|
boolean: (arg) => ZodBoolean.create({
|
||||||
...arg,
|
...arg,
|
||||||
coerce: true
|
coerce: true
|
||||||
})),
|
}),
|
||||||
bigint: ((arg) => ZodBigInt.create({ ...arg, coerce: true })),
|
bigint: (arg) => ZodBigInt.create({ ...arg, coerce: true }),
|
||||||
date: ((arg) => ZodDate.create({ ...arg, coerce: true }))
|
date: (arg) => ZodDate.create({ ...arg, coerce: true })
|
||||||
};
|
};
|
||||||
NEVER = INVALID;
|
NEVER = INVALID;
|
||||||
z = /* @__PURE__ */ Object.freeze({
|
z = /* @__PURE__ */ Object.freeze({
|
||||||
@@ -7840,7 +7835,6 @@ var init_recipe_loader = __esm({
|
|||||||
super(message);
|
super(message);
|
||||||
this.recipe = recipe;
|
this.recipe = recipe;
|
||||||
}
|
}
|
||||||
recipe;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -8847,7 +8841,7 @@ function recordRecipeStats(env, recipeKeys, ok, at, ctx) {
|
|||||||
)
|
)
|
||||||
).then(() => void 0);
|
).then(() => void 0);
|
||||||
if (ctx?.waitUntil) ctx.waitUntil(promise);
|
if (ctx?.waitUntil) ctx.waitUntil(promise);
|
||||||
else void promise;
|
else ;
|
||||||
}
|
}
|
||||||
function generateToken() {
|
function generateToken() {
|
||||||
const tokenBytes = crypto.getRandomValues(new Uint8Array(16));
|
const tokenBytes = crypto.getRandomValues(new Uint8Array(16));
|
||||||
@@ -8889,7 +8883,7 @@ async function executeWebhookGraph(env, graph, triggerContext, token, apiKey, ct
|
|||||||
result.trace
|
result.trace
|
||||||
);
|
);
|
||||||
if (ctx?.waitUntil) ctx.waitUntil(statsPromise);
|
if (ctx?.waitUntil) ctx.waitUntil(statsPromise);
|
||||||
else void statsPromise;
|
else ;
|
||||||
}
|
}
|
||||||
return { success: true, data: result.data, duration_ms };
|
return { success: true, data: result.data, duration_ms };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -8913,7 +8907,7 @@ async function executeWebhookGraph(env, graph, triggerContext, token, apiKey, ct
|
|||||||
err.trace
|
err.trace
|
||||||
);
|
);
|
||||||
if (ctx?.waitUntil) ctx.waitUntil(statsPromise);
|
if (ctx?.waitUntil) ctx.waitUntil(statsPromise);
|
||||||
else void statsPromise;
|
else ;
|
||||||
}
|
}
|
||||||
if (err instanceof ExecutionError) {
|
if (err instanceof ExecutionError) {
|
||||||
const traceFormatted = err.trace.map((s) => ({
|
const traceFormatted = err.trace.map((s) => ({
|
||||||
@@ -9380,7 +9374,6 @@ function extractTarget(input) {
|
|||||||
return typeof raw2 === "string" ? raw2 : JSON.stringify(raw2);
|
return typeof raw2 === "string" ? raw2 : JSON.stringify(raw2);
|
||||||
}
|
}
|
||||||
async function writeExecutionVerdict(env, workflowId, nodes, verdict, durationMs, message, input, apiKey) {
|
async function writeExecutionVerdict(env, workflowId, nodes, verdict, durationMs, message, input, apiKey) {
|
||||||
void nodes;
|
|
||||||
try {
|
try {
|
||||||
const { base, headers } = kbdbBase(env);
|
const { base, headers } = kbdbBase(env);
|
||||||
await fetch(`${base}/execution-log/record`, {
|
await fetch(`${base}/execution-log/record`, {
|
||||||
@@ -12557,6 +12550,45 @@ init_kbdb_proxy();
|
|||||||
|
|
||||||
// cypher-executor/src/routes/console-auth.ts
|
// cypher-executor/src/routes/console-auth.ts
|
||||||
init_dist();
|
init_dist();
|
||||||
|
|
||||||
|
// cypher-executor/src/lib/tenant.ts
|
||||||
|
var TenantUnresolvedError = class extends Error {
|
||||||
|
constructor(message) {
|
||||||
|
super(message);
|
||||||
|
this.name = "TenantUnresolvedError";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
function knowledgeOwner(env) {
|
||||||
|
const injected = (env.ARCRUN_NAMESPACE ?? "").trim();
|
||||||
|
if (injected) return injected;
|
||||||
|
const legacy = (env.CONSOLE_TENANT ?? "").trim();
|
||||||
|
if (legacy) return legacy;
|
||||||
|
throw new TenantUnresolvedError(
|
||||||
|
"\u9019\u500B\u90E8\u7F72\u6C92\u6709\u77E5\u8B58\u547D\u540D\u7A7A\u9593\uFF08ARCRUN_NAMESPACE / CONSOLE_TENANT \u90FD\u6C92\u8A2D\uFF09\u2014\u2014\u4E0D\u77E5\u9053\u8981\u53BB\u54EA\u4E00\u683C\u627E\u8CC7\u6599\u3002\u8ACB\u8DD1 `acr update` \u8B93\u5B83\u5F9E\u4F60\u7684 ~/.arcrun/config.yaml \u6CE8\u5165\u3002"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function tenantFromApiKey(apiKey) {
|
||||||
|
const key = (apiKey ?? "").trim();
|
||||||
|
if (!key) throw new TenantUnresolvedError("\u7F3A\u5C11 X-Arcrun-API-Key\uFF0C\u7121\u6CD5\u6C7A\u5B9A\u67E5\u8A62\u7BC4\u570D");
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
function accountTenant(env) {
|
||||||
|
return env.CONSOLE_TENANT || "leo";
|
||||||
|
}
|
||||||
|
function ownerQuery(tenant2) {
|
||||||
|
return `owner_id=${encodeURIComponent(tenant2)}`;
|
||||||
|
}
|
||||||
|
function ownerField(tenant2) {
|
||||||
|
return tenant2;
|
||||||
|
}
|
||||||
|
function censusQueryAllTenants() {
|
||||||
|
return "owner_id=";
|
||||||
|
}
|
||||||
|
function isOwnedBy(value, tenant2) {
|
||||||
|
return typeof value === "string" && value === tenant2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cypher-executor/src/routes/console-auth.ts
|
||||||
var consoleAuthRouter = new Hono2();
|
var consoleAuthRouter = new Hono2();
|
||||||
var CREDS_KEY = "console:credentials";
|
var CREDS_KEY = "console:credentials";
|
||||||
var SESSION_PREFIX = "console_sess:";
|
var SESSION_PREFIX = "console_sess:";
|
||||||
@@ -12583,7 +12615,7 @@ async function hashPassword(password, salt) {
|
|||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
function tenantOf(c) {
|
function tenantOf(c) {
|
||||||
return c.env.CONSOLE_TENANT || "leo";
|
return knowledgeOwner(c.env);
|
||||||
}
|
}
|
||||||
async function loadCredentials(env) {
|
async function loadCredentials(env) {
|
||||||
let fromStore = readAuthStore(env).console;
|
let fromStore = readAuthStore(env).console;
|
||||||
@@ -12854,10 +12886,10 @@ var DEFAULT_SESSION_TTL = 604800;
|
|||||||
var USER_TEMPLATE = "portal_user";
|
var USER_TEMPLATE = "portal_user";
|
||||||
var LIBRARY_TEMPLATE = "portal_library";
|
var LIBRARY_TEMPLATE = "portal_library";
|
||||||
function portalTenant(env) {
|
function portalTenant(env) {
|
||||||
return env.CONSOLE_TENANT || "leo";
|
return accountTenant(env);
|
||||||
}
|
}
|
||||||
function portalNamespace(env) {
|
function portalNamespace(env) {
|
||||||
return `${portalTenant(env)}::portal`;
|
return `${accountTenant(env)}::portal`;
|
||||||
}
|
}
|
||||||
function sessionTtl(env) {
|
function sessionTtl(env) {
|
||||||
const n = Number.parseInt(env.PORTAL_SESSION_TTL ?? "", 10);
|
const n = Number.parseInt(env.PORTAL_SESSION_TTL ?? "", 10);
|
||||||
@@ -12886,6 +12918,9 @@ async function run(c, fn) {
|
|||||||
if (e instanceof AuthStoreWriteError) {
|
if (e instanceof AuthStoreWriteError) {
|
||||||
return c.json({ error: `\u8A8D\u8B49\u5132\u5B58\u5BEB\u5165\u5931\u6557\uFF1A${e.message}`, code: "auth_store_not_writable" }, 502);
|
return c.json({ error: `\u8A8D\u8B49\u5132\u5B58\u5BEB\u5165\u5931\u6557\uFF1A${e.message}`, code: "auth_store_not_writable" }, 502);
|
||||||
}
|
}
|
||||||
|
if (e instanceof TenantUnresolvedError) {
|
||||||
|
return c.json({ error: e.message, code: "tenant_unresolved" }, 500);
|
||||||
|
}
|
||||||
if (e instanceof KbdbError) return c.json({ error: `KBDB \u4E0D\u53EF\u9054\u6216\u56DE\u932F\uFF1A${e.message}` }, 502);
|
if (e instanceof KbdbError) return c.json({ error: `KBDB \u4E0D\u53EF\u9054\u6216\u56DE\u932F\uFF1A${e.message}` }, 502);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
@@ -13768,10 +13803,9 @@ portalRouter.post(
|
|||||||
return c.json({ error: "email \u6216\u5BC6\u78BC\u932F\u8AA4" }, 401);
|
return c.json({ error: "email \u6216\u5BC6\u78BC\u932F\u8AA4" }, 401);
|
||||||
}
|
}
|
||||||
await clearLoginFail(c.env, email);
|
await clearLoginFail(c.env, email);
|
||||||
const tenant2 = portalTenant(c.env);
|
|
||||||
const daemonCfg = {
|
const daemonCfg = {
|
||||||
cypher_url: new URL(c.req.url).origin,
|
cypher_url: new URL(c.req.url).origin,
|
||||||
namespace: tenant2,
|
namespace: knowledgeOwner(c.env),
|
||||||
library: "kb",
|
library: "kb",
|
||||||
email,
|
email,
|
||||||
instance_name: String(rec.values.display_name ?? "")
|
instance_name: String(rec.values.display_name ?? "")
|
||||||
@@ -13787,7 +13821,7 @@ portalRouter.post(
|
|||||||
const body = await c.req.json().catch(() => null);
|
const body = await c.req.json().catch(() => null);
|
||||||
const key = String(body?.key ?? "").trim();
|
const key = String(body?.key ?? "").trim();
|
||||||
if (!key) return c.json({ error: "\u8ACB\u8CBC\u4E0A\u4F60\u7684 Google AI \u91D1\u9470" }, 400);
|
if (!key) return c.json({ error: "\u8ACB\u8CBC\u4E0A\u4F60\u7684 Google AI \u91D1\u9470" }, 400);
|
||||||
const tenant2 = portalTenant(c.env);
|
const tenant2 = knowledgeOwner(c.env);
|
||||||
const kvKey2 = `${tenant2}:wf:rag_chat`;
|
const kvKey2 = `${tenant2}:wf:rag_chat`;
|
||||||
const raw2 = await c.env.WEBHOOKS.get(kvKey2, "text");
|
const raw2 = await c.env.WEBHOOKS.get(kvKey2, "text");
|
||||||
if (!raw2) return c.json({ error: "\u9019\u500B\u5BE6\u4F8B\u6C92\u6709\u5B89\u88DD AI \u554F\u7B54\u5DE5\u4F5C\u6D41" }, 404);
|
if (!raw2) return c.json({ error: "\u9019\u500B\u5BE6\u4F8B\u6C92\u6709\u5B89\u88DD AI \u554F\u7B54\u5DE5\u4F5C\u6D41" }, 404);
|
||||||
@@ -13839,8 +13873,8 @@ portalRouter.get(
|
|||||||
});
|
});
|
||||||
const known = new Set(out.map((l) => l.name));
|
const known = new Set(out.map((l) => l.name));
|
||||||
try {
|
try {
|
||||||
const tenant2 = portalTenant(c.env);
|
const tenant2 = knowledgeOwner(c.env);
|
||||||
const ownerParam = `owner_id=${encodeURIComponent(tenant2)}`;
|
const ownerParam = ownerQuery(tenant2);
|
||||||
const [autoRes, cardRes, tripletRes] = await Promise.all([
|
const [autoRes, cardRes, tripletRes] = await Promise.all([
|
||||||
kbdbFetch(c.env, `/entries/libraries?${ownerParam}`).catch(() => null),
|
kbdbFetch(c.env, `/entries/libraries?${ownerParam}`).catch(() => null),
|
||||||
kbdbFetch(c.env, `/entries/library-stats?${ownerParam}`).catch(() => null),
|
kbdbFetch(c.env, `/entries/library-stats?${ownerParam}`).catch(() => null),
|
||||||
@@ -13989,8 +14023,8 @@ portalRouter.get(
|
|||||||
(c) => run(c, async () => {
|
(c) => run(c, async () => {
|
||||||
const auth = await requirePortalAdmin(c);
|
const auth = await requirePortalAdmin(c);
|
||||||
if (!auth.ok) return auth.res;
|
if (!auth.ok) return auth.res;
|
||||||
const ownerId = portalTenant(c.env);
|
const ownerId = knowledgeOwner(c.env);
|
||||||
const res = await kbdbFetch(c.env, `/execution-log/retention?owner_id=${encodeURIComponent(ownerId)}`);
|
const res = await kbdbFetch(c.env, `/execution-log/retention?${ownerQuery(ownerId)}`);
|
||||||
if (!res.ok) throw new KbdbError(`GET /execution-log/retention \u2192 ${res.status}`);
|
if (!res.ok) throw new KbdbError(`GET /execution-log/retention \u2192 ${res.status}`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
return c.json({ success: true, retention_days: data.retention_days ?? null, default_days: data.default_days ?? 90 });
|
return c.json({ success: true, retention_days: data.retention_days ?? null, default_days: data.default_days ?? 90 });
|
||||||
@@ -14006,10 +14040,10 @@ portalRouter.put(
|
|||||||
if (days !== null && days !== void 0 && (typeof days !== "number" || !Number.isFinite(days) || days <= 0)) {
|
if (days !== null && days !== void 0 && (typeof days !== "number" || !Number.isFinite(days) || days <= 0)) {
|
||||||
return c.json({ error: "retention_days \u5FC5\u9808\u662F\u6B63\u6574\u6578\uFF0C\u6216 null\uFF08\u4EE3\u8868\u4E0D\u522A\u9664\uFF09" }, 400);
|
return c.json({ error: "retention_days \u5FC5\u9808\u662F\u6B63\u6574\u6578\uFF0C\u6216 null\uFF08\u4EE3\u8868\u4E0D\u522A\u9664\uFF09" }, 400);
|
||||||
}
|
}
|
||||||
const ownerId = portalTenant(c.env);
|
const ownerId = knowledgeOwner(c.env);
|
||||||
const res = await kbdbFetch(c.env, "/execution-log/retention", {
|
const res = await kbdbFetch(c.env, "/execution-log/retention", {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: JSON.stringify({ owner_id: ownerId, retention_days: days === void 0 ? null : days })
|
body: JSON.stringify({ owner_id: ownerField(ownerId), retention_days: days === void 0 ? null : days })
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new KbdbError(`PUT /execution-log/retention \u2192 ${res.status}`);
|
if (!res.ok) throw new KbdbError(`PUT /execution-log/retention \u2192 ${res.status}`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
@@ -14026,10 +14060,10 @@ portalRouter.delete(
|
|||||||
const confirm = String(body?.confirm ?? "").trim();
|
const confirm = String(body?.confirm ?? "").trim();
|
||||||
if (!confirm) return c.json({ error: 'body \u9808\u5E36 { confirm: "<\u5EAB\u540D>" } \u624D\u57F7\u884C\uFF08\u79FB\u9664\u6703\u5F71\u97FF\u8CC7\u6599\u53EF\u641C\u6027\uFF09' }, 400);
|
if (!confirm) return c.json({ error: 'body \u9808\u5E36 { confirm: "<\u5EAB\u540D>" } \u624D\u57F7\u884C\uFF08\u79FB\u9664\u6703\u5F71\u97FF\u8CC7\u6599\u53EF\u641C\u6027\uFF09' }, 400);
|
||||||
if (confirm !== name) return c.json({ error: `confirm \u503C\u300C${confirm}\u300D\u8207\u5EAB\u540D\u300C${name}\u300D\u4E0D\u7B26` }, 400);
|
if (confirm !== name) return c.json({ error: `confirm \u503C\u300C${confirm}\u300D\u8207\u5EAB\u540D\u300C${name}\u300D\u4E0D\u7B26` }, 400);
|
||||||
const ownerId = portalTenant(c.env);
|
const ownerId = knowledgeOwner(c.env);
|
||||||
const res = await kbdbFetch(c.env, "/entries/deprecate-by-library", {
|
const res = await kbdbFetch(c.env, "/entries/deprecate-by-library", {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
body: JSON.stringify({ owner_id: ownerId, library: name })
|
body: JSON.stringify({ owner_id: ownerField(ownerId), library: name })
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new KbdbError(`PATCH /entries/deprecate-by-library \u2192 ${res.status}`);
|
if (!res.ok) throw new KbdbError(`PATCH /entries/deprecate-by-library \u2192 ${res.status}`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
@@ -14085,8 +14119,8 @@ async function buildDiagnostics(env, tenant2) {
|
|||||||
let embedding = { checked: false };
|
let embedding = { checked: false };
|
||||||
try {
|
try {
|
||||||
const [statusRes, selftestRes] = await Promise.all([
|
const [statusRes, selftestRes] = await Promise.all([
|
||||||
kbdbFetch(env, `/embed/backfill/status?${new URLSearchParams({ owner_id: tenant2 }).toString()}`),
|
kbdbFetch(env, `/embed/backfill/status?${ownerQuery(tenant2)}`),
|
||||||
kbdbFetch(env, `/embed/selftest?${new URLSearchParams({ owner_id: tenant2 }).toString()}`)
|
kbdbFetch(env, `/embed/selftest?${ownerQuery(tenant2)}`)
|
||||||
]);
|
]);
|
||||||
const statusBody = await statusRes.json().catch(() => null);
|
const statusBody = await statusRes.json().catch(() => null);
|
||||||
const selftestBody = await selftestRes.json().catch(() => null);
|
const selftestBody = await selftestRes.json().catch(() => null);
|
||||||
@@ -14108,7 +14142,7 @@ async function buildDiagnostics(env, tenant2) {
|
|||||||
}
|
}
|
||||||
let library_count = 0;
|
let library_count = 0;
|
||||||
let triplet_count = 0;
|
let triplet_count = 0;
|
||||||
const ownerParam = new URLSearchParams({ owner_id: tenant2 }).toString();
|
const ownerParam = ownerQuery(tenant2);
|
||||||
try {
|
try {
|
||||||
const [registeredLibs, autoRes, tripletRes] = await Promise.all([
|
const [registeredLibs, autoRes, tripletRes] = await Promise.all([
|
||||||
listRecordsByTemplate(env, LIBRARY_TEMPLATE).catch(() => []),
|
listRecordsByTemplate(env, LIBRARY_TEMPLATE).catch(() => []),
|
||||||
@@ -14132,7 +14166,7 @@ async function buildDiagnostics(env, tenant2) {
|
|||||||
let library_scope_check = { ran: false };
|
let library_scope_check = { ran: false };
|
||||||
if (library_count === 0 && triplet_count === 0) {
|
if (library_count === 0 && triplet_count === 0) {
|
||||||
try {
|
try {
|
||||||
const probeRes = await kbdbFetch(env, `/entries?${new URLSearchParams({ owner_id: tenant2, limit: "1" }).toString()}`);
|
const probeRes = await kbdbFetch(env, `/entries?${new URLSearchParams({ owner_id: ownerField(tenant2), limit: "1" }).toString()}`);
|
||||||
const probeBody = await probeRes.json().catch(() => null);
|
const probeBody = await probeRes.json().catch(() => null);
|
||||||
const total = probeBody?.total ?? 0;
|
const total = probeBody?.total ?? 0;
|
||||||
library_scope_check = {
|
library_scope_check = {
|
||||||
@@ -14155,7 +14189,7 @@ portalRouter.get(
|
|||||||
(c) => run(c, async () => {
|
(c) => run(c, async () => {
|
||||||
const apiKey = (c.req.header("X-Arcrun-API-Key") ?? "").trim();
|
const apiKey = (c.req.header("X-Arcrun-API-Key") ?? "").trim();
|
||||||
if (!apiKey) return c.json({ error: "\u7F3A\u5C11 X-Arcrun-API-Key header" }, 401);
|
if (!apiKey) return c.json({ error: "\u7F3A\u5C11 X-Arcrun-API-Key header" }, 401);
|
||||||
const core = await buildDiagnostics(c.env, apiKey);
|
const core = await buildDiagnostics(c.env, tenantFromApiKey(apiKey));
|
||||||
return c.json({
|
return c.json({
|
||||||
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
||||||
instance_url: new URL(c.req.url).origin,
|
instance_url: new URL(c.req.url).origin,
|
||||||
@@ -14710,7 +14744,7 @@ async function cachedGiteaSprint(env, nowMs, waitUntil, fetcher = fetchGiteaSpri
|
|||||||
return { ...fresh, cache: "miss" };
|
return { ...fresh, cache: "miss" };
|
||||||
}
|
}
|
||||||
consoleDashboardRouter.get("/console/dashboard-data", async (c) => {
|
consoleDashboardRouter.get("/console/dashboard-data", async (c) => {
|
||||||
const tenant2 = c.env.CONSOLE_TENANT || "leo";
|
const tenant2 = knowledgeOwner(c.env);
|
||||||
const now2 = Date.now();
|
const now2 = Date.now();
|
||||||
const { base: kbdbUrl, headers: kbdbHeaders } = kbdbBase(c.env);
|
const { base: kbdbUrl, headers: kbdbHeaders } = kbdbBase(c.env);
|
||||||
const graphUrl = graphBase(c.env);
|
const graphUrl = graphBase(c.env);
|
||||||
@@ -14862,7 +14896,7 @@ consoleDashboardRouter.get("/console/dashboard-data", async (c) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
consoleDashboardRouter.get("/console/kb-scale-data", async (c) => {
|
consoleDashboardRouter.get("/console/kb-scale-data", async (c) => {
|
||||||
const tenant2 = c.env.CONSOLE_TENANT || "leo";
|
const tenant2 = knowledgeOwner(c.env);
|
||||||
const { base, headers } = kbdbBase(c.env);
|
const { base, headers } = kbdbBase(c.env);
|
||||||
const now2 = Date.now();
|
const now2 = Date.now();
|
||||||
const [wikiCards, tripletTotal, embedStatus] = await Promise.all([
|
const [wikiCards, tripletTotal, embedStatus] = await Promise.all([
|
||||||
@@ -14897,7 +14931,7 @@ consoleDashboardRouter.get("/console/settings-data", (c) => {
|
|||||||
consoleDashboardRouter.get("/console/triage-data", async (c) => {
|
consoleDashboardRouter.get("/console/triage-data", async (c) => {
|
||||||
const ok = await validateConsoleSession(c.env, c.req.header("authorization"));
|
const ok = await validateConsoleSession(c.env, c.req.header("authorization"));
|
||||||
if (!ok) return c.json({ error: "\u9700\u8981\u767B\u5165\uFF08console session\uFF09" }, 401);
|
if (!ok) return c.json({ error: "\u9700\u8981\u767B\u5165\uFF08console session\uFF09" }, 401);
|
||||||
const tenant2 = c.env.CONSOLE_TENANT || "leo";
|
const tenant2 = knowledgeOwner(c.env);
|
||||||
const [todoEntries, inboxEntries] = await Promise.all([
|
const [todoEntries, inboxEntries] = await Promise.all([
|
||||||
fetchEntries(c.env, tenant2, "todo", 500),
|
fetchEntries(c.env, tenant2, "todo", 500),
|
||||||
fetchEntries(c.env, tenant2, "inbox", 200)
|
fetchEntries(c.env, tenant2, "inbox", 200)
|
||||||
@@ -14912,7 +14946,7 @@ consoleDashboardRouter.post("/console/triage-check", async (c) => {
|
|||||||
const entryId = typeof body?.entry_id === "string" ? body.entry_id.trim() : "";
|
const entryId = typeof body?.entry_id === "string" ? body.entry_id.trim() : "";
|
||||||
if (!entryId) return c.json({ error: "entry_id \u5FC5\u586B" }, 400);
|
if (!entryId) return c.json({ error: "entry_id \u5FC5\u586B" }, 400);
|
||||||
const action = body?.action === "restore" ? "restore" : "check";
|
const action = body?.action === "restore" ? "restore" : "check";
|
||||||
const tenant2 = c.env.CONSOLE_TENANT || "leo";
|
const tenant2 = knowledgeOwner(c.env);
|
||||||
const { base, headers } = kbdbBase(c.env);
|
const { base, headers } = kbdbBase(c.env);
|
||||||
const got = await fetchJson(
|
const got = await fetchJson(
|
||||||
`${base}/entries/${encodeURIComponent(entryId)}`,
|
`${base}/entries/${encodeURIComponent(entryId)}`,
|
||||||
@@ -14940,7 +14974,7 @@ init_kbdb_proxy();
|
|||||||
init_webhook_handlers();
|
init_webhook_handlers();
|
||||||
var portalDataRouter = new Hono2();
|
var portalDataRouter = new Hono2();
|
||||||
async function getTenantWorkflowGraph(env, name) {
|
async function getTenantWorkflowGraph(env, name) {
|
||||||
const raw2 = await env.WEBHOOKS.get(`${portalTenant(env)}:wf:${name}`, "text");
|
const raw2 = await env.WEBHOOKS.get(`${knowledgeOwner(env)}:wf:${name}`, "text");
|
||||||
if (!raw2) return null;
|
if (!raw2) return null;
|
||||||
try {
|
try {
|
||||||
const rec = JSON.parse(raw2);
|
const rec = JSON.parse(raw2);
|
||||||
@@ -15033,7 +15067,7 @@ function findBestNodeMatch(searchTerm, nodeNames) {
|
|||||||
}
|
}
|
||||||
async function tripletCount(env, owner) {
|
async function tripletCount(env, owner) {
|
||||||
try {
|
try {
|
||||||
const res = await kbdbFetch(env, `/records/triplet-stats?owner_id=${encodeURIComponent(owner)}`);
|
const res = await kbdbFetch(env, `/records/triplet-stats?${owner === null ? censusQueryAllTenants() : ownerQuery(owner)}`);
|
||||||
if (!res.ok) return null;
|
if (!res.ok) return null;
|
||||||
const body = await res.json().catch(() => null);
|
const body = await res.json().catch(() => null);
|
||||||
if (!body || !Array.isArray(body.stats)) return null;
|
if (!body || !Array.isArray(body.stats)) return null;
|
||||||
@@ -15050,11 +15084,11 @@ async function tripletCount(env, owner) {
|
|||||||
async function tripletCensus(env, tenant2) {
|
async function tripletCensus(env, tenant2) {
|
||||||
const owned = await tripletCount(env, tenant2);
|
const owned = await tripletCount(env, tenant2);
|
||||||
if (owned !== 0) return { owned, any: null };
|
if (owned !== 0) return { owned, any: null };
|
||||||
return { owned, any: await tripletCount(env, "") };
|
return { owned, any: await tripletCount(env, null) };
|
||||||
}
|
}
|
||||||
async function fuzzyFindNode(env, tenant2, searchTerm) {
|
async function fuzzyFindNode(env, tenant2, searchTerm) {
|
||||||
try {
|
try {
|
||||||
const res = await kbdbFetch(env, `/records/by-template/triplet?owner_id=${encodeURIComponent(tenant2)}`);
|
const res = await kbdbFetch(env, `/records/by-template/triplet?${ownerQuery(tenant2)}`);
|
||||||
if (!res.ok) return null;
|
if (!res.ok) return null;
|
||||||
const body = await res.json().catch(() => null);
|
const body = await res.json().catch(() => null);
|
||||||
if (!body || !Array.isArray(body.records)) return null;
|
if (!body || !Array.isArray(body.records)) return null;
|
||||||
@@ -15082,7 +15116,7 @@ portalDataRouter.get(
|
|||||||
if (libraries.length === 0) {
|
if (libraries.length === 0) {
|
||||||
return c.json({ success: true, entries: [], count: 0, mode: "keyword", note: "\u6B64\u5E33\u865F\u5C1A\u672A\u88AB\u6388\u6B0A\u4EFB\u4F55\u77E5\u8B58\u5EAB\uFF0C\u8ACB\u806F\u7D61\u7BA1\u7406\u54E1\u3002" });
|
return c.json({ success: true, entries: [], count: 0, mode: "keyword", note: "\u6B64\u5E33\u865F\u5C1A\u672A\u88AB\u6388\u6B0A\u4EFB\u4F55\u77E5\u8B58\u5EAB\uFF0C\u8ACB\u806F\u7D61\u7BA1\u7406\u54E1\u3002" });
|
||||||
}
|
}
|
||||||
const params = new URLSearchParams({ q, owner_id: portalTenant(c.env) });
|
const params = new URLSearchParams({ q, owner_id: ownerField(knowledgeOwner(c.env)) });
|
||||||
if (!libraries.includes("*")) params.set("library", libraries.join(","));
|
if (!libraries.includes("*")) params.set("library", libraries.join(","));
|
||||||
if (c.req.query("mode") === "semantic") {
|
if (c.req.query("mode") === "semantic") {
|
||||||
params.set("mode", "semantic");
|
params.set("mode", "semantic");
|
||||||
@@ -15118,7 +15152,7 @@ portalDataRouter.get(
|
|||||||
const body = await res.json();
|
const body = await res.json();
|
||||||
const entry = body.entry;
|
const entry = body.entry;
|
||||||
if (!entry) return notFound(c);
|
if (!entry) return notFound(c);
|
||||||
if ((entry.owner_id ?? "") !== portalTenant(c.env)) return notFound(c);
|
if (!isOwnedBy(entry.owner_id, knowledgeOwner(c.env))) return notFound(c);
|
||||||
if (!canReadLibrary(libraries, entryLibrary(entry))) return notFound(c);
|
if (!canReadLibrary(libraries, entryLibrary(entry))) return notFound(c);
|
||||||
return c.json({ success: true, entry });
|
return c.json({ success: true, entry });
|
||||||
})
|
})
|
||||||
@@ -15133,7 +15167,7 @@ portalDataRouter.get(
|
|||||||
return c.json({ error: "\u7121\u77E5\u8B58\u5716\u8B5C\u6AA2\u8996\u6B0A\u9650" }, 403);
|
return c.json({ error: "\u7121\u77E5\u8B58\u5716\u8B5C\u6AA2\u8996\u6B0A\u9650" }, 403);
|
||||||
}
|
}
|
||||||
const nodeName = normalizeCjkQuery(c.req.param("name"));
|
const nodeName = normalizeCjkQuery(c.req.param("name"));
|
||||||
const tenant2 = portalTenant(c.env);
|
const tenant2 = knowledgeOwner(c.env);
|
||||||
const wfGraph = await getTenantWorkflowGraph(c.env, "graph_neighbors");
|
const wfGraph = await getTenantWorkflowGraph(c.env, "graph_neighbors");
|
||||||
if (wfGraph) {
|
if (wfGraph) {
|
||||||
const depthRaw = c.req.query("depth") ?? "";
|
const depthRaw = c.req.query("depth") ?? "";
|
||||||
@@ -15187,9 +15221,9 @@ portalDataRouter.get(
|
|||||||
if (!await hasGraphAccess(c.env, libraries)) {
|
if (!await hasGraphAccess(c.env, libraries)) {
|
||||||
return c.json({ error: "\u7121\u77E5\u8B58\u5716\u8B5C\u6AA2\u8996\u6B0A\u9650" }, 403);
|
return c.json({ error: "\u7121\u77E5\u8B58\u5716\u8B5C\u6AA2\u8996\u6B0A\u9650" }, 403);
|
||||||
}
|
}
|
||||||
const tenant2 = portalTenant(c.env);
|
const tenant2 = knowledgeOwner(c.env);
|
||||||
const [res, census] = await Promise.all([
|
const [res, census] = await Promise.all([
|
||||||
kbdbFetch(c.env, `/records/by-template/triplet?owner_id=${encodeURIComponent(tenant2)}&limit=500`),
|
kbdbFetch(c.env, `/records/by-template/triplet?${ownerQuery(tenant2)}&limit=500`),
|
||||||
tripletCensus(c.env, tenant2)
|
tripletCensus(c.env, tenant2)
|
||||||
]);
|
]);
|
||||||
const tripletsTotal = census.owned;
|
const tripletsTotal = census.owned;
|
||||||
@@ -15260,7 +15294,7 @@ portalDataRouter.get(
|
|||||||
wfGraph,
|
wfGraph,
|
||||||
{ question },
|
{ question },
|
||||||
"rag_chat",
|
"rag_chat",
|
||||||
portalTenant(c.env),
|
knowledgeOwner(c.env),
|
||||||
c.executionCtx
|
c.executionCtx
|
||||||
);
|
);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
@@ -15336,7 +15370,7 @@ portalDataRouter.get(
|
|||||||
if (!workflowsVisible(c.env, auth.user.values.role ?? "user")) {
|
if (!workflowsVisible(c.env, auth.user.values.role ?? "user")) {
|
||||||
return c.json({ error: "\u9700\u8981 admin \u6B0A\u9650" }, 403);
|
return c.json({ error: "\u9700\u8981 admin \u6B0A\u9650" }, 403);
|
||||||
}
|
}
|
||||||
const tenant2 = portalTenant(c.env);
|
const tenant2 = knowledgeOwner(c.env);
|
||||||
const prefix = `${tenant2}:wf:`;
|
const prefix = `${tenant2}:wf:`;
|
||||||
const list = await c.env.WEBHOOKS.list({ prefix });
|
const list = await c.env.WEBHOOKS.list({ prefix });
|
||||||
const workflows = await Promise.all(
|
const workflows = await Promise.all(
|
||||||
@@ -15358,7 +15392,7 @@ portalDataRouter.get(
|
|||||||
let last_execution = null;
|
let last_execution = null;
|
||||||
const execRes = await kbdbFetch(
|
const execRes = await kbdbFetch(
|
||||||
c.env,
|
c.env,
|
||||||
`/execution-log/latest?${new URLSearchParams({ workflow_id: name, owner_id: tenant2 }).toString()}`
|
`/execution-log/latest?${new URLSearchParams({ workflow_id: name, owner_id: ownerField(tenant2) }).toString()}`
|
||||||
);
|
);
|
||||||
const execBody = await execRes.json().catch(() => null);
|
const execBody = await execRes.json().catch(() => null);
|
||||||
if (execRes.ok && execBody?.success && execBody.execution) {
|
if (execRes.ok && execBody?.success && execBody.execution) {
|
||||||
@@ -15375,7 +15409,7 @@ function recordLibrary(values) {
|
|||||||
return typeof lib === "string" && lib.trim() ? lib.trim() : null;
|
return typeof lib === "string" && lib.trim() ? lib.trim() : null;
|
||||||
}
|
}
|
||||||
function canReadRecord(rec, tenant2, libraries) {
|
function canReadRecord(rec, tenant2, libraries) {
|
||||||
if ((rec.owner_id ?? "") !== tenant2) return false;
|
if (!isOwnedBy(rec.owner_id, tenant2)) return false;
|
||||||
const lib = recordLibrary(rec.values);
|
const lib = recordLibrary(rec.values);
|
||||||
return lib === null || canReadLibrary(libraries, lib);
|
return lib === null || canReadLibrary(libraries, lib);
|
||||||
}
|
}
|
||||||
@@ -15386,9 +15420,17 @@ portalDataRouter.get(
|
|||||||
if (!auth.ok) return auth.res;
|
if (!auth.ok) return auth.res;
|
||||||
const libraries = parseLibraries(auth.user.values.libraries);
|
const libraries = parseLibraries(auth.user.values.libraries);
|
||||||
if (libraries.length === 0) {
|
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" });
|
return c.json({
|
||||||
|
success: true,
|
||||||
|
libraries: [],
|
||||||
|
count: 0,
|
||||||
|
empty_confirmed: true,
|
||||||
|
empty_reason: "no_library_grant",
|
||||||
|
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))}`);
|
const tenant2 = knowledgeOwner(c.env);
|
||||||
|
const res = await kbdbFetch(c.env, `/map?${ownerQuery(tenant2)}`);
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
return new Response(res.body, { status: res.status, headers: { "Content-Type": "application/json" } });
|
return new Response(res.body, { status: res.status, headers: { "Content-Type": "application/json" } });
|
||||||
}
|
}
|
||||||
@@ -15399,7 +15441,49 @@ portalDataRouter.get(
|
|||||||
const allowed = body.libraries.filter(
|
const allowed = body.libraries.filter(
|
||||||
(l) => typeof l?.library === "string" && canReadLibrary(libraries, l.library)
|
(l) => typeof l?.library === "string" && canReadLibrary(libraries, l.library)
|
||||||
);
|
);
|
||||||
return c.json({ success: true, libraries: allowed, count: allowed.length });
|
if (allowed.length > 0) {
|
||||||
|
return c.json({ success: true, libraries: allowed, count: allowed.length, empty_confirmed: false, empty_reason: null });
|
||||||
|
}
|
||||||
|
if (body.libraries.length > 0) {
|
||||||
|
return c.json({
|
||||||
|
success: true,
|
||||||
|
libraries: [],
|
||||||
|
count: 0,
|
||||||
|
empty_confirmed: true,
|
||||||
|
empty_reason: "filtered_out",
|
||||||
|
note: "\u9019\u500B\u5E33\u865F\u76EE\u524D\u6C92\u6709\u4EFB\u4F55\u77E5\u8B58\u5EAB\u7684\u6AA2\u8996\u6B0A\u9650\uFF0C\u8ACB\u806F\u7D61\u7BA1\u7406\u54E1\u958B\u901A\u3002"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const census = await tripletCensus(c.env, tenant2);
|
||||||
|
if (census.owned === null || census.owned === 0 && census.any === null) {
|
||||||
|
return c.json({
|
||||||
|
success: true,
|
||||||
|
libraries: [],
|
||||||
|
count: 0,
|
||||||
|
empty_confirmed: false,
|
||||||
|
empty_reason: "unreadable",
|
||||||
|
note: "\u8B80\u4E0D\u5230\u77E5\u8B58\u5EAB\u7684\u7D71\u8A08\uFF0C\u7121\u6CD5\u78BA\u8A8D\u5EAB\u88E1\u6709\u6C92\u6709\u6771\u897F\u2014\u2014\u9019\u4E0D\u662F\u300C\u9084\u6C92\u6709\u77E5\u8B58\u300D\uFF0C\u662F\u9019\u6B21\u8B80\u53D6\u5931\u6557\u3002\u8ACB\u7A0D\u5F8C\u91CD\u6574\u6216\u901A\u77E5\u7BA1\u7406\u54E1\u3002"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (census.owned === 0 && (census.any ?? 0) > 0) {
|
||||||
|
return c.json({
|
||||||
|
success: true,
|
||||||
|
libraries: [],
|
||||||
|
count: 0,
|
||||||
|
empty_confirmed: false,
|
||||||
|
empty_reason: "scope_mismatch",
|
||||||
|
instance_triplet_count: census.any,
|
||||||
|
note: `\u8B80\u4E0D\u5230\u4F60\u9019\u500B\u5E33\u865F\u7BC4\u570D\u5167\u7684\u85CF\u66F8\u2014\u2014\u4F46\u9019\u53F0\u5BE6\u4F8B\u88E1\u6709 ${census.any} \u689D\u77E5\u8B58\u95DC\u806F\u3002\u9019\u4E0D\u662F\u300C\u9084\u6C92\u6709\u77E5\u8B58\u300D\uFF0C\u4E0D\u7528\u53BB\u91CD\u65B0\u4E0A\u50B3\uFF1B\u6BD4\u8F03\u50CF\u77E5\u8B58\u7684\u6B78\u5C6C\u547D\u540D\u7A7A\u9593\u5C0D\u4E0D\u4E0A\u3002\u8ACB\u901A\u77E5\u7BA1\u7406\u54E1\u8DD1\u4E00\u6B21 \`acr update\`\uFF08\u6703\u628A\u4F60\u5B89\u88DD\u6642\u7684\u547D\u540D\u7A7A\u9593\u540C\u6B65\u7D66\u96F2\u7AEF\uFF09\uFF0C\u6216\u6AA2\u67E5 ARCRUN_NAMESPACE \u8A2D\u5B9A\u3002`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return c.json({
|
||||||
|
success: true,
|
||||||
|
libraries: [],
|
||||||
|
count: 0,
|
||||||
|
empty_confirmed: true,
|
||||||
|
empty_reason: "confirmed_empty",
|
||||||
|
note: "\u77E5\u8B58\u5EAB\u9084\u6C92\u6709\u4EFB\u4F55\u5167\u5BB9\u2014\u2014\u4E0A\u50B3\u6587\u4EF6\u5F8C\u5C31\u6703\u51FA\u73FE\u5728\u9019\u88E1\u3002"
|
||||||
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
portalDataRouter.get(
|
portalDataRouter.get(
|
||||||
@@ -15412,7 +15496,7 @@ portalDataRouter.get(
|
|||||||
if (!canReadLibrary(libraries, library)) return notFound(c);
|
if (!canReadLibrary(libraries, library)) return notFound(c);
|
||||||
const res = await kbdbFetch(
|
const res = await kbdbFetch(
|
||||||
c.env,
|
c.env,
|
||||||
`/map/${encodeURIComponent(library)}?owner_id=${encodeURIComponent(portalTenant(c.env))}`
|
`/map/${encodeURIComponent(library)}?${ownerQuery(knowledgeOwner(c.env))}`
|
||||||
);
|
);
|
||||||
if (res.status === 404) return notFound(c);
|
if (res.status === 404) return notFound(c);
|
||||||
if (!res.ok) return c.json({ error: `KBDB \u56DE\u932F\uFF08HTTP ${res.status}\uFF09` }, 502);
|
if (!res.ok) return c.json({ error: `KBDB \u56DE\u932F\uFF08HTTP ${res.status}\uFF09` }, 502);
|
||||||
@@ -15445,7 +15529,7 @@ portalDataRouter.post(
|
|||||||
name: body.name,
|
name: body.name,
|
||||||
slots: body.slots,
|
slots: body.slots,
|
||||||
description: typeof body.description === "string" ? body.description : void 0,
|
description: typeof body.description === "string" ? body.description : void 0,
|
||||||
created_by: portalTenant(c.env)
|
created_by: knowledgeOwner(c.env)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
return new Response(res.body, { status: res.status, headers: { "Content-Type": "application/json" } });
|
return new Response(res.body, { status: res.status, headers: { "Content-Type": "application/json" } });
|
||||||
@@ -15458,10 +15542,10 @@ portalDataRouter.get(
|
|||||||
if (!auth.ok) return auth.res;
|
if (!auth.ok) return auth.res;
|
||||||
const libraries = parseLibraries(auth.user.values.libraries);
|
const libraries = parseLibraries(auth.user.values.libraries);
|
||||||
if (libraries.length === 0) return c.json({ success: true, records: [], count: 0 });
|
if (libraries.length === 0) return c.json({ success: true, records: [], count: 0 });
|
||||||
const tenant2 = portalTenant(c.env);
|
const tenant2 = knowledgeOwner(c.env);
|
||||||
const res = await kbdbFetch(
|
const res = await kbdbFetch(
|
||||||
c.env,
|
c.env,
|
||||||
`/records/by-template/${encodeURIComponent(c.req.param("template"))}?owner_id=${encodeURIComponent(tenant2)}`
|
`/records/by-template/${encodeURIComponent(c.req.param("template"))}?${ownerQuery(tenant2)}`
|
||||||
);
|
);
|
||||||
if (!res.ok) return c.json({ error: `KBDB \u56DE\u932F\uFF08HTTP ${res.status}\uFF09` }, 502);
|
if (!res.ok) return c.json({ error: `KBDB \u56DE\u932F\uFF08HTTP ${res.status}\uFF09` }, 502);
|
||||||
const body = await res.json().catch(() => null);
|
const body = await res.json().catch(() => null);
|
||||||
@@ -15485,7 +15569,7 @@ portalDataRouter.get(
|
|||||||
const body = await res.json().catch(() => null);
|
const body = await res.json().catch(() => null);
|
||||||
const record = body?.record;
|
const record = body?.record;
|
||||||
if (!record) return notFound(c);
|
if (!record) return notFound(c);
|
||||||
if (!canReadRecord(record, portalTenant(c.env), libraries)) return notFound(c);
|
if (!canReadRecord(record, knowledgeOwner(c.env), libraries)) return notFound(c);
|
||||||
return c.json({ success: true, record });
|
return c.json({ success: true, record });
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -15510,7 +15594,7 @@ portalDataRouter.post(
|
|||||||
const res = await kbdbFetch(c.env, "/records", {
|
const res = await kbdbFetch(c.env, "/records", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ template: body.template, values, owner_id: portalTenant(c.env) })
|
body: JSON.stringify({ template: body.template, values, owner_id: ownerField(knowledgeOwner(c.env)) })
|
||||||
});
|
});
|
||||||
return new Response(res.body, { status: res.status, headers: { "Content-Type": "application/json" } });
|
return new Response(res.body, { status: res.status, headers: { "Content-Type": "application/json" } });
|
||||||
})
|
})
|
||||||
@@ -15520,7 +15604,7 @@ portalDataRouter.get(
|
|||||||
(c) => run(c, async () => {
|
(c) => run(c, async () => {
|
||||||
const auth = await requirePortalUser(c);
|
const auth = await requirePortalUser(c);
|
||||||
if (!auth.ok) return auth.res;
|
if (!auth.ok) return auth.res;
|
||||||
const tenant2 = portalTenant(c.env);
|
const tenant2 = knowledgeOwner(c.env);
|
||||||
const core = await buildDiagnostics(c.env, tenant2);
|
const core = await buildDiagnostics(c.env, tenant2);
|
||||||
return c.json({
|
return c.json({
|
||||||
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
||||||
@@ -15570,10 +15654,10 @@ app.route("/", consoleAuthRouter);
|
|||||||
app.route("/", consoleDashboardRouter);
|
app.route("/", consoleDashboardRouter);
|
||||||
app.route("/", portalRouter);
|
app.route("/", portalRouter);
|
||||||
app.route("/", portalDataRouter);
|
app.route("/", portalDataRouter);
|
||||||
var index_default = {
|
var src_default = {
|
||||||
fetch: app.fetch,
|
fetch: app.fetch,
|
||||||
scheduled: handleScheduled
|
scheduled: handleScheduled
|
||||||
};
|
};
|
||||||
export {
|
export {
|
||||||
index_default as default
|
src_default as default
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1427,7 +1427,7 @@ var Hono = class _Hono {
|
|||||||
var emptyParam = [];
|
var emptyParam = [];
|
||||||
function match(method, path) {
|
function match(method, path) {
|
||||||
const matchers = this.buildAllMatchers();
|
const matchers = this.buildAllMatchers();
|
||||||
const match2 = ((method2, path2) => {
|
const match2 = (method2, path2) => {
|
||||||
const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
|
const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
|
||||||
const staticMatch = matcher[2][path2];
|
const staticMatch = matcher[2][path2];
|
||||||
if (staticMatch) {
|
if (staticMatch) {
|
||||||
@@ -1439,7 +1439,7 @@ function match(method, path) {
|
|||||||
}
|
}
|
||||||
const index = match3.indexOf("", 1);
|
const index = match3.indexOf("", 1);
|
||||||
return [matcher[1][index], match3];
|
return [matcher[1][index], match3];
|
||||||
});
|
};
|
||||||
this.match = match2;
|
this.match = match2;
|
||||||
return match2(method, path);
|
return match2(method, path);
|
||||||
}
|
}
|
||||||
@@ -2522,7 +2522,7 @@ app.post("/", async (c) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
var index_default = app;
|
var src_default = app;
|
||||||
async function runWasm(input) {
|
async function runWasm(input) {
|
||||||
const hostFunctions = {
|
const hostFunctions = {
|
||||||
http_request: async (url, method, headersJson, body) => {
|
http_request: async (url, method, headersJson, body) => {
|
||||||
@@ -2568,5 +2568,5 @@ async function runWasm(input) {
|
|||||||
return JSON.parse(stdout);
|
return JSON.parse(stdout);
|
||||||
}
|
}
|
||||||
export {
|
export {
|
||||||
index_default as default
|
src_default as default
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1444,7 +1444,7 @@ var Hono = class _Hono {
|
|||||||
var emptyParam = [];
|
var emptyParam = [];
|
||||||
function match(method, path) {
|
function match(method, path) {
|
||||||
const matchers = this.buildAllMatchers();
|
const matchers = this.buildAllMatchers();
|
||||||
const match2 = ((method2, path2) => {
|
const match2 = (method2, path2) => {
|
||||||
const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
|
const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
|
||||||
const staticMatch = matcher[2][path2];
|
const staticMatch = matcher[2][path2];
|
||||||
if (staticMatch) {
|
if (staticMatch) {
|
||||||
@@ -1456,7 +1456,7 @@ function match(method, path) {
|
|||||||
}
|
}
|
||||||
const index = match3.indexOf("", 1);
|
const index = match3.indexOf("", 1);
|
||||||
return [matcher[1][index], match3];
|
return [matcher[1][index], match3];
|
||||||
});
|
};
|
||||||
this.match = match2;
|
this.match = match2;
|
||||||
return match2(method, path);
|
return match2(method, path);
|
||||||
}
|
}
|
||||||
@@ -4157,7 +4157,7 @@ app.route("/recipe-stats", recipeStatRoutes);
|
|||||||
app.route("/execution-log", executionLogRoutes);
|
app.route("/execution-log", executionLogRoutes);
|
||||||
app.route("/embed", embedRoutes);
|
app.route("/embed", embedRoutes);
|
||||||
app.route("/map", mapRoutes);
|
app.route("/map", mapRoutes);
|
||||||
var index_default = app;
|
var src_default = app;
|
||||||
export {
|
export {
|
||||||
index_default as default
|
src_default as default
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,11 +5,7 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
|
|||||||
var __getProtoOf = Object.getPrototypeOf;
|
var __getProtoOf = Object.getPrototypeOf;
|
||||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||||
var __commonJS = (cb, mod) => function __require() {
|
var __commonJS = (cb, mod) => function __require() {
|
||||||
try {
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
||||||
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
||||||
} catch (e) {
|
|
||||||
throw mod = 0, e;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
var __export = (target, all) => {
|
var __export = (target, all) => {
|
||||||
for (var name in all)
|
for (var name in all)
|
||||||
@@ -8227,7 +8223,7 @@ var Hono = class _Hono {
|
|||||||
var emptyParam = [];
|
var emptyParam = [];
|
||||||
function match(method, path) {
|
function match(method, path) {
|
||||||
const matchers = this.buildAllMatchers();
|
const matchers = this.buildAllMatchers();
|
||||||
const match2 = ((method2, path2) => {
|
const match2 = (method2, path2) => {
|
||||||
const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
|
const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
|
||||||
const staticMatch = matcher[2][path2];
|
const staticMatch = matcher[2][path2];
|
||||||
if (staticMatch) {
|
if (staticMatch) {
|
||||||
@@ -8239,7 +8235,7 @@ function match(method, path) {
|
|||||||
}
|
}
|
||||||
const index = match3.indexOf("", 1);
|
const index = match3.indexOf("", 1);
|
||||||
return [matcher[1][index], match3];
|
return [matcher[1][index], match3];
|
||||||
});
|
};
|
||||||
this.match = match2;
|
this.match = match2;
|
||||||
return match2(method, path);
|
return match2(method, path);
|
||||||
}
|
}
|
||||||
@@ -12963,7 +12959,7 @@ ZodNaN.create = (params) => {
|
|||||||
...processCreateParams(params)
|
...processCreateParams(params)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
var BRAND = /* @__PURE__ */ Symbol("zod_brand");
|
var BRAND = Symbol("zod_brand");
|
||||||
var ZodBranded = class extends ZodType {
|
var ZodBranded = class extends ZodType {
|
||||||
_parse(input) {
|
_parse(input) {
|
||||||
const { ctx } = this._processInputParams(input);
|
const { ctx } = this._processInputParams(input);
|
||||||
@@ -13165,14 +13161,14 @@ var ostring = () => stringType().optional();
|
|||||||
var onumber = () => numberType().optional();
|
var onumber = () => numberType().optional();
|
||||||
var oboolean = () => booleanType().optional();
|
var oboolean = () => booleanType().optional();
|
||||||
var coerce = {
|
var coerce = {
|
||||||
string: ((arg) => ZodString.create({ ...arg, coerce: true })),
|
string: (arg) => ZodString.create({ ...arg, coerce: true }),
|
||||||
number: ((arg) => ZodNumber.create({ ...arg, coerce: true })),
|
number: (arg) => ZodNumber.create({ ...arg, coerce: true }),
|
||||||
boolean: ((arg) => ZodBoolean.create({
|
boolean: (arg) => ZodBoolean.create({
|
||||||
...arg,
|
...arg,
|
||||||
coerce: true
|
coerce: true
|
||||||
})),
|
}),
|
||||||
bigint: ((arg) => ZodBigInt.create({ ...arg, coerce: true })),
|
bigint: (arg) => ZodBigInt.create({ ...arg, coerce: true }),
|
||||||
date: ((arg) => ZodDate.create({ ...arg, coerce: true }))
|
date: (arg) => ZodDate.create({ ...arg, coerce: true })
|
||||||
};
|
};
|
||||||
var NEVER = INVALID;
|
var NEVER = INVALID;
|
||||||
|
|
||||||
@@ -13223,6 +13219,7 @@ function $constructor(name, initializer3, params) {
|
|||||||
Object.defineProperty(_, "name", { value: name });
|
Object.defineProperty(_, "name", { value: name });
|
||||||
return _;
|
return _;
|
||||||
}
|
}
|
||||||
|
var $brand = Symbol("zod_brand");
|
||||||
var $ZodAsyncError = class extends Error {
|
var $ZodAsyncError = class extends Error {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`);
|
super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`);
|
||||||
@@ -15726,6 +15723,8 @@ function en_default2() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// mcp/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/registries.js
|
// mcp/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/registries.js
|
||||||
|
var $output = Symbol("ZodOutput");
|
||||||
|
var $input = Symbol("ZodInput");
|
||||||
var $ZodRegistry = class {
|
var $ZodRegistry = class {
|
||||||
constructor() {
|
constructor() {
|
||||||
this._map = /* @__PURE__ */ new Map();
|
this._map = /* @__PURE__ */ new Map();
|
||||||
@@ -17003,10 +17002,10 @@ var ZodMiniType = /* @__PURE__ */ $constructor("ZodMiniType", (inst, def) => {
|
|||||||
};
|
};
|
||||||
inst.clone = (_def, params) => clone(inst, _def, params);
|
inst.clone = (_def, params) => clone(inst, _def, params);
|
||||||
inst.brand = () => inst;
|
inst.brand = () => inst;
|
||||||
inst.register = ((reg, meta) => {
|
inst.register = (reg, meta) => {
|
||||||
reg.add(inst, meta);
|
reg.add(inst, meta);
|
||||||
return inst;
|
return inst;
|
||||||
});
|
};
|
||||||
});
|
});
|
||||||
var ZodMiniObject = /* @__PURE__ */ $constructor("ZodMiniObject", (inst, def) => {
|
var ZodMiniObject = /* @__PURE__ */ $constructor("ZodMiniObject", (inst, def) => {
|
||||||
$ZodObject.init(inst, def);
|
$ZodObject.init(inst, def);
|
||||||
@@ -17269,10 +17268,10 @@ var ZodType2 = /* @__PURE__ */ $constructor("ZodType", (inst, def) => {
|
|||||||
};
|
};
|
||||||
inst.clone = (def2, params) => clone(inst, def2, params);
|
inst.clone = (def2, params) => clone(inst, def2, params);
|
||||||
inst.brand = () => inst;
|
inst.brand = () => inst;
|
||||||
inst.register = ((reg, meta) => {
|
inst.register = (reg, meta) => {
|
||||||
reg.add(inst, meta);
|
reg.add(inst, meta);
|
||||||
return inst;
|
return inst;
|
||||||
});
|
};
|
||||||
inst.parse = (data, params) => parse2(inst, data, params, { callee: inst.parse });
|
inst.parse = (data, params) => parse2(inst, data, params, { callee: inst.parse });
|
||||||
inst.safeParse = (data, params) => safeParse3(inst, data, params);
|
inst.safeParse = (data, params) => safeParse3(inst, data, params);
|
||||||
inst.parseAsync = async (data, params) => parseAsync2(inst, data, params, { callee: inst.parseAsync });
|
inst.parseAsync = async (data, params) => parseAsync2(inst, data, params, { callee: inst.parseAsync });
|
||||||
@@ -19244,13 +19243,11 @@ function assertCompleteRequestPrompt(request) {
|
|||||||
if (request.params.ref.type !== "ref/prompt") {
|
if (request.params.ref.type !== "ref/prompt") {
|
||||||
throw new TypeError(`Expected CompleteRequestPrompt, but got ${request.params.ref.type}`);
|
throw new TypeError(`Expected CompleteRequestPrompt, but got ${request.params.ref.type}`);
|
||||||
}
|
}
|
||||||
void request;
|
|
||||||
}
|
}
|
||||||
function assertCompleteRequestResourceTemplate(request) {
|
function assertCompleteRequestResourceTemplate(request) {
|
||||||
if (request.params.ref.type !== "ref/resource") {
|
if (request.params.ref.type !== "ref/resource") {
|
||||||
throw new TypeError(`Expected CompleteRequestResourceTemplate, but got ${request.params.ref.type}`);
|
throw new TypeError(`Expected CompleteRequestResourceTemplate, but got ${request.params.ref.type}`);
|
||||||
}
|
}
|
||||||
void request;
|
|
||||||
}
|
}
|
||||||
var CompleteResultSchema = ResultSchema.extend({
|
var CompleteResultSchema = ResultSchema.extend({
|
||||||
completion: looseObject({
|
completion: looseObject({
|
||||||
@@ -19403,7 +19400,7 @@ function isTerminal(status) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// mcp/node_modules/.pnpm/zod-to-json-schema@3.25.2_zod@3.25.76/node_modules/zod-to-json-schema/dist/esm/Options.js
|
// mcp/node_modules/.pnpm/zod-to-json-schema@3.25.2_zod@3.25.76/node_modules/zod-to-json-schema/dist/esm/Options.js
|
||||||
var ignoreOverride = /* @__PURE__ */ Symbol("Let zodToJsonSchema decide on which parser to use");
|
var ignoreOverride = Symbol("Let zodToJsonSchema decide on which parser to use");
|
||||||
var defaultOptions = {
|
var defaultOptions = {
|
||||||
name: void 0,
|
name: void 0,
|
||||||
$refStrategy: "root",
|
$refStrategy: "root",
|
||||||
@@ -22379,7 +22376,7 @@ var Server = class extends Protocol {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// mcp/node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/completable.js
|
// mcp/node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/completable.js
|
||||||
var COMPLETABLE_SYMBOL = /* @__PURE__ */ Symbol.for("mcp.completable");
|
var COMPLETABLE_SYMBOL = Symbol.for("mcp.completable");
|
||||||
function isCompletable(schema4) {
|
function isCompletable(schema4) {
|
||||||
return !!schema4 && typeof schema4 === "object" && COMPLETABLE_SYMBOL in schema4;
|
return !!schema4 && typeof schema4 === "object" && COMPLETABLE_SYMBOL in schema4;
|
||||||
}
|
}
|
||||||
@@ -24694,13 +24691,13 @@ function registerAllIntrospectionTools(server, env) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// mcp/node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/nodes/identity.js
|
// mcp/node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/nodes/identity.js
|
||||||
var ALIAS = /* @__PURE__ */ Symbol.for("yaml.alias");
|
var ALIAS = Symbol.for("yaml.alias");
|
||||||
var DOC = /* @__PURE__ */ Symbol.for("yaml.document");
|
var DOC = Symbol.for("yaml.document");
|
||||||
var MAP = /* @__PURE__ */ Symbol.for("yaml.map");
|
var MAP = Symbol.for("yaml.map");
|
||||||
var PAIR = /* @__PURE__ */ Symbol.for("yaml.pair");
|
var PAIR = Symbol.for("yaml.pair");
|
||||||
var SCALAR = /* @__PURE__ */ Symbol.for("yaml.scalar");
|
var SCALAR = Symbol.for("yaml.scalar");
|
||||||
var SEQ = /* @__PURE__ */ Symbol.for("yaml.seq");
|
var SEQ = Symbol.for("yaml.seq");
|
||||||
var NODE_TYPE = /* @__PURE__ */ Symbol.for("yaml.node.type");
|
var NODE_TYPE = Symbol.for("yaml.node.type");
|
||||||
var isAlias = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === ALIAS;
|
var isAlias = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === ALIAS;
|
||||||
var isDocument = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === DOC;
|
var isDocument = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === DOC;
|
||||||
var isMap = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === MAP;
|
var isMap = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === MAP;
|
||||||
@@ -24730,9 +24727,9 @@ function isNode(node) {
|
|||||||
var hasAnchor = (node) => (isScalar(node) || isCollection(node)) && !!node.anchor;
|
var hasAnchor = (node) => (isScalar(node) || isCollection(node)) && !!node.anchor;
|
||||||
|
|
||||||
// mcp/node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/visit.js
|
// mcp/node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/visit.js
|
||||||
var BREAK = /* @__PURE__ */ Symbol("break visit");
|
var BREAK = Symbol("break visit");
|
||||||
var SKIP = /* @__PURE__ */ Symbol("skip children");
|
var SKIP = Symbol("skip children");
|
||||||
var REMOVE = /* @__PURE__ */ Symbol("remove node");
|
var REMOVE = Symbol("remove node");
|
||||||
function visit(node, visitor) {
|
function visit(node, visitor) {
|
||||||
const visitor_ = initVisitor(visitor);
|
const visitor_ = initVisitor(visitor);
|
||||||
if (isDocument(node)) {
|
if (isDocument(node)) {
|
||||||
@@ -29325,9 +29322,9 @@ ${end.comment}` : end.comment;
|
|||||||
};
|
};
|
||||||
|
|
||||||
// mcp/node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/parse/cst-visit.js
|
// mcp/node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/parse/cst-visit.js
|
||||||
var BREAK2 = /* @__PURE__ */ Symbol("break visit");
|
var BREAK2 = Symbol("break visit");
|
||||||
var SKIP2 = /* @__PURE__ */ Symbol("skip children");
|
var SKIP2 = Symbol("skip children");
|
||||||
var REMOVE2 = /* @__PURE__ */ Symbol("remove item");
|
var REMOVE2 = Symbol("remove item");
|
||||||
function visit2(cst, visitor) {
|
function visit2(cst, visitor) {
|
||||||
if ("type" in cst && cst.type === "document")
|
if ("type" in cst && cst.type === "document")
|
||||||
cst = { start: cst.start, value: cst.value };
|
cst = { start: cst.start, value: cst.value };
|
||||||
@@ -33599,7 +33596,7 @@ app.post("/", partnerAuthMiddleware, async (c) => {
|
|||||||
const identity = resolveKnowledgeIdentity(c.get("auth_path"), c.get("portal"));
|
const identity = resolveKnowledgeIdentity(c.get("auth_path"), c.get("portal"));
|
||||||
return handleMcpRequest(c.req.raw, c.env, orgNamespace, partnerToken, identity);
|
return handleMcpRequest(c.req.raw, c.env, orgNamespace, partnerToken, identity);
|
||||||
});
|
});
|
||||||
var index_default = _app;
|
var src_default = _app;
|
||||||
export {
|
export {
|
||||||
index_default as default
|
src_default as default
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
{
|
{
|
||||||
"schema": 1,
|
"schema": 1,
|
||||||
"built_for": "arcrun-tier2-worker-artifacts",
|
"built_for": "arcrun-tier2-worker-artifacts",
|
||||||
"generated_at": "2026-08-12T14:22:56.880Z",
|
"generated_at": "2026-08-12T16:16:33.433Z",
|
||||||
"repo_head": "21293568d550ab7ef50cec2020165d8bf4376104",
|
"repo_head": "b223a698844be289c1b01f99eb34a8e2ac85bb74",
|
||||||
"repo_dirty": false,
|
"repo_dirty": false,
|
||||||
"workers": [
|
"workers": [
|
||||||
{
|
{
|
||||||
"name": "arcrun-cypher-executor",
|
"name": "arcrun-cypher-executor",
|
||||||
"source_dir": "cypher-executor",
|
"source_dir": "cypher-executor",
|
||||||
"source_commit": "53b05c6d3d4a5a02661880dd5565fa9feb743bb6",
|
"source_commit": "b223a698844be289c1b01f99eb34a8e2ac85bb74",
|
||||||
"main_module": "worker.mjs",
|
"main_module": "worker.mjs",
|
||||||
"main_file": "arcrun-cypher-executor/worker.mjs",
|
"main_file": "arcrun-cypher-executor/worker.mjs",
|
||||||
"js_bytes": 584721,
|
"js_bytes": 588397,
|
||||||
"content_sha256": "b7c810d7654c05d197abe9a37acce2ed5f77e1af09595d5b4316b69166edf11a",
|
"content_sha256": "e0026a23792f8b6b02e35c91081604b9a761c608a2cae0e6ee8ab0f34a484501",
|
||||||
"modules": [],
|
"modules": [],
|
||||||
"compat_date": "2025-02-19",
|
"compat_date": "2025-02-19",
|
||||||
"compat_flags": [
|
"compat_flags": [
|
||||||
@@ -61,8 +61,8 @@
|
|||||||
"source_commit": "f87d0e92f49690253e7c89c5badc82a08eb5d21b",
|
"source_commit": "f87d0e92f49690253e7c89c5badc82a08eb5d21b",
|
||||||
"main_module": "worker.mjs",
|
"main_module": "worker.mjs",
|
||||||
"main_file": "arcrun-kbdb/worker.mjs",
|
"main_file": "arcrun-kbdb/worker.mjs",
|
||||||
"js_bytes": 149797,
|
"js_bytes": 149791,
|
||||||
"content_sha256": "8b23853cbc88aee0ca15ef20ca46e92bd8e75064cd311af2847f4d51811960b1",
|
"content_sha256": "9c6d41895d78cbf3048fb539690591c86071b7d6ebf7fb0b8b51607c6b1f8ee9",
|
||||||
"modules": [],
|
"modules": [],
|
||||||
"compat_date": "2025-02-19",
|
"compat_date": "2025-02-19",
|
||||||
"compat_flags": [
|
"compat_flags": [
|
||||||
@@ -90,8 +90,8 @@
|
|||||||
"source_commit": "1e85dfb49b0e8d81c0854781d93ee4e6a300c7b3",
|
"source_commit": "1e85dfb49b0e8d81c0854781d93ee4e6a300c7b3",
|
||||||
"main_module": "worker.mjs",
|
"main_module": "worker.mjs",
|
||||||
"main_file": "arcrun-http-request/worker.mjs",
|
"main_file": "arcrun-http-request/worker.mjs",
|
||||||
"js_bytes": 80079,
|
"js_bytes": 80073,
|
||||||
"content_sha256": "cdd97364f277587cbade69e09bb40812c68f26a1e8bc9aa632c65b1b962b0b85",
|
"content_sha256": "9a9dcb71879a7bdfd9fec1bd94eb9742e12cb63733d822ce63eeb1be30008d15",
|
||||||
"modules": [
|
"modules": [
|
||||||
{
|
{
|
||||||
"name": "component.wasm",
|
"name": "component.wasm",
|
||||||
@@ -122,8 +122,8 @@
|
|||||||
"source_commit": "621cb8d948d61be6202063fd02effb3f538437fe",
|
"source_commit": "621cb8d948d61be6202063fd02effb3f538437fe",
|
||||||
"main_module": "worker.mjs",
|
"main_module": "worker.mjs",
|
||||||
"main_file": "arcrun-code/worker.mjs",
|
"main_file": "arcrun-code/worker.mjs",
|
||||||
"js_bytes": 153758,
|
"js_bytes": 153671,
|
||||||
"content_sha256": "751634a3fc9a99cc2da662026818d754c48f031d10bff3b3be2d3a8ee2311bd6",
|
"content_sha256": "285a7406ec694ae47dccfaf48517f712c74d207a1689dffa15c39f1555b45be5",
|
||||||
"modules": [
|
"modules": [
|
||||||
{
|
{
|
||||||
"name": "quickjs.wasm",
|
"name": "quickjs.wasm",
|
||||||
@@ -151,8 +151,8 @@
|
|||||||
"source_commit": "10d150ac2b4385af95a457f3c411430c4a146cf9",
|
"source_commit": "10d150ac2b4385af95a457f3c411430c4a146cf9",
|
||||||
"main_module": "worker.mjs",
|
"main_module": "worker.mjs",
|
||||||
"main_file": "arcrun-mcp/worker.mjs",
|
"main_file": "arcrun-mcp/worker.mjs",
|
||||||
"js_bytes": 1179487,
|
"js_bytes": 1179229,
|
||||||
"content_sha256": "1cd4c4d079d72bf7cba7c490ba6a88476f70b3ea51af7e5c93f9a184ae3c0ce6",
|
"content_sha256": "3ebc0d441bc04ae56a1205507da701f93bed9efa9b1e53c1777c04cbef5bdb67",
|
||||||
"modules": [],
|
"modules": [],
|
||||||
"compat_date": "2024-11-27",
|
"compat_date": "2024-11-27",
|
||||||
"compat_flags": [
|
"compat_flags": [
|
||||||
|
|||||||
+2
-3
@@ -8,12 +8,11 @@
|
|||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm run build:harness && npm run check:harness && npm run check:rule && tsc",
|
"build": "npm run build:harness && npm run check:harness && tsc",
|
||||||
"build:harness": "node scripts/build-harness-skill.mjs",
|
"build:harness": "node scripts/build-harness-skill.mjs",
|
||||||
"check:harness": "node scripts/check-harness-generation.mjs",
|
"check:harness": "node scripts/check-harness-generation.mjs",
|
||||||
"check:rule": "node ../scripts/sync-resource-rule.mjs --check",
|
|
||||||
"dev": "tsc --watch",
|
"dev": "tsc --watch",
|
||||||
"test": "npm run check:rule && node --experimental-transform-types --import ./tests/register-ts-hooks.mjs --test \"tests/**/*.test.ts\"",
|
"test": "node --experimental-transform-types --import ./tests/register-ts-hooks.mjs --test \"tests/**/*.test.ts\"",
|
||||||
"prepublishOnly": "npm run build && chmod +x dist/index.js"
|
"prepublishOnly": "npm run build && chmod +x dist/index.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { loadConfig } from '../lib/config.js';
|
|||||||
import {
|
import {
|
||||||
wranglerAvailable,
|
wranglerAvailable,
|
||||||
downloadAndDeploy,
|
downloadAndDeploy,
|
||||||
|
namespaceHasKnowledge,
|
||||||
type DeployContext,
|
type DeployContext,
|
||||||
} from '../lib/deploy.js';
|
} from '../lib/deploy.js';
|
||||||
|
|
||||||
@@ -63,6 +64,32 @@ export async function cmdUpdate(opts: { force?: boolean } = {}): Promise<void> {
|
|||||||
kbdbEmbed: config.kbdb_embed !== false,
|
kbdbEmbed: config.kbdb_embed !== false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Arcrun#108:把「你的知識住在哪個命名空間」同步給雲端——但**先驗再寫**。
|
||||||
|
//
|
||||||
|
// 病灶:你 push 工作流、小幫手上傳知識、MCP 查詢,用的都是 config 的 `api_key`;
|
||||||
|
// 而 cypher 讀藏書地圖/搜尋/工作流時,過濾用的 owner_id 來自 worker 的環境變數
|
||||||
|
// (repo toml 帶的官方預設 `CONSOLE_TENANT = "leo"`)。兩個來源對不上 ⇒ 你的東西全被濾掉。
|
||||||
|
//
|
||||||
|
// 為什麼不無條件寫:一鍵安裝的實例,知識可能本來就寫在 `CONSOLE_TENANT` 底下。
|
||||||
|
// 無條件蓋成本機 api_key,會把一台**原本正常**的實例指向空的那一格
|
||||||
|
// ——那就是 #97/#106 那類「更新一次把人家的東西弄不見」。所以查得到才寫,查不到就不碰。
|
||||||
|
if (config.api_key && config.cypher_executor_url) {
|
||||||
|
process.stdout.write(chalk.gray(' → 核對雲端要用哪個知識命名空間...'));
|
||||||
|
const hasKnowledge = await namespaceHasKnowledge(config.cypher_executor_url, config.api_key);
|
||||||
|
if (hasKnowledge === true) {
|
||||||
|
ctx.knowledgeNamespace = config.api_key;
|
||||||
|
console.log(chalk.green(' ✓'));
|
||||||
|
console.log(chalk.gray(` ARCRUN_NAMESPACE = ${config.api_key}(這個命名空間底下查得到你的知識庫)`));
|
||||||
|
} else if (hasKnowledge === false) {
|
||||||
|
console.log(chalk.yellow(' ⚠'));
|
||||||
|
console.log(chalk.gray(` ${config.api_key} 底下目前查不到任何知識庫 → 這趟不動雲端的命名空間設定`));
|
||||||
|
console.log(chalk.gray(' (若藏書地圖是空的,請把這行連同 acr update 的輸出一起回報)'));
|
||||||
|
} else {
|
||||||
|
console.log(chalk.yellow(' ⚠'));
|
||||||
|
console.log(chalk.gray(' 問不到實例(可能正在啟動或版本較舊)→ 這趟不動雲端的命名空間設定'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// mode:'update' → 資源解析在「一顆該更新的 worker 都找不到」時會停手而不是重建一整套
|
// mode:'update' → 資源解析在「一顆該更新的 worker 都找不到」時會停手而不是重建一整套
|
||||||
//(Arcrun#97 的另一道門:名字對不上時別假裝這是全新安裝)。
|
//(Arcrun#97 的另一道門:名字對不上時別假裝這是全新安裝)。
|
||||||
const result = await downloadAndDeploy(ctx, 'main', { force: opts.force, mode: 'update' });
|
const result = await downloadAndDeploy(ctx, 'main', { force: opts.force, mode: 'update' });
|
||||||
|
|||||||
+159
-52
@@ -3,8 +3,7 @@
|
|||||||
* 使用 CF REST API 直接存取用戶的 KV namespace,不依賴 Wrangler CLI
|
* 使用 CF REST API 直接存取用戶的 KV namespace,不依賴 Wrangler CLI
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createCloudflareResourceApi } from './resource-rule/cf-resource-api.mjs';
|
import type { LiveBinding, ResourceApi, ScriptBindings } from './resource-resolver.js';
|
||||||
import type { ResourceApi, ScriptBindings } from './resource-resolver.js';
|
|
||||||
|
|
||||||
const CF_API_BASE = 'https://api.cloudflare.com/client/v4';
|
const CF_API_BASE = 'https://api.cloudflare.com/client/v4';
|
||||||
|
|
||||||
@@ -87,81 +86,189 @@ export class CfKvClient {
|
|||||||
* 對應 SDD:.agents/specs/arcrun/sdk-and-website/self-hosted-init.md §3 step 1-2
|
* 對應 SDD:.agents/specs/arcrun/sdk-and-website/self-hosted-init.md §3 step 1-2
|
||||||
*/
|
*/
|
||||||
export class CfAccountClient implements ResourceApi {
|
export class CfAccountClient implements ResourceApi {
|
||||||
/**
|
private accountBase: string;
|
||||||
* `ResourceApi` 的七個方法**全部委派**給共用規則附的那支 client
|
private headers: Record<string, string>;
|
||||||
* (`shared/resource-rule/cf-resource-api.mjs`)。
|
|
||||||
*
|
|
||||||
* 🔴 為什麼不是在這裡自己實作一份:判斷一致還不夠,**看到的東西**也要一致。
|
|
||||||
* 兩條路各自寫一份 CF client,只要有一邊把 404 當錯誤、漏了 per_page、少認一種
|
|
||||||
* 欄位名,那一邊就會「看不到既有綁定」——而看不到既有綁定的下一步,依規則就是新建。
|
|
||||||
* Arcrun#97 不需要規則寫錯,眼睛不一樣就足以重演。
|
|
||||||
*/
|
|
||||||
private readonly rule: ReturnType<typeof createCloudflareResourceApi>;
|
|
||||||
|
|
||||||
constructor(accountId: string, apiToken: string) {
|
constructor(accountId: string, apiToken: string) {
|
||||||
this.rule = createCloudflareResourceApi({ accountId, apiToken });
|
this.accountBase = `${CF_API_BASE}/accounts/${accountId}`;
|
||||||
|
this.headers = {
|
||||||
|
'Authorization': `Bearer ${apiToken}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async cf<T>(path: string, init?: RequestInit): Promise<T> {
|
private async cf<T>(path: string, init?: RequestInit): Promise<T> {
|
||||||
const { ok, status, result, error } = await this.rule.cfRaw(path, init);
|
const { ok, status, result, error } = await this.cfRaw<T>(path, init);
|
||||||
if (!ok) throw new Error(`CF API ${path} 失敗:${error ?? `HTTP ${status}`}`);
|
if (!ok) throw new Error(`CF API ${path} 失敗:${error ?? `HTTP ${status}`}`);
|
||||||
return result as T;
|
return result as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 同 cf(),但把 HTTP status 交回呼叫端自己判斷(要區分「404 不存在」和「其他錯誤」時用)。 */
|
||||||
|
private async cfRaw<T>(
|
||||||
|
path: string,
|
||||||
|
init?: RequestInit,
|
||||||
|
): Promise<{ ok: boolean; status: number; result?: T; error?: string }> {
|
||||||
|
const res = await fetch(`${this.accountBase}${path}`, {
|
||||||
|
...init,
|
||||||
|
headers: { ...this.headers, ...(init?.headers ?? {}) },
|
||||||
|
});
|
||||||
|
const data = await res.json().catch(() => null) as
|
||||||
|
| { success: boolean; result: T; errors?: Array<{ message: string }> }
|
||||||
|
| null;
|
||||||
|
if (!res.ok || !data?.success) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
status: res.status,
|
||||||
|
error: data?.errors?.map(e => e.message).filter(Boolean).join('; ') || `HTTP ${res.status}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { ok: true, status: res.status, result: data.result };
|
||||||
|
}
|
||||||
|
|
||||||
/** 驗證 token 能存取此 account(權限不足會在後續建立操作報錯,這裡先確認 account 可達)。*/
|
/** 驗證 token 能存取此 account(權限不足會在後續建立操作報錯,這裡先確認 account 可達)。*/
|
||||||
async verifyAccess(): Promise<void> {
|
async verifyAccess(): Promise<void> {
|
||||||
// GET /accounts/{id} 能通 = token 有此 account 的基本讀權限
|
// GET /accounts/{id} 能通 = token 有此 account 的基本讀權限
|
||||||
await this.cf<{ id: string; name: string }>('');
|
await this.cf<{ id: string; name: string }>('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 列出現有 KV namespace(冪等用:已存在就重用,不重建)。回傳 title → id 對照。*/
|
||||||
|
async listKvNamespaces(): Promise<Map<string, string>> {
|
||||||
|
const result = await this.cf<Array<{ id: string; title: string }>>(
|
||||||
|
'/storage/kv/namespaces?per_page=100',
|
||||||
|
);
|
||||||
|
const map = new Map<string, string>();
|
||||||
|
for (const ns of result) map.set(ns.title, ns.id);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 無條件新建一顆 KV namespace。
|
||||||
|
*
|
||||||
|
* 🔴 Arcrun#97:這裡**故意沒有**「找不到同名就順手建一顆」的 ensure 版本。
|
||||||
|
* 「照名字找 → 找不到 → 新建 → 綁上去」正是把使用者實例洗成空的那條路
|
||||||
|
* (安裝器取的名字跟我們的 binding 名不一樣,永遠對不上 ⇒ 每次更新都新建)。
|
||||||
|
* 要不要建,一律先經過 resource-resolver 的 planResources 判斷;那裡只有在
|
||||||
|
* 「確定沒有任何已部署的 worker 綁過這個 binding」時才會排進 create。
|
||||||
|
*/
|
||||||
|
async createKvNamespace(title: string): Promise<string> {
|
||||||
|
const result = await this.cf<{ id: string; title: string }>(
|
||||||
|
'/storage/kv/namespaces',
|
||||||
|
{ method: 'POST', body: JSON.stringify({ title }) },
|
||||||
|
);
|
||||||
|
return result.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 讀一顆已部署 worker 現在綁著哪些資源——**使用者那側的事實**(Arcrun#97 的唯一真相源)。
|
||||||
|
* CF:`GET /accounts/{id}/workers/scripts/{script}/settings` → `result.bindings[]`。
|
||||||
|
*
|
||||||
|
* - script 不存在(404)→ `{ deployed: false }`,這是「還沒部署」,不是錯誤。
|
||||||
|
* - 其他任何失敗 → throw。呼叫端必須把它當「我不知道」而**不是**「它沒有」——
|
||||||
|
* 把查不到當成不存在,就是 #97 的根因。
|
||||||
|
*/
|
||||||
|
async getScriptBindings(script: string): Promise<ScriptBindings> {
|
||||||
|
const path = `/workers/scripts/${encodeURIComponent(script)}/settings`;
|
||||||
|
const res = await this.cfRaw<{ bindings?: RawWorkerBinding[] }>(path);
|
||||||
|
if (!res.ok) {
|
||||||
|
if (res.status === 404) return { deployed: false, bindings: [], vars: {} };
|
||||||
|
throw new Error(`讀 ${script} 綁定失敗:${res.error}`);
|
||||||
|
}
|
||||||
|
const raw = res.result?.bindings ?? [];
|
||||||
|
// #106:同一份回應裡也帶著 plain_text var(實測 CF `/settings` 會回 `text` 值)。
|
||||||
|
// 舊版只挑資源類、把 var 整批丟掉 → 重部署等於把它們洗掉。
|
||||||
|
return { deployed: true, bindings: normalizeBindings(raw), vars: normalizeVars(raw) };
|
||||||
|
}
|
||||||
|
|
||||||
/** 查 workers.dev subdomain(cypher-executor WORKER_SUBDOMAIN 用,組對內 component URL)。*/
|
/** 查 workers.dev subdomain(cypher-executor WORKER_SUBDOMAIN 用,組對內 component URL)。*/
|
||||||
async getWorkersSubdomain(): Promise<string> {
|
async getWorkersSubdomain(): Promise<string> {
|
||||||
const result = await this.cf<{ subdomain: string }>('/workers/subdomain');
|
const result = await this.cf<{ subdomain: string }>('/workers/subdomain');
|
||||||
return result.subdomain;
|
return result.subdomain;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 以下七支=`ResourceApi`,一律委派共用規則,**這個檔案不得自己實作** ────────────
|
// D1 (KBDB Base). Free on Workers Free plan, no credit card (kbdb-base Q4 verified).
|
||||||
// (`shared/resource-rule/cf-resource-api.mjs`;委派而非複製的理由見本 class 開頭)
|
async listD1Databases(): Promise<Map<string, string>> {
|
||||||
|
const result = await this.cf<Array<{ uuid: string; name: string }>>('/d1/database?per_page=100');
|
||||||
/** 讀一顆已部署 worker 現在綁著哪些資源——使用者那側的事實(Arcrun#97 的唯一真相源)。 */
|
const map = new Map<string, string>();
|
||||||
getScriptBindings(script: string): Promise<ScriptBindings> {
|
for (const db of result) map.set(db.name, db.uuid);
|
||||||
return this.rule.getScriptBindings(script);
|
return map;
|
||||||
}
|
|
||||||
|
|
||||||
/** 帳號上現有的 KV namespace(title → id)。判斷「綁著的那顆還在不在」用。 */
|
|
||||||
listKvNamespaces(): Promise<Map<string, string>> {
|
|
||||||
return this.rule.listKvNamespaces();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 帳號上現有的 D1(name → uuid)。 */
|
|
||||||
listD1Databases(): Promise<Map<string, string>> {
|
|
||||||
return this.rule.listD1Databases();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 帳號上現有的 Vectorize index 名單。 */
|
|
||||||
listVectorizeIndexes(): Promise<string[]> {
|
|
||||||
return this.rule.listVectorizeIndexes();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 無條件新建一顆 KV namespace。
|
|
||||||
*
|
|
||||||
* 🔴 Arcrun#97:**故意沒有**「找不到同名就順手建一顆」的 ensure 版本。
|
|
||||||
* 「照名字找 → 找不到 → 新建 → 綁上去」正是把使用者實例洗成空的那條路。
|
|
||||||
* 要不要建,一律先經過 planResources;那裡只有在「確定沒有任何已部署的 worker
|
|
||||||
* 綁過這個 binding」時才會排進 create。
|
|
||||||
*/
|
|
||||||
createKvNamespace(title: string): Promise<string> {
|
|
||||||
return this.rule.createKvNamespace(title);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 無條件新建 D1。沒有 ensure 版本,理由同 createKvNamespace(Arcrun#97)。 */
|
/** 無條件新建 D1。沒有 ensure 版本,理由同 createKvNamespace(Arcrun#97)。 */
|
||||||
createD1Database(name: string): Promise<string> {
|
async createD1Database(name: string): Promise<string> {
|
||||||
return this.rule.createD1Database(name);
|
const result = await this.cf<{ uuid: string; name: string }>(
|
||||||
|
'/d1/database',
|
||||||
|
{ method: 'POST', body: JSON.stringify({ name }) },
|
||||||
|
);
|
||||||
|
return result.uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 新建 KBDB embed 用的 Vectorize index。沒有 ensure 版本,理由同上(Arcrun#97)。 */
|
/** 帳號上現有的 Vectorize index 名單(判斷「綁著的那顆還在不在」用)。 */
|
||||||
createVectorizeIndex(name: string): Promise<string> {
|
async listVectorizeIndexes(): Promise<string[]> {
|
||||||
return this.rule.createVectorizeIndex(name);
|
const result = await this.cf<Array<{ name: string }>>('/vectorize/v2/indexes');
|
||||||
|
return (result ?? []).map(i => i.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新建 KBDB embed 用的 Vectorize index(**bge-m3 = 1024 維 / cosine**,見 deploy.ts 常數說明)。
|
||||||
|
* 已存在(409 / already exists)視為成功——並行或重跑不該炸。沒有 ensure 版本:
|
||||||
|
* 「要不要建」由 planResources 判斷,這裡只負責建(Arcrun#97)。
|
||||||
|
*/
|
||||||
|
async createVectorizeIndex(name: string): Promise<string> {
|
||||||
|
const res = await this.cfRaw<{ name: string }>('/vectorize/v2/indexes', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
name,
|
||||||
|
config: { dimensions: 1024, metric: 'cosine' },
|
||||||
|
description: 'arcrun KBDB embed module — bge-m3 1024d (issue #7 / #59)',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (res.ok) return name;
|
||||||
|
const detail = (res.error ?? '').toLowerCase();
|
||||||
|
if (res.status === 409 || /already exists|duplicate|conflict/.test(detail)) return name;
|
||||||
|
throw new Error(`建 Vectorize index ${name} 失敗:${res.error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** CF `/settings` 回的 binding 原始形狀(同一種資源在不同 API 版本欄位名不一,故全都收)。 */
|
||||||
|
interface RawWorkerBinding {
|
||||||
|
type?: string;
|
||||||
|
name?: string;
|
||||||
|
namespace_id?: string;
|
||||||
|
id?: string;
|
||||||
|
database_id?: string;
|
||||||
|
index_name?: string;
|
||||||
|
/** `plain_text` 綁定的值(#106;secret_text 不會回值,本來就讀不到,也不該讀)。 */
|
||||||
|
text?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 抽出已部署 worker 上的 `plain_text` var(#106)。
|
||||||
|
*
|
||||||
|
* 只收 `plain_text`——**`secret_text` 一律不碰**(CF 本來就不回值,也不該被 CLI 搬來搬去;
|
||||||
|
* wrangler deploy 不會動 secret,它們自己會留著)。
|
||||||
|
*/
|
||||||
|
function normalizeVars(raw: RawWorkerBinding[]): Record<string, string> {
|
||||||
|
const out: Record<string, string> = {};
|
||||||
|
for (const b of raw) {
|
||||||
|
if (b?.type === 'plain_text' && b.name && typeof b.text === 'string') out[b.name] = b.text;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 把 CF 的 binding 陣列收斂成 resolver 認得的三種資源。不認得的型別直接略過。 */
|
||||||
|
function normalizeBindings(raw: RawWorkerBinding[]): LiveBinding[] {
|
||||||
|
const out: LiveBinding[] = [];
|
||||||
|
for (const b of raw) {
|
||||||
|
if (!b?.name) continue;
|
||||||
|
if (b.type === 'kv_namespace') {
|
||||||
|
const value = b.namespace_id ?? b.id;
|
||||||
|
if (value) out.push({ kind: 'kv_namespace', binding: b.name, value });
|
||||||
|
} else if (b.type === 'd1' || b.type === 'd1_database') {
|
||||||
|
const value = b.id ?? b.database_id;
|
||||||
|
if (value) out.push({ kind: 'd1', binding: b.name, value });
|
||||||
|
} else if (b.type === 'vectorize') {
|
||||||
|
if (b.index_name) out.push({ kind: 'vectorize', binding: b.name, value: b.index_name });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|||||||
@@ -298,6 +298,44 @@ export interface DeployContext {
|
|||||||
// [[vectorize]]+[ai] binding(取消 wrangler.toml 註解段)→ embed 模組啟用。未設/false → 不建、不注入,
|
// [[vectorize]]+[ai] binding(取消 wrangler.toml 註解段)→ embed 模組啟用。未設/false → 不建、不注入,
|
||||||
// base 維持 LIKE keyword(free-tier 友善)。
|
// base 維持 LIKE keyword(free-tier 友善)。
|
||||||
kbdbEmbed?: boolean;
|
kbdbEmbed?: boolean;
|
||||||
|
/**
|
||||||
|
* Arcrun#108:這台實例的知識命名空間(=`~/.arcrun/config.yaml` 的 `api_key`),
|
||||||
|
* 會寫進 cypher worker 的 `ARCRUN_NAMESPACE` var,讓「讀」用的 owner_id 與「寫」的一致。
|
||||||
|
*
|
||||||
|
* **只在驗證過該 namespace 底下真的有知識時才給值**(見 `resolveKnowledgeNamespace`)——
|
||||||
|
* 給了就會覆蓋 worker 上的既有值,沒給則原封保留(preservedVars)。
|
||||||
|
*/
|
||||||
|
knowledgeNamespace?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 這把 namespace 底下到底有沒有知識?(Arcrun#108 的「先驗再寫」)
|
||||||
|
*
|
||||||
|
* 打的是實例自己的 `GET /kbdb/map?owner_id=<ns>`(cypher 既有的純轉發端點,CLI 平常就在用
|
||||||
|
* 這條路 + `X-Arcrun-API-Key`)。回傳:
|
||||||
|
* true = 這個 namespace 底下查得到庫 → 寫 ARCRUN_NAMESPACE 是安全的
|
||||||
|
* false = 查得到但是空的 → 不寫(可能知識其實在別的命名空間,蓋下去會把畫面弄空)
|
||||||
|
* null = 問不到(實例還沒起來 / 舊版沒這條路 / 網路斷)→ 不寫,也不宣稱任何事
|
||||||
|
*
|
||||||
|
* 誠實邊界:這支只回答「有沒有」,不猜「應該是哪一個」。猜錯的代價是把人家的資料藏起來。
|
||||||
|
*/
|
||||||
|
export async function namespaceHasKnowledge(
|
||||||
|
cypherUrl: string,
|
||||||
|
namespace: string,
|
||||||
|
): Promise<boolean | null> {
|
||||||
|
if (!cypherUrl || !namespace) return null;
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`${cypherUrl.replace(/\/+$/, '')}/kbdb/map?owner_id=${encodeURIComponent(namespace)}`,
|
||||||
|
{ headers: { 'X-Arcrun-API-Key': namespace } },
|
||||||
|
);
|
||||||
|
if (!res.ok) return null;
|
||||||
|
const body = (await res.json().catch(() => null)) as { libraries?: unknown } | null;
|
||||||
|
if (!body || !Array.isArray(body.libraries)) return null;
|
||||||
|
return body.libraries.length > 0;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -561,6 +599,21 @@ export async function downloadAndDeploy(
|
|||||||
vars.ARCRUN_BUNDLE_VERSION = stamp.version;
|
vars.ARCRUN_BUNDLE_VERSION = stamp.version;
|
||||||
if (stamp.commit) vars.ARCRUN_BUNDLE_COMMIT = stamp.commit;
|
if (stamp.commit) vars.ARCRUN_BUNDLE_COMMIT = stamp.commit;
|
||||||
}
|
}
|
||||||
|
// Arcrun#108:把「你的知識實際住在哪個命名空間」告訴雲端。
|
||||||
|
//
|
||||||
|
// 為什麼需要:cypher 讀藏書地圖/搜尋/工作流時要用一個 owner_id 去過濾,而它以前拿的是
|
||||||
|
// repo toml 帶的官方預設值(`CONSOLE_TENANT = "leo"`)。寫入端(CLI push、小幫手上傳、
|
||||||
|
// MCP)用的卻是你 `~/.arcrun/config.yaml` 的 `api_key` ⇒ 兩邊對不上就整個空掉
|
||||||
|
//(leo 實撞:1854 條三元組被過濾成 0 個庫)。
|
||||||
|
//
|
||||||
|
// 🔴 **只在「這個 namespace 底下真的查得到知識」時才寫**(呼叫端已先驗過,見
|
||||||
|
// resolveKnowledgeNamespace)。理由是反過來的那個災難:一鍵安裝的實例,知識可能
|
||||||
|
// 本來就寫在 CONSOLE_TENANT 底下;若這裡無條件蓋成本機 api_key,會把一台**原本正常**
|
||||||
|
// 的實例改成指向空的那一格——跟 #97/#106 同一類「更新一次把人家的東西弄不見」。
|
||||||
|
// 驗不過就不寫;既有值由 preservedVars 原封保留,等於這趟什麼都沒改。
|
||||||
|
if (ctx.knowledgeNamespace && script === VERSION_STAMP_WORKER) {
|
||||||
|
vars.ARCRUN_NAMESPACE = ctx.knowledgeNamespace;
|
||||||
|
}
|
||||||
if (Object.keys(vars).length > 0) extraVarsByDir.set(dir, vars);
|
if (Object.keys(vars).length > 0) extraVarsByDir.set(dir, vars);
|
||||||
}
|
}
|
||||||
if (preservedTotal.length > 0) {
|
if (preservedTotal.length > 0) {
|
||||||
|
|||||||
@@ -1,42 +1,431 @@
|
|||||||
/**
|
/**
|
||||||
* resource-resolver.ts — **這裡沒有邏輯**,只是把共用規則接到 CLI 的既有 import 路徑上。
|
* resource-resolver.ts — 資源解析:「已部署的 worker 現在綁著什麼,那就是事實」
|
||||||
*
|
*
|
||||||
* 「這個實例該用哪些資源」的規則住在 `shared/resource-rule/`(repo 根目錄),
|
* 🔴 Arcrun#97(2026-08-12 實害,leo 的實例中了):
|
||||||
* 那是**唯一一份人手維護的實作**;`./resource-rule/` 是該目錄的逐位元組鏡射
|
* 舊做法叫「照名字 ensure」——`acr update` 拿 **binding 名**(`WEBHOOKS`)當成 Cloudflare 上的
|
||||||
* (`scripts/sync-resource-rule.mjs` 產生,`npm run build` / `npm test` 會跑 `--check` 擋漂移)。
|
* **資源標題**去找,找不到就**新建一顆空的、然後綁到 worker 上**。
|
||||||
* 之所以要有這份鏡射:`arcrun` 是獨立 npm 套件,`npm pack` 打不進套件目錄外的檔案。
|
* 安裝器建的資源不叫那個名字(它叫 `arcrun-rag-<instance>-kv-webhooks`)⇒ 一次例行更新
|
||||||
|
* 新建了 9 顆 KV、1 顆 D1,使用者的工作流/登入狀態/子庫**在畫面上全部消失**。
|
||||||
|
* 資料沒有被刪,但 worker 被綁去空的那幾顆——從使用者的角度,他的東西就是不見了。
|
||||||
*
|
*
|
||||||
* 為什麼規則不在 CLI(leo 2026-08-12):
|
* 根因不是「KV 那段寫錯」,是**「用名字猜使用者的資源」這個做法本身**:
|
||||||
* 「根本就不應該在 CLI,我要的是一個大家都可以用到的規則。」
|
* 名字是**使用者那側的事實**(安裝器要怎麼取名由它決定,而且它有權改),
|
||||||
* ——`acr` 有這條規則、安裝器沒有,結果就是 Arcrun#97:
|
* 我們不能拿自己的命名慣例去對號入座,更不能在對不上的時候自作主張生一顆新的。
|
||||||
* 安裝器照名字找、找不到就建一顆空的綁上去,使用者的工作流與登入狀態整片消失。
|
* ——所以修法不是「多比對幾種名字」,是**不再用名字當識別**。
|
||||||
* 規則搬到共用層之後,安裝器直接 import 同一份原稿,**不再有第二種答案**。
|
|
||||||
*
|
*
|
||||||
* 🔴 不要把任何判斷寫回這個檔案。要改規則 → 改 `shared/resource-rule/rule.mjs`。
|
* ── 新規則(三句話)────────────────────────────────────────────────
|
||||||
|
* 1. **已部署的 worker 上綁著什麼,那就是事實** → 原封不動沿用,不管那顆資源叫什麼名字。
|
||||||
|
* 2. **只有「確定沒有任何人綁過它」才准新建**(新版本新增的 binding、或真的全新帳號)。
|
||||||
|
* 3. **只要有一點說不準就整趟停手**(讀不到綁定/綁著的資源不見了/同一個 binding 指向兩顆/
|
||||||
|
* 該更新的 worker 一顆都不在),**什麼都不建、什麼都不部署**,把話說清楚讓人來判斷。
|
||||||
|
*
|
||||||
|
* ── 為什麼拆成 plan / apply 兩段 ─────────────────────────────────────
|
||||||
|
* `planResources()` **完全不寫入**,只回一份「要沿用什麼、要新建什麼、有什麼不敢動的」。
|
||||||
|
* `applyResourcePlan()` 看到有任何 blocker 就直接拒絕執行。
|
||||||
|
* ⇒「被擋下的時候一顆資源都不會被建出來」是**結構上的保證**,
|
||||||
|
* 不是靠某個人記得在對的地方寫 early return。#97 正是死在「先動手、後判斷」。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export {
|
/** 這支負責的資源種類。要加新種類(R2/Queue/Hyperdrive…)就加在這裡,
|
||||||
planResources,
|
* 一律走同一道門——不准任何呼叫端自己「照名字 ensure」繞過去。 */
|
||||||
applyResourcePlan,
|
export type ResourceKind = 'kv_namespace' | 'd1' | 'vectorize';
|
||||||
parseWranglerRequirements,
|
|
||||||
normalizeLiveBindings,
|
|
||||||
normalizeLiveVars,
|
|
||||||
bindingKey,
|
|
||||||
ResourcePlanBlocked,
|
|
||||||
KIND_LABEL,
|
|
||||||
TABLE_KIND,
|
|
||||||
} from './resource-rule/rule.mjs';
|
|
||||||
|
|
||||||
export type {
|
/** 從已部署 worker 上讀回來的一條綁定。`value`:KV/D1 是資源 id,Vectorize 是 index 名。 */
|
||||||
ResourceKind,
|
export interface LiveBinding {
|
||||||
LiveBinding,
|
kind: ResourceKind;
|
||||||
ScriptBindings,
|
binding: string;
|
||||||
ResourceApi,
|
value: string;
|
||||||
BindingRequirement,
|
}
|
||||||
PlannedAdopt,
|
|
||||||
PlannedCreate,
|
export interface ScriptBindings {
|
||||||
ResourcePlan,
|
/** false = 這顆 worker 在帳號上還不存在(全新部署),不是「讀取失敗」。讀取失敗要 throw。 */
|
||||||
ResolvedResource,
|
deployed: boolean;
|
||||||
WranglerRequirements,
|
bindings: LiveBinding[];
|
||||||
RawWorkerBinding,
|
/**
|
||||||
} from './resource-rule/rule.mjs';
|
* 這顆 worker 現在掛著的 `plain_text` var(名 → 值)。
|
||||||
|
*
|
||||||
|
* 🔴 Arcrun#106:#97 只把「資源類」綁定當成事實沿用(KV/D1/Vectorize),
|
||||||
|
* plain_text var 整批沒人管 ⇒ 重部署把它們洗成 repo toml 的預設值。
|
||||||
|
* 最痛的一個是 `ARCRUN_BUNDLE_VERSION`(安裝器注入的版本標籤)——
|
||||||
|
* 更新完就消失,Portal 設定頁變成「無法讀取目前版本」。
|
||||||
|
* **保留了櫃子,沒保留櫃子上的標籤**。這個欄位就是那些標籤。
|
||||||
|
*/
|
||||||
|
vars?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** resolver 需要的 CF 能力(收窄成介面,方便離線測試餵假帳號)。 */
|
||||||
|
export interface ResourceApi {
|
||||||
|
getScriptBindings(script: string): Promise<ScriptBindings>;
|
||||||
|
/** title → id */
|
||||||
|
listKvNamespaces(): Promise<Map<string, string>>;
|
||||||
|
/** name → uuid */
|
||||||
|
listD1Databases(): Promise<Map<string, string>>;
|
||||||
|
listVectorizeIndexes(): Promise<string[]>;
|
||||||
|
createKvNamespace(title: string): Promise<string>;
|
||||||
|
createD1Database(name: string): Promise<string>;
|
||||||
|
createVectorizeIndex(name: string): Promise<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 「這顆 worker 需要這個 binding」。createName 只在**真的要新建**時才會被拿來當名字用。 */
|
||||||
|
export interface BindingRequirement {
|
||||||
|
kind: ResourceKind;
|
||||||
|
binding: string;
|
||||||
|
/** 需要它的 worker script 名(= wrangler.toml 的 `name`)。 */
|
||||||
|
worker: string;
|
||||||
|
createName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlannedAdopt {
|
||||||
|
kind: ResourceKind;
|
||||||
|
binding: string;
|
||||||
|
value: string;
|
||||||
|
/** 從哪顆已部署的 worker 上讀到的 */
|
||||||
|
from: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlannedCreate {
|
||||||
|
kind: ResourceKind;
|
||||||
|
binding: string;
|
||||||
|
createName: string;
|
||||||
|
wantedBy: string[];
|
||||||
|
/** 其他也指向同一顆資源的 binding(見 shareSameResource)。建一顆,大家共用。 */
|
||||||
|
alsoBind: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResourcePlan {
|
||||||
|
adopt: PlannedAdopt[];
|
||||||
|
create: PlannedCreate[];
|
||||||
|
/** 非空 = 整趟停手。applyResourcePlan 會拒絕執行。 */
|
||||||
|
blockers: string[];
|
||||||
|
/**
|
||||||
|
* 每顆**已部署** worker 現在掛著的 plain_text var(script → 名/值)。未部署的不在裡面。
|
||||||
|
*
|
||||||
|
* Arcrun#106:讀綁定的時候本來就把整份 `bindings[]` 拿回來了,var 就在同一份回應裡——
|
||||||
|
* 順手帶出來,**不另外打一次 API**,也不新增一種「查不到」的失敗模式
|
||||||
|
* (讀不到綁定這件事已經在上面 blockers 那一關擋掉了)。
|
||||||
|
*/
|
||||||
|
liveVars: Map<string, Record<string, string>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResolvedResource {
|
||||||
|
kind: ResourceKind;
|
||||||
|
binding: string;
|
||||||
|
value: string;
|
||||||
|
origin: 'adopted' | 'created';
|
||||||
|
from?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** plan 被擋下時丟這個,讓呼叫端能把每一條原因原文轉給使用者。 */
|
||||||
|
export class ResourcePlanBlocked extends Error {
|
||||||
|
constructor(readonly blockers: string[]) {
|
||||||
|
super(`資源解析被擋下(${blockers.length} 項)`);
|
||||||
|
this.name = 'ResourcePlanBlocked';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bindingKey(kind: ResourceKind, binding: string): string {
|
||||||
|
return `${kind}:${binding}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const KIND_LABEL: Record<ResourceKind, string> = {
|
||||||
|
kv_namespace: 'KV namespace',
|
||||||
|
d1: 'D1 資料庫',
|
||||||
|
vectorize: 'Vectorize index',
|
||||||
|
};
|
||||||
|
|
||||||
|
function msg(e: unknown): string {
|
||||||
|
return e instanceof Error ? e.message : String(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 決定每個 binding 要沿用哪顆資源/要不要新建,**不寫入任何東西**。
|
||||||
|
*
|
||||||
|
* @param mode 'update' = 這台照定義已經裝過了(見下方「一顆都不在」規則);'init' = 全新安裝,允許從零建。
|
||||||
|
*/
|
||||||
|
export async function planResources(
|
||||||
|
api: ResourceApi,
|
||||||
|
requirements: readonly BindingRequirement[],
|
||||||
|
mode: 'update' | 'init',
|
||||||
|
): Promise<ResourcePlan> {
|
||||||
|
const blockers: string[] = [];
|
||||||
|
const adopt: PlannedAdopt[] = [];
|
||||||
|
const create: PlannedCreate[] = [];
|
||||||
|
|
||||||
|
// ── 1. 先讀「即將被覆蓋的每一顆 worker」現在綁著什麼 ──────────────────
|
||||||
|
// 讀取失敗 ≠ 沒有綁。#97 的災情就是把「我查不到」當成「它不存在」。
|
||||||
|
const scripts = [...new Set(requirements.map((r) => r.worker))].sort();
|
||||||
|
const live = new Map<string, LiveBinding[]>();
|
||||||
|
const liveVars = new Map<string, Record<string, string>>();
|
||||||
|
let readFailed = false;
|
||||||
|
for (const script of scripts) {
|
||||||
|
try {
|
||||||
|
const res = await api.getScriptBindings(script);
|
||||||
|
if (res.deployed) {
|
||||||
|
live.set(script, res.bindings);
|
||||||
|
// #106:同一份回應裡的 plain_text var 一起收下(呼叫端要拿它決定哪些 var 該沿用)。
|
||||||
|
liveVars.set(script, res.vars ?? {});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
readFailed = true;
|
||||||
|
blockers.push(
|
||||||
|
`讀不到已部署的 worker「${script}」目前綁著哪些資源(${msg(e)})。` +
|
||||||
|
`不確定它現在用的是哪一顆,就不能重新綁——整趟更新停手,沒有動任何東西。`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 「這台照定義已經裝過了,卻一顆 worker 都找不到」= 我對不上它的實例(名字不同/token 看不到)。
|
||||||
|
// 這種時候繼續走下去,等於把一整套資源重新生一遍再綁上去——正是 #97 的形狀,只是換一道門進來。
|
||||||
|
if (mode === 'update' && !readFailed && live.size === 0 && scripts.length > 0) {
|
||||||
|
blockers.push(
|
||||||
|
`在這個 Cloudflare 帳號上找不到任何一顆要更新的 worker(找過:${scripts.join('、')})。` +
|
||||||
|
`acr update 的前提是「這台已經裝好了」——對不上就不猜:` +
|
||||||
|
`可能是 API token 看得到的帳號不對,或這台實例的 worker 用了別的名字。` +
|
||||||
|
`已停手,沒有新建任何資源。`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 2. 逐個 binding 決定:沿用 / 新建 / 停手 ─────────────────────────
|
||||||
|
const byKey = new Map<string, BindingRequirement[]>();
|
||||||
|
for (const req of requirements) {
|
||||||
|
const key = bindingKey(req.kind, req.binding);
|
||||||
|
const list = byKey.get(key);
|
||||||
|
if (list) list.push(req);
|
||||||
|
else byKey.set(key, [req]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingCache = new Map<ResourceKind, Set<string>>();
|
||||||
|
const listExisting = async (kind: ResourceKind): Promise<Set<string>> => {
|
||||||
|
const hit = existingCache.get(kind);
|
||||||
|
if (hit) return hit;
|
||||||
|
let set: Set<string>;
|
||||||
|
if (kind === 'kv_namespace') set = new Set((await api.listKvNamespaces()).values());
|
||||||
|
else if (kind === 'd1') set = new Set((await api.listD1Databases()).values());
|
||||||
|
else set = new Set(await api.listVectorizeIndexes());
|
||||||
|
existingCache.set(kind, set);
|
||||||
|
return set;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [, reqs] of byKey) {
|
||||||
|
const { kind, binding } = reqs[0];
|
||||||
|
|
||||||
|
const found: Array<{ value: string; script: string }> = [];
|
||||||
|
for (const [script, bindings] of live) {
|
||||||
|
const hit = bindings.find((b) => b.kind === kind && b.binding === binding);
|
||||||
|
if (hit) found.push({ value: hit.value, script });
|
||||||
|
}
|
||||||
|
const distinct = [...new Set(found.map((f) => f.value))];
|
||||||
|
|
||||||
|
// 2a. 同一個 binding 名在不同 worker 上指向不同資源 → 分不出哪個才是使用者要的。
|
||||||
|
// 自己挑一個 = 有一半機率把另外那半的資料從畫面上抹掉。不猜。
|
||||||
|
if (distinct.length > 1) {
|
||||||
|
blockers.push(
|
||||||
|
`綁定「${binding}」在不同 worker 上指向不同的 ${KIND_LABEL[kind]}` +
|
||||||
|
`(${found.map((f) => `${f.script} → ${f.value}`).join('、')})。` +
|
||||||
|
`分不出哪一顆才是你在用的,不猜——停手。`,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2b. 有人綁著它 → 這就是事實,沿用。名字長什麼樣完全不看。
|
||||||
|
if (distinct.length === 1) {
|
||||||
|
const value = distinct[0];
|
||||||
|
let existing: Set<string>;
|
||||||
|
try {
|
||||||
|
existing = await listExisting(kind);
|
||||||
|
} catch (e) {
|
||||||
|
blockers.push(
|
||||||
|
`查不到帳號上的 ${KIND_LABEL[kind]} 清單,無法確認「${binding}」綁著的 ${value} 還在不在` +
|
||||||
|
`(${msg(e)})。不確定就不動——停手。`,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!existing.has(value)) {
|
||||||
|
// 這正是 #97 的入口:舊版在這裡會安靜地新建一顆空的頂上去。
|
||||||
|
blockers.push(
|
||||||
|
`worker「${found[0].script}」的「${binding}」綁著 ${KIND_LABEL[kind]} ${value},` +
|
||||||
|
`但這顆在你的 Cloudflare 帳號上找不到了。` +
|
||||||
|
`這裡**不會**幫你新建一顆空的頂上去(Arcrun#97 的災情就是那樣來的)——` +
|
||||||
|
`請先確認那顆資源是被刪掉了,還是這把 API token 看不到它。`,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
adopt.push({ kind, binding, value, from: found[0].script });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2c. 沒有任何已部署的 worker 綁過它 → 新版本新增的 binding,或全新帳號。
|
||||||
|
// 這種情況下新建不會弄丟任何東西(本來就沒有東西可丟)。
|
||||||
|
create.push({
|
||||||
|
kind,
|
||||||
|
binding,
|
||||||
|
createName: reqs[0].createName,
|
||||||
|
wantedBy: [...new Set(reqs.map((r) => r.worker))],
|
||||||
|
alsoBind: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { adopt, create: shareSameResource(adopt, create, byKey), blockers, liveVars };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收斂「不同 binding 其實是同一顆資源」的情況。
|
||||||
|
*
|
||||||
|
* 判準是 **toml 自己宣告的名字**(`database_name` / `index_name`),不是使用者那側的資源名——
|
||||||
|
* cypher 的 `CREDENTIALS_DB` 與 kbdb 的 `DB` 都寫 `database_name = "arcrun-kbdb"`,
|
||||||
|
* 那是**我們**在宣告「這兩個綁定指向同一顆庫」,跟 #97 那種「拿名字去猜使用者的資源」是兩回事。
|
||||||
|
*
|
||||||
|
* 沒有這一步會出兩種錯:
|
||||||
|
* ① 全新安裝時建出兩顆同名 D1,KBDB 的資料與 credential 目錄從此分家。
|
||||||
|
* ② 一邊已部署(沿用既有)、另一邊沒有(新建一顆空的)→ 半套資料,比全壞更難查。
|
||||||
|
*/
|
||||||
|
function shareSameResource(
|
||||||
|
adopt: PlannedAdopt[],
|
||||||
|
create: PlannedCreate[],
|
||||||
|
byKey: Map<string, BindingRequirement[]>,
|
||||||
|
): PlannedCreate[] {
|
||||||
|
const declaredName = (kind: ResourceKind, binding: string): string | undefined =>
|
||||||
|
byKey.get(bindingKey(kind, binding))?.[0]?.createName;
|
||||||
|
|
||||||
|
const out: PlannedCreate[] = [];
|
||||||
|
const groups = new Map<string, PlannedCreate>();
|
||||||
|
|
||||||
|
for (const c of create) {
|
||||||
|
const groupKey = `${c.kind} | ||||||