@sema-agent/server 7.53.0 → 7.55.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 +5 -0
- package/dist/approval-ask-audit-store.d.ts +129 -0
- package/dist/approval-ask-audit-store.js +284 -0
- package/dist/approval-card.d.ts +18 -6
- package/dist/approval-card.js +2 -2
- package/dist/approval-reconciler.d.ts +2 -1
- package/dist/approval-reconciler.js +1 -1
- package/dist/boot/config-center.js +2 -2
- package/dist/boot/coordinators.d.ts +2 -0
- package/dist/boot/coordinators.js +18 -1
- package/dist/boot/leader.js +2 -0
- package/dist/boot/reapers.d.ts +2 -1
- package/dist/boot/resolve-spec.js +1 -3
- package/dist/boot/runner-deps.d.ts +20 -0
- package/dist/boot/runner-deps.js +14 -8
- package/dist/boot/shutdown.js +1 -1
- package/dist/boot/stores.js +8 -1
- package/dist/capabilities/team.d.ts +10 -9
- package/dist/config-center/apply-effective.d.ts +25 -26
- package/dist/config-center/apply-effective.js +28 -66
- package/dist/config-center/effective-keys.d.ts +30 -0
- package/dist/config-center/effective-keys.js +49 -0
- package/dist/config-center/facade.d.ts +3 -3
- package/dist/config-center/facade.js +1 -1
- package/dist/config-center/restart-signal.js +3 -9
- package/dist/config-center/types.d.ts +19 -40
- package/dist/config-lkg.js +7 -1
- package/dist/config-provider.js +1 -1
- package/dist/device-enrollment.d.ts +5 -3
- package/dist/device-store.d.ts +81 -3
- package/dist/device-store.js +37 -0
- package/dist/device-ws-hub.d.ts +15 -2
- package/dist/device-ws-hub.js +31 -5
- package/dist/http/routes/approvals-assistant.js +1 -0
- package/dist/http/routes/devices.d.ts +64 -0
- package/dist/http/routes/devices.js +173 -0
- package/dist/http/server.d.ts +14 -0
- package/dist/http/server.js +32 -2
- package/dist/http/wire-types.d.ts +19 -0
- package/dist/leader/wire.d.ts +12 -0
- package/dist/leader/wire.js +6 -5
- package/dist/main.js +3 -1
- package/dist/observability/fail-open.d.ts +20 -0
- package/dist/observability/fail-open.js +20 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +11 -1
- package/dist/plugins/approval-ask-store-memory.js +20 -3
- package/dist/plugins/approval-ask-store-sql.d.ts +82 -0
- package/dist/plugins/approval-ask-store-sql.js +41 -10
- package/dist/plugins/device-store-sql.d.ts +38 -1
- package/dist/plugins/device-store-sql.js +82 -2
- package/dist/plugins/pg-pool.d.ts +29 -2
- package/dist/plugins/pg-pool.js +45 -2
- package/dist/plugins/remote-env-device.d.ts +4 -2
- package/dist/plugins/remote-env-device.js +12 -2
- package/dist/plugins/roster-store-sql.d.ts +1 -3
- package/dist/plugins/sql-errors.d.ts +12 -0
- package/dist/plugins/sql-errors.js +10 -0
- package/dist/plugins/store-backend.js +1 -1
- package/dist/plugins/tidb-pool.d.ts +13 -0
- package/dist/runs.js +1 -0
- package/dist/runtime-governance.d.ts +4 -0
- package/dist/runtime-governance.js +23 -1
- package/dist/tool-approval.d.ts +63 -39
- package/dist/tool-approval.js +322 -109
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +2 -0
- package/dist/trace/injection-tier.d.ts +11 -21
- package/dist/trace/injection-tier.js +3 -4
- package/dist/trace/ledger-events.d.ts +9 -0
- package/dist/trace/project.d.ts +1 -0
- package/dist/trace/project.js +1 -0
- package/package.json +3 -3
package/USAGE.md
CHANGED
|
@@ -508,6 +508,11 @@ READ_DENY_PATTERNS='[{"pattern":"**/secrets/**"},{"pattern":".ssh","caseSensitiv
|
|
|
508
508
|
一律启动期点名拒——一个手滑的 deny 集比没有 deny 集更糟(以为挡了其实没挡)。
|
|
509
509
|
- **缺席不铸**:两个键不设就**不传给引擎**——引擎抬默认/扩内建集那天,存量部署自动跟上(与重试上限
|
|
510
510
|
同款「引擎当家」纪律)。
|
|
511
|
+
- **leader 车道同席(S-28,7.54 起)**:`LEADER_ENABLED` 的 leader 编排(planner / worker / repair / conflict 四类
|
|
512
|
+
任务)与主车道吃**同一份**四键(连同 `MEMORY_DELEGATION_EVIDENCE` / `MEMORY_PROVENANCE` / `MEMORY_CAPTURE_POLICY`
|
|
513
|
+
与委派入口 caps;取值点唯一 = `buildDeploymentPostureSeats`,leader 不另读 env)。此前(≤7.53)那条腿一席都不带:
|
|
514
|
+
deny 追加对 leader worker **静默不生效**(core 内建 deny 表仍在)。仍缺 tracer / usageWindows / promptSource 三席
|
|
515
|
+
(#311 扩面,另车)。
|
|
511
516
|
- **组织下发腿(config-center,7.29 起)**:四键(`READ_FACE` / `READ_DENY_PATTERNS` /
|
|
512
517
|
`READ_DENY_BUILTIN_TIERS` / `READ_DENY_BUILTIN_EXCLUDE`)也可由 config-center 的 `readFace` 域下发
|
|
513
518
|
(`{face?, denyPatterns?, denyBuiltinTiers?, denyBuiltinExclude?}`)。三条规则:
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/** 数据子目录名(挂在 local 数据根下,与 `workflow-notify/` 同层同形)。 */
|
|
2
|
+
export declare const APPROVAL_ASK_AUDIT_DIR = "approval-ask-audit";
|
|
3
|
+
/** 崩溃收敛行的读面上限(新前旧后截断;有界 wire —— 读面不许随月份线性长)。 */
|
|
4
|
+
export declare const CRASH_CONVERGED_LIST_MAX = 200;
|
|
5
|
+
/** 铸造点入参(协调器 askBroadcast 的 register-before-emit 之后挂线)。 */
|
|
6
|
+
export interface AuditAskInput {
|
|
7
|
+
/** wire 身份 = 呈卡帧上那把 `approvalId`(respond/queue 同源可达;不是本地 pending 键)。 */
|
|
8
|
+
approvalId: string;
|
|
9
|
+
/** 出处 run 的 taskId(`origin.taskId`,不是投递连接的)。 */
|
|
10
|
+
taskId: string;
|
|
11
|
+
sourceTaskId?: string;
|
|
12
|
+
sessionId?: string;
|
|
13
|
+
owner: string | null;
|
|
14
|
+
toolName: string;
|
|
15
|
+
toolCallId?: string;
|
|
16
|
+
/** 出处腿此刻是否在协调器 `runWithContext` 的广播目标里(⇒ 腿闭信号可达)。decided 臂的收敛前提。 */
|
|
17
|
+
legTracked: boolean;
|
|
18
|
+
expiresAtMs: number;
|
|
19
|
+
mintedAtMs: number;
|
|
20
|
+
}
|
|
21
|
+
/** 决议点入参(`settle()` 公共咽喉挂线;五臂全过,不漏窗到期/断连/取消)。 */
|
|
22
|
+
export interface AuditSettleInput {
|
|
23
|
+
approvalId: string;
|
|
24
|
+
outcome: "allowed" | "denied" | "expired";
|
|
25
|
+
/** 走了 park 路由(windowRouteUnavailable)—— 该行的续命在 durable park 面,收敛器不当孤儿。 */
|
|
26
|
+
parkRouted?: boolean;
|
|
27
|
+
/** 宿主自报来源(HostSettledBy;在场才带)。收敛器不消费,纯审计。 */
|
|
28
|
+
settledBy?: string;
|
|
29
|
+
settledAtMs: number;
|
|
30
|
+
}
|
|
31
|
+
/** 协调器消费的窄写口(协调器只 import type,不拖实现依赖)。 */
|
|
32
|
+
export interface ApprovalAskAuditSink {
|
|
33
|
+
recordAsk(input: AuditAskInput): void;
|
|
34
|
+
recordSettle(input: AuditSettleInput): void;
|
|
35
|
+
/** 腿闭点(`runWithContext` finally)。只为铸过审计行的 taskId 真落行(店内去重)。 */
|
|
36
|
+
noteLegClosed(taskId: string): void;
|
|
37
|
+
}
|
|
38
|
+
/** v1.1 §6 两分臂(闭集)。 */
|
|
39
|
+
export type CrashOrphanState = "pending" | "decided";
|
|
40
|
+
/** `GET /v1/approvals` additive 键 `crashConverged` 的行形(wire 键集刻意窄:args/卡体不上列表,
|
|
41
|
+
* 与 `livePending` 行同纪律 —— 不开第二个脱敏面)。 */
|
|
42
|
+
export interface CrashConvergedRow {
|
|
43
|
+
approvalId: string;
|
|
44
|
+
toolName: string;
|
|
45
|
+
taskId: string;
|
|
46
|
+
sessionId?: string;
|
|
47
|
+
/** 铸造时刻(键名照 `livePending[].ts`)。 */
|
|
48
|
+
ts: number;
|
|
49
|
+
expiresAtMs: number;
|
|
50
|
+
/** 终态 DENIED 同码(稿 §2 (c) 施工形;SQL 车道收敛器的 fail-closed DENIED 同向)。 */
|
|
51
|
+
decision: "denied";
|
|
52
|
+
cause: "crashed_before_park";
|
|
53
|
+
orphanState: CrashOrphanState;
|
|
54
|
+
/** decided 臂:人当时批的是 approve(这句话不许被终态同码吞掉)。 */
|
|
55
|
+
originalDecision?: "approve";
|
|
56
|
+
decidedAtMs?: number;
|
|
57
|
+
convergedAtMs: number;
|
|
58
|
+
/** v1.1 §6 指路位:pending 臂 = 工具零执行可机械证明 ⇒ re-run(core resume 补偿腿)安全,壳可
|
|
59
|
+
* 默认自动续跑;decided 臂 = 可能半截副作用 ⇒ 留人工确认。 */
|
|
60
|
+
resumeSafe: boolean;
|
|
61
|
+
}
|
|
62
|
+
/** 读面窄口(HTTP deps 消费;写口/收敛不外漏)。 */
|
|
63
|
+
export interface ApprovalAskAuditReadFace {
|
|
64
|
+
listCrashConverged(scope: string | undefined): CrashConvergedRow[];
|
|
65
|
+
}
|
|
66
|
+
export declare class FileApprovalAskAuditStore implements ApprovalAskAuditSink, ApprovalAskAuditReadFace {
|
|
67
|
+
/** 账本绝对路径(测试/取证按文件字节对账 —— 崩溃语义的真源是文件,不是内存投影)。 */
|
|
68
|
+
readonly ledgerPath: string;
|
|
69
|
+
private fd;
|
|
70
|
+
/** codex R2-F1(采):上一次 append 抛出时可能已落**部分**字节(半写)—— 置位后,下一次成功写必须
|
|
71
|
+
* 先落一个孤立换行把残尾隔离并 fsync(与 boot 修尾同一形,只是发生在同进程内),否则新记录会与残尾
|
|
72
|
+
* 并成一条坏行、重放双双丢失(丢 settle+leg_closed 会让 ask-only 组冒充 pending 孤儿)。定界自己
|
|
73
|
+
* 失败 ⇒ 脏标保持,本条记录走留痕臂 —— 绝不往可能的残尾上直接续字节。 */
|
|
74
|
+
private tailDirty;
|
|
75
|
+
/** 写字节口(测试注入席:半写故障的确定性构造;生产恒缺省 = `fs.writeSync`)。 */
|
|
76
|
+
private readonly writeBytes;
|
|
77
|
+
/** boot 重放快照(冻结于构造时刻 = 前世账本;本进程新写的行不进它 —— 收敛只对前世行)。 */
|
|
78
|
+
private readonly replayed;
|
|
79
|
+
/** 前世账本里的腿闭事件(taskId + 行序;decided 臂按「决议行之后」判)。 */
|
|
80
|
+
private readonly replayedLegClosed;
|
|
81
|
+
/** 已收敛行(读面数据源;含前世已收敛 + 本次 boot 新收敛)。owner 只做 scope 门,不上 wire。 */
|
|
82
|
+
private readonly convergedRows;
|
|
83
|
+
/** 本进程铸过审计行、且腿闭信号可达的 taskId(腿闭去重 + 防无关腿刷账本)。 */
|
|
84
|
+
private readonly openLegTasks;
|
|
85
|
+
private convergedOnce;
|
|
86
|
+
/**
|
|
87
|
+
* boot 期 fail-stop:mkdir/重放失败**拒启**(File 店族同律)。root = local 数据根
|
|
88
|
+
* (`config.localDataRoot`;与 `LocalBackend` 同源,见头注装配段)。
|
|
89
|
+
* 0o700 目录 + 0o600 账本:行携租户身份(owner/sessionId),不得组/他人可读
|
|
90
|
+
* (`workflow-notify-journal.ts` 同姿态)。
|
|
91
|
+
*/
|
|
92
|
+
constructor(root: string, internals?: {
|
|
93
|
+
writeBytes?: (fd: number, buf: Buffer, off: number, len: number) => number;
|
|
94
|
+
});
|
|
95
|
+
/** 重放前世账本。撕尾/坏行 = 未提交态,跳过(计数,一条 warn 级 stderr 契约由调用方日志承担 ——
|
|
96
|
+
* 这里不持 logger 席,与 journal 同姿态;坏行数暴露给收敛日志)。 */
|
|
97
|
+
private corruptLines;
|
|
98
|
+
/** 尾部无终止换行(崩在 append 半途)—— ctor 在首次 append 前补孤立换行隔离之(codex R1-F3)。
|
|
99
|
+
* ⚠️ 判据是**换行缺席**不是「解析失败」:崩在「JSON 全落、换行未落」的字节窗时撕尾能 parse 成功,
|
|
100
|
+
* 但不修尾照样会与下一条记录并行。 */
|
|
101
|
+
private tornTail;
|
|
102
|
+
private replay;
|
|
103
|
+
private group;
|
|
104
|
+
/** append + fsync(逐行落盘:进程猝死不丢已记的行 —— 本店的全部价值就在崩溃时刻)。
|
|
105
|
+
* 运行期失败**响亮不阻断**:审批可用性不押在审计盘上(盘满/只读时人还得能批),但丢行必须在遥测
|
|
106
|
+
* 显形([ref] 三件套,census 登记行同批)。 */
|
|
107
|
+
private append;
|
|
108
|
+
recordAsk(input: AuditAskInput): void;
|
|
109
|
+
recordSettle(input: AuditSettleInput): void;
|
|
110
|
+
noteLegClosed(taskId: string): void;
|
|
111
|
+
/**
|
|
112
|
+
* boot 崩溃收敛器(装配层在构造后、协调器开始服务前调一次;幂等跨 boot)。
|
|
113
|
+
* 判据 = 头注「boot 收敛判据」段(一处成文,不复述)。返回两分臂计数 + 坏行数供 boot 日志。
|
|
114
|
+
*/
|
|
115
|
+
convergeCrashedAsks(): {
|
|
116
|
+
pending: number;
|
|
117
|
+
decided: number;
|
|
118
|
+
inconsistent: number;
|
|
119
|
+
corruptLines: number;
|
|
120
|
+
};
|
|
121
|
+
private projectRow;
|
|
122
|
+
/** scope 门与 `listLivePending` 逐字同律:`undefined` = operator 全量(含 owner null 行);串 = 只见
|
|
123
|
+
* `owner === scope` 的行(null-owner 行对 principal 不可见,fail-closed 宁少列不超范围;`"__none__"`
|
|
124
|
+
* 未鉴权哨兵自然恒空)。 */
|
|
125
|
+
listCrashConverged(scope: string | undefined): CrashConvergedRow[];
|
|
126
|
+
/** 收 append fd(优雅关闭腿 best-effort;SIGKILL 下不跑也不丢 —— 每行已 fsync)。 */
|
|
127
|
+
close(): void;
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=approval-ask-audit-store.d.ts.map
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, writeSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { recordFailOpen } from "./observability/fail-open.js";
|
|
4
|
+
export const APPROVAL_ASK_AUDIT_DIR = "approval-ask-audit";
|
|
5
|
+
export const CRASH_CONVERGED_LIST_MAX = 200;
|
|
6
|
+
function readLedgerRecord(u) {
|
|
7
|
+
if (u === null || typeof u !== "object" || Array.isArray(u))
|
|
8
|
+
return undefined;
|
|
9
|
+
const o = u;
|
|
10
|
+
const s = (k) => {
|
|
11
|
+
const v = o[k];
|
|
12
|
+
return typeof v === "string" ? v : undefined;
|
|
13
|
+
};
|
|
14
|
+
const num = (k) => {
|
|
15
|
+
const v = o[k];
|
|
16
|
+
return typeof v === "number" && Number.isFinite(v) ? v : undefined;
|
|
17
|
+
};
|
|
18
|
+
const bool = (k) => {
|
|
19
|
+
const v = o[k];
|
|
20
|
+
return typeof v === "boolean" ? v : undefined;
|
|
21
|
+
};
|
|
22
|
+
switch (o.kind) {
|
|
23
|
+
case "ask": {
|
|
24
|
+
const approvalId = s("approvalId");
|
|
25
|
+
const taskId = s("taskId");
|
|
26
|
+
const toolName = s("toolName");
|
|
27
|
+
const legTracked = bool("legTracked");
|
|
28
|
+
const expiresAtMs = num("expiresAtMs");
|
|
29
|
+
const mintedAtMs = num("mintedAtMs");
|
|
30
|
+
const ownerV = o.owner;
|
|
31
|
+
const owner = typeof ownerV === "string" ? ownerV : ownerV === null ? null : undefined;
|
|
32
|
+
if (approvalId === undefined || taskId === undefined || toolName === undefined || legTracked === undefined || expiresAtMs === undefined || mintedAtMs === undefined || owner === undefined) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
const toolCallId = s("toolCallId");
|
|
36
|
+
const sessionId = s("sessionId");
|
|
37
|
+
const sourceTaskId = s("sourceTaskId");
|
|
38
|
+
return {
|
|
39
|
+
kind: "ask",
|
|
40
|
+
approvalId,
|
|
41
|
+
taskId,
|
|
42
|
+
toolName,
|
|
43
|
+
legTracked,
|
|
44
|
+
expiresAtMs,
|
|
45
|
+
mintedAtMs,
|
|
46
|
+
owner,
|
|
47
|
+
...(toolCallId !== undefined ? { toolCallId } : {}),
|
|
48
|
+
...(sessionId !== undefined ? { sessionId } : {}),
|
|
49
|
+
...(sourceTaskId !== undefined ? { sourceTaskId } : {}),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
case "settle": {
|
|
53
|
+
const approvalId = s("approvalId");
|
|
54
|
+
const settledAtMs = num("settledAtMs");
|
|
55
|
+
const outcomeV = o.outcome;
|
|
56
|
+
const outcome = outcomeV === "allowed" || outcomeV === "denied" || outcomeV === "expired" ? outcomeV : undefined;
|
|
57
|
+
if (approvalId === undefined || settledAtMs === undefined || outcome === undefined)
|
|
58
|
+
return undefined;
|
|
59
|
+
const settledBy = s("settledBy");
|
|
60
|
+
return { kind: "settle", approvalId, outcome, settledAtMs, ...(bool("parkRouted") === true ? { parkRouted: true } : {}), ...(settledBy !== undefined ? { settledBy } : {}) };
|
|
61
|
+
}
|
|
62
|
+
case "leg_closed": {
|
|
63
|
+
const taskId = s("taskId");
|
|
64
|
+
const closedAtMs = num("closedAtMs");
|
|
65
|
+
if (taskId === undefined || closedAtMs === undefined)
|
|
66
|
+
return undefined;
|
|
67
|
+
return { kind: "leg_closed", taskId, closedAtMs };
|
|
68
|
+
}
|
|
69
|
+
case "crash_converged": {
|
|
70
|
+
const approvalId = s("approvalId");
|
|
71
|
+
const convergedAtMs = num("convergedAtMs");
|
|
72
|
+
const st = o.orphanState;
|
|
73
|
+
const orphanState = st === "pending" || st === "decided" ? st : undefined;
|
|
74
|
+
if (approvalId === undefined || convergedAtMs === undefined || orphanState === undefined)
|
|
75
|
+
return undefined;
|
|
76
|
+
return { kind: "crash_converged", approvalId, orphanState, convergedAtMs };
|
|
77
|
+
}
|
|
78
|
+
default:
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export class FileApprovalAskAuditStore {
|
|
83
|
+
ledgerPath;
|
|
84
|
+
fd;
|
|
85
|
+
tailDirty = false;
|
|
86
|
+
writeBytes;
|
|
87
|
+
replayed = new Map();
|
|
88
|
+
replayedLegClosed = [];
|
|
89
|
+
convergedRows = [];
|
|
90
|
+
openLegTasks = new Set();
|
|
91
|
+
convergedOnce = false;
|
|
92
|
+
constructor(root, internals) {
|
|
93
|
+
this.writeBytes = internals?.writeBytes ?? ((fd, buf, off, len) => writeSync(fd, buf, off, len));
|
|
94
|
+
const dir = join(root, APPROVAL_ASK_AUDIT_DIR);
|
|
95
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
96
|
+
this.ledgerPath = join(dir, "ledger.jsonl");
|
|
97
|
+
this.replay();
|
|
98
|
+
if (this.tornTail) {
|
|
99
|
+
this.fd = openSync(this.ledgerPath, "a", 0o600);
|
|
100
|
+
writeSync(this.fd, "\n");
|
|
101
|
+
fsyncSync(this.fd);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
corruptLines = 0;
|
|
105
|
+
tornTail = false;
|
|
106
|
+
replay() {
|
|
107
|
+
if (!existsSync(this.ledgerPath))
|
|
108
|
+
return;
|
|
109
|
+
const text = readFileSync(this.ledgerPath, "utf8");
|
|
110
|
+
this.tornTail = text.length > 0 && !text.endsWith("\n");
|
|
111
|
+
let line = 0;
|
|
112
|
+
for (const raw of text.split("\n")) {
|
|
113
|
+
if (raw.trim() === "")
|
|
114
|
+
continue;
|
|
115
|
+
line += 1;
|
|
116
|
+
let parsed;
|
|
117
|
+
try {
|
|
118
|
+
parsed = JSON.parse(raw);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
this.corruptLines += 1;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const rec = readLedgerRecord(parsed);
|
|
125
|
+
if (rec === undefined) {
|
|
126
|
+
this.corruptLines += 1;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
switch (rec.kind) {
|
|
130
|
+
case "ask": {
|
|
131
|
+
const g = this.group(rec.approvalId, line);
|
|
132
|
+
if (g.ask === undefined) {
|
|
133
|
+
g.ask = rec;
|
|
134
|
+
g.askLine = line;
|
|
135
|
+
}
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case "settle": {
|
|
139
|
+
const g = this.group(rec.approvalId, line);
|
|
140
|
+
if (g.settle === undefined) {
|
|
141
|
+
g.settle = rec;
|
|
142
|
+
g.settleLine = line;
|
|
143
|
+
}
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
case "leg_closed":
|
|
147
|
+
this.replayedLegClosed.push({ taskId: rec.taskId, line });
|
|
148
|
+
break;
|
|
149
|
+
case "crash_converged": {
|
|
150
|
+
const g = this.group(rec.approvalId, line);
|
|
151
|
+
g.converged = rec;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
default: {
|
|
155
|
+
const unreachable = rec;
|
|
156
|
+
throw new Error(`approval-ask-audit replay: unreachable record ${String(unreachable)}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
group(approvalId, line) {
|
|
162
|
+
let g = this.replayed.get(approvalId);
|
|
163
|
+
if (!g) {
|
|
164
|
+
g = { firstLine: line };
|
|
165
|
+
this.replayed.set(approvalId, g);
|
|
166
|
+
}
|
|
167
|
+
return g;
|
|
168
|
+
}
|
|
169
|
+
append(rec) {
|
|
170
|
+
try {
|
|
171
|
+
if (this.fd === undefined)
|
|
172
|
+
this.fd = openSync(this.ledgerPath, "a", 0o600);
|
|
173
|
+
if (this.tailDirty) {
|
|
174
|
+
const nl = Buffer.from("\n", "utf8");
|
|
175
|
+
let noff = 0;
|
|
176
|
+
while (noff < nl.length)
|
|
177
|
+
noff += this.writeBytes(this.fd, nl, noff, nl.length - noff);
|
|
178
|
+
fsyncSync(this.fd);
|
|
179
|
+
this.tailDirty = false;
|
|
180
|
+
}
|
|
181
|
+
const buf = Buffer.from(`${JSON.stringify(rec)}\n`, "utf8");
|
|
182
|
+
let off = 0;
|
|
183
|
+
while (off < buf.length)
|
|
184
|
+
off += this.writeBytes(this.fd, buf, off, buf.length - off);
|
|
185
|
+
fsyncSync(this.fd);
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
this.tailDirty = true;
|
|
189
|
+
recordFailOpen("server.approval-ask-audit.append-failed", `kind=${rec.kind} err=${err instanceof Error ? err.message : String(err)}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
recordAsk(input) {
|
|
193
|
+
if (input.legTracked)
|
|
194
|
+
this.openLegTasks.add(input.taskId);
|
|
195
|
+
this.append({ kind: "ask", ...input });
|
|
196
|
+
}
|
|
197
|
+
recordSettle(input) {
|
|
198
|
+
this.append({ kind: "settle", ...input });
|
|
199
|
+
}
|
|
200
|
+
noteLegClosed(taskId) {
|
|
201
|
+
if (!this.openLegTasks.has(taskId))
|
|
202
|
+
return;
|
|
203
|
+
this.openLegTasks.delete(taskId);
|
|
204
|
+
this.append({ kind: "leg_closed", taskId, closedAtMs: Date.now() });
|
|
205
|
+
}
|
|
206
|
+
convergeCrashedAsks() {
|
|
207
|
+
if (this.convergedOnce)
|
|
208
|
+
return { pending: 0, decided: 0, inconsistent: 0, corruptLines: this.corruptLines };
|
|
209
|
+
this.convergedOnce = true;
|
|
210
|
+
let pending = 0;
|
|
211
|
+
let decided = 0;
|
|
212
|
+
let inconsistent = 0;
|
|
213
|
+
for (const [approvalId, g] of this.replayed) {
|
|
214
|
+
if (g.ask === undefined)
|
|
215
|
+
continue;
|
|
216
|
+
if (g.converged !== undefined) {
|
|
217
|
+
this.convergedRows.push({ row: this.projectRow(g, g.converged.orphanState, g.converged.convergedAtMs), owner: g.ask.owner });
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
let orphanState;
|
|
221
|
+
if (g.settle === undefined) {
|
|
222
|
+
if (this.replayedLegClosed.some((l) => l.taskId === g.ask.taskId && l.line > (g.askLine ?? 0))) {
|
|
223
|
+
inconsistent += 1;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
orphanState = "pending";
|
|
227
|
+
}
|
|
228
|
+
else if (g.settle.outcome === "allowed" &&
|
|
229
|
+
g.ask.legTracked &&
|
|
230
|
+
!this.replayedLegClosed.some((l) => l.taskId === g.ask.taskId && l.line > (g.settleLine ?? 0))) {
|
|
231
|
+
orphanState = "decided";
|
|
232
|
+
}
|
|
233
|
+
if (orphanState === undefined)
|
|
234
|
+
continue;
|
|
235
|
+
const convergedAtMs = Date.now();
|
|
236
|
+
this.append({ kind: "crash_converged", approvalId, orphanState, convergedAtMs });
|
|
237
|
+
this.convergedRows.push({ row: this.projectRow(g, orphanState, convergedAtMs), owner: g.ask.owner });
|
|
238
|
+
if (orphanState === "pending")
|
|
239
|
+
pending += 1;
|
|
240
|
+
else
|
|
241
|
+
decided += 1;
|
|
242
|
+
}
|
|
243
|
+
this.convergedRows.sort((a, b) => b.row.convergedAtMs - a.row.convergedAtMs);
|
|
244
|
+
this.convergedRows.length = Math.min(this.convergedRows.length, CRASH_CONVERGED_LIST_MAX);
|
|
245
|
+
return { pending, decided, inconsistent, corruptLines: this.corruptLines };
|
|
246
|
+
}
|
|
247
|
+
projectRow(g, orphanState, convergedAtMs) {
|
|
248
|
+
const ask = g.ask;
|
|
249
|
+
return {
|
|
250
|
+
approvalId: ask.approvalId,
|
|
251
|
+
toolName: ask.toolName,
|
|
252
|
+
taskId: ask.taskId,
|
|
253
|
+
...(ask.sessionId !== undefined ? { sessionId: ask.sessionId } : {}),
|
|
254
|
+
ts: ask.mintedAtMs,
|
|
255
|
+
expiresAtMs: ask.expiresAtMs,
|
|
256
|
+
decision: "denied",
|
|
257
|
+
cause: "crashed_before_park",
|
|
258
|
+
orphanState,
|
|
259
|
+
...(orphanState === "decided" ? { originalDecision: "approve", ...(g.settle !== undefined ? { decidedAtMs: g.settle.settledAtMs } : {}) } : {}),
|
|
260
|
+
convergedAtMs,
|
|
261
|
+
resumeSafe: orphanState === "pending",
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
listCrashConverged(scope) {
|
|
265
|
+
const rows = [];
|
|
266
|
+
for (const e of this.convergedRows) {
|
|
267
|
+
if (scope !== undefined && e.owner !== scope)
|
|
268
|
+
continue;
|
|
269
|
+
rows.push(e.row);
|
|
270
|
+
}
|
|
271
|
+
return rows;
|
|
272
|
+
}
|
|
273
|
+
close() {
|
|
274
|
+
if (this.fd !== undefined) {
|
|
275
|
+
try {
|
|
276
|
+
closeSync(this.fd);
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
}
|
|
280
|
+
this.fd = undefined;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
//# sourceMappingURL=approval-ask-audit-store.js.map
|
package/dist/approval-card.d.ts
CHANGED
|
@@ -488,10 +488,13 @@ export interface ApprovalRequestFrame {
|
|
|
488
488
|
* 那一次原子事务真正翻成 `VOID` 的行集(`expireAsk`/`bindBatch` 的 `voidedSiblings` 返回形),不是
|
|
489
489
|
* 「本批全体」的推断。
|
|
490
490
|
*
|
|
491
|
-
* 🔴 **发射面 =
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
*
|
|
491
|
+
* 🔴 **发射面 = 逐 ctx 分发,durable 腿落账本、sync 腿 live-only**([ref] [ref] 改述;原句「live emit
|
|
492
|
+
* only——reaper/收敛器无 seq 可分配」把**调用点**的约束错安在投递面上):帧恒经 `emitRevokeTo` 交给各
|
|
493
|
+
* ctx 的 `emitRevoke` 钩子,bg/resume 两条 durable 腿的钩子就是本腿账本的 `append`(seq 由腿的 ledger
|
|
494
|
+
* 链步分配,与帧从哪个调用点来无关),于是「卡帧在哪个账本、撤帧就在哪个账本」逐 ctx 自洽;sync 腿的
|
|
495
|
+
* 钩子仍是 live SSE 写(行为零变)。丢帧(ctx 已退场/append 失败)的结构补偿不变 = 172 §3.1 的
|
|
496
|
+
* 「preamble 是壳侧卡集**全量对账基准**,不在基准内的一律清」——壳重连时的 preamble 本身就会让一张
|
|
497
|
+
* 没收到撤卡帧的僵尸卡消失;账本里重放的撤帧只用于时间线渲染,不是卡集基准。
|
|
495
498
|
*
|
|
496
499
|
* v1 **不带 per-ask 终态**(§7-4 裁定):中选者另有 `PARKED` 的下行面(410 体 + 重放消失),壳按
|
|
497
500
|
* `askIds` 清卡即可。
|
|
@@ -506,10 +509,19 @@ export interface ApprovalRevokeFrame {
|
|
|
506
509
|
/** `superseded_by_park` = 降级连坐(窗到期/收敛器绑定中选者,兄弟被撤);`aborted` = run 取消。 */
|
|
507
510
|
reason: "superseded_by_park" | "aborted";
|
|
508
511
|
serverNowMs: number;
|
|
512
|
+
/**
|
|
513
|
+
* [ref](codex R1-[high] 采,additive):**出处 run** 的 wire id(= 同 ask 呈卡帧的 `taskId`,
|
|
514
|
+
* `AskOriginIdentity.taskId` / 行上 `task_id` 同源)。为什么必须带:分发集合是 broker 的 session 形
|
|
515
|
+
* (`boundAsk` 的继承链闭包 + 收敛器/reaper 的 `emitRevoke(frame, target)` 都按 (owner, sessionId)
|
|
516
|
+
* 反查),集合里可躺着**别的 run** 的 ctx —— durable 腿的 ctx 收帧即落**自己**账本,无此键则那条行
|
|
517
|
+
* 无法归属,消费端也无从过滤(收敛器 current-set 形下甚至可能是一条无配对卡帧的孤儿撤帧行)。
|
|
518
|
+
*/
|
|
519
|
+
taskId: string;
|
|
509
520
|
}
|
|
510
521
|
/** 撤卡帧的纯构造(`build*`:返回纯数据,无方法无捕获行为)。`askIds` 拷贝一份 —— 调用方传进来的多是
|
|
511
|
-
* store
|
|
512
|
-
|
|
522
|
+
* store 返回的数组,帧不该与它共享可变引用。`originTaskId` 必填([ref] 归属轴,见帧顶注)——发射点
|
|
523
|
+
* 恒有出处(askBroadcast 的 `origin.taskId` / 收敛器与 reaper 的行上 `task_id`),可选会让漏传静默。 */
|
|
524
|
+
export declare function buildRevokeFrame(batchId: string, askIds: readonly string[], reason: ApprovalRevokeFrame["reason"], serverNowMs: number, originTaskId: string): ApprovalRevokeFrame;
|
|
513
525
|
/**
|
|
514
526
|
* 持久行 → 重放帧(设计稿 §5.2 逐字)。
|
|
515
527
|
*
|
package/dist/approval-card.js
CHANGED
|
@@ -269,8 +269,8 @@ export function buildApprovalRequestFrame(input) {
|
|
|
269
269
|
approvalId: input.approvalId,
|
|
270
270
|
};
|
|
271
271
|
}
|
|
272
|
-
export function buildRevokeFrame(batchId, askIds, reason, serverNowMs) {
|
|
273
|
-
return { type: "approval_revoke", schemaVersion: 1, batchId, askIds: [...askIds], reason, serverNowMs };
|
|
272
|
+
export function buildRevokeFrame(batchId, askIds, reason, serverNowMs, originTaskId) {
|
|
273
|
+
return { type: "approval_revoke", schemaVersion: 1, batchId, askIds: [...askIds], reason, serverNowMs, taskId: originTaskId };
|
|
274
274
|
}
|
|
275
275
|
export function buildReplayFrame(row, nowMs) {
|
|
276
276
|
const parsed = ApprovalCardEnvelopeSchema.safeParse(row.cardJson);
|
|
@@ -246,7 +246,8 @@ export interface ReconcileStats {
|
|
|
246
246
|
/** 段二:被代打 `expireAsk` 的孤儿 `STREAM_PENDING` 行数(赢 CAS 的)。 */
|
|
247
247
|
orphansExpired: number;
|
|
248
248
|
}
|
|
249
|
-
/** 撤卡帧的投递面(live
|
|
249
|
+
/** 撤卡帧的投递面(逐 ctx 分发:durable 腿落账本、sync 腿 live,见 `ApprovalRevokeFrame` 顶注)。
|
|
250
|
+
* 收敛器不认识 SSE 也不认识账本,只交给这个口(broker 反查活 ctx,各 ctx 自带自己的写口)。 */
|
|
250
251
|
export type ApprovalRevokeEmitter = (frame: ApprovalRevokeFrame, target: {
|
|
251
252
|
owner: string | null;
|
|
252
253
|
sessionId: string;
|
|
@@ -83,7 +83,7 @@ export function createApprovalReconciler(deps) {
|
|
|
83
83
|
if (askIds.length === 0 || !emitRevoke)
|
|
84
84
|
return;
|
|
85
85
|
try {
|
|
86
|
-
emitRevoke(buildRevokeFrame(row.batchId, askIds, reason, nowMs), { owner: row.owner, sessionId: row.sessionId, taskId: row.taskId });
|
|
86
|
+
emitRevoke(buildRevokeFrame(row.batchId, askIds, reason, nowMs, row.taskId), { owner: row.owner, sessionId: row.sessionId, taskId: row.taskId });
|
|
87
87
|
}
|
|
88
88
|
catch (err) {
|
|
89
89
|
try {
|
|
@@ -400,7 +400,7 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
400
400
|
if (effective?.skills && config.configCenter) {
|
|
401
401
|
skills = await applyCenterSkills(skills, effective.skills, config.configCenter.baseUrl, config.configCenter.token, logger, undefined, configLkgEnabled() ? defaultSkillCacheDir() : undefined);
|
|
402
402
|
}
|
|
403
|
-
if (effective?.plugins
|
|
403
|
+
if (effective?.plugins) {
|
|
404
404
|
const pluginOut = await applyCenterPlugins(skills, effective, {
|
|
405
405
|
cacheRoot: join(config.localDataRoot ?? join(homedir(), ".ai-agent"), "plugin-cache"),
|
|
406
406
|
allowHosts: config.pluginsAllowHosts,
|
|
@@ -618,7 +618,7 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
618
618
|
}
|
|
619
619
|
logger.info("sema_registry_refreshed", {
|
|
620
620
|
version: r.effective.version,
|
|
621
|
-
note: "
|
|
621
|
+
note: "collab + runtime governance + limits + models/roles/roster hot-applied; skills/mcp/runtime-gates(approvalRequire)/scenarios are restart-to-apply",
|
|
622
622
|
...(pendingRestart ? { restartRequired: true, restartReasons: pendingRestart.reasons } : {}),
|
|
623
623
|
});
|
|
624
624
|
latestEffective = r.effective;
|
|
@@ -12,6 +12,7 @@ import { ElicitationCoordinator } from "../elicitation.js";
|
|
|
12
12
|
import type { Logger } from "../observability/logger.js";
|
|
13
13
|
import { QuestionCoordinator } from "../question.js";
|
|
14
14
|
import { ToolApprovalCoordinator, type ParkedAskRedeem } from "../tool-approval.js";
|
|
15
|
+
import { FileApprovalAskAuditStore } from "../approval-ask-audit-store.js";
|
|
15
16
|
import { SendUserFileEmitter } from "../capabilities/send-user-file-tool.js";
|
|
16
17
|
import { TaskEnvRegistry } from "../capabilities/sandbox-file-send.js";
|
|
17
18
|
import type { StoreBackend } from "../plugins/store-backend.js";
|
|
@@ -38,6 +39,7 @@ export declare function createLiveCoordinators(ctx: LiveCoordinatorsCtx): {
|
|
|
38
39
|
toolApproval: ToolApprovalCoordinator | undefined;
|
|
39
40
|
durableEnabled: boolean;
|
|
40
41
|
streamApprovalGate: import("../tool-approval.js").StreamApprovalGate;
|
|
42
|
+
approvalAskAudit: FileApprovalAskAuditStore | undefined;
|
|
41
43
|
sendUserFileEmitter: SendUserFileEmitter | undefined;
|
|
42
44
|
sendFileLedger: import("../plugins/send-file-ledger.js").SendFileLedger | undefined;
|
|
43
45
|
sendUserFileToolSpec: import("@sema-agent/core").ToolSpec<import("typebox").TSchema> | undefined;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ElicitationCoordinator } from "../elicitation.js";
|
|
2
2
|
import { QuestionCoordinator } from "../question.js";
|
|
3
3
|
import { ToolApprovalCoordinator, resolveStreamApprovalGate } from "../tool-approval.js";
|
|
4
|
+
import { FileApprovalAskAuditStore } from "../approval-ask-audit-store.js";
|
|
4
5
|
import { SendUserFileEmitter, SEND_USER_FILE_MAX_BYTES, sendUserFileTool } from "../capabilities/send-user-file-tool.js";
|
|
5
6
|
import { createSandboxFileSend, TaskEnvRegistry } from "../capabilities/sandbox-file-send.js";
|
|
6
7
|
import { createSendUserFileIssuer } from "../plugins/send-user-file.js";
|
|
@@ -27,6 +28,21 @@ export function createLiveCoordinators(ctx) {
|
|
|
27
28
|
toolApproval: config.toolApprovalEnabled === true,
|
|
28
29
|
});
|
|
29
30
|
}
|
|
31
|
+
const approvalAskAudit = config.toolApprovalEnabled && backend?.kind === "local" && config.localDataRoot !== undefined
|
|
32
|
+
? new FileApprovalAskAuditStore(config.localDataRoot)
|
|
33
|
+
: undefined;
|
|
34
|
+
if (approvalAskAudit) {
|
|
35
|
+
const converged = approvalAskAudit.convergeCrashedAsks();
|
|
36
|
+
if (converged.pending > 0 || converged.decided > 0 || converged.inconsistent > 0 || converged.corruptLines > 0) {
|
|
37
|
+
logger.warn("approval_ask_audit_crash_converged", {
|
|
38
|
+
pending: converged.pending,
|
|
39
|
+
decided: converged.decided,
|
|
40
|
+
inconsistent: converged.inconsistent,
|
|
41
|
+
corruptLines: converged.corruptLines,
|
|
42
|
+
note: "orphan approval asks from a previous crash were converged to crashed_before_park; see GET /v1/approvals crashConverged",
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
30
46
|
const toolApproval = config.toolApprovalEnabled
|
|
31
47
|
? new ToolApprovalCoordinator({
|
|
32
48
|
...(streamApprovalGate.active
|
|
@@ -39,6 +55,7 @@ export function createLiveCoordinators(ctx) {
|
|
|
39
55
|
...(ruleScopeRootFor !== undefined ? { ruleScopeRootFor } : {}),
|
|
40
56
|
unattendedPolicy: config.unattendedApprovalPolicy,
|
|
41
57
|
...(getParkedRedeem !== undefined ? { parkedRedeem: getParkedRedeem } : {}),
|
|
58
|
+
...(approvalAskAudit !== undefined ? { askAudit: approvalAskAudit } : {}),
|
|
42
59
|
})
|
|
43
60
|
: undefined;
|
|
44
61
|
const sendUserFileEmitter = (() => {
|
|
@@ -83,6 +100,6 @@ export function createLiveCoordinators(ctx) {
|
|
|
83
100
|
: rawSend;
|
|
84
101
|
return sendUserFileTool({ send, emitter: sendUserFileEmitter });
|
|
85
102
|
})();
|
|
86
|
-
return { elicitation, question, toolApproval, durableEnabled, streamApprovalGate, sendUserFileEmitter, sendFileLedger, sendUserFileToolSpec };
|
|
103
|
+
return { elicitation, question, toolApproval, durableEnabled, streamApprovalGate, approvalAskAudit, sendUserFileEmitter, sendFileLedger, sendUserFileToolSpec };
|
|
87
104
|
}
|
|
88
105
|
//# sourceMappingURL=coordinators.js.map
|
package/dist/boot/leader.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createLeaderEndpoint } from "../leader/endpoint.js";
|
|
2
2
|
import { createLeaderRunner } from "../leader/wire.js";
|
|
3
|
+
import { buildDeploymentPostureSeats } from "./runner-deps.js";
|
|
3
4
|
export function leaderK8sMinioGap(leaderEnabled, leaderProvider, hasS3) {
|
|
4
5
|
if (!(leaderEnabled && leaderProvider === "k8s" && !hasS3))
|
|
5
6
|
return null;
|
|
@@ -49,6 +50,7 @@ export function createLeaderFace(ctx) {
|
|
|
49
50
|
lockedConfig: governanceSeams.lockedConfig,
|
|
50
51
|
retentionPolicy: governanceSeams.retentionPolicy,
|
|
51
52
|
},
|
|
53
|
+
deploymentPosture: buildDeploymentPostureSeats(config),
|
|
52
54
|
fanoutEnabled: config.leaderFanoutEnabled,
|
|
53
55
|
...((process.env.MODEL_ROUTER_ID || process.env.MODEL_CHEAP_ID)
|
|
54
56
|
? { routerModel: (process.env.MODEL_ROUTER_ID || process.env.MODEL_CHEAP_ID) }
|
package/dist/boot/reapers.d.ts
CHANGED
|
@@ -82,7 +82,8 @@ export interface ReapersCtx {
|
|
|
82
82
|
rosterStore: RunnerDeps["rosterStore"];
|
|
83
83
|
backgroundAgentStore: RunnerDeps["backgroundAgentStore"];
|
|
84
84
|
mailboxStore: RunnerDeps["mailboxStore"];
|
|
85
|
-
/** [ref] 车6
|
|
85
|
+
/** [ref] 车6:流内审批撤卡帧的投递口(收敛器/孤儿代打腿产出的帧经它反查 broker 的活连接;[ref] 起
|
|
86
|
+
* 活连接里的 durable 腿 ctx 会把帧落进本腿账本,sync 腿仍 live)。
|
|
86
87
|
* `TOOL_APPROVAL_ENABLED=false` 的部署恒 undefined ⇒ 收敛器照常收敛,只是不发通知帧(壳侧靠
|
|
87
88
|
* 重连 preamble 对账,见 approval-card.ts 的 `ApprovalRevokeFrame` 顶注)。 */
|
|
88
89
|
toolApproval: ToolApprovalCoordinator | undefined;
|
|
@@ -289,9 +289,7 @@ export function createResolveSpec(ctx) {
|
|
|
289
289
|
...(() => { const v = toolNameListFromBody(body.deferTools); return v ? { deferTools: v } : {}; })(),
|
|
290
290
|
...(() => { const v = promptProfileFromBody(body.promptProfile); return v ? { promptProfile: v } : {}; })(),
|
|
291
291
|
...(() => { const v = toolMaterializeStrategyFromBody(body.toolMaterializeStrategy); return v ? { toolMaterializeStrategy: v } : {}; })(),
|
|
292
|
-
...(typeof body.interactiveTools === "boolean"
|
|
293
|
-
? { interactiveTools: body.interactiveTools }
|
|
294
|
-
: {}),
|
|
292
|
+
...(typeof body.interactiveTools === "boolean" ? { interactiveTools: body.interactiveTools } : {}),
|
|
295
293
|
...(declaresInteractivePosture(body, req, opts) ? { interactionPosture: "interactive" } : {}),
|
|
296
294
|
...(typeof body.oneShot === "boolean" ? { oneShot: body.oneShot } : {}),
|
|
297
295
|
resilience: normalizeResilience(body.resilience, explicitOperatorOk(auth?.principal, config.operatorPrincipals)),
|
|
@@ -230,6 +230,26 @@ export declare function createEngineNoticeSeat(logger: {
|
|
|
230
230
|
} | undefined, router?: EngineNoticeRouter): {
|
|
231
231
|
onNotice?: NonNullable<RunnerDeps["onNotice"]>;
|
|
232
232
|
};
|
|
233
|
+
/**
|
|
234
|
+
* [ref](黑板 [ref],[ref] 登记)—— **部署姿态座席族**:运维在配置面写下的姿态(READ 面四席 + 记忆三席 +
|
|
235
|
+
* 委派入口 caps)。core 明写「每个 run 读**准备它的那只 Runner** 的 deps」⇒ 一台部署里跑任务的**每一只**
|
|
236
|
+
* Runner 都必须带同一份:主 runner / subRunner / run-local(经 {@link createSharedRunnerDeps} 展开)+
|
|
237
|
+
* **leader lane 五只**(`boot/leader.ts` → `LeaderWireConfig.deploymentPosture` → `src/leader/wire.ts`
|
|
238
|
+
* 五处 `...deploymentPostureSeat`)。
|
|
239
|
+
*
|
|
240
|
+
* 🔴 本函数是这一族**唯一**的取值点。两处各读一遍 config 就是 [ref]§三族A「主挂了、副漏挂」的病族形——
|
|
241
|
+
* [ref] 正是 leader 那条腿漏了**全族**(修前 wire.ts 五处一席都没有;边界曾靠
|
|
242
|
+
* `test/memory-delegation-evidence-lane.test.ts` ⑤ 组的机器门如实登记,现已翻成正向门)。leader 不许自己
|
|
243
|
+
* 再读 config 铸一份,只吃本函数经 cfg 递过去的产物(`test/leader-deployment-posture-seats.test.ts` ③ 钉)。
|
|
244
|
+
*
|
|
245
|
+
* 纯数据(键值全是 config 透传,无闭包)⇒ `build*`(CLAUDE.md 工厂律)。键**恒在场**、缺席 = `undefined`
|
|
246
|
+
* ([ref] S4 house style;core 消费点全按 `!== undefined` / `??` 判 —— dist
|
|
247
|
+
* `prepare-hands-readface.js:96-97`、`prepare-config-doors.js:199/215/229`、`prepare-task.js:1487` 亲读,
|
|
248
|
+
* 「键为 undefined」与「键缺席」对引擎逐字等价)。**不复制引擎缺省**(`roots`/`static-face`/`carry`/`open`/
|
|
249
|
+
* CC parity 20/200 一个都不抄):上游改缺省之日起本仓不会成为第二份真源。
|
|
250
|
+
*/
|
|
251
|
+
export type DeploymentPostureSeats = Pick<RunnerDeps, "readFace" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "memoryDelegationEvidence" | "memoryProvenance" | "memoryCapturePolicy" | "delegationEntryCaps">;
|
|
252
|
+
export declare function buildDeploymentPostureSeats(config: Pick<ServiceConfig, keyof DeploymentPostureSeats>): DeploymentPostureSeats;
|
|
233
253
|
export declare function createSharedRunnerDeps(ctx: SharedRunnerDepsCtx): SharedRunnerDeps;
|
|
234
254
|
export declare function createRunnerDeps(ctx: RunnerDepsCtx): RunnerDeps;
|
|
235
255
|
//# sourceMappingURL=runner-deps.d.ts.map
|