@sema-agent/server 7.5.0 → 7.6.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/LICENSE +1 -1
- package/README.md +18 -3
- package/README.zh-CN.md +14 -3
- package/USAGE.md +37 -1
- package/dist/approval-reconciler.d.ts +1 -1
- package/dist/approval-reconciler.js +6 -5
- package/dist/boot/config-center.js +15 -2
- package/dist/boot/execution-env.js +1 -1
- package/dist/boot/parked-revive-gate.d.ts +78 -0
- package/dist/boot/parked-revive-gate.js +114 -0
- package/dist/boot/resolve-spec.d.ts +3 -20
- package/dist/boot/resolve-spec.js +25 -119
- package/dist/budget.d.ts +1 -1
- package/dist/budget.js +1 -1
- package/dist/capabilities/repo-tools.d.ts +1 -1
- package/dist/capabilities/repo-tools.js +8 -2
- package/dist/config-provider.d.ts +1 -0
- package/dist/config-provider.js +23 -3
- package/dist/config-types.d.ts +5 -3
- package/dist/config.js +5 -4
- package/dist/deployment-governance.d.ts +168 -0
- package/dist/deployment-governance.js +206 -0
- package/dist/fleet/fleet-bus.d.ts +11 -1
- package/dist/fleet/fleet-bus.js +43 -0
- package/dist/fleet/fleet-terminal-window.d.ts +98 -0
- package/dist/fleet/fleet-terminal-window.js +316 -0
- package/dist/http/routes/approvals-assistant.js +6 -7
- package/dist/http/routes/fleet.js +160 -14
- package/dist/http/routes/runs.js +5 -1
- package/dist/http/routes/trace-usage.js +3 -4
- package/dist/http/send.d.ts +23 -0
- package/dist/http/send.js +23 -0
- package/dist/http/server.d.ts +4 -0
- package/dist/http/server.js +5 -2
- package/dist/http/sse-log.js +3 -4
- package/dist/leader/diffout.d.ts +10 -0
- package/dist/leader/diffout.js +14 -2
- package/dist/leader/diffup.js +3 -2
- package/dist/leader/planner.js +7 -0
- package/dist/main.js +22 -26
- package/dist/observability/fail-open.d.ts +4 -0
- package/dist/observability/fail-open.js +4 -0
- package/dist/orchestration/workflow-notify-journal.d.ts +1 -1
- package/dist/orchestration/workflow-notify-journal.js +14 -34
- package/dist/plugins/approval-ask-store-sql.d.ts +33 -0
- package/dist/plugins/approval-ask-store-sql.js +66 -33
- package/dist/plugins/background-agent-store-sql.js +16 -16
- package/dist/plugins/breaker-state-sql.js +2 -2
- package/dist/plugins/checkpoint-store-sql.d.ts +5 -2
- package/dist/plugins/checkpoint-store-sql.js +5 -2
- package/dist/plugins/image-bake-store-sql.d.ts +1 -1
- package/dist/plugins/image-bake-store-sql.js +27 -27
- package/dist/plugins/image-index-sql.js +15 -15
- package/dist/plugins/mailbox-store-sql.js +3 -3
- package/dist/plugins/memory-engine-pg.js +9 -9
- package/dist/plugins/memory-engine-tidb.js +7 -7
- package/dist/plugins/memory-sync-store-pg.js +13 -13
- package/dist/plugins/memory-sync-store-tidb.js +5 -5
- package/dist/plugins/outcome-ledger-sql.js +7 -7
- package/dist/plugins/pg-cost-quota.js +3 -3
- package/dist/plugins/pg-pool.js +84 -75
- package/dist/plugins/pg-rate-limiter.js +3 -3
- package/dist/plugins/pg-session-storage.d.ts +1 -1
- package/dist/plugins/pg-session-storage.js +12 -13
- package/dist/plugins/remote-env-host.js +3 -1
- package/dist/plugins/remote-env-local-docker.js +6 -3
- package/dist/plugins/remote-env-ssh.d.ts +13 -1
- package/dist/plugins/roster-store-sql.js +8 -8
- package/dist/plugins/store-contracts.d.ts +19 -0
- package/dist/plugins/store-contracts.js +42 -0
- package/dist/plugins/task-attachment-store.js +5 -5
- package/dist/plugins/task-list-store-sql.js +1 -1
- package/dist/plugins/tidb-cost-quota.js +1 -1
- package/dist/plugins/tidb-pool.js +83 -60
- package/dist/plugins/tidb-rate-limiter.js +1 -1
- package/dist/plugins/tidb-session-store.js +2 -5
- package/dist/plugins/tool-result-store-sql.js +2 -2
- package/dist/plugins/usage-window-store-sql.js +13 -13
- package/dist/plugins/write-behind-counter.d.ts +10 -2
- package/dist/plugins/write-behind-counter.js +13 -3
- package/dist/resource-suspend.d.ts +3 -1
- package/dist/resource-suspend.js +3 -1
- package/dist/run-local.d.ts +73 -1
- package/dist/run-local.js +146 -5
- package/dist/runs.d.ts +3 -1
- package/dist/runs.js +3 -1
- package/dist/security.d.ts +12 -0
- package/dist/security.js +12 -0
- package/dist/session-sync-kernel.d.ts +13 -0
- package/dist/session-sync-kernel.js +13 -0
- package/dist/trace/core-keyset-guard.d.ts +1 -1
- package/dist/trace/project.d.ts +10 -1
- package/dist/trace/project.js +31 -0
- package/package.json +3 -3
|
@@ -2,7 +2,7 @@ import { projectEvents, taskSummary, mapTraceEvent } from "../../trace/project.j
|
|
|
2
2
|
import { projectArtifacts } from "../../trace/artifacts.js";
|
|
3
3
|
import { usageSummary, usageSeries, usageBreakdown } from "../../usage-analytics.js";
|
|
4
4
|
import { sleep } from "../sse-log.js";
|
|
5
|
-
import { sendJson, sendError, sseHeaders } from "../send.js";
|
|
5
|
+
import { sendJson, sendError, sseHeaders, SSE_MAX_STREAM_MS, SSE_HEARTBEAT_IDLE_MS } from "../send.js";
|
|
6
6
|
import { gatedPrincipal } from "../principal-gate.js";
|
|
7
7
|
const TRACE_RE = /^\/v1\/tasks(?:\/([^/]+)(\/turns|\/stream|\/artifacts))?$/;
|
|
8
8
|
export async function handleTraceUsage(req, res, url, ctx) {
|
|
@@ -248,7 +248,6 @@ async function streamTaskTrace(req, res, runStore, taskId, staleMs) {
|
|
|
248
248
|
closed = true;
|
|
249
249
|
});
|
|
250
250
|
const start = Date.now();
|
|
251
|
-
const MAX_MS = 15 * 60 * 1000;
|
|
252
251
|
let lastBeat = Date.now();
|
|
253
252
|
const write = (events) => {
|
|
254
253
|
for (const ev of events) {
|
|
@@ -272,14 +271,14 @@ async function streamTaskTrace(req, res, runStore, taskId, staleMs) {
|
|
|
272
271
|
res.write(`event: error\ndata: ${JSON.stringify({ type: "error", errorCode: "WORKER_DOWN", message: "run stalled (instance lost?)" })}\n\n`);
|
|
273
272
|
break;
|
|
274
273
|
}
|
|
275
|
-
if (Date.now() - start >
|
|
274
|
+
if (Date.now() - start > SSE_MAX_STREAM_MS) {
|
|
276
275
|
// BL-16: the stream hit its 15-min cap but the RUN is still going — emit a terminal event so the client
|
|
277
276
|
// can tell "reconnect to continue" from "task completed" (a silent close looked like completion).
|
|
278
277
|
res.write(`event: error\ndata: ${JSON.stringify({ type: "error", errorCode: "STREAM_MAX_DURATION", message: "trace stream reached its 15-minute cap — reconnect with Last-Event-ID to continue (the run is still active)" })}\n\n`);
|
|
279
278
|
break;
|
|
280
279
|
}
|
|
281
280
|
if (events.length === 0) {
|
|
282
|
-
if (Date.now() - lastBeat >
|
|
281
|
+
if (Date.now() - lastBeat > SSE_HEARTBEAT_IDLE_MS) {
|
|
283
282
|
res.write(`event: heartbeat\ndata: {}\n\n`);
|
|
284
283
|
lastBeat = Date.now();
|
|
285
284
|
}
|
package/dist/http/send.d.ts
CHANGED
|
@@ -9,6 +9,29 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { ServerResponse } from "node:http";
|
|
11
11
|
export declare function sseHeaders(res: ServerResponse, extra?: Record<string, string>): void;
|
|
12
|
+
/**
|
|
13
|
+
* 一条 SSE 连接的墙钟上限。到点发一帧 `error`/`STREAM_MAX_DURATION`(**不是**静默关闭 —— 客户端分不出
|
|
14
|
+
* 「到帽了请重连」和「跑完了」)然后收流。
|
|
15
|
+
*
|
|
16
|
+
* 🔴 为什么共享(2026-08-08,#176 二轮扫描 finding):这个数与下面的心跳间隔此前在**三处**各手抄一份
|
|
17
|
+
* —— `http/sse-log.ts`(task_run + image_bake 共用的那台泵)、`http/routes/trace-usage.ts`、
|
|
18
|
+
* `http/routes/approvals-assistant.ts`(它的顶注还自称「parity with streamTaskTrace」)。三处各自有测试
|
|
19
|
+
* 钉着**自己那一份**,没有任何跨副本的一致性钉 ⇒ 单边调帽后「parity」只剩注释里那句话。
|
|
20
|
+
*
|
|
21
|
+
* ⚠️ 改这个数请连**文案**一起改:三处的 `STREAM_MAX_DURATION` 消息里把「15-minute」写死在散文里
|
|
22
|
+
* (文案面另有 freeze 门,故此处不做插值——散文与常量的同步靠这行告诫,不靠机器)。
|
|
23
|
+
*/
|
|
24
|
+
export declare const SSE_MAX_STREAM_MS: number;
|
|
25
|
+
/**
|
|
26
|
+
* 长时间无事件时的心跳间隔(防中继的 idle timeout 掐流)。与 {@link SSE_MAX_STREAM_MS} 同批收口。
|
|
27
|
+
*
|
|
28
|
+
* ⚠️ 收口的只有**间隔**,**不含帧形**:`sse-log` / `trace-usage` 发 `data: {}`,`approvals-assistant`
|
|
29
|
+
* 发 `data: {"type":"heartbeat"}`。这是真分歧不是笔误 —— trace-usage 自己的 meta 帧还引着 [2373]C-12
|
|
30
|
+
* 「每个 data payload 都带 type」的约定,而它的心跳帧没有 type(probe-sse-1 的注释把这条记成「按
|
|
31
|
+
* data.type 分派的客户端会拿到 undefined」)。统一帧形是**行为面**改动(要么给两处加 type=多发一个键,
|
|
32
|
+
* 要么给 approvals 减 type=消费端破坏),属候裁,不在本批的零行为码范围内。
|
|
33
|
+
*/
|
|
34
|
+
export declare const SSE_HEARTBEAT_IDLE_MS = 15000;
|
|
12
35
|
export declare function sendJson(res: ServerResponse, status: number, body: unknown): void;
|
|
13
36
|
/** A6(design/158 lens1 §C3/lens3 §C3a): canonical error-response sender for sites that already carry a
|
|
14
37
|
* machine `errorCode` — `errorCode` is a required positional arg (not optional-bag) so a call site
|
package/dist/http/send.js
CHANGED
|
@@ -6,6 +6,29 @@ export function sseHeaders(res, extra) {
|
|
|
6
6
|
...extra,
|
|
7
7
|
});
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* 一条 SSE 连接的墙钟上限。到点发一帧 `error`/`STREAM_MAX_DURATION`(**不是**静默关闭 —— 客户端分不出
|
|
11
|
+
* 「到帽了请重连」和「跑完了」)然后收流。
|
|
12
|
+
*
|
|
13
|
+
* 🔴 为什么共享(2026-08-08,#176 二轮扫描 finding):这个数与下面的心跳间隔此前在**三处**各手抄一份
|
|
14
|
+
* —— `http/sse-log.ts`(task_run + image_bake 共用的那台泵)、`http/routes/trace-usage.ts`、
|
|
15
|
+
* `http/routes/approvals-assistant.ts`(它的顶注还自称「parity with streamTaskTrace」)。三处各自有测试
|
|
16
|
+
* 钉着**自己那一份**,没有任何跨副本的一致性钉 ⇒ 单边调帽后「parity」只剩注释里那句话。
|
|
17
|
+
*
|
|
18
|
+
* ⚠️ 改这个数请连**文案**一起改:三处的 `STREAM_MAX_DURATION` 消息里把「15-minute」写死在散文里
|
|
19
|
+
* (文案面另有 freeze 门,故此处不做插值——散文与常量的同步靠这行告诫,不靠机器)。
|
|
20
|
+
*/
|
|
21
|
+
export const SSE_MAX_STREAM_MS = 15 * 60 * 1000;
|
|
22
|
+
/**
|
|
23
|
+
* 长时间无事件时的心跳间隔(防中继的 idle timeout 掐流)。与 {@link SSE_MAX_STREAM_MS} 同批收口。
|
|
24
|
+
*
|
|
25
|
+
* ⚠️ 收口的只有**间隔**,**不含帧形**:`sse-log` / `trace-usage` 发 `data: {}`,`approvals-assistant`
|
|
26
|
+
* 发 `data: {"type":"heartbeat"}`。这是真分歧不是笔误 —— trace-usage 自己的 meta 帧还引着 [2373]C-12
|
|
27
|
+
* 「每个 data payload 都带 type」的约定,而它的心跳帧没有 type(probe-sse-1 的注释把这条记成「按
|
|
28
|
+
* data.type 分派的客户端会拿到 undefined」)。统一帧形是**行为面**改动(要么给两处加 type=多发一个键,
|
|
29
|
+
* 要么给 approvals 减 type=消费端破坏),属候裁,不在本批的零行为码范围内。
|
|
30
|
+
*/
|
|
31
|
+
export const SSE_HEARTBEAT_IDLE_MS = 15_000;
|
|
9
32
|
export function sendJson(res, status, body) {
|
|
10
33
|
res.writeHead(status, { "content-type": "application/json" });
|
|
11
34
|
res.end(JSON.stringify(body));
|
package/dist/http/server.d.ts
CHANGED
|
@@ -187,6 +187,10 @@ export interface ServiceCoordinatorDeps {
|
|
|
187
187
|
* (the live multi-row task/workflow fleet view: status/elapsed/tokens/doneCount transitions pushed over time).
|
|
188
188
|
* Present when wired in main.ts; the run + workflow lifecycle publish their per-row deltas to it. */
|
|
189
189
|
fleetBus?: FleetEventBus;
|
|
190
|
+
/** [3193] 病理1:boot recover(判死前世 running 行并回灌 fleet)完成屏障——`/v1/fleet/stream` 快照组装
|
|
191
|
+
* 对它做**有界**等待(3s cap,routes/fleet.ts),SIGKILL 后壳重连不再与 recover 赛跑。缺席或已
|
|
192
|
+
* resolve=立即组装(memory 后端、无 notify gate 的部署);main.ts 铸(recover 收官即落,失败也落)。 */
|
|
193
|
+
fleetRecoveryDone?: Promise<void>;
|
|
190
194
|
/** [1196] session-watch registry(SSE 订阅共享探针面)。present ⇔ sessionStorage 有 getLeafId/ownerOf
|
|
191
195
|
* (main.ts 建);absent ⇒ /v1/sessions/:id/events 501(capabilities.sessionEvents 同谓词,永不漂)。 */
|
|
192
196
|
sessionWatch?: import("../session-watch.js").SessionWatchRegistry;
|
package/dist/http/server.js
CHANGED
|
@@ -5,7 +5,7 @@ import { uuidv7, isThinkingLevel, expandTiers, resumeWithVerification, Checkpoin
|
|
|
5
5
|
import { decideParkedAgent, findParkedAgentForCheckpoint } from "../parked-decide.js";
|
|
6
6
|
import { matchCatalogModel } from "../model-select.js";
|
|
7
7
|
import {} from "../config-center/facade.js";
|
|
8
|
-
import { HttpError, principalFrom, verifiedPrincipal, setSsoPrincipal, ssoVerifiedPrincipal, setSsoScope, isUuidV7, isDestructiveSessionWrite, decodeCheckpointScope, CHECKPOINT_PUBLIC_SCOPE } from "../security.js";
|
|
8
|
+
import { HttpError, principalFrom, verifiedPrincipal, setSsoPrincipal, ssoVerifiedPrincipal, setSsoScope, isUuidV7, isDestructiveSessionWrite, decodeCheckpointScope, CHECKPOINT_PUBLIC_SCOPE, PRINCIPAL_TOKEN_HEADER, APPROVAL_MAC_HEADER, APPROVAL_MAC_KID_HEADER } from "../security.js";
|
|
9
9
|
import { verifyDirectDoorProof } from "../principal-jwt.js";
|
|
10
10
|
import {} from "../memory-sync.js";
|
|
11
11
|
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_PERMISSION_RULES, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS } from "../task-settings.js";
|
|
@@ -2813,7 +2813,10 @@ function cors(res, req, origins, principalHeader) {
|
|
|
2813
2813
|
// 后五项都是**真被 handler 读**的自定义头:`idempotency-key`(tasks/runs/images)、
|
|
2814
2814
|
// `x-detach-on-disconnect`(tasks 断连不中止)、direct-door 决策证明三件套(approvals /decide 的
|
|
2815
2815
|
// crypto 绑定腿 —— 缺了它,direct-door worker 上的 web 审批面在浏览器里根本用不了)。
|
|
2816
|
-
res.setHeader("access-control-allow-headers",
|
|
2816
|
+
res.setHeader("access-control-allow-headers",
|
|
2817
|
+
// direct-door 三件套用 security.ts 的具名导出插值(与同句的 `${principalHeader}` 同姿势)——手抄的
|
|
2818
|
+
// 头名漏改在这一行是**静默**的:预检挡下 ⇒ 请求到不了 handler,任何一处都不会报错。
|
|
2819
|
+
`content-type, authorization, last-event-id, ${principalHeader}, idempotency-key, x-detach-on-disconnect, ${PRINCIPAL_TOKEN_HEADER}, ${APPROVAL_MAC_HEADER}, ${APPROVAL_MAC_KID_HEADER}, if-none-match`);
|
|
2817
2820
|
// 🔴 expose-headers 此前**整个缺席** ⇒ fetch 类客户端读不到 `X-Task-Id`(routes/tasks.ts 经 sseHeaders
|
|
2818
2821
|
// 下发的 durable rewind handle)。tasks.ts 的 G15 meta 帧注释把这归因为「EventSource 与部分代理读不到
|
|
2819
2822
|
// 响应头」—— 对 EventSource 成立,但对 fetch 客户端真因是这里缺 expose,meta 帧只是绕过。
|
package/dist/http/sse-log.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { sendJson, sendError, sseHeaders } from "./send.js";
|
|
1
|
+
import { sendJson, sendError, sseHeaders, SSE_MAX_STREAM_MS, SSE_HEARTBEAT_IDLE_MS } from "./send.js";
|
|
2
2
|
/** #151 车3 §5.3:preamble 的**有界**等待上限。挂起的 store 不许拖住开流——开流是壳的主路径,
|
|
3
3
|
* 而 preamble 只是一个锦上添花的对账基准。超时即按「无卡集基准」继续(与今天等同)。 */
|
|
4
4
|
export const SSE_PREAMBLE_TIMEOUT_MS = 2_000;
|
|
@@ -83,7 +83,6 @@ export async function streamSseLog(req, res, provider, id, staleMs) {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
const start = Date.now();
|
|
86
|
-
const MAX_MS = 15 * 60 * 1000;
|
|
87
86
|
let lastBeat = Date.now();
|
|
88
87
|
const write = (events) => {
|
|
89
88
|
for (const ev of events) {
|
|
@@ -116,7 +115,7 @@ export async function streamSseLog(req, res, provider, id, staleMs) {
|
|
|
116
115
|
res.write(`event: ${sf.event}\ndata: ${JSON.stringify(sf.data)}\n\n`);
|
|
117
116
|
break;
|
|
118
117
|
}
|
|
119
|
-
if (Date.now() - start >
|
|
118
|
+
if (Date.now() - start > SSE_MAX_STREAM_MS) {
|
|
120
119
|
// BL-16: cap reached but the run continues — emit a terminal reconnect signal (not a silent close, which
|
|
121
120
|
// a client couldn't distinguish from a completed run). Distinct from the `failed`/`done` run events.
|
|
122
121
|
res.write(`event: error\ndata: ${JSON.stringify({ type: "error", errorCode: "STREAM_MAX_DURATION", message: "stream reached its 15-minute cap — reconnect with Last-Event-ID to continue (the run is still active)" })}\n\n`);
|
|
@@ -124,7 +123,7 @@ export async function streamSseLog(req, res, provider, id, staleMs) {
|
|
|
124
123
|
}
|
|
125
124
|
if (events.length === 0) {
|
|
126
125
|
// Heartbeat during a long idle tail (e.g. a debate phase) so the relay's idle timeout doesn't fire.
|
|
127
|
-
if (Date.now() - lastBeat >
|
|
126
|
+
if (Date.now() - lastBeat > SSE_HEARTBEAT_IDLE_MS) {
|
|
128
127
|
res.write(`event: heartbeat\ndata: {}\n\n`);
|
|
129
128
|
lastBeat = Date.now();
|
|
130
129
|
}
|
package/dist/leader/diffout.d.ts
CHANGED
|
@@ -38,6 +38,16 @@ export type PullDiffResult = {
|
|
|
38
38
|
truncated?: boolean;
|
|
39
39
|
exitCode?: number;
|
|
40
40
|
};
|
|
41
|
+
/**
|
|
42
|
+
* 一次 diff 的字节帽(pull 腿与 push/上行腿**共用同一个数**)。
|
|
43
|
+
*
|
|
44
|
+
* 🔴 exported 是判据的一部分(2026-08-08,#176 二轮扫描 finding):`diffup.ts` 原先另抄了一份同值常量,
|
|
45
|
+
* 而它**本就已经** `import { safeRev } from "./diffout.js"` —— 依赖边在,共享零成本。两份手抄的后果不是
|
|
46
|
+
* 「多写一行」,是**单边改值后上行/pull 两腿对同一个 diff 的过/拒判定分家**:同一份补丁在一条腿上过、
|
|
47
|
+
* 另一条腿上被截断,而两处都「按自己的常量」正确工作,没有任何一处会报错。
|
|
48
|
+
* (同族先例:`checkpoint-store-sql.ts` 的 `export const TERMINAL_GRACE_MS`。)
|
|
49
|
+
*/
|
|
50
|
+
export declare const DEFAULT_MAX_DIFF_BYTES: number;
|
|
41
51
|
export declare function safeRev(rev: string): boolean;
|
|
42
52
|
/**
|
|
43
53
|
* Pull `<baseSha>..<ref>` from a worker container as a `git format-patch` series. Never throws — failures
|
package/dist/leader/diffout.js
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 一次 diff 的字节帽(pull 腿与 push/上行腿**共用同一个数**)。
|
|
3
|
+
*
|
|
4
|
+
* 🔴 exported 是判据的一部分(2026-08-08,#176 二轮扫描 finding):`diffup.ts` 原先另抄了一份同值常量,
|
|
5
|
+
* 而它**本就已经** `import { safeRev } from "./diffout.js"` —— 依赖边在,共享零成本。两份手抄的后果不是
|
|
6
|
+
* 「多写一行」,是**单边改值后上行/pull 两腿对同一个 diff 的过/拒判定分家**:同一份补丁在一条腿上过、
|
|
7
|
+
* 另一条腿上被截断,而两处都「按自己的常量」正确工作,没有任何一处会报错。
|
|
8
|
+
* (同族先例:`checkpoint-store-sql.ts` 的 `export const TERMINAL_GRACE_MS`。)
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_MAX_DIFF_BYTES = 5 * 1024 * 1024;
|
|
2
11
|
const DEFAULT_READ_TIMEOUT_MS = 30_000;
|
|
3
|
-
/** A safe git rev token: sha, branch/tag name, or `A..B`. No shell metacharacters, no leading dash.
|
|
12
|
+
/** A safe git rev token: sha, branch/tag name, or `A..B`. No shell metacharacters, no leading dash.
|
|
13
|
+
* 🔗 近亲判据:`leader/planner.ts` 的 branch 校验用同一套字符类 + 同一个 200 上限 + 同样拒前导 `-`,
|
|
14
|
+
* 只在 `..` 上有意分家(这里要放行 `A..B` 区间形,那边全拒)。差异与候裁说明写在 planner 那处
|
|
15
|
+
* (#176 二轮扫描 finding)——单边加字符类成员时,请两处一起看。 */
|
|
4
16
|
const SAFE_REV = /^[A-Za-z0-9._/-]{1,200}$/;
|
|
5
17
|
export function safeRev(rev) {
|
|
6
18
|
return SAFE_REV.test(rev) && !rev.startsWith("-") && !rev.includes("..--");
|
package/dist/leader/diffup.js
CHANGED
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
* A worker that skips the upload surfaces as `{ok:false}` → merge quarantines it (honest failure, no hang).
|
|
15
15
|
* pullDiff stays as the compat path for static (leader-owned) envs — M2 降兼容.
|
|
16
16
|
*/
|
|
17
|
-
|
|
17
|
+
// 字节帽与 `safeRev` 同源取自 pull 腿(理由见 diffout.ts 的 DEFAULT_MAX_DIFF_BYTES 顶注:同值双抄一旦
|
|
18
|
+
// 单边改值,上行/pull 两腿对同一个 diff 的过/拒判定就分家,且两边都「按自己的常量」静默正确)。
|
|
19
|
+
import { safeRev, DEFAULT_MAX_DIFF_BYTES } from "./diffout.js";
|
|
18
20
|
import { OBJECT_STORE_IO_TIMEOUT_MS } from "../plugins/blob-backend.js";
|
|
19
|
-
const DEFAULT_MAX_DIFF_BYTES = 5 * 1024 * 1024;
|
|
20
21
|
/** The staged upload script. `repoDir`/`workDir` are deployment config; `baseSha` is interpolated into a
|
|
21
22
|
* shell line, so it is re-validated HERE (fail-closed) — callers should already have applied diffout's
|
|
22
23
|
* safeRev rules, but this function is the last line before the shell. The patch is written OUTSIDE the repo
|
package/dist/leader/planner.js
CHANGED
|
@@ -91,6 +91,13 @@ export function validateSubtasks(arr, opts = {}) {
|
|
|
91
91
|
const branch = typeof e.branch === "string" && e.branch.trim() ? e.branch.trim() : `worker/${workerId}`;
|
|
92
92
|
// Reject a leading "-" (would be parsed as an option by `git checkout -b <branch>`), ".." (ref escape), and
|
|
93
93
|
// an over-long ref (git rejects it anyway; cap keeps the value tidy). The char set is shell-safe.
|
|
94
|
+
//
|
|
95
|
+
// 🔗 近亲判据(2026-08-08,#176 二轮扫描 finding):`leader/diffout.ts` 的 `safeRev` 用**同一套**字符
|
|
96
|
+
// 类 `[A-Za-z0-9._/-]`、**同一个** 200 长度上限、**同样**拒前导 `-`。唯一的真差异在 `..`:safeRev 只拒
|
|
97
|
+
// `..--`(rev 要支持 `A..B` 区间形),这里全拒 `..`(branch **名**里 `..` 无合法用途,git 自己也拒)
|
|
98
|
+
// —— 是**有意收紧**,不是抄漏。两处此前零互指、无共享基元、无跨处钉;单边加一个字符类成员时另一边
|
|
99
|
+
// 不会有任何提示。是否抽一个共享基元(比如 `refToken(s, { allowRange })`)属结构面决定,候裁;本批
|
|
100
|
+
// 只把「近亲关系 + 差异所在」两侧成文。
|
|
94
101
|
if (branch.length > 200 || !/^[A-Za-z0-9._/-]+$/.test(branch) || branch.startsWith("-") || branch.includes("..")) {
|
|
95
102
|
throw new Error(`sub-task ${workerId}: invalid branch`);
|
|
96
103
|
}
|
package/dist/main.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { Runner, TtlSessionStore, uuidv7, defaultTaskRegistry,
|
|
4
|
+
import { Runner, TtlSessionStore, uuidv7, defaultTaskRegistry, createAllowDenyPolicy, workflowsCapability, createWebFetchSummarizer, resolveTaskModel as coreResolveTaskModel, probeSearchBackend, describeStaticWiring } from "@sema-agent/core";
|
|
5
5
|
import { createSessionTitler } from "./session-titler.js";
|
|
6
6
|
import { posIntEnv } from "./session-watch.js";
|
|
7
7
|
import { selectEnvironmentTool } from "./capabilities/select-environment-tool.js";
|
|
@@ -14,7 +14,7 @@ import { ensureChildSessionDurableWithPromotion } from "./plugins/session-store.
|
|
|
14
14
|
import { ForkRoutingSessionStore } from "./plugins/fork-routing-session-store.js";
|
|
15
15
|
import { webSearchConfigFromEnv, createWebSearchBackend, setWebSearchBadPayloadObserver, shouldProbeWebSearchOnBoot } from "./plugins/web-search.js";
|
|
16
16
|
import { createAuthorizer, encodeCheckpointScope } from "./security.js";
|
|
17
|
-
import { assertGateIntentServiceable,
|
|
17
|
+
import { assertGateIntentServiceable, hasOperatorGateIntent } from "./approval.js";
|
|
18
18
|
import { loadSkills } from "./capabilities/skills.js";
|
|
19
19
|
import { GiteaClient } from "./capabilities/repo-tools.js";
|
|
20
20
|
import { buildScenarios, builtinScenarioDetails } from "./capabilities/scenarios.js";
|
|
@@ -38,7 +38,8 @@ import { createHookLlm } from "./hooks/hook-llm.js";
|
|
|
38
38
|
import { startFleetClientFromEnv } from "./fleet-client.js";
|
|
39
39
|
import { reclaimOrphansAtBoot } from "./boot-reclaim.js";
|
|
40
40
|
import { createConfigCenterRuntime } from "./boot/config-center.js";
|
|
41
|
-
import {
|
|
41
|
+
import { createResolveSpec } from "./boot/resolve-spec.js";
|
|
42
|
+
import { createParkedReviveInheritedGate } from "./boot/parked-revive-gate.js";
|
|
42
43
|
import { startReapers } from "./boot/reapers.js";
|
|
43
44
|
import { openStores } from "./boot/stores.js";
|
|
44
45
|
import { createBudgetAndTracing } from "./boot/budget-tracing.js";
|
|
@@ -629,29 +630,12 @@ async function main() {
|
|
|
629
630
|
// checkpointStore 同实例天然成立;per-request 项如 webSearch 对 revive 无关——objective 在 core
|
|
630
631
|
// driveParkedResume 里被丢弃)。checkpointStore/agentStore 任一缺席 = park 本就不可达,分支不接线。
|
|
631
632
|
const parkedReviveTool = checkpointStore && backgroundAgentStore ? scenarios.default?.({})?.tools.find((t) => t.name === "Agent") : undefined;
|
|
632
|
-
// [1596]/[1597]
|
|
633
|
-
//
|
|
634
|
-
//
|
|
635
|
-
//
|
|
636
|
-
// 重建链上的豁免语义与原链一致。重建不出(无 root 锚)= 不供席 = core pre-CAS 门诚实拒(现状)。
|
|
637
|
-
// #152 复审 A2:「同参重建」是本工厂自述的不变式,所以 question 门必须调 resolve-spec 的**同一个**
|
|
638
|
-
// 工厂并传同一个活体面,不能自折 core 原形——分腿改动只落一侧就是两份拷贝开始漂移的那一刻。
|
|
639
|
-
// 赎回腿今天无 question ALS ctx(/decide 路由触发,core driveParkedResume 内驱动)⇒ 判 ask,与原形同判。
|
|
633
|
+
// [1596]/[1597] 跨副本父约束重供席工厂:host 任务的 toolPolicy 解析槽按**当前**部署配置重建
|
|
634
|
+
// 「部署 ⊇ 操作员」两层完整链(design/181 件二收编;实现与全部理由在 boot/parked-revive-gate.ts,
|
|
635
|
+
// 提出去的唯一理由是 main.ts 顶层 `void main()` 让那条腿的运行期语义在原地一格都钉不住)。
|
|
636
|
+
// 构造条件逐字保持:裸 Agent 工具在场 ∧ 部署真开了 durable 审批。
|
|
640
637
|
const parkedReviveInheritedGate = parkedReviveTool && config.durableApproval
|
|
641
|
-
? (
|
|
642
|
-
parentConstraints: [{
|
|
643
|
-
policy: combinePolicies(createDurableQuestionGate(question), createDurableAskPolicy({
|
|
644
|
-
requireApproval: config.approvalRequire, deny: config.approvalDeny, autoBudget: config.approvalAutoBudget, neverAuto: config.approvalNeverAuto,
|
|
645
|
-
...(approvalExemptionStore && row.rootSessionId
|
|
646
|
-
? {
|
|
647
|
-
exempt: (toolName) => approvalExemptionStore.has(row.rootSessionId, toolName),
|
|
648
|
-
onExempted: (toolName, rawToolName) => logger.info("approval_exempted", { sessionId: row.rootSessionId, toolName, rawToolName, leg: "parked_revive" }),
|
|
649
|
-
}
|
|
650
|
-
: {}),
|
|
651
|
-
})),
|
|
652
|
-
durableMandate: true,
|
|
653
|
-
}],
|
|
654
|
-
})
|
|
638
|
+
? createParkedReviveInheritedGate({ config, question, approvalExemptionStore, logger, localRoot })
|
|
655
639
|
: undefined;
|
|
656
640
|
// 场景详情:内建 details 必须在 overlay 合并【前】构建(探针要打纯内建工厂,不是被 center 顶掉的);
|
|
657
641
|
// center details 随 overlay 同判定源盖同名——source 语义与 selectScenario 实际取用永一致(约定①)。
|
|
@@ -779,6 +763,10 @@ async function main() {
|
|
|
779
763
|
// Undefined-safe: the env-only/no-backend worker 501s the routes.
|
|
780
764
|
backend: backend ? backend : undefined,
|
|
781
765
|
};
|
|
766
|
+
// [3193] 病理1:fleet 快照的 recovery 屏障(deferred 形,声明先于 deps 装配、落定在下方 recover 块——
|
|
767
|
+
// 对装配顺序鲁棒;无 notify gate 的部署在 recover 块 else 臂立即落)。
|
|
768
|
+
let resolveFleetRecovery;
|
|
769
|
+
const fleetRecoveryDone = new Promise((r) => { resolveFleetRecovery = r; });
|
|
782
770
|
/** 进程内活体协调面(本副本 only,重启即空:HITL 协调器/可 steer 句柄注册表/总线/收件箱) */
|
|
783
771
|
const coordinators = {
|
|
784
772
|
elicitation: elicitation ? elicitation : undefined, // E23: live-only inbound-elicitation coordinator (respond route + ALS wraps)
|
|
@@ -788,6 +776,7 @@ async function main() {
|
|
|
788
776
|
subagentSteerRegistry, // C2 (core 1.219): steerable Task-subagent handle registry (POST /v1/runs/:id/subagents/:target/steer)
|
|
789
777
|
workflowCompletionInbox: workflowCompletionInbox ? workflowCompletionInbox : undefined, // P1 ①②: drain → workflow_complete frame on stream-open
|
|
790
778
|
fleetBus,
|
|
779
|
+
fleetRecoveryDone, // [3193] 病理1:快照组装的有界等待信号(recover 块落定)
|
|
791
780
|
// [1196] session 级 SSE 订阅:watch registry(N 订阅共享一条探针,自适应拍距,无订阅即停)。谓词与
|
|
792
781
|
// /head 探针同门(getLeafId+ownerOf);缺 store 面不建=路由 501+capabilities.sessionEvents=false。
|
|
793
782
|
sessionWatch: sessionWatchRegistry ? sessionWatchRegistry : undefined, // [1196] 建于 purgeSession 之前(delete 即 dropSession 的同副本围栏)
|
|
@@ -1040,10 +1029,17 @@ async function main() {
|
|
|
1040
1029
|
// sweep throw is logged, never fatal.
|
|
1041
1030
|
if (workflowNotifyGate) {
|
|
1042
1031
|
// [2995]/[2999] opts 单点合成在 workflow-orchestration(可见性回灌 + replica-local 形 boot 孤儿判死)。
|
|
1032
|
+
// [3193] 病理1:recover 收官(成败皆算)即落 fleet 快照屏障——「serving 不等 recovery」对**提交请求**
|
|
1033
|
+
// 仍成立,但 fleet 快照组装现在对它做有界等待(routes/fleet.ts,3s cap):SIGKILL 后壳重连不再
|
|
1034
|
+
// 与 boot 判死赛跑(cli [3193] 插桩实证的竞态)。
|
|
1043
1035
|
void workflowNotifyGate
|
|
1044
1036
|
.recover(workflowRecoverOpts)
|
|
1045
1037
|
.then((tally) => logger.info("workflow_notify_recovered", tally))
|
|
1046
|
-
.catch((err) => logger.warn("workflow_notify_recover_failed", { err: String(err) }))
|
|
1038
|
+
.catch((err) => logger.warn("workflow_notify_recover_failed", { err: String(err) }))
|
|
1039
|
+
.finally(() => resolveFleetRecovery());
|
|
1040
|
+
}
|
|
1041
|
+
else {
|
|
1042
|
+
resolveFleetRecovery();
|
|
1047
1043
|
}
|
|
1048
1044
|
// design/158 A10:收尾段搬到 src/boot/shutdown.ts(逐字)。⚠️ 调用点必须留在 listen 之后 —— 位置即契约,
|
|
1049
1045
|
// 理由(信号注册时点/三信号相对次序/clearInterval 先于 server.close)见该文件头注。
|
|
@@ -37,6 +37,10 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
37
37
|
readonly cls: "F";
|
|
38
38
|
readonly note: "已验签的提示词工件回填本地缓存失败。工件本身已返回给调用方;代价是下次同 digest 还要再取一次网络。";
|
|
39
39
|
};
|
|
40
|
+
readonly "server.fleet.snapshot-terminal-window-unavailable": {
|
|
41
|
+
readonly cls: "F";
|
|
42
|
+
readonly note: "`/v1/fleet/stream` 连接时快照的**近期终态行窗**(#189:引擎重启后 boot 判死的 workflow 行,pull 自 durable store)读失败或超预算(2s)⇒ 本次快照少这几行历史。放行的最坏后果=面板首屏看不到刚结束的 workflow(与本修之前的行为等同,不是新损失);真源不受影响(`GET /v1/workflows` 照常)。方向刻意 fail-open:一次慢查询不该把整条 SSE 的握手拖住——少几行是难看,连不上流是坏掉。";
|
|
43
|
+
};
|
|
40
44
|
readonly "server.fleet.subscriber-callback-threw": {
|
|
41
45
|
readonly cls: "F";
|
|
42
46
|
readonly note: "fleet bus 某订阅回调抛错 ⇒ 该回调本帧作废,其余订阅方与发布方不受影响。隔离是承重的:扇出同步,修前异常会传回发布方 put/update 投影点,core 持久化 catch{} 且不推进 storeRev ⇒ durable 行冻在 running 而 notify 已 ack(#183 复审 R3 HIGH)。丢的只是一个消费方的一帧渲染,故 F 类;但必须留痕——静默吞掉等于订阅方病灶永不显形。";
|
|
@@ -60,6 +60,10 @@ export const FAIL_OPEN_TAGS = {
|
|
|
60
60
|
cls: "F",
|
|
61
61
|
note: "已验签的提示词工件回填本地缓存失败。工件本身已返回给调用方;代价是下次同 digest 还要再取一次网络。",
|
|
62
62
|
},
|
|
63
|
+
"server.fleet.snapshot-terminal-window-unavailable": {
|
|
64
|
+
cls: "F",
|
|
65
|
+
note: "`/v1/fleet/stream` 连接时快照的**近期终态行窗**(#189:引擎重启后 boot 判死的 workflow 行,pull 自 durable store)读失败或超预算(2s)⇒ 本次快照少这几行历史。放行的最坏后果=面板首屏看不到刚结束的 workflow(与本修之前的行为等同,不是新损失);真源不受影响(`GET /v1/workflows` 照常)。方向刻意 fail-open:一次慢查询不该把整条 SSE 的握手拖住——少几行是难看,连不上流是坏掉。",
|
|
66
|
+
},
|
|
63
67
|
"server.fleet.subscriber-callback-threw": {
|
|
64
68
|
cls: "F",
|
|
65
69
|
note: "fleet bus 某订阅回调抛错 ⇒ 该回调本帧作废,其余订阅方与发布方不受影响。隔离是承重的:扇出同步,修前异常会传回发布方 put/update 投影点,core 持久化 catch{} 且不推进 storeRev ⇒ durable 行冻在 running 而 notify 已 ack(#183 复审 R3 HIGH)。丢的只是一个消费方的一帧渲染,故 F 类;但必须留痕——静默吞掉等于订阅方病灶永不显形。",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { WorkflowRun, WorkflowRunStore, WorkflowRunStatus, WorkflowRunSummary } from "@sema-agent/core";
|
|
2
|
-
import type
|
|
2
|
+
import { type FleetEventBus } from "../fleet/fleet-bus.js";
|
|
3
3
|
/** A workflow completion notification's terminal payload — the bounded, redacted shape core's notifier seam
|
|
4
4
|
* carries, re-derivable from a durable {@link import("@sema-agent/core").WorkflowRun} on the recovery path. */
|
|
5
5
|
export interface WorkflowCompletionPayload {
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
*/
|
|
42
42
|
import { mkdirSync, readFileSync, existsSync, openSync, writeSync, fsyncSync, closeSync } from "node:fs";
|
|
43
43
|
import { join } from "node:path";
|
|
44
|
-
import {
|
|
45
|
-
import {
|
|
44
|
+
import { buildFleetWorkflowRow } from "../fleet/fleet-bus.js";
|
|
45
|
+
import { isTerminalWorkflowRowStatus, seedTerminalWorkflowRow } from "../fleet/fleet-terminal-window.js";
|
|
46
46
|
/** Build a bounded + redacted completion summary from a durable run's terminal state (the recovery path — the
|
|
47
47
|
* live path uses core's own `boundedSummary`). We never have the raw result here (the run store keeps the run,
|
|
48
48
|
* not the originator's payload), so we synthesize a conservative one: status + the run's already-bounded error.
|
|
@@ -500,43 +500,23 @@ export class JournalingWorkflowRunStore {
|
|
|
500
500
|
publishFleet(id, run) {
|
|
501
501
|
if (!this.fleetBus)
|
|
502
502
|
return;
|
|
503
|
-
|
|
504
|
-
//
|
|
505
|
-
//
|
|
506
|
-
|
|
507
|
-
const done = agents.filter((a) => a.status === "completed").length;
|
|
508
|
-
const failed = agents.filter((a) => a.status === "failed").length;
|
|
509
|
-
// cli [1726] 二①:CC 规模告警的分母是 **started**(已启动),而 `totalCount`(= agents.length)是**计划总数**
|
|
510
|
-
// (含排队中)。判别口径走 core 导出的 `deriveAgentDisplayStatus` —— 一个 agent 已在 `run.agents` 里但
|
|
511
|
-
// `startedAt` 未置就是排队中。**不自己写 `startedAt !== undefined`**:那是 core 的显示契约,两端各写一遍
|
|
512
|
-
// 必然漂移(本仓刚在 coarse 门镜像上吃过同款),用它的函数则 core 一改、两端同时跟随。
|
|
513
|
-
const started = agents.filter((a) => deriveAgentDisplayStatus(a, run.status) !== "queued").length;
|
|
514
|
-
const terminal = run.status === "completed" || run.status === "failed";
|
|
503
|
+
// A-002.3(#194 病族 F1 同源性断裂):终态判据走 fleet-terminal-window 的共享谓词(其穷举表对
|
|
504
|
+
// core WorkflowRunStatus 编译期闭合)——此前手写两值比较是第二份独立定义,core 加终态词时穷举表
|
|
505
|
+
// 红、这里恒 false ⇒ 行既不 seed 进终态窗也不 removeWorkflow,面板留幽灵 running 行。
|
|
506
|
+
const terminal = isTerminalWorkflowRowStatus(run.status);
|
|
515
507
|
// 撤行**不挂在发帧成功上**(复审第 2 轮 MEDIUM):「终态行必须离场」是不变量、发终帧是尽力而为。
|
|
516
508
|
// try/finally ⇒ 发布本体抛错照样撤行(异常仍向上抛,不吞:吞了就是一条未登记的 fail-open 臂)。
|
|
517
509
|
// 订阅方抛错已在 fleet bus 的 subscribe 隔离层被吞并记 fail-open——本 finally 守的是发布机器本身
|
|
518
510
|
// 的抛错(理论缝级,fresh 复审盘点后判近死防御但保留:防未来发布本体改动回退)。两腿一处收口。
|
|
511
|
+
// 行投影本体 = {@link buildFleetWorkflowRow}(#189:快照的终态行窗与本写路径共用同一份投影,
|
|
512
|
+
// 单源不漂;本函数的发布/撤行语义逐字不变)。
|
|
513
|
+
// [3156] 轮2 修:终态行**写路径顺手 seed** 进程内终态窗缓存——重启-重连剧本里(boot recover 判死),
|
|
514
|
+
// fleetWide 连接的 durable pull 没有正确的查询键可用(caller principal ≠ 引擎铸行 scope),seed 是
|
|
515
|
+
// 唯一同源可达径。放在 publish 之前:seed 是纯内存 push,不会抛;终态行入窗与发帧同拍。
|
|
516
|
+
if (terminal)
|
|
517
|
+
seedTerminalWorkflowRow(buildFleetWorkflowRow(id, run));
|
|
519
518
|
try {
|
|
520
|
-
this.fleetBus.publishWorkflow(
|
|
521
|
-
id,
|
|
522
|
-
// [WF2-A parity] redact the workflow label surfaces for parity with the run + subagent-child names (fleet-bus.ts):
|
|
523
|
-
// a tool-launched (LLM-authored) workflow's meta.name/description is task-controlled and could carry a secret shape.
|
|
524
|
-
name: redactSecrets(run.name ?? "Dynamic workflow"),
|
|
525
|
-
...(run.description ? { description: redactSecrets(run.description) } : {}),
|
|
526
|
-
scope: run.scope,
|
|
527
|
-
// codex-6 F2:sessionId 必须随行——streamFleet 对无 sessionId 的行按「同 principal 全会话可见」
|
|
528
|
-
// 兜底,漏发=A 会话的 workflow 名/进度/token 泄进 B 会话的 ?session= 过滤流。
|
|
529
|
-
...(run.originatingSessionId ? { sessionId: run.originatingSessionId } : {}),
|
|
530
|
-
status: run.status,
|
|
531
|
-
doneCount: done,
|
|
532
|
-
totalCount: agents.length,
|
|
533
|
-
failedCount: failed,
|
|
534
|
-
startedCount: started,
|
|
535
|
-
tokens: (run.stats?.tokens ?? 0) + (run.stats?.nested?.tokens ?? 0),
|
|
536
|
-
// [1294]:跑动中也带时长(1.232 只在 endedAt 后带——clay 验收轮实锚面板恒显 0s)。终态用
|
|
537
|
-
// endedAt 定格,活跑用 now-startedAt(每次 put/update 观察点刷新,壳侧读帧即当前时长)。
|
|
538
|
-
elapsedMs: (run.endedAt ?? Date.now()) - run.startedAt,
|
|
539
|
-
});
|
|
519
|
+
this.fleetBus.publishWorkflow(buildFleetWorkflowRow(id, run));
|
|
540
520
|
}
|
|
541
521
|
finally {
|
|
542
522
|
if (terminal)
|
|
@@ -297,6 +297,39 @@ export declare const APPROVAL_BATCHES_TABLE = "approval_batches";
|
|
|
297
297
|
*
|
|
298
298
|
* SCHEMA POLICY 同 `tidb-pool.ts` 头注:纯 CREATE,禁 ALTER 增量 seam,改列直接改这里 + 重建库
|
|
299
299
|
* (`docs/schema/baseline-mysql.sql` 是由这些文本机器录制出的**产物**,永不手改)。
|
|
300
|
+
*
|
|
301
|
+
* ── 列宽的**测量依据**(#192 A10;U2 逐列取证在档)──────────────────────────────────────────────────
|
|
302
|
+
* 本表家族原先一律 VARCHAR(255),是"先建表后想"的产物。按"每列的值到底由谁铸、有没有入口上限"重新裁:
|
|
303
|
+
*
|
|
304
|
+
* 收窄(有硬上限背书,证据即上限本身):
|
|
305
|
+
* · `ask_id` / `batch_id` / `bound_ask_id` → 64:`deriveAskId`/`deriveBatchId` 恒是 sha256 hex 取前
|
|
306
|
+
* 64 字符(`approval-ask-machine.ts` deterministicId),按构造不可能更长。
|
|
307
|
+
* · `task_id` → 64:入口 `UUIDV7_RE`(`security.ts:134`)只放行 36 字符的规范 uuidv7;与兄弟列
|
|
308
|
+
* `task_run.task_id VARCHAR(64)` 同宽(此前 255 是本家族独有的偏离,无依据)。
|
|
309
|
+
* · `session_id` → 64:提交入口硬拒 >64(`http/server.ts` "sessionId must be at most 64 characters"),
|
|
310
|
+
* 与 `session_meta`/`task_run` 同宽。
|
|
311
|
+
* · `owner` → 190:`assertPrincipalShape` 的 `PRINCIPAL_MAX_LENGTH = 190`(`security.ts:350`)硬拒更长者;
|
|
312
|
+
* 全仓每一根 owner/scope 轴都是 190,此前 255 同样是无依据偏离。
|
|
313
|
+
* · `gate_token` → 120:它抄的是 checkpoint 的 token(core `mintCheckpointToken` = 16 字节 hex = 32 字符),
|
|
314
|
+
* 取与**被抄那一列** `checkpoint.token VARCHAR(120)` 同宽 —— 同一个值在两张表上宽度必须一致,
|
|
315
|
+
* 否则抄的那一步就是一道静默截断。512 是原先的 16 倍冗余。
|
|
316
|
+
*
|
|
317
|
+
* 🔑 上面四条收窄之所以**不引入任何新暴露面**,靠的不是入口断言而是一条更硬的事实:同一个 session_id /
|
|
318
|
+
* task_id / owner **早就**同时躺在 `session_meta.session_id VARCHAR(64)` / `task_run.task_id VARCHAR(64)` /
|
|
319
|
+
* `task_run.owner VARCHAR(190)` 里。任何长到能撑爆本表新宽度的值,在写到那几张**更中心**的表时就已经
|
|
320
|
+
* 先炸了 —— 本表此前的 255 从来不是一道额外的安全余量,只是一处与全仓不一致的偏离。
|
|
321
|
+
* ⇒ 收窄的风险上界 = 0;真正要担心的是反过来:留着 255 会让人误以为这里可以存更长的 id。
|
|
322
|
+
*
|
|
323
|
+
* 🔴 **刻意不收窄**(测量结果不支持,记在这里免得下一个人以为是漏了):
|
|
324
|
+
* · `source_task_id` / `tool_call_id` / `parent_tool_call_id` / `gate_bound_call_id` —— 这四列的值是
|
|
325
|
+
* **模型/引擎铸的原始 id**,服务端在写行这一步**没有任何长度断言**(`approval-card.ts` 的
|
|
326
|
+
* `MAX_IDENT` 只裁了给人看的那份 card 投影,不是本列)。没有入口上限就收窄 = 把"存不下"这件事
|
|
327
|
+
* 推迟到 INSERT 才炸(MySQL 更糟:静默截断),换来的只是几十字节。要收窄,先补入口断言
|
|
328
|
+
* (照 `assertPrincipalShape`/`assertIdempotencyKeyShape` 的姿势),那是另一件事。
|
|
329
|
+
* · `bound_input_hash` / `gate_bound_input_hash` —— 按约定是 sha256 hex(64),但 `readBoundInputHash`
|
|
330
|
+
* 只校验"非空字符串",没校长度/字形 ⇒ 同上,先有断言再谈收窄。
|
|
331
|
+
* · `idempotency_key` —— 255 **正是**入口断言的上限本身(zod `.max(255)` + 店内 `assertIdempotencyKeyShape`
|
|
332
|
+
* 双执法),宽度与上限同源,恰好正确,动它反而制造截断面。
|
|
300
333
|
*/
|
|
301
334
|
export declare const TIDB_APPROVAL_ASK_STATEMENTS: readonly string[];
|
|
302
335
|
/** {@link TIDB_APPROVAL_ASK_STATEMENTS} 的遍历壳——生产路径走 `tidb-pool.ts` 的中央 `ensureSchema`
|