@sema-agent/server 7.3.0 → 7.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/USAGE.md +20 -0
- package/dist/approval-reconciler.d.ts +12 -5
- package/dist/boot/coordinators.js +3 -1
- package/dist/boot/deferred-sandbox-path-env.d.ts +99 -0
- package/dist/boot/deferred-sandbox-path-env.js +279 -0
- package/dist/boot/execution-env.js +11 -1
- package/dist/boot/lexical-path-env.d.ts +0 -4
- package/dist/boot/lexical-path-env.js +6 -34
- package/dist/boot/resolve-spec.js +50 -38
- package/dist/capabilities/center-prompts.js +4 -1
- package/dist/capabilities/oa-tools.d.ts +15 -0
- package/dist/capabilities/oa-tools.js +54 -0
- package/dist/config-types.d.ts +8 -1
- package/dist/config.js +9 -1
- package/dist/elicitation.js +5 -1
- package/dist/finance/cost-taxonomy.d.ts +34 -0
- package/dist/finance/cost-taxonomy.js +26 -0
- package/dist/hooks/hook-runner.js +32 -0
- package/dist/http/routes/diagnostics.d.ts +84 -0
- package/dist/http/routes/diagnostics.js +140 -0
- package/dist/http/routes/tasks.js +31 -10
- package/dist/http/server.d.ts +5 -0
- package/dist/http/server.js +6 -0
- package/dist/main.js +34 -3
- package/dist/observability/fail-open.d.ts +98 -0
- package/dist/observability/fail-open.js +216 -0
- package/dist/observability/prompt-manifest.d.ts +13 -0
- package/dist/observability/prompt-manifest.js +8 -0
- package/dist/plugins/approval-store-sql.d.ts +116 -0
- package/dist/plugins/approval-store-sql.js +151 -0
- package/dist/plugins/file-workflow-journal-store.d.ts +12 -0
- package/dist/plugins/file-workflow-journal-store.js +12 -0
- package/dist/plugins/pg-approval-store.d.ts +9 -0
- package/dist/plugins/pg-approval-store.js +9 -0
- package/dist/plugins/pg-breaker-state.d.ts +8 -0
- package/dist/plugins/pg-breaker-state.js +8 -0
- package/dist/plugins/pg-checkpoint-store.d.ts +10 -0
- package/dist/plugins/pg-checkpoint-store.js +10 -0
- package/dist/plugins/pg-file-snapshot-store.d.ts +8 -0
- package/dist/plugins/pg-file-snapshot-store.js +8 -0
- package/dist/plugins/pg-image-bake.d.ts +12 -0
- package/dist/plugins/pg-image-bake.js +11 -0
- package/dist/plugins/pg-image-index.d.ts +12 -0
- package/dist/plugins/pg-image-index.js +11 -0
- package/dist/plugins/pg-outcome-ledger.d.ts +12 -0
- package/dist/plugins/pg-outcome-ledger.js +11 -0
- package/dist/plugins/pg-resume-anchor-store.d.ts +7 -0
- package/dist/plugins/pg-resume-anchor-store.js +7 -0
- package/dist/plugins/pg-run-store.d.ts +9 -0
- package/dist/plugins/pg-run-store.js +9 -0
- package/dist/plugins/pg-session-policy-store.d.ts +7 -0
- package/dist/plugins/pg-session-policy-store.js +7 -0
- package/dist/plugins/pg-session-store.d.ts +12 -0
- package/dist/plugins/pg-session-store.js +12 -0
- package/dist/plugins/pg-tool-result-store.d.ts +9 -0
- package/dist/plugins/pg-tool-result-store.js +9 -0
- package/dist/plugins/pg-workflow-journal-store.d.ts +9 -0
- package/dist/plugins/pg-workflow-journal-store.js +9 -0
- package/dist/plugins/pg-workflow-run-store.d.ts +9 -0
- package/dist/plugins/pg-workflow-run-store.js +9 -0
- package/dist/plugins/tidb-approval-store.d.ts +8 -0
- package/dist/plugins/tidb-approval-store.js +8 -0
- package/dist/plugins/tidb-breaker-state.d.ts +7 -0
- package/dist/plugins/tidb-breaker-state.js +7 -0
- package/dist/plugins/tidb-checkpoint-store.d.ts +9 -0
- package/dist/plugins/tidb-checkpoint-store.js +9 -0
- package/dist/plugins/tidb-file-snapshot-store.d.ts +8 -0
- package/dist/plugins/tidb-file-snapshot-store.js +8 -0
- package/dist/plugins/tidb-image-bake.d.ts +12 -0
- package/dist/plugins/tidb-image-bake.js +11 -0
- package/dist/plugins/tidb-image-index.d.ts +12 -0
- package/dist/plugins/tidb-image-index.js +11 -0
- package/dist/plugins/tidb-outcome-ledger.d.ts +12 -0
- package/dist/plugins/tidb-outcome-ledger.js +12 -0
- package/dist/plugins/tidb-resume-anchor-store.d.ts +7 -0
- package/dist/plugins/tidb-resume-anchor-store.js +7 -0
- package/dist/plugins/tidb-run-store.d.ts +10 -0
- package/dist/plugins/tidb-run-store.js +9 -0
- package/dist/plugins/tidb-session-policy-store.d.ts +7 -0
- package/dist/plugins/tidb-session-policy-store.js +7 -0
- package/dist/plugins/tidb-tool-result-store.d.ts +8 -0
- package/dist/plugins/tidb-tool-result-store.js +10 -0
- package/dist/plugins/tidb-workflow-journal-store.d.ts +9 -0
- package/dist/plugins/tidb-workflow-journal-store.js +9 -0
- package/dist/plugins/tidb-workflow-run-store.d.ts +10 -0
- package/dist/plugins/tidb-workflow-run-store.js +10 -0
- package/dist/plugins/workflow-journal-limits.d.ts +12 -0
- package/dist/plugins/workflow-journal-limits.js +12 -0
- package/dist/question.d.ts +18 -14
- package/dist/question.js +83 -34
- package/dist/sema-registry.d.ts +41 -0
- package/dist/sema-registry.js +40 -0
- package/dist/task-settings.d.ts +13 -12
- package/dist/task-settings.js +6 -6
- package/dist/tool-approval.js +15 -7
- package/dist/trace/project.d.ts +36 -1
- package/dist/trace/project.js +55 -2
- package/package.json +3 -3
|
@@ -48,7 +48,7 @@ import { resolveRequestMcp } from "../task-mcp.js";
|
|
|
48
48
|
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, acceptAppendSystemPrompt, applyTaskSettings, coercePermissionMode, effectiveThinking, hasConstitutionAnchors, parseTaskSettings, providerDropsAppend, withPermissionMode } from "../task-settings.js";
|
|
49
49
|
import { enableForkFromBody, normalizeRetainSubagentSessions, selfOrchestrationFromBody } from "../task-workflow.js";
|
|
50
50
|
import { redactSecrets } from "../trace/redact.js";
|
|
51
|
-
import {
|
|
51
|
+
import { DeferredSandboxPathEnv, isSandboxPathAdjudicationLane, sandboxPathEnvSlots } from "./deferred-sandbox-path-env.js";
|
|
52
52
|
/** #152([2703] 案二):durable 部署上的 AskUserQuestion 门。活体面(QuestionCoordinator)缺席 ⇒ 原形
|
|
53
53
|
* `createDurableQuestionPolicy()`(恒 ask ⇒ 恒 durable park)。在场 ⇒ **判决时**按活流上下文分腿:
|
|
54
54
|
* 活流腿(bg/SSE,coordinator.runWithContext 包裹且投递面此刻可达,ALS 判)allow——工具执行落到
|
|
@@ -866,7 +866,8 @@ export function createResolveSpec(ctx) {
|
|
|
866
866
|
// [820]③ scratchpadDir, hoisted from the envFacts block below so the SAME per-session dir feeds BOTH the
|
|
867
867
|
// `# Environment` fact and the fs-write gate's exemptDirs (single-source path via scratchpadDirFor inside
|
|
868
868
|
// ensureScratchpadDir). HOST-semantics lanes only + session required — rationale at the envFacts consumer.
|
|
869
|
-
|
|
869
|
+
// 判别式单一真源:补集(沙箱裁决 lane)是写门代理与工厂装饰器的挂载条件,两处各写一份正是漂移的成因。
|
|
870
|
+
const hostSemanticsLane = !isSandboxPathAdjudicationLane(config.remoteExec?.provider);
|
|
870
871
|
// G3([816]③/[820]③,[1840]§四):壳供 body.scratchpadDir 优先——壳(CC 形)有自己的 per-session
|
|
871
872
|
// scratchpad 约定路径,接受后事实/豁免/根围栏三处同源指向壳的目录(壳侧提示词与 server 写门不再
|
|
872
873
|
// 两个 scratchpad)。验收门 fail-closed(多租户/远程 lane/浅路径全拒收,warn 后回落自算)——
|
|
@@ -885,18 +886,14 @@ export function createResolveSpec(ctx) {
|
|
|
885
886
|
// target/dir 交给给定 env 的**真实 fs**(exists/canonicalPath/readLink,dist 亲读),所以 env 必须
|
|
886
887
|
// 就是 hand 工具真正写的那块盘 —— 这里给 worker 本机的 NodeExecutionEnv(构造是纯字段赋值,不 spawn)。
|
|
887
888
|
//
|
|
888
|
-
// ② 沙箱 lane(e2b/k8s/ssh/adb/local-docker,#
|
|
889
|
+
// ② 沙箱 lane(e2b/k8s/ssh/adb/local-docker,#165):沙箱 env 由 core 在 spec **之后**才铸
|
|
889
890
|
// (executionEnvFactory),spec 期把 worker 本机 env 递进去会拿**别人机器上的 fs 事实**裁决
|
|
890
|
-
// (symlink/exists 全答错),而错误的 allow
|
|
891
|
-
//
|
|
892
|
-
//
|
|
893
|
-
//
|
|
894
|
-
//
|
|
895
|
-
//
|
|
896
|
-
// 下面那条臂的注里,合起来的效果是 acceptEdits 在沙箱 lane 退化成与 default 同形(全 ask),与本段
|
|
897
|
-
// ① 里 host factory lane 的哨兵先例同一个 fail-safe 方向。
|
|
898
|
-
// 已知残余面(symlink 形看不见)与终局 seam(core [2751] 给 HookToolContext 加只读 env 能力窄面,
|
|
899
|
-
// 排 174/#22 窗;到货后本过渡形整体退役换正解)成文在 lexical-path-env.ts 的文件头。
|
|
891
|
+
// (symlink/exists 全答错),而错误的 allow 比没有门更糟。解法是把「取 env」推迟到裁决时刻:
|
|
892
|
+
// `DeferredSandboxPathEnv` 是个代理,per-session slot 由工厂装饰器在铸 env 时填(boot/execution-env.ts
|
|
893
|
+
// 链尾),写门每次 canonicalize 都转发那台**真沙箱**的 exists/canonicalPath/fileInfo/readLink。
|
|
894
|
+
// 唯一不转发的是 `absolutePath`(相对路径基准之争,理由在下面那条臂与代理模块的文件头)。
|
|
895
|
+
// cwd 仍不铸 ⇒ 无 acceptDirs、相对路径恒 ask;scratchpad 豁免按 host 同形补回(真 canonicalize
|
|
896
|
+
// 在场,软链逃逸形会落回真身裁决)。sensitivePatterns 的 deny 腿与 isExempt 会话豁免探针照常施加。
|
|
900
897
|
//
|
|
901
898
|
// 修5(三路复审 W4,cwd 基准漂移):gate 的 cwd 基准=与 hands 真跑处同源,分三形:
|
|
902
899
|
// · session 注册过 cwd(cwdHonored 已在注册处把关)⇒ 用它——与 host factory 的 effectiveHostWorkspace
|
|
@@ -948,34 +945,49 @@ export function createResolveSpec(ctx) {
|
|
|
948
945
|
...gateCommon,
|
|
949
946
|
};
|
|
950
947
|
})()
|
|
951
|
-
: // 沙箱 lane(#
|
|
952
|
-
//
|
|
953
|
-
//
|
|
948
|
+
: // 沙箱 lane(#165):裁决 env = `DeferredSandboxPathEnv` 代理 —— 裁决时刻(必在 prepare 之后)
|
|
949
|
+
// 去 per-session slot 里取本 task 的**真沙箱 env**,转发 exists/canonicalPath/fileInfo/readLink。
|
|
950
|
+
// 于是绝对路径的 symlink 真身、存在性都是真沙箱 fs 的事实,域内软链指向守卫段照样 deny;
|
|
951
|
+
// slot 空/歧义 ⇒ 转发面报错 ⇒ core 判 `unresolvedSymlink` ⇒ 写门 ask + sensitive deny(fail-closed)。
|
|
954
952
|
//
|
|
955
|
-
//
|
|
956
|
-
//
|
|
957
|
-
//
|
|
958
|
-
//
|
|
959
|
-
//
|
|
960
|
-
//
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
953
|
+
// **`cwd` 仍不给**(显式裁定,非过渡欠款):`cwd` 同时是 core 的 `rootPath`(相对路径基准)与
|
|
954
|
+
// acceptEdits 的 accept 域。沙箱里那个基准是 engine 跟踪的 cwd,不是 env 自己的 cwd,server 侧要
|
|
955
|
+
// 铸它得先建一个 per-task 跟踪面——那是独立一件(与 host 臂哨兵同族的漂移风险)。后果:相对路径
|
|
956
|
+
// 恒 ask,acceptEdits 在沙箱 lane 与 default 同形(全 ask),fail-safe 方向。
|
|
957
|
+
//
|
|
958
|
+
// **scratchpad 豁免按 host 同形补回**:`/tmp/scratchpad/<sessionId>`(remote-scratchpad.ts 单一
|
|
959
|
+
// 真源,e2b/k8s/local-docker/ssh 四 lane;adb 无此约定故无豁免)。#156 当年不豁免是因为词法版
|
|
960
|
+
// 看不见软链——域内一个指向域外的软链会被判成域内;现在 exemptDirs 与写目标两侧都过真
|
|
961
|
+
// canonicalize,逃逸形会解出域外真身而落回真身裁决,host lane 的同一保证在这里成立了。
|
|
962
|
+
// 注:远程 lane 的**根围栏**admit 走的是 core 对 `spec.envFacts.scratchpadDir` 的自建根(prepare 期
|
|
963
|
+
// canonicalize/实体化),而那条事实在 `SANDBOX_ENV_FACTS=false` 下不发——那种配置里根围栏先拒,
|
|
964
|
+
// 本豁免够不着(方向更严,不是放行面);host 臂的 additionalDirectories 折叠不受影响。
|
|
965
|
+
(() => {
|
|
966
|
+
const sandboxScratchpadDir = isRemoteScratchpadLane(config.remoteExec?.provider) ? remoteScratchpadDirFor(auth?.sessionId) : undefined;
|
|
967
|
+
return {
|
|
968
|
+
env: new DeferredSandboxPathEnv({
|
|
969
|
+
sessionId: auth?.sessionId,
|
|
970
|
+
slots: sandboxPathEnvSlots,
|
|
971
|
+
logger: { warn: (event, fields) => logger.warn(event, { ...(fields ?? {}), lane: config.remoteExec?.provider }) },
|
|
972
|
+
}),
|
|
973
|
+
...gateCommon,
|
|
974
|
+
...(sandboxScratchpadDir ? { scratchpadDir: sandboxScratchpadDir } : {}),
|
|
975
|
+
// codex 复审 B(2026-08-05,dist 亲读+实测):core 的 canonicalizeTarget 把反斜杠 UNC 形在**入口**
|
|
976
|
+
// 短路成 ok:true/key=原样——不经本 env,canon 便「成功」,gate 随后就咨询豁免探针;name-keyed
|
|
977
|
+
// 命中即 allow,「非 POSIX 绝对形一律 ask」就被豁免会话越过(相对形/盘符形没这个洞:它们的
|
|
978
|
+
// canon 失败先于豁免咨询)。沙箱臂因此给探针加 canonical-key 守门:只有 `/` 开头(=真经过本
|
|
979
|
+
// 代理的路径面)的 key 才享豁免,其余落回 defaultWrite 的 ask。host 臂不加
|
|
980
|
+
// (真 fs canonicalize 的 key 可信,[841]① 原契约不动)。
|
|
981
|
+
...(gateCommon.isExempt
|
|
982
|
+
? {
|
|
983
|
+
isExempt: (toolName, canonicalPath) => canonicalPath.startsWith("/") ? gateCommon.isExempt(toolName) : false,
|
|
984
|
+
}
|
|
985
|
+
: {}),
|
|
986
|
+
};
|
|
987
|
+
})();
|
|
976
988
|
// [1248]②/codex F2 — the workflow ask leg's session-exemption probe. Name-keyed and never fs-adjudicated,
|
|
977
989
|
// so it was on EVERY lane from day one (the fs gate has been too since #156, by a different route: a
|
|
978
|
-
//
|
|
990
|
+
// lane-specific adjudication env rather than no gate). Same store + canonical toolName key space as the
|
|
979
991
|
// fs-gate probe — one remember="session" grant serves both; without it the durable lane re-parks every
|
|
980
992
|
// later run_workflow call after an operator already granted "don't ask again this session".
|
|
981
993
|
const workflowGate = approvalExemptionStore && auth?.sessionId
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
import { verifyPromptArtifact } from "@sema-agent/core";
|
|
23
23
|
import { CORE_ENGINE_VERSION } from "../prompts-domain-validate.js";
|
|
24
24
|
import { SHA256_HEX_RE } from "../digest-form.js";
|
|
25
|
+
import { recordFailOpen } from "../observability/fail-open.js";
|
|
25
26
|
/** 判形半场(validateCenterPrompts/validatePromptsDomain/CORE_ENGINE_VERSION 及两 interface)已下沉到
|
|
26
27
|
* 中立叶子 `src/prompts-domain-validate.ts`(design/158 S6);消费者直引真源([2354] 兼容面全清,
|
|
27
28
|
* 此处兼容 re-export 已删)。 */
|
|
@@ -56,7 +57,9 @@ export function withPromptArtifactBackfill(inner, fetchRaw, log) {
|
|
|
56
57
|
log.warn("center_prompts_backfill_rejected", { digest: canonical, ...(v.ok ? { got: v.artifact.artifactDigest, code: "digest_mismatch" } : { code: v.code, reason: v.reason }) });
|
|
57
58
|
return undefined;
|
|
58
59
|
}
|
|
59
|
-
|
|
60
|
+
// cache best-effort —— #157:F 类兜底(工件已验签且已返回;失败的代价只是下次再取一次网络),
|
|
61
|
+
// 留痕是为了让"缓存其实一直没写成"不必靠回填流量异常来发现。
|
|
62
|
+
await inner.put(raw).catch(() => recordFailOpen("server.prompts.artifact-cache-write-failed", `digest=${canonical}`));
|
|
60
63
|
log.info("center_prompts_backfilled", { digest: canonical });
|
|
61
64
|
return raw;
|
|
62
65
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ToolSpec } from "@sema-agent/core";
|
|
2
|
+
/**
|
|
3
|
+
* OA scenario — tools are thin HTTP clients to the OA backend's existing APIs (read = /api/v1/*,
|
|
4
|
+
* write = /api/assets/*). The shared service token + the request's principal are forwarded so OA
|
|
5
|
+
* enforces THAT end-user's RBAC + audit (the service never holds per-user creds). Decoupled: the
|
|
6
|
+
* service knows no OA schema/DB — OA stays the source of truth (its /api/v1/meta is self-describing).
|
|
7
|
+
*/
|
|
8
|
+
export interface OaCtx {
|
|
9
|
+
baseUrl: string;
|
|
10
|
+
token: string;
|
|
11
|
+
principal?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function oaTools(ctx: OaCtx): ToolSpec[];
|
|
14
|
+
export declare const oaPrompt: import("@sema-agent/core").PromptProvider;
|
|
15
|
+
//# sourceMappingURL=oa-tools.d.ts.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { stablePrompt } from "./prompt.js";
|
|
3
|
+
async function oaFetch(ctx, path, init) {
|
|
4
|
+
if (!ctx.baseUrl || !ctx.token)
|
|
5
|
+
return "OA 后端未配置(OA_API_BASEURL / OA_SERVICE_TOKEN)";
|
|
6
|
+
try {
|
|
7
|
+
const res = await fetch(ctx.baseUrl + path, {
|
|
8
|
+
method: init?.method ?? "GET",
|
|
9
|
+
headers: {
|
|
10
|
+
"content-type": "application/json",
|
|
11
|
+
authorization: `Bearer ${ctx.token}`,
|
|
12
|
+
...(ctx.principal ? { "x-agent-principal": ctx.principal } : {}),
|
|
13
|
+
},
|
|
14
|
+
body: init?.body !== undefined ? JSON.stringify(init.body) : undefined,
|
|
15
|
+
});
|
|
16
|
+
const text = await res.text();
|
|
17
|
+
if (!res.ok)
|
|
18
|
+
return `OA ${res.status}: ${text.slice(0, 600)}`;
|
|
19
|
+
return text.slice(0, 60_000);
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
return `调用 OA 失败:${e.message}`;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const needWrite = (ctx) => (!ctx.principal ? "缺少身份(principal),无法代表用户写库;请让调用方带上 x-agent-principal。" : null);
|
|
26
|
+
export function oaTools(ctx) {
|
|
27
|
+
const obj = Type.Object({}, { additionalProperties: true });
|
|
28
|
+
return [
|
|
29
|
+
{ name: "oa_schema", effect: "read", description: "取 OA 数据结构(每类资产的字段/必填/条件必填 requiredWhen/联动 showWhen/枚举、生命周期、表名)。建/改前先调它,别凭记忆。", parameters: Type.Object({}), execute: () => oaFetch(ctx, "/api/v1/meta") },
|
|
30
|
+
{ name: "oa_alerts", effect: "read", description: "派生告警:GP 闲置(已注册未变现) / APP 未上架(已接广告未 LIVE)。", parameters: Type.Object({}), execute: () => oaFetch(ctx, "/api/v1/alerts") },
|
|
31
|
+
{ name: "oa_list", effect: "read", description: "列出某类资产。type∈gp/package/ad-platform/ad-unit/email/environment/proxy。可选 q 关键字。", parameters: Type.Object({ type: Type.String(), q: Type.Optional(Type.String()) }), execute: (a) => { const x = a; return oaFetch(ctx, `/api/v1/entities/${encodeURIComponent(x.type)}${x.q ? `?q=${encodeURIComponent(x.q)}` : ""}`); } },
|
|
32
|
+
{ name: "oa_get", effect: "read", description: "取某资产详情(敏感字段为掩码)。", parameters: Type.Object({ type: Type.String(), id: Type.String() }), execute: (a) => { const x = a; return oaFetch(ctx, `/api/v1/entities/${encodeURIComponent(x.type)}/${encodeURIComponent(x.id)}`); } },
|
|
33
|
+
{ name: "oa_lookup", effect: "read", description: "约定字段值反查(同手机/证件=同一实物)。field: phone|idCardNo|bizLicenseNo。", parameters: Type.Object({ field: Type.String(), value: Type.String() }), execute: (a) => { const x = a; return oaFetch(ctx, `/api/v1/lookup?field=${encodeURIComponent(x.field)}&value=${encodeURIComponent(x.value)}`); } },
|
|
34
|
+
{ name: "oa_create", effect: "write", description: "创建实体(走 OA 校验+当前用户 RBAC+审计)。type + data(字段对象,参照 oa_schema)。失败会返回缺失/非法字段。", parameters: Type.Object({ type: Type.String(), data: obj }), execute: (a) => { const e = needWrite(ctx); if (e)
|
|
35
|
+
return e; const x = a; return oaFetch(ctx, `/api/assets/${encodeURIComponent(x.type)}`, { method: "POST", body: x.data ?? {} }); } },
|
|
36
|
+
{ name: "oa_update", effect: "write", description: "改字段(敏感留空=保持原值;状态字段改不动,用 oa_transition)。", parameters: Type.Object({ type: Type.String(), id: Type.String(), data: obj }), execute: (a) => { const e = needWrite(ctx); if (e)
|
|
37
|
+
return e; const x = a; return oaFetch(ctx, `/api/assets/${encodeURIComponent(x.type)}/${encodeURIComponent(x.id)}`, { method: "PATCH", body: x.data ?? {} }); } },
|
|
38
|
+
{ name: "oa_transition", effect: "write", description: "受控状态流转(先 oa_schema 看可用迁移;风险/弃用等需 note)。", parameters: Type.Object({ type: Type.String(), id: Type.String(), to: Type.String(), note: Type.Optional(Type.String()) }), execute: (a) => { const e = needWrite(ctx); if (e)
|
|
39
|
+
return e; const x = a; return oaFetch(ctx, `/api/assets/${encodeURIComponent(x.type)}/${encodeURIComponent(x.id)}/transition`, { method: "POST", body: { to: x.to, note: x.note ?? null } }); } },
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
// OA business prompt MOVED OUT — migration complete (design/29, 2026-06-05). Persona / 工作准则 / UI
|
|
43
|
+
// markers (::FORM:: / ::GOTO:: / ::SUGGEST:: / ::ISSUE_FORM::) / 分级确认 / 报bug流程 are OA's business,
|
|
44
|
+
// and OA now injects the WHOLE thing per request via `request.systemPrompt` (it holds the service token,
|
|
45
|
+
// content is stable per OA version → cacheable prefix; page context still rides `objective`). Keeping
|
|
46
|
+
// any of it here would just DUPLICATE OA's prompt, so the base is EMPTY — single source of truth = OA.
|
|
47
|
+
// The service stays business-agnostic: it provides the oa_* tools (referenced by NAME from OA's prompt;
|
|
48
|
+
// they carry their own descriptions) and nothing else. `stablePrompt` keeps STABLE→VARIABLE order —
|
|
49
|
+
// OA's systemPrompt is appended after this (empty) base, before core's <user_memory>.
|
|
50
|
+
// (::ISSUE_FORM:: is parsed/rendered + submitted entirely by OA's frontend via OA /api/ai/issue — the
|
|
51
|
+
// service never touches it; `open_gitea_issue` remains only as the unattended/automated fallback.)
|
|
52
|
+
const OA_SYSTEM = "";
|
|
53
|
+
export const oaPrompt = stablePrompt(OA_SYSTEM);
|
|
54
|
+
//# sourceMappingURL=oa-tools.js.map
|
package/dist/config-types.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type { SealedKeyPoison } from "./sealed-key.js";
|
|
|
9
9
|
import type { McpServerSpec, Model, ModelRoles } from "@sema-agent/core";
|
|
10
10
|
import type { ApprovalHmacKey, PrincipalJwtKey } from "./auth-keys.js";
|
|
11
11
|
import type { ElicitationThrottle } from "./elicitation.js";
|
|
12
|
+
import type { QuestionThrottle } from "./question.js";
|
|
12
13
|
import type { InfraCostRates } from "./observability/cost-taxonomy.js";
|
|
13
14
|
import type { Autonomy, CommandRule } from "./runtime-governance.js";
|
|
14
15
|
/** A sema-registry MCP server resolved to a core spec (env-NAME refs already → real values) plus the
|
|
@@ -831,6 +832,12 @@ export interface ServiceConfigFlat {
|
|
|
831
832
|
* stream (the durable-suspend leg is wired separately). A CC cap → posture-gated (single-user turnkey →
|
|
832
833
|
* ON; multi-tenant opt-in); `ASK_QUESTION_ENABLED=true/false` overrides. */
|
|
833
834
|
askQuestionEnabled: boolean;
|
|
835
|
+
/** #166 裁2:AskUserQuestion 的**裁量窗**——每 run 腿的并发帽 / 总量帽 / 无人应答的释放窗。窗用尽或到期
|
|
836
|
+
* 不是拒绝、也不是替人作答:协调器报 `unavailable`,落点(durable park / 非 durable 合成续跑)归 core。
|
|
837
|
+
* `QUESTION_MAX_CONCURRENT_PER_RUN`(默认 2)/ `QUESTION_MAX_TOTAL_PER_RUN`(20)/ `QUESTION_TTL_MS`
|
|
838
|
+
* (300000),越界值启动期响亮拒(照 `MCP_ELICITATION_*` 同门,不静默夹取)。缺席 ⇒ 协调器出厂窗
|
|
839
|
+
* {@link import("./question.js").DEFAULT_QUESTION_THROTTLE}(与本旋钮族默认逐字同值)。 */
|
|
840
|
+
questionThrottle?: QuestionThrottle;
|
|
834
841
|
/** [816]/[820]② live tool-approval HITL — mount the ToolApprovalCoordinator on `RunnerDeps.onAsk` + the
|
|
835
842
|
* `POST /v1/tool-approvals/:id/respond` route so a policy `ask` (the fs-write gate, ask-lists, non-durable safety
|
|
836
843
|
* asks) routes to the live human as the CC three-choice card on the run's stream (core 1.290 sync-ask leg). A CC
|
|
@@ -975,7 +982,7 @@ export type ServiceStoreConfig = Pick<ServiceConfigFlat, "sessionBackend" | "ses
|
|
|
975
982
|
export type ServiceModelPlaneConfig = Pick<ServiceConfigFlat, "gatewayBaseUrl" | "gatewayApiKey" | "gatewayFallbackUrls" | "gatewayMaxRetries" | "anthropic" | "resilience" | "model" | "models" | "modelApiKeyEnv" | "modelApiKeys" | "modelQuotaWeights" | "tiers" | "projects" | "roles" | "cascadeLadder" | "degrade">;
|
|
976
983
|
/** 组:approval(审批 / HITL 门)。`directDoorActive` 无 env 解析腿(装配层三域合取的产物),但语义上
|
|
977
984
|
* 就是本组的门状态,故进组;`parseApprovalDomain` 的返回类型相应是 `Omit<…, "directDoorActive">`。 */
|
|
978
|
-
export type ServiceApprovalConfig = Pick<ServiceConfigFlat, "approvalRequire" | "approvalDeny" | "approvalTimeoutSec" | "approvalAutoBudget" | "approvalNeverAuto" | "approvalHmacKeys" | "durableApproval" | "directApprovalDoor" | "directDoorActive" | "resourceSuspend" | "resourceSuspendTtlSec" | "askQuestionEnabled" | "toolApprovalEnabled" | "streamAskWindowMarginMs" | "streamApproval" | "mcpElicitation" | "sensitiveWritePatterns" | "manualModeShellGate">;
|
|
985
|
+
export type ServiceApprovalConfig = Pick<ServiceConfigFlat, "approvalRequire" | "approvalDeny" | "approvalTimeoutSec" | "approvalAutoBudget" | "approvalNeverAuto" | "approvalHmacKeys" | "durableApproval" | "directApprovalDoor" | "directDoorActive" | "resourceSuspend" | "resourceSuspendTtlSec" | "askQuestionEnabled" | "questionThrottle" | "toolApprovalEnabled" | "streamAskWindowMarginMs" | "streamApproval" | "mcpElicitation" | "sensitiveWritePatterns" | "manualModeShellGate">;
|
|
979
986
|
/** 组:memory(记忆面 + TOC 同步腿)。 */
|
|
980
987
|
export type ServiceMemoryConfig = Pick<ServiceConfigFlat, "memoryEngineEnabled" | "memoryEngineDir" | "memoryEngineRemoteLaneAllowed" | "memoryEngineBackend" | "memoryScope" | "memorySync" | "memoryOrgAdmissionMode" | "memoryOrgDirectoryJson" | "memoryOrgGrantTtlMs" | "memoryOrgUnavailableBackoffMs" | "projectMemoryEnabled" | "syncImportLeaseStaleSec">;
|
|
981
988
|
/** 组:auth(鉴权 / 身份 / 治理棒)。`commandPolicy` 只有 sema-registry 腿(无 env 标量形),故 env 解析
|
package/dist/config.js
CHANGED
|
@@ -5,6 +5,7 @@ import { CODE_AGENT_PROMPT, formatUserScope, isThinkingLevel, PROTOCOL_TABLE, pr
|
|
|
5
5
|
import { ROSTER_PRIMARY_ROLES, ROSTER_CHEAP_ROLES } from "@sema-agent/registry-core";
|
|
6
6
|
import { parseApprovalHmacKeys, parsePrincipalJwks } from "./auth-keys.js"; // design/158 A4: the parser leaf — NOT security.js (base config layer must not value-import the 55KiB auth module)
|
|
7
7
|
import { DEFAULT_ELICITATION_THROTTLE } from "./elicitation.js";
|
|
8
|
+
import { DEFAULT_QUESTION_THROTTLE } from "./question.js";
|
|
8
9
|
function csv(name) {
|
|
9
10
|
return (process.env[name] ?? "")
|
|
10
11
|
.split(",")
|
|
@@ -998,6 +999,13 @@ function parseApprovalDomain(ctx) {
|
|
|
998
999
|
},
|
|
999
1000
|
},
|
|
1000
1001
|
askQuestionEnabled: postureOn("ASK_QUESTION_ENABLED"), // §4④ AskUserQuestion live HITL; posture-gated (single-user → ON)
|
|
1002
|
+
// #166 裁2:AskUserQuestion 裁量窗三旋钮,姿势照 MCP_ELICITATION_* 抄(bounded ⇒ 越界启动期拒,不静默
|
|
1003
|
+
// 夹取)。默认值 = 协调器出厂窗逐字(纯旋钮化,零行为改动)。
|
|
1004
|
+
questionThrottle: {
|
|
1005
|
+
maxConcurrentPerRun: numEnvBounded("QUESTION_MAX_CONCURRENT_PER_RUN", String(DEFAULT_QUESTION_THROTTLE.maxConcurrentPerRun), 1, 64),
|
|
1006
|
+
maxTotalPerRun: numEnvBounded("QUESTION_MAX_TOTAL_PER_RUN", String(DEFAULT_QUESTION_THROTTLE.maxTotalPerRun), 1, 10_000),
|
|
1007
|
+
ttlMs: numEnvBounded("QUESTION_TTL_MS", String(DEFAULT_QUESTION_THROTTLE.ttlMs), 1_000, 3_600_000),
|
|
1008
|
+
},
|
|
1001
1009
|
toolApprovalEnabled: postureOn("TOOL_APPROVAL_ENABLED"), // [816]/[820]② live tool-approval HITL; posture-gated (single-user → ON), mirrors askQuestion
|
|
1002
1010
|
// #151 车2(design/172 §3.3 D3):ToolApprovalCoordinator 窗长三元的安全余量。numEnv fail-loud 形,
|
|
1003
1011
|
// 照邻居旋钮(approvalTimeoutSec 等)抄——坏形(非数字)在启动期炸,不静默折成 NaN。
|
|
@@ -1483,7 +1491,7 @@ const MODEL_PLANE_GROUP_KEYS = [
|
|
|
1483
1491
|
const APPROVAL_GROUP_KEYS = [
|
|
1484
1492
|
"approvalRequire", "approvalDeny", "approvalTimeoutSec", "approvalAutoBudget", "approvalNeverAuto",
|
|
1485
1493
|
"approvalHmacKeys", "durableApproval", "directApprovalDoor", "directDoorActive", "resourceSuspend",
|
|
1486
|
-
"resourceSuspendTtlSec", "askQuestionEnabled", "toolApprovalEnabled", "streamAskWindowMarginMs", "streamApproval", "mcpElicitation",
|
|
1494
|
+
"resourceSuspendTtlSec", "askQuestionEnabled", "questionThrottle", "toolApprovalEnabled", "streamAskWindowMarginMs", "streamApproval", "mcpElicitation",
|
|
1487
1495
|
"sensitiveWritePatterns", "manualModeShellGate",
|
|
1488
1496
|
];
|
|
1489
1497
|
const MEMORY_GROUP_KEYS = [
|
package/dist/elicitation.js
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
28
28
|
import { uuidv7, delimitUntrusted } from "@sema-agent/core";
|
|
29
29
|
import { redactSecrets, redactDeep } from "./trace/redact.js";
|
|
30
|
+
import { recordFailOpen } from "./observability/fail-open.js";
|
|
30
31
|
/** Size bounds on the UNTRUSTED server-controlled payload (the throttle bounds COUNT, these bound SIZE — an
|
|
31
32
|
* opted-in-but-hostile server otherwise amplifies a multi-MB prompt/schema into the durable log + SSE socket).
|
|
32
33
|
* The message is a human-facing prompt (a few KB is ample); a requestedSchema over the cap is dropped (the form
|
|
@@ -187,6 +188,8 @@ export class ElicitationCoordinator {
|
|
|
187
188
|
rc.lastByServer.set(req.server, t);
|
|
188
189
|
}
|
|
189
190
|
catch {
|
|
191
|
+
// #157:投递失败 ⇒ decline。方向已是 fail-closed(问不到人就是拒),缺的只是留痕。
|
|
192
|
+
recordFailOpen("server.elicitation.open-frame-undelivered", `elicitation=${id} server=${req.server}`);
|
|
190
193
|
settle({ action: "decline" }); // inflight rolled back in settle
|
|
191
194
|
}
|
|
192
195
|
const answer = await answerP;
|
|
@@ -195,7 +198,8 @@ export class ElicitationCoordinator {
|
|
|
195
198
|
// ordering-uncritical: the shell already has the answer via respond; a late/lost dismiss is cosmetic.
|
|
196
199
|
void Promise.resolve()
|
|
197
200
|
.then(() => ctx.emit({ type: "elicitation_complete", elicitationId: id, mcpServerName: req.server, action: answer.action, serverNowMs: this.now() }))
|
|
198
|
-
.
|
|
201
|
+
// #157:F 类兜底,与 question.ts 的孪生面同形同 tag 形(丢收尾渲染,不丢答案)。
|
|
202
|
+
.catch(() => recordFailOpen("server.elicitation.complete-breadcrumb-dropped", `elicitation=${id}`));
|
|
199
203
|
return answer;
|
|
200
204
|
};
|
|
201
205
|
/** `POST /v1/elicitations/:id/respond` — resolve a parked elicitation with the shell's answer. Owner-gated with a
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface InfraCostRates {
|
|
2
|
+
toolCallMicroUsd: number;
|
|
3
|
+
sandboxSecMicroUsd: number;
|
|
4
|
+
egressGbMicroUsd: number;
|
|
5
|
+
}
|
|
6
|
+
export interface InfraUsage {
|
|
7
|
+
toolCalls: number;
|
|
8
|
+
sandboxWalltimeMs: number;
|
|
9
|
+
egressBytes: number;
|
|
10
|
+
}
|
|
11
|
+
export interface InfraCostBreakdown {
|
|
12
|
+
toolCallMicroUsd: number;
|
|
13
|
+
sandboxWalltimeMicroUsd: number;
|
|
14
|
+
egressMicroUsd: number;
|
|
15
|
+
totalMicroUsd: number;
|
|
16
|
+
}
|
|
17
|
+
export interface LlmCostBreakdown {
|
|
18
|
+
llmRootMicroUsd: number;
|
|
19
|
+
nestedSubagentMicroUsd: number;
|
|
20
|
+
memoryConsolidationMicroUsd: number;
|
|
21
|
+
compactionMicroUsd: number;
|
|
22
|
+
}
|
|
23
|
+
export interface SupervisorCostBreakdown {
|
|
24
|
+
llm: LlmCostBreakdown | null;
|
|
25
|
+
infra: InfraCostBreakdown;
|
|
26
|
+
totalMicroUsd: number;
|
|
27
|
+
}
|
|
28
|
+
export declare function infraCost(usage: InfraUsage, rates: InfraCostRates): InfraCostBreakdown;
|
|
29
|
+
export declare function composeSupervisorCost(llm: LlmCostBreakdown | null, llmTotalMicroUsd: number, infra: InfraCostBreakdown): SupervisorCostBreakdown;
|
|
30
|
+
export declare function hasInfraPricing(r: InfraCostRates): boolean;
|
|
31
|
+
export declare function infraUsageFromEvents(events: ReadonlyArray<{
|
|
32
|
+
type?: string;
|
|
33
|
+
}>, runDurationMs: number): InfraUsage;
|
|
34
|
+
//# sourceMappingURL=cost-taxonomy.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const nonNeg = (n) => (Number.isFinite(n) && n > 0 ? n : 0);
|
|
2
|
+
export function infraCost(usage, rates) {
|
|
3
|
+
const toolCallMicroUsd = nonNeg(usage.toolCalls) * nonNeg(rates.toolCallMicroUsd);
|
|
4
|
+
const sandboxWalltimeMicroUsd = (nonNeg(usage.sandboxWalltimeMs) / 1000) * nonNeg(rates.sandboxSecMicroUsd);
|
|
5
|
+
const egressMicroUsd = (nonNeg(usage.egressBytes) / 1_000_000_000) * nonNeg(rates.egressGbMicroUsd);
|
|
6
|
+
return {
|
|
7
|
+
toolCallMicroUsd,
|
|
8
|
+
sandboxWalltimeMicroUsd,
|
|
9
|
+
egressMicroUsd,
|
|
10
|
+
totalMicroUsd: toolCallMicroUsd + sandboxWalltimeMicroUsd + egressMicroUsd,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function composeSupervisorCost(llm, llmTotalMicroUsd, infra) {
|
|
14
|
+
return { llm, infra, totalMicroUsd: nonNeg(llmTotalMicroUsd) + infra.totalMicroUsd };
|
|
15
|
+
}
|
|
16
|
+
export function hasInfraPricing(r) {
|
|
17
|
+
return r.toolCallMicroUsd > 0 || r.sandboxSecMicroUsd > 0 || r.egressGbMicroUsd > 0;
|
|
18
|
+
}
|
|
19
|
+
export function infraUsageFromEvents(events, runDurationMs) {
|
|
20
|
+
return {
|
|
21
|
+
toolCalls: events.reduce((n, e) => (e.type === "tool_start" ? n + 1 : n), 0),
|
|
22
|
+
sandboxWalltimeMs: nonNeg(runDurationMs),
|
|
23
|
+
egressBytes: 0,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=cost-taxonomy.js.map
|
|
@@ -706,6 +706,10 @@ export function createTaskHooks(config, ctx) {
|
|
|
706
706
|
let currentInput = input;
|
|
707
707
|
let rewritten = false;
|
|
708
708
|
let asked;
|
|
709
|
+
// #157 fail-open 治理(P 类,tag `server.hooks.pretooluse-unknown-permission-word`):本轮见过
|
|
710
|
+
// 词表外的 `permissionDecision`。安全域闭集词表的 miss 不是"没意见"——钩子作者**表过态**,只是
|
|
711
|
+
// 用了我们不认识的词;把它折成放行等于替他改判。置位后禁止本轮铸 `action:"allow"`(见末尾折叠)。
|
|
712
|
+
let unknownDecision = false;
|
|
709
713
|
const contexts = [];
|
|
710
714
|
const deadlineAt = Date.now() + MAX_HOOK_EVENT_TOTAL_SECONDS * 1000; // 跨全部组的事件级预算
|
|
711
715
|
// 逐条串行(deny 立即短路;updatedInput 串进后续条目 payload——确定性优先,CC 的并行聚合语义
|
|
@@ -771,6 +775,18 @@ export function createTaskHooks(config, ctx) {
|
|
|
771
775
|
rewritten = true;
|
|
772
776
|
}
|
|
773
777
|
}
|
|
778
|
+
else if (decision !== "deny" && decision !== "ask") {
|
|
779
|
+
// 闭集 miss(拼错大小写的 deny / 自造词 / 非字符串 / null)。该条目的裁决**整条不采信**:
|
|
780
|
+
// 它的 updatedInput 也不进串(上面的 if 已排除),并置位禁 allow。CC 的 `allow`/`deny`/
|
|
781
|
+
// `ask` 三词是唯一词表;新词入表须同时进这里,否则它在本仓的语义就是"不采信"。
|
|
782
|
+
unknownDecision = true;
|
|
783
|
+
ctx.logger.warn("hook_permission_decision_unknown", {
|
|
784
|
+
event: "PreToolUse",
|
|
785
|
+
decision: clip(typeof decision === "string" ? decision : typeof decision, 200),
|
|
786
|
+
tool: toolName,
|
|
787
|
+
effect: "hook ruling not honored (closed word set is allow|deny|ask); this call falls back to the engine gate",
|
|
788
|
+
});
|
|
789
|
+
}
|
|
774
790
|
}
|
|
775
791
|
}
|
|
776
792
|
}
|
|
@@ -785,6 +801,22 @@ export function createTaskHooks(config, ctx) {
|
|
|
785
801
|
...(contexts.length ? { additionalContext: contexts.join("\n") } : {}),
|
|
786
802
|
};
|
|
787
803
|
}
|
|
804
|
+
if (unknownDecision) {
|
|
805
|
+
// #157:本轮有一条**不采信**的裁决 ⇒ 一律折 `ask`(人在环)。两条都不行,各有其害:
|
|
806
|
+
// · `allow` —— 显式放行短路,引擎门不再裁决,等于替钩子作者把 DENY 改判成放行;
|
|
807
|
+
// · `undefined`(无裁决)—— 看着中立,其实**不是** fail-closed(codex 复审 HIGH,2026-08-07):
|
|
808
|
+
// 引擎拿到无裁决就走普通门,而 bypassPermissions / acceptEdits 档下普通门就是放行,
|
|
809
|
+
// 钩子作者写的 DENY 照样跑。词表 miss 的语义是「保护型判词坏了」而不是「钩子没表态」。
|
|
810
|
+
// 已有改写则随 `ask` 投递:改写通常正是 redact/clamp,丢了它 approve 后执行的是【原始】args
|
|
811
|
+
// —— `ask` 是唯一既 fail-closed 又能带 updatedInput 的裁决形。不采信的那条自己的改写不在其中
|
|
812
|
+
// (上面的 if 已把它挡在串外),否则一个坏判词就是一条免审的 args 改写通道。
|
|
813
|
+
return {
|
|
814
|
+
action: "ask",
|
|
815
|
+
message: "a PreToolUse hook returned an unrecognized permissionDecision — approval required",
|
|
816
|
+
...(rewritten ? { updatedInput: currentInput } : {}),
|
|
817
|
+
...(contexts.length ? { additionalContext: contexts.join("\n") } : {}),
|
|
818
|
+
};
|
|
819
|
+
}
|
|
788
820
|
if (rewritten || contexts.length > 0) {
|
|
789
821
|
return {
|
|
790
822
|
action: "allow",
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #154 件二+件三 —— **装配自证**域:启动期静态自检(拒启判据)与 operator 诊断读面
|
|
3
|
+
* (`GET /v1/diagnostics/wiring`)。
|
|
4
|
+
*
|
|
5
|
+
* 🔴 两件为什么同住一个模块:它们回答的是同一个问题(「这台 worker 到底接了什么线」),只是一个在 boot
|
|
6
|
+
* 用它拒绝启动、一个在运行期把它读给 operator。分成两处 = 拒启用的判据与诊断报的读数会各自演化,而
|
|
7
|
+
* 「拒启说没矛盾、诊断页显示矛盾」正是本设计要消灭的那类装配谎言。故 boot 半场是本文件导出的**纯函数**
|
|
8
|
+
* (可单测、无 I/O),路由半场只是它的读口。
|
|
9
|
+
*
|
|
10
|
+
* 分层:本模块不值 import `server.ts`(那条边会闭合运行时装载环),对它只有 `import type`。
|
|
11
|
+
*/
|
|
12
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
13
|
+
import type { WiringManifest } from "@sema-agent/core";
|
|
14
|
+
import { type StreamApprovalGate, type StreamApprovalGateInput } from "../../tool-approval.js";
|
|
15
|
+
import type { RouteCtx } from "../route-ctx.js";
|
|
16
|
+
/** 流内审批门的读数形:上场即 `"active"`,否则是那五个合取项里**第一个**不满足的原因词
|
|
17
|
+
* (与 `resolveStreamApprovalGate` 同一闭集,新增 reason 在此处编译红)。 */
|
|
18
|
+
export type StreamApprovalGateReading = "active" | Extract<StreamApprovalGate, {
|
|
19
|
+
active: false;
|
|
20
|
+
}>["reason"];
|
|
21
|
+
/** server 自己的装配谓词读数(core 的静态半场之外的那一半:本仓在这台 worker 上做出的判断)。 */
|
|
22
|
+
export interface ServerWiringGates {
|
|
23
|
+
streamApproval: StreamApprovalGateReading;
|
|
24
|
+
durableApproval: boolean;
|
|
25
|
+
checkpointStore: boolean;
|
|
26
|
+
}
|
|
27
|
+
/** `buildServerWiringGates` 的入参 = 三个谓词各自的**真输入**(不接受已算好的布尔:那样这里读到的就
|
|
28
|
+
* 不再是装配现场的事实,而是别处算完可能已漂的复述)。 */
|
|
29
|
+
export interface ServerWiringGateInput {
|
|
30
|
+
toolApprovalEnabled: boolean;
|
|
31
|
+
streamApprovalEnabled: boolean;
|
|
32
|
+
/** 谓词自己的入参类型(结构形,不 import store 依赖树)—— 取型而非重述,两处不可能漂。 */
|
|
33
|
+
backend: StreamApprovalGateInput["backend"];
|
|
34
|
+
/** 在场性即事实;本面不取实例,故 `unknown` 足够(取窄型会把整棵 store 类型树拖进诊断域)。 */
|
|
35
|
+
checkpointStore: unknown;
|
|
36
|
+
durableApproval: boolean;
|
|
37
|
+
}
|
|
38
|
+
/** 纯数据产物(`build*`):三个门的当前读数。`streamApproval` 走**单一谓词**
|
|
39
|
+
* `resolveStreamApprovalGate` —— 与 `/v1/capabilities` 的 `streamApproval` 位、与回决口的 501 门同一个
|
|
40
|
+
* 符号,所以诊断页读到的与消费端撞到的行为不可能不同。 */
|
|
41
|
+
export declare function buildServerWiringGates(input: ServerWiringGateInput): ServerWiringGates;
|
|
42
|
+
/** 启动自检的判决:`refusals` 非空 ⇒ 拒启(装配谎言);`warnings` ⇒ 留痕但放行。 */
|
|
43
|
+
export interface StaticWiringAudit {
|
|
44
|
+
refusals: readonly string[];
|
|
45
|
+
warnings: readonly string[];
|
|
46
|
+
}
|
|
47
|
+
/** 自检输入 = core 的静态半场 + 我方装配现场的两件事实。 */
|
|
48
|
+
export interface StaticWiringFacts {
|
|
49
|
+
/** core `describeStaticWiring(deps, specTemplate)` 的产物。 */
|
|
50
|
+
manifest: WiringManifest;
|
|
51
|
+
/** 我方喂给 `resolveStreamApprovalGate` 的 `parkFacility` 实参(= checkpoint 店在场 ∧ DURABLE_APPROVAL)。 */
|
|
52
|
+
parkFacility: boolean;
|
|
53
|
+
/** store backend 的种类;`undefined` = env-only worker(无 backend)。 */
|
|
54
|
+
backendKind: "mysql" | "pg" | "local" | undefined;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 三项对表(设计稿件二)。判据全是**两个独立谓词对同一件事的答案**,矛盾即装配谎言 —— 装配谎言不是
|
|
58
|
+
* 「配置不理想」,是「服务端对自己接了什么线的两套说法互斥」,继续启动只会把矛盾带进运行期的每一条腿。
|
|
59
|
+
*
|
|
60
|
+
* ① park 设施:core 从 `spec.checkpointStore ?? deps.checkpointStore` 判 `parkLane.capable`,我方从
|
|
61
|
+
* `backend.checkpoint ∧ DURABLE_APPROVAL` 判 `parkFacility` 并据此决定流内审批协议上不上场。两者
|
|
62
|
+
* 不等 ⇒ 协议按「窗到期能 park」上场而引擎侧根本不 park(结局是 core fail-closed deny),或反之
|
|
63
|
+
* 白白关掉一个可用的协议。`capable` 真而 `effective` 不真同样致命:能力在、这条部署形上却不会真的
|
|
64
|
+
* park,窗到期依旧无降级目的地。
|
|
65
|
+
* ② 会话店持久性:manifest 声明 `declared_durable` 而后端是 local / 根本没有后端 ⇒ 「重启存活」的声明
|
|
66
|
+
* 没有介质兑现。这条与 memory+durable 两条既有拒启同族(声明与介质必须对得上)。
|
|
67
|
+
* ③ interaction posture 声明 `interactive` 却没有任何 question 投递面 ⇒ 只 **warn**:core 在 prepare 时
|
|
68
|
+
* 会按腿拒,boot 先一步把话说成人话。不拒启,因为部署形本身可能只是暂时没接活体面,而 core 侧已有
|
|
69
|
+
* 兜底门 —— 拒启会把一个可运行的部署挡在门外。
|
|
70
|
+
* ④ park 车道的持久性读数不可信时 ⇒ **warn**(理由与这条判据为什么不是拒启,见其行内注)。
|
|
71
|
+
*
|
|
72
|
+
* 判决分两级是有判据的:**拒启**留给「两个谓词对同一件事互斥」(其中一个必错,继续跑就是带着谎言服务);
|
|
73
|
+
* **警告**留给「读数不可信/声明不完整」(信号本身还不足以断定谁错,据此拒启会挡住正常部署)。
|
|
74
|
+
*/
|
|
75
|
+
export declare function buildStaticWiringAudit(facts: StaticWiringFacts): StaticWiringAudit;
|
|
76
|
+
/**
|
|
77
|
+
* boot 消费口:`refusals` 非空即抛(拒启,与 memory+durable 两条既有拒启同族的 fail-loud 形),
|
|
78
|
+
* `warnings` 逐条 warn。返回判决本体,调用方可以继续把读数打进启动 info 行。
|
|
79
|
+
*/
|
|
80
|
+
export declare function assertStaticWiringConsistent(facts: StaticWiringFacts, logger: {
|
|
81
|
+
warn: (msg: string, meta?: Record<string, unknown>) => void;
|
|
82
|
+
} | undefined): StaticWiringAudit;
|
|
83
|
+
export declare function handleDiagnostics(req: IncomingMessage, res: ServerResponse, url: string, ctx: RouteCtx): Promise<boolean>;
|
|
84
|
+
//# sourceMappingURL=diagnostics.d.ts.map
|