@sema-agent/server 7.113.0 → 7.114.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/MIGRATION.md +37 -2
- package/USAGE.md +1 -1
- package/dist/boot/memory-boundary.d.ts +2 -1
- package/dist/boot/memory-consolidation.d.ts +1 -1
- package/dist/boot/resolve-spec.js +2 -2
- package/dist/config-catalog.js +6 -5
- package/dist/config-invariants.d.ts +27 -0
- package/dist/config-invariants.js +54 -39
- package/dist/config-types.d.ts +4 -10
- package/dist/config-types.js +0 -1
- package/dist/config.d.ts +0 -16
- package/dist/config.js +42 -68
- package/dist/env-facts.d.ts +3 -1
- package/dist/env-facts.js +5 -1
- package/dist/execution-lane-caps.d.ts +54 -30
- package/dist/execution-lane-caps.js +48 -20
- package/dist/hooks/hook-runner.d.ts +85 -5
- package/dist/hooks/hook-runner.js +109 -16
- package/dist/hosted-posture.d.ts +2 -2
- package/dist/http/admission.js +2 -2
- package/dist/http/routes/capabilities.js +2 -2
- package/dist/observability/corrupt-read-seat.d.ts +3 -3
- package/dist/observability/corrupt-read-seat.js +5 -4
- package/dist/observability/fail-open.d.ts +5 -1
- package/dist/observability/fail-open.js +5 -1
- package/dist/plugins/remote-env-host.d.ts +0 -35
- package/dist/plugins/remote-env-host.js +0 -8
- package/dist/plugins/remote-env-local-docker.d.ts +5 -13
- package/dist/plugins/remote-env-local-docker.js +2 -4
- package/dist/store-posture.d.ts +7 -7
- package/dist/store-posture.js +0 -1
- package/dist/task-settings.d.ts +13 -0
- package/dist/task-settings.js +3 -0
- package/package.json +3 -3
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import vm from "node:vm";
|
|
2
3
|
import { hostShell, resolveHostShell, adoptHostForegroundChild } from "../plugins/host-platform.js";
|
|
3
4
|
import { HooksConfig, DEFAULT_HOOK_TIMEOUT_SECONDS, HOOK_EVENT_OWNER, HOOK_EVENT_MATCHER_FIELD, } from "@sema-agent/settings-schema/hooks";
|
|
4
5
|
import { describeToolCatalog, isEngineRefusal, receiveHookResult } from "@sema-agent/core";
|
|
@@ -57,7 +58,9 @@ function toAssistantTurnError(errorKind) {
|
|
|
57
58
|
return "unknown";
|
|
58
59
|
return Object.prototype.hasOwnProperty.call(BRAIN_TO_TURN_ERROR, errorKind) ? (BRAIN_TO_TURN_ERROR[errorKind] ?? "unknown") : "unknown";
|
|
59
60
|
}
|
|
60
|
-
const
|
|
61
|
+
export const MAX_MATCHER_EVAL_CHARS = MAX_HOOK_COMMAND_CHARS;
|
|
62
|
+
export const ADMISSION_MATCHER_EVAL_BUDGET_MS = 100;
|
|
63
|
+
const MATCHER_UNJUDGED_TAG = "server.hooks.matcher-unjudged";
|
|
61
64
|
function matcherReadsWireToolName(event) {
|
|
62
65
|
return HOOK_EVENT_OWNER[event] === "engine" && HOOK_EVENT_MATCHER_FIELD[event] === "tool_name";
|
|
63
66
|
}
|
|
@@ -77,25 +80,85 @@ function engineToolAliases() {
|
|
|
77
80
|
engineToolAliasTable = table;
|
|
78
81
|
return table;
|
|
79
82
|
}
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
let matcherEvalBox;
|
|
84
|
+
function matcherPatternMatchesWithin(matcher, name, budget) {
|
|
85
|
+
const remaining = budget.deadlineMs - budget.now();
|
|
86
|
+
if (remaining <= 0)
|
|
87
|
+
return "over_budget";
|
|
88
|
+
matcherEvalBox ??= {
|
|
89
|
+
script: new vm.Script("matcherPatternMatches(matcher, name)"),
|
|
90
|
+
context: vm.createContext({ matcherPatternMatches, matcher: "", name: "" }),
|
|
91
|
+
};
|
|
92
|
+
matcherEvalBox.context.matcher = matcher;
|
|
93
|
+
matcherEvalBox.context.name = name;
|
|
94
|
+
try {
|
|
95
|
+
const verdict = matcherEvalBox.script.runInContext(matcherEvalBox.context, { timeout: Math.max(1, Math.ceil(remaining)) });
|
|
96
|
+
return verdict === true ? true : verdict === false ? false : "invalid";
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
if (typeof err === "object" && err !== null && "code" in err && err.code === "ERR_SCRIPT_EXECUTION_TIMEOUT")
|
|
100
|
+
return "over_budget";
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function deadAliasInMatcher(matcher, depth, budget) {
|
|
105
|
+
if (matcher.length > MAX_MATCHER_EVAL_CHARS)
|
|
106
|
+
return { kind: "unjudged", reason: "oversize" };
|
|
107
|
+
if (matcher === "" || matcher === "*")
|
|
108
|
+
return undefined;
|
|
109
|
+
const names = matcherLiteralNames(matcher);
|
|
110
|
+
if (names !== undefined) {
|
|
111
|
+
for (const [alias, canonical] of engineToolAliases()) {
|
|
112
|
+
if (names.includes(alias) && !names.includes(canonical))
|
|
113
|
+
return { kind: "dead", alias, canonical };
|
|
114
|
+
}
|
|
82
115
|
return undefined;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
116
|
+
}
|
|
117
|
+
switch (depth) {
|
|
118
|
+
case "literal":
|
|
119
|
+
return undefined;
|
|
120
|
+
case "regex":
|
|
121
|
+
break;
|
|
122
|
+
default: {
|
|
123
|
+
const unreachable = depth;
|
|
124
|
+
return unreachable;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const selects = (name) => {
|
|
128
|
+
const verdict = matcherPatternMatchesWithin(matcher, name, budget);
|
|
129
|
+
return verdict === "invalid" ? "invalid_regex" : verdict;
|
|
130
|
+
};
|
|
131
|
+
for (const [alias, canonical] of engineToolAliases()) {
|
|
132
|
+
const selectsAlias = selects(alias);
|
|
133
|
+
if (typeof selectsAlias === "string")
|
|
134
|
+
return { kind: "unjudged", reason: selectsAlias };
|
|
135
|
+
if (!selectsAlias)
|
|
136
|
+
continue;
|
|
137
|
+
const selectsCanonical = selects(canonical);
|
|
138
|
+
if (typeof selectsCanonical === "string")
|
|
139
|
+
return { kind: "unjudged", reason: selectsCanonical };
|
|
140
|
+
if (!selectsCanonical)
|
|
141
|
+
return { kind: "dead", alias, canonical };
|
|
89
142
|
}
|
|
90
143
|
return undefined;
|
|
91
144
|
}
|
|
92
|
-
export function deadToolMatcherIssue(config) {
|
|
145
|
+
export function deadToolMatcherIssue(config, depth, budget = {}) {
|
|
146
|
+
const now = budget.now ?? (() => performance.now());
|
|
147
|
+
const deadline = { deadlineMs: now() + (budget.budgetMs ?? ADMISSION_MATCHER_EVAL_BUDGET_MS), now };
|
|
93
148
|
for (const [event, groups] of Object.entries(config)) {
|
|
149
|
+
if (!matcherReadsWireToolName(event))
|
|
150
|
+
continue;
|
|
94
151
|
for (const group of groups ?? []) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
152
|
+
if (group.matcher === undefined)
|
|
153
|
+
continue;
|
|
154
|
+
const verdict = deadAliasInMatcher(group.matcher, depth, deadline);
|
|
155
|
+
if (verdict === undefined)
|
|
156
|
+
continue;
|
|
157
|
+
if (verdict.kind === "unjudged") {
|
|
158
|
+
recordFailOpen(MATCHER_UNJUDGED_TAG, `event=${event} reason=${verdict.reason} matcherChars=${group.matcher.length}`);
|
|
159
|
+
continue;
|
|
98
160
|
}
|
|
161
|
+
return `settings.hooks.${event}: matcher "${verdict.alias}" is an alias; tool-event matchers match the tool's wire name (PostToolUse / PostToolUseFailure since 7.107.0) — use "${verdict.canonical}"`;
|
|
99
162
|
}
|
|
100
163
|
}
|
|
101
164
|
return undefined;
|
|
@@ -144,9 +207,31 @@ export function parseHooksConfig(raw) {
|
|
|
144
207
|
}
|
|
145
208
|
return { config };
|
|
146
209
|
}
|
|
147
|
-
export function
|
|
148
|
-
|
|
149
|
-
|
|
210
|
+
export function matcherLiteralNames(matcher) {
|
|
211
|
+
const names = [];
|
|
212
|
+
let first = -1;
|
|
213
|
+
let last = -1;
|
|
214
|
+
for (let i = 0; i <= matcher.length; i++) {
|
|
215
|
+
const c = i < matcher.length ? matcher.charCodeAt(i) : 0x7c;
|
|
216
|
+
if (c === 0x7c || c === 0x2c) {
|
|
217
|
+
if (first !== -1)
|
|
218
|
+
names.push(matcher.slice(first, last + 1));
|
|
219
|
+
first = -1;
|
|
220
|
+
last = -1;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (c === 0x20)
|
|
224
|
+
continue;
|
|
225
|
+
const nameChar = (c >= 0x30 && c <= 0x39) || (c >= 0x41 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a) || c === 0x5f || c === 0x2d;
|
|
226
|
+
if (!nameChar)
|
|
227
|
+
return undefined;
|
|
228
|
+
if (first === -1)
|
|
229
|
+
first = i;
|
|
230
|
+
last = i;
|
|
231
|
+
}
|
|
232
|
+
return names;
|
|
233
|
+
}
|
|
234
|
+
function matcherPatternMatches(matcher, value) {
|
|
150
235
|
if (matcher === value)
|
|
151
236
|
return true;
|
|
152
237
|
try {
|
|
@@ -156,6 +241,14 @@ export function hookMatcherMatches(matcher, value) {
|
|
|
156
241
|
return "invalid";
|
|
157
242
|
}
|
|
158
243
|
}
|
|
244
|
+
export function hookMatcherMatches(matcher, value) {
|
|
245
|
+
if (matcher === undefined || matcher === "" || matcher === "*")
|
|
246
|
+
return true;
|
|
247
|
+
const names = matcherLiteralNames(matcher);
|
|
248
|
+
if (names !== undefined)
|
|
249
|
+
return names.includes(value);
|
|
250
|
+
return matcherPatternMatches(matcher, value);
|
|
251
|
+
}
|
|
159
252
|
export function hookIfMatches(cond, payload) {
|
|
160
253
|
if (cond.length > 500 || (cond.match(/\*/g)?.length ?? 0) > 16)
|
|
161
254
|
return "unevaluable";
|
package/dist/hosted-posture.d.ts
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* 比手抄的值更难发现烂掉,[ref] 的第一排查点就是它;
|
|
14
14
|
* · 能力授予面的赦免挂在 **`requirePrincipal !== true`** 上 —— 但**三条面各有各的谓词,不是一只**
|
|
15
15
|
* (异源复核实测,别照「它们都一样」写):
|
|
16
|
-
* · `settings.hooks` —— `
|
|
17
|
-
* ⇒
|
|
16
|
+
* · `settings.hooks` —— `task-settings.ts` 的 `taskHooksHonored`(唯一属主;resolve-spec 装配 / 受理面 / 能力位三处同调),判据**只有**
|
|
17
|
+
* `requirePrincipal !== true` ⇒ 采纳,否则忽略(**不看车道**,任何 `REMOTE_EXEC` 都一样);
|
|
18
18
|
* · `additionalDirectories` / `additionalReadDirectories` —— **只有** `cwdHonored`
|
|
19
19
|
* (= `provider === "host" ∧ requirePrincipal !== true`);
|
|
20
20
|
* · `settings.env` / `cwd` —— `cwdHonored ‖ deviceCwdHonored`,而
|
package/dist/http/admission.js
CHANGED
|
@@ -2,7 +2,7 @@ import { isThinkingLevel, expandTiers } from "@sema-agent/core";
|
|
|
2
2
|
import { isModelAllowlisted, matchCatalogModel, refusedCatalogEntry, selectableCatalog } from "../model-select.js";
|
|
3
3
|
import { modelUnavailableMessage } from "../model-entry-refusal.js";
|
|
4
4
|
import { HttpError } from "../security.js";
|
|
5
|
-
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS, SETTINGS_PERMISSION_MODE_WORDS, isSettingsPermissionMode, parseTaskSettings, settingsPermissionBucketIssue, skillListingBudgetFractionOk, skillListingMaxDescOk, taskSettingsKeyIssue } from "../task-settings.js";
|
|
5
|
+
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS, SETTINGS_PERMISSION_MODE_WORDS, isSettingsPermissionMode, parseTaskSettings, settingsPermissionBucketIssue, skillListingBudgetFractionOk, skillListingMaxDescOk, taskHooksHonored, taskSettingsKeyIssue } from "../task-settings.js";
|
|
6
6
|
import { taskBodyKeyIssue } from "../request-key-closure.js";
|
|
7
7
|
import { FILE_HISTORY_DISABLED_NO_RESTORE, fileHistoryRestoreConflict, inheritEnvFieldIssue, singleMemberOffFieldIssue } from "../request-value-closure.js";
|
|
8
8
|
import { selfOrchestrationDenial } from "../task-workflow.js";
|
|
@@ -200,7 +200,7 @@ export function createAdmission(ctx) {
|
|
|
200
200
|
sendError(res, 400, "request.field_invalid", hooksParsed.error ?? "settings.hooks: invalid shape");
|
|
201
201
|
return null;
|
|
202
202
|
}
|
|
203
|
-
const deadMatcher = deadToolMatcherIssue(hooksParsed.config);
|
|
203
|
+
const deadMatcher = deadToolMatcherIssue(hooksParsed.config, taskHooksHonored(deps.config) ? "regex" : "literal");
|
|
204
204
|
if (deadMatcher !== undefined) {
|
|
205
205
|
sendError(res, 400, "request.field_invalid", deadMatcher);
|
|
206
206
|
return null;
|
|
@@ -24,7 +24,7 @@ import { projectApprovalsStreamLiveCapability } from "../../capabilities/approva
|
|
|
24
24
|
import { workflowModelAllowlistFor, selfOrchestrationDenial } from "../../task-workflow.js";
|
|
25
25
|
import { centerEntitlementSourceWired } from "../../runtime-caps-resolver.js";
|
|
26
26
|
import { judgeAutoModeArming } from "../../auto-mode-face.js";
|
|
27
|
-
import { coercePermissionModeStrict, SETTINGS_PERMISSION_MODE_WORDS } from "../../task-settings.js";
|
|
27
|
+
import { coercePermissionModeStrict, SETTINGS_PERMISSION_MODE_WORDS, taskHooksHonored } from "../../task-settings.js";
|
|
28
28
|
import { gatedPrincipal, sendPrincipalRefusal } from "../principal-gate.js";
|
|
29
29
|
import { sessionBackgroundFacesAvailable } from "./background.js";
|
|
30
30
|
function routePairingOf(deps, name, m) {
|
|
@@ -121,7 +121,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
121
121
|
scratchpadDir: true,
|
|
122
122
|
subagentSteer: Boolean(deps.subagentSteerRegistry) && Boolean(deps.runStore),
|
|
123
123
|
subagentResume: Boolean(deps.subagentSteerRegistry) && Boolean(deps.runStore),
|
|
124
|
-
taskSettings: { permissions: true, permissionMode: true, model: true, outputStyle: true, env: cwdHonored(deps.config) || deviceCwdHonored(deps.config), hooks: deps.config
|
|
124
|
+
taskSettings: { permissions: true, permissionMode: true, model: true, outputStyle: true, env: cwdHonored(deps.config) || deviceCwdHonored(deps.config), hooks: taskHooksHonored(deps.config) },
|
|
125
125
|
permissionModeAuto: {
|
|
126
126
|
accepted: true,
|
|
127
127
|
classifierSeat: deps.autoModeFace?.seatMounted === true,
|
|
@@ -43,9 +43,9 @@ export declare function createCorruptReadFailOpenSeat(tag: FailOpenTag, leg?: (i
|
|
|
43
43
|
*
|
|
44
44
|
* ## 逐命中响亮不降级
|
|
45
45
|
* 这一座里有安全轴的脸(session-policy),所以每命中一行 warn 保留(降成「每进程一次」是降级,S-990 的裁定);**在此之上**,认出的脸另记账:
|
|
46
|
-
* · session-policy 脸 ⇒ `P-DEBT` tag(`server.session-policy.corrupt-read-fail-open`)—— 计数逐次 + 探针行 + 一次性 warn
|
|
47
|
-
* (
|
|
48
|
-
*
|
|
46
|
+
* · session-policy 脸 ⇒ `P-DEBT` tag(`server.session-policy.corrupt-read-fail-open`)—— 计数逐次 + 探针行 + 一次性 warn。core 7.39.2 起按键读拒,
|
|
47
|
+
* 但枚举 / 别的会话抹除 / 数据根重绑三张脸仍按「无规则」(会话规则只许收紧 ⇒ 丢的恰是收紧);座上分不出读法 ⇒ 这一脸每一发都计(含已拒的按键读)。
|
|
48
|
+
* 别的会话抹除的常见形 core **不披露** ⇒ 这一形座上数不到,只能在 CENSUS / CHANGELOG 披露。放行是 core 的裁定、本仓不包层(源头修复纪律),所以**只登债、不改放行**;
|
|
49
49
|
* · 其余脸 ⇒ 不计(见 {@link DurableAbsenceFace})。
|
|
50
50
|
* 记账**先于** warn:记账是总函数,warn 可能抛(core 的 guard 会吞掉那个抛),一次日志故障不许吃掉计数。
|
|
51
51
|
*
|
|
@@ -85,10 +85,11 @@ export function createDurableAbsenceSeat(warn, root, thirdFace) {
|
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
87
|
function buildDurableAbsenceNote(thirdFace) {
|
|
88
|
-
return ("a durable read
|
|
89
|
-
"Consequence depends on which store read it: session-policy =
|
|
90
|
-
"
|
|
91
|
-
|
|
88
|
+
return ("a durable read hit unreadable bytes (never a plain ENOENT). " +
|
|
89
|
+
"Consequence depends on which store read it: session-policy = a keyed read (a task's session rules, GET/PUT .../policy) was REFUSED " +
|
|
90
|
+
"(the run fails with code \"corrupt\"), but enumeration (session sync export), another session's erase sweep and a data-root rebind still " +
|
|
91
|
+
"read that row as NO RULES — fix or delete the named file (deleting it means no session rules); session repo = a listing silently " +
|
|
92
|
+
`omitted rows; ${thirdFace}. Inspect the named path before deciding.`);
|
|
92
93
|
}
|
|
93
94
|
const RULE_LANE_REMEDY = "the permission-rule lane is fail-closed until a person inspects the file (moving it aside restarts the lane with an empty log)";
|
|
94
95
|
export const JSONL_REPLAY_SEATS = {
|
|
@@ -209,6 +209,10 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
209
209
|
readonly cls: "F";
|
|
210
210
|
readonly note: "#310:`engine_notice` 分流器把一条白名单通告投给某条 run 腿的登记口时,那只口抛了(live 口写向已断/已撕裂的 SSE socket,或 durable 口的账本写同步抛)。放行的最坏后果 = **这一条通告的这一个终点**缺席:①日志终点在分流之前已经逐字打过(事实一条不丢);②两个终点注册成两只独立 sink ⇒ live 抛不牵连 durable(断连后仍看得见的那半保住);③同会话其余口照投。故 F 类。不放行的代价是把异常回抛给 core 的 `deliverEngineNotice` —— 它会吞掉,于是同一次失败**既没有留痕也没有第二只口**,正是本 tag 要根除的形。留痕是承重的:静默吞掉之后「wire 腿为什么总有几条通告不到」在遥测里与「core 本来就没发」同形。";
|
|
211
211
|
};
|
|
212
|
+
readonly "server.hooks.matcher-unjudged": {
|
|
213
|
+
readonly cls: "F";
|
|
214
|
+
readonly note: "S-995(`hooks/hook-runner.ts` 的 `deadToolMatcherIssue`,fresh 受理面「工具事件 matcher 选中别名而选不中正名 ⇒ 400」判据的**未判**臂;detail = `event=… reason=… matcherChars=…`,不带 matcher 原字节):一只 matcher 没能被判 ⇒ 照旧受理。判据一条顺序、两档共用(7.114.0 修复轮):长度上限 → 空 / `*` → 字面读法(两档都先走、不占预算)→ 正则形。三种 reason 同一个 tag —— `oversize`(两档:超 8 192 字符不读;多租户车道上 hooks 不装配,这一笔只为让超长输入显形)/ `over_budget`(regex 档正则形:一次受理共享的 100 ms 墙钟预算用尽,`node:vm` 的 timeout 打断正则回溯)/ `invalid_regex`(regex 档正则形编不过;确定性的坏配置,运行期另记 `hook_matcher_invalid`,拒它是新检测臂、不在本规则)。**不记**的:literal 档(多租户车道)遇正则形 = 不适用 —— 该车道 hooks 整只不装配,没有被守的对象,不是未判。放行的最坏后果 = **那一只 matcher 没被这条判据守住**:它若真写了别名,照旧永不命中(7.112.0 对正则形一律不判,同一后果);**不是安全闸**(受理面的有效性判 = S-924 的「响亮」,决定不了任何放行 / 拒绝 / 权限;hook 的 deny 语义在运行期 hook-runner)。反方向(越界即拒)会把负载抖动 / 病态但合法的 matcher 变成对合法请求的 400。拒放集合相对 7.113.0 的一处放宽:超过上限的字面别名 matcher 7.113.0 拒、7.114.0 两档未判受理(已披露)。计数让「受理面一直判不了」(攻击者喂灾难正则 / 超长 matcher)显形。";
|
|
215
|
+
};
|
|
212
216
|
readonly "server.hooks.notice-sink-threw": {
|
|
213
217
|
readonly cls: "F";
|
|
214
218
|
readonly note: "#281:hook 观测回调(`onHookNotice`,部署把它接到 fleet 流)自己抛了。放行的最坏后果 = **这一条观测帧**缺席;补偿是结构性的:①日志终点在投递之前就已逐字落定(`hook_command_failed` / `hook_command_timeout` / `hook_command_spawn_failed` 一族),运维面零回退;②观测帧不参与任何门/裁决(纯 observe,#281 全族判据里「裁决面一个字节不动」是承重条款),故 F 类。不放行的代价严重不对称:异常会掀回 core 的 hook 回调栈,把一次**观察动作**变成一次**工具调用失败** —— 「观察一件事的动作反过来改变了那件事」正是本 tag 要根除的形,而它伤的恰恰是 hook 已经坏掉、用户最需要任务照跑的那一刻。留痕承重:静默吞掉之后「这台机器为什么一条 hook 通知都没有」在遥测里与「hook 从来没坏过」同形。";
|
|
@@ -367,7 +371,7 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
367
371
|
};
|
|
368
372
|
readonly "server.session-policy.corrupt-read-fail-open": {
|
|
369
373
|
readonly cls: "P-DEBT";
|
|
370
|
-
readonly note: "S-999(`observability/corrupt-read-seat.ts` 的 `createDurableAbsenceSeat`,`file_store_corrupt_read` 那只多脸座里认出的 **session-policy 脸**;local 车道,HTTP 服务的 local 腿与 run-local 同铸点):会话规则文件(数据根 `session-policy/` 下的行文件)
|
|
374
|
+
readonly note: "S-999(`observability/corrupt-read-seat.ts` 的 `createDurableAbsenceSeat`,`file_store_corrupt_read` 那只多脸座里认出的 **session-policy 脸**;local 车道,HTTP 服务的 local 腿与 run-local 同铸点):会话规则文件(数据根 `session-policy/` 下的行文件)坏了。core 7.39.2(#1299,本仓请托)起**按键读**(任务装规则、`GET` / `PUT /v1/sessions/:id/policy`)拒 —— `SessionPolicyError` 码 `corrupt`;但 core 包内 CHANGELOG 自陈另三张脸仍把坏行读成「无规则」:① `listBySession` 跳过坏行(会话同步导出读它 ⇒ 导出里没有这位 principal 的规则,目标端按无规则跑);② 别的会话的 `deleteBySession` 把撕尾行一并扫掉(常见形**不披露**),此后按键读得 ENOENT = 无规则;③ 数据根重绑把撕尾行留在旧键下。会话规则**只许收紧** ⇒ 丢的恰是调用方加的收紧(部署策略 / hooks / shell 门仍在)。方向 = fail-open,**明知不对**;SQL 孪生(`plugins/session-policy-store-sql.ts`)对同形响亮抛(CENSUS 第 81 行 P 终态)。归 P 类的理由是它决定权限门读到的规则层;归 DEBT 的理由是放行是 **core 的裁定**,源头修复纪律下本仓不包层,只让债在遥测里显形:计数逐次 + 探针行(`kind` / `path` / `sessionId` / `principal` / `reason`)+ 一次性 warn;逐命中 warn(`file_store_corrupt_read`)原样保留。⚠️ **如实全记**:座上分不出是哪一只读法披露的(载荷不带读法),所以这一脸的每一发都计 —— 含已被拒的按键读(那一发本身是 fail-closed);② 的常见形 core 不披露,座上**数不到**。**出路 = core 把三张脸也改成拒,届时撤本 tag**。计数非零 = 有会话的规则文件坏了:运维按探针里的 `sessionId` / `path` 去修(删文件即放弃那层规则,删前先看 `reason`)。认脸判据见 `durableAbsenceFaceOf`(披露路径落在本数据根的 `session-policy/` 之下);认不出的脸不计。7.114.0 合流时车 UY 曾以「core 已修」整只撤掉本 tag,修复轮恢复(那次删除未发版)。";
|
|
371
375
|
};
|
|
372
376
|
readonly "server.outbound.connect-observer-threw": {
|
|
373
377
|
readonly cls: "F";
|
|
@@ -200,6 +200,10 @@ export const FAIL_OPEN_TAGS = {
|
|
|
200
200
|
cls: "F",
|
|
201
201
|
note: "#310:`engine_notice` 分流器把一条白名单通告投给某条 run 腿的登记口时,那只口抛了(live 口写向已断/已撕裂的 SSE socket,或 durable 口的账本写同步抛)。放行的最坏后果 = **这一条通告的这一个终点**缺席:①日志终点在分流之前已经逐字打过(事实一条不丢);②两个终点注册成两只独立 sink ⇒ live 抛不牵连 durable(断连后仍看得见的那半保住);③同会话其余口照投。故 F 类。不放行的代价是把异常回抛给 core 的 `deliverEngineNotice` —— 它会吞掉,于是同一次失败**既没有留痕也没有第二只口**,正是本 tag 要根除的形。留痕是承重的:静默吞掉之后「wire 腿为什么总有几条通告不到」在遥测里与「core 本来就没发」同形。",
|
|
202
202
|
},
|
|
203
|
+
"server.hooks.matcher-unjudged": {
|
|
204
|
+
cls: "F",
|
|
205
|
+
note: "S-995(`hooks/hook-runner.ts` 的 `deadToolMatcherIssue`,fresh 受理面「工具事件 matcher 选中别名而选不中正名 ⇒ 400」判据的**未判**臂;detail = `event=… reason=… matcherChars=…`,不带 matcher 原字节):一只 matcher 没能被判 ⇒ 照旧受理。判据一条顺序、两档共用(7.114.0 修复轮):长度上限 → 空 / `*` → 字面读法(两档都先走、不占预算)→ 正则形。三种 reason 同一个 tag —— `oversize`(两档:超 8 192 字符不读;多租户车道上 hooks 不装配,这一笔只为让超长输入显形)/ `over_budget`(regex 档正则形:一次受理共享的 100 ms 墙钟预算用尽,`node:vm` 的 timeout 打断正则回溯)/ `invalid_regex`(regex 档正则形编不过;确定性的坏配置,运行期另记 `hook_matcher_invalid`,拒它是新检测臂、不在本规则)。**不记**的:literal 档(多租户车道)遇正则形 = 不适用 —— 该车道 hooks 整只不装配,没有被守的对象,不是未判。放行的最坏后果 = **那一只 matcher 没被这条判据守住**:它若真写了别名,照旧永不命中(7.112.0 对正则形一律不判,同一后果);**不是安全闸**(受理面的有效性判 = S-924 的「响亮」,决定不了任何放行 / 拒绝 / 权限;hook 的 deny 语义在运行期 hook-runner)。反方向(越界即拒)会把负载抖动 / 病态但合法的 matcher 变成对合法请求的 400。拒放集合相对 7.113.0 的一处放宽:超过上限的字面别名 matcher 7.113.0 拒、7.114.0 两档未判受理(已披露)。计数让「受理面一直判不了」(攻击者喂灾难正则 / 超长 matcher)显形。",
|
|
206
|
+
},
|
|
203
207
|
"server.hooks.notice-sink-threw": {
|
|
204
208
|
cls: "F",
|
|
205
209
|
note: "#281:hook 观测回调(`onHookNotice`,部署把它接到 fleet 流)自己抛了。放行的最坏后果 = **这一条观测帧**缺席;补偿是结构性的:①日志终点在投递之前就已逐字落定(`hook_command_failed` / `hook_command_timeout` / `hook_command_spawn_failed` 一族),运维面零回退;②观测帧不参与任何门/裁决(纯 observe,#281 全族判据里「裁决面一个字节不动」是承重条款),故 F 类。不放行的代价严重不对称:异常会掀回 core 的 hook 回调栈,把一次**观察动作**变成一次**工具调用失败** —— 「观察一件事的动作反过来改变了那件事」正是本 tag 要根除的形,而它伤的恰恰是 hook 已经坏掉、用户最需要任务照跑的那一刻。留痕承重:静默吞掉之后「这台机器为什么一条 hook 通知都没有」在遥测里与「hook 从来没坏过」同形。",
|
|
@@ -337,7 +341,7 @@ export const FAIL_OPEN_TAGS = {
|
|
|
337
341
|
},
|
|
338
342
|
"server.session-policy.corrupt-read-fail-open": {
|
|
339
343
|
cls: "P-DEBT",
|
|
340
|
-
note: "S-999(`observability/corrupt-read-seat.ts` 的 `createDurableAbsenceSeat`,`file_store_corrupt_read` 那只多脸座里认出的 **session-policy 脸**;local 车道,HTTP 服务的 local 腿与 run-local 同铸点):会话规则文件(数据根 `session-policy/` 下的行文件)
|
|
344
|
+
note: "S-999(`observability/corrupt-read-seat.ts` 的 `createDurableAbsenceSeat`,`file_store_corrupt_read` 那只多脸座里认出的 **session-policy 脸**;local 车道,HTTP 服务的 local 腿与 run-local 同铸点):会话规则文件(数据根 `session-policy/` 下的行文件)坏了。core 7.39.2(#1299,本仓请托)起**按键读**(任务装规则、`GET` / `PUT /v1/sessions/:id/policy`)拒 —— `SessionPolicyError` 码 `corrupt`;但 core 包内 CHANGELOG 自陈另三张脸仍把坏行读成「无规则」:① `listBySession` 跳过坏行(会话同步导出读它 ⇒ 导出里没有这位 principal 的规则,目标端按无规则跑);② 别的会话的 `deleteBySession` 把撕尾行一并扫掉(常见形**不披露**),此后按键读得 ENOENT = 无规则;③ 数据根重绑把撕尾行留在旧键下。会话规则**只许收紧** ⇒ 丢的恰是调用方加的收紧(部署策略 / hooks / shell 门仍在)。方向 = fail-open,**明知不对**;SQL 孪生(`plugins/session-policy-store-sql.ts`)对同形响亮抛(CENSUS 第 81 行 P 终态)。归 P 类的理由是它决定权限门读到的规则层;归 DEBT 的理由是放行是 **core 的裁定**,源头修复纪律下本仓不包层,只让债在遥测里显形:计数逐次 + 探针行(`kind` / `path` / `sessionId` / `principal` / `reason`)+ 一次性 warn;逐命中 warn(`file_store_corrupt_read`)原样保留。⚠️ **如实全记**:座上分不出是哪一只读法披露的(载荷不带读法),所以这一脸的每一发都计 —— 含已被拒的按键读(那一发本身是 fail-closed);② 的常见形 core 不披露,座上**数不到**。**出路 = core 把三张脸也改成拒,届时撤本 tag**。计数非零 = 有会话的规则文件坏了:运维按探针里的 `sessionId` / `path` 去修(删文件即放弃那层规则,删前先看 `reason`)。认脸判据见 `durableAbsenceFaceOf`(披露路径落在本数据根的 `session-policy/` 之下);认不出的脸不计。7.114.0 合流时车 UY 曾以「core 已修」整只撤掉本 tag,修复轮恢复(那次删除未发版)。",
|
|
341
345
|
},
|
|
342
346
|
"server.outbound.connect-observer-threw": {
|
|
343
347
|
cls: "F",
|
|
@@ -419,39 +419,4 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
|
|
|
419
419
|
*/
|
|
420
420
|
export declare function hostExecutionEnvFactory(config?: HostEnvConfig): ExecutionEnvFactory;
|
|
421
421
|
export { localDockerExecutionEnvFactory, type LocalDockerEnvConfig, type LocalDockerEnvDeps, RemoteLocalDockerExecutionEnv } from "./remote-env-local-docker.js";
|
|
422
|
-
import { type LocalDockerEnvConfig, type LocalDockerEnvDeps } from "./remote-env-local-docker.js";
|
|
423
|
-
/**
|
|
424
|
-
* Config for the `remote-docker` provider: run each task in a container on a REMOTE docker daemon
|
|
425
|
-
* (DUAL-MODE-DESIGN §5 — "a user-specified REMOTE host's docker"; registry-core `RemoteExecSpec`).
|
|
426
|
-
*
|
|
427
|
-
* It is {@link LocalDockerEnvConfig} with the endpoint made REQUIRED — that is the whole difference, by design:
|
|
428
|
-
* the container adapter is already daemon-agnostic (workspace `mkdir`'d INSIDE the container, never a host
|
|
429
|
-
* bind-mount; bytes over `docker cp` / `docker exec cat`, which stream through the daemon API), and the endpoint
|
|
430
|
-
* rides as `-H <dockerHost>` on every invocation. So every local-docker knob (memory/cpus/pidsLimit/dropAllCaps/
|
|
431
|
-
* network/timeouts/env/dockerPath) applies unchanged to the remote lane.
|
|
432
|
-
*/
|
|
433
|
-
export interface RemoteDockerEnvConfig extends Omit<LocalDockerEnvConfig, "dockerHost" | "providerLabel"> {
|
|
434
|
-
/** Remote docker daemon endpoint (e.g. `tcp://host:2376` or `ssh://user@host`). REQUIRED for remote-docker. */
|
|
435
|
-
dockerHost: string;
|
|
436
|
-
}
|
|
437
|
-
/**
|
|
438
|
-
* `ExecutionEnvFactory` for the TOC `remote-docker` backend (DUAL-MODE-DESIGN §5): a per-task container on
|
|
439
|
-
* SOMEONE ELSE'S docker daemon (offload — the operator's box stays free; isolation:true, suspendable:false).
|
|
440
|
-
* Same lifetime contract as local-docker: unconnected/lazy, one container per task, `destroy()` = `docker rm -f`.
|
|
441
|
-
*
|
|
442
|
-
* 🔴 **Endpoint credentials are the docker CLI's own** — this adapter deliberately carries none. The docker CLI
|
|
443
|
-
* reads `DOCKER_CERT_PATH` + `DOCKER_TLS_VERIFY` from the worker's environment (inherited by every spawn) for a
|
|
444
|
-
* `tcp://` + TLS daemon, and uses `ssh-agent` / `~/.ssh/config` for an `ssh://` one. That keeps key material out
|
|
445
|
-
* of this process's config objects entirely. Consequence to know: `tcp://…:2376` WITHOUT those env vars fails
|
|
446
|
-
* loud at connect (the daemon refuses plaintext) — it does not silently downgrade; a plaintext `tcp://…:2375`
|
|
447
|
-
* daemon is unauthenticated and remains the operator's decision, exactly as in the registry-core schema.
|
|
448
|
-
* (The registry-core `remote-docker` arm additionally models `tlsCertEnv`/`tlsKeyEnv`/`tlsCaEnv`/`sshKeyEnv` =
|
|
449
|
-
* env-NAMEs the control plane resolves to PEM BYTES. Materializing those bytes to short-lived 0600 files and
|
|
450
|
-
* passing `--tlsverify --tlscert/--tlskey/--tlscacert` is the remaining config-surface follow-up, together with
|
|
451
|
-
* the `provider:"remote-docker"` arm on `AppConfig.remoteExec` — see the note on that union in config-types.ts.)
|
|
452
|
-
*
|
|
453
|
-
* Throws at factory-BUILD time on a missing/blank endpoint: a "remote" lane with no endpoint would otherwise
|
|
454
|
-
* quietly become the LOCAL daemon — an isolation-topology surprise, so it fails loud instead.
|
|
455
|
-
*/
|
|
456
|
-
export declare function remoteDockerExecutionEnvFactory(config: RemoteDockerEnvConfig, deps?: LocalDockerEnvDeps): ExecutionEnvFactory;
|
|
457
422
|
//# sourceMappingURL=remote-env-host.d.ts.map
|
|
@@ -1373,12 +1373,4 @@ export function hostExecutionEnvFactory(config = {}) {
|
|
|
1373
1373
|
});
|
|
1374
1374
|
}
|
|
1375
1375
|
export { localDockerExecutionEnvFactory, RemoteLocalDockerExecutionEnv } from "./remote-env-local-docker.js";
|
|
1376
|
-
import { localDockerExecutionEnvFactory } from "./remote-env-local-docker.js";
|
|
1377
|
-
export function remoteDockerExecutionEnvFactory(config, deps = {}) {
|
|
1378
|
-
const dockerHost = config.dockerHost?.trim();
|
|
1379
|
-
if (!dockerHost) {
|
|
1380
|
-
throw new RemoteExecutionError("connect_failed", "remoteExec provider 'remote-docker' requires a non-empty dockerHost (the remote daemon endpoint, e.g. tcp://host:2376 or ssh://user@host) — use provider 'local-docker' for THIS machine's daemon");
|
|
1381
|
-
}
|
|
1382
|
-
return localDockerExecutionEnvFactory({ ...config, dockerHost, providerLabel: "remote-docker" }, deps);
|
|
1383
|
-
}
|
|
1384
1376
|
//# sourceMappingURL=remote-env-host.js.map
|
|
@@ -27,19 +27,13 @@ export interface LocalDockerEnvConfig {
|
|
|
27
27
|
* DOCKER_HOST override for the docker CLI (e.g. a non-default socket). Passed as `-H <value>` on EVERY docker
|
|
28
28
|
* invocation (not the process env) so it can't leak / drift. Absent = the daemon DOCKER_HOST points at.
|
|
29
29
|
*
|
|
30
|
-
* Pointing this OFF-BOX (`tcp://host:2376`, `ssh://user@host`) is
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* `mkdir`'d INSIDE the container (never a bind-mount of a
|
|
34
|
-
* `docker exec cat`, which stream over the daemon API.
|
|
30
|
+
* Pointing this OFF-BOX (`tcp://host:2376`, `ssh://user@host`) is how a REMOTE docker daemon is used — the
|
|
31
|
+
* lane stays `local-docker` (`REMOTE_EXEC=local-docker` + `DOCKER_HOST`; the settings-schema lane table lists
|
|
32
|
+
* `DOCKER_HOST` as this lane's optional coordinate, and there is no separate `remote-docker` lane since 7.0.0).
|
|
33
|
+
* Nothing about the class is host-local: the workspace is `mkdir`'d INSIDE the container (never a bind-mount of a
|
|
34
|
+
* host path) and bytes move by `docker cp` / `docker exec cat`, which stream over the daemon API.
|
|
35
35
|
*/
|
|
36
36
|
dockerHost?: string;
|
|
37
|
-
/**
|
|
38
|
-
* Provider NAME reported on the {@link WorkspaceHandle}. Default `"local-docker"`; the `remote-docker` arm
|
|
39
|
-
* passes `"remote-docker"` so a handle never mislabels which lane it is (the CLASS is shared — `remote-docker`
|
|
40
|
-
* IS this adapter aimed at a remote daemon). Cosmetic/observability only: it selects no behavior.
|
|
41
|
-
*/
|
|
42
|
-
providerLabel?: string;
|
|
43
37
|
/** Container memory limit (docker `--memory`, e.g. "2g"). Absent = no limit. */
|
|
44
38
|
memory?: string;
|
|
45
39
|
/** Container CPU limit (docker `--cpus`, e.g. 1.5). Absent = no limit. */
|
|
@@ -88,8 +82,6 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
|
|
|
88
82
|
private readonly spawnFn;
|
|
89
83
|
/** Stable container NAME (also our handle id). Created on connect; targeted by every docker exec/cp/rm. */
|
|
90
84
|
private readonly containerName;
|
|
91
|
-
/** Provider name put on the WorkspaceHandle ("local-docker" | "remote-docker"). Observability only. */
|
|
92
|
-
private readonly providerName;
|
|
93
85
|
/** Set once the container is up + the workspace dir created. */
|
|
94
86
|
private containerId?;
|
|
95
87
|
private handle?;
|
|
@@ -24,7 +24,6 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
24
24
|
cfg;
|
|
25
25
|
spawnFn;
|
|
26
26
|
containerName;
|
|
27
|
-
providerName;
|
|
28
27
|
containerId;
|
|
29
28
|
handle;
|
|
30
29
|
destroyed = false;
|
|
@@ -47,7 +46,6 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
47
46
|
const id = config.id ?? randomBytes(6).toString("hex");
|
|
48
47
|
const suffix = randomBytes(4).toString("hex");
|
|
49
48
|
this.containerName = `sema-docker-${sanitizeId(id)}-${suffix}`;
|
|
50
|
-
this.providerName = config.providerLabel?.trim() || PROVIDER;
|
|
51
49
|
}
|
|
52
50
|
resolve(p) {
|
|
53
51
|
return path.posix.isAbsolute(p) ? path.posix.normalize(p) : path.posix.normalize(path.posix.join(this.cwd, p));
|
|
@@ -113,7 +111,7 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
113
111
|
return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env destroyed during connect") };
|
|
114
112
|
}
|
|
115
113
|
this.containerId = run.value.stdout.trim() || this.containerName;
|
|
116
|
-
this.handle = { sandboxId: this.containerName, provider:
|
|
114
|
+
this.handle = { sandboxId: this.containerName, provider: PROVIDER, mountPath: this.cfg.mountPath, sessionToken: this.containerName };
|
|
117
115
|
if (config?.secrets?.length) {
|
|
118
116
|
}
|
|
119
117
|
return ok(this.handle);
|
|
@@ -136,7 +134,7 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
136
134
|
const insp = await this.docker(["inspect", "-f", "{{.State.Running}}", this.containerName], { timeoutMs: this.cfg.controlTimeoutMs });
|
|
137
135
|
if (insp.ok && insp.value.exitCode === 0 && insp.value.stdout.trim() === "true") {
|
|
138
136
|
this.containerId = this.containerName;
|
|
139
|
-
this.handle ??= { sandboxId: this.containerName, provider:
|
|
137
|
+
this.handle ??= { sandboxId: this.containerName, provider: PROVIDER, mountPath: this.cfg.mountPath, sessionToken: this.containerName };
|
|
140
138
|
return ok(this.handle);
|
|
141
139
|
}
|
|
142
140
|
return { ok: false, error: new RemoteExecutionError("connect_failed", `container '${this.containerName}' is not running (gone or stopped) — no managed snapshot to resume`) };
|
package/dist/store-posture.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 由调用方自己读出落差。`degradedReason` present-iff 降级臂走过(按后端族定词,{@link storeDegradedReasonFor})。
|
|
13
13
|
* 顶层 `sql` 并入 `store.sql`(硬 breaking,零别名;对象逐字同形)。
|
|
14
14
|
*/
|
|
15
|
-
import {
|
|
15
|
+
import type { StoreBackend as StoreBackendWord } from "@sema-agent/settings-schema/execution-lanes";
|
|
16
16
|
import type { StoreBackend } from "./plugins/store-backend.js";
|
|
17
17
|
import type { SqlEngineFacts } from "./plugins/sql-driver.js";
|
|
18
18
|
import { type SqlEngineCapability } from "./sql-engine-posture.js";
|
|
@@ -25,23 +25,23 @@ import { type SqlEngineCapability } from "./sql-engine-posture.js";
|
|
|
25
25
|
export declare const STORE_DEGRADED_REASONS: readonly ["db_unreachable", "local_unwritable"];
|
|
26
26
|
export type StoreDegradedReason = (typeof STORE_DEGRADED_REASONS)[number];
|
|
27
27
|
/** 降级臂按后端族定词(穷举 switch:词表加员 ⇒ 编译红)。`memory` 从不尝试打开后端,走不到降级臂 —— 到了就是装配缺陷,响亮抛。 */
|
|
28
|
-
export declare function storeDegradedReasonFor(declared:
|
|
28
|
+
export declare function storeDegradedReasonFor(declared: StoreBackendWord): StoreDegradedReason;
|
|
29
29
|
/** 装配值(纯数据;`boot/stores.ts` 铸一次,gauge / `/health` / caps 三面同读)。 */
|
|
30
30
|
export interface StorePosture {
|
|
31
|
-
readonly declared:
|
|
32
|
-
readonly backend:
|
|
31
|
+
readonly declared: StoreBackendWord;
|
|
32
|
+
readonly backend: StoreBackendWord;
|
|
33
33
|
readonly degradedReason?: StoreDegradedReason;
|
|
34
34
|
}
|
|
35
35
|
/** `build*`(纯数据)。`assembled` = 装配出的 `StoreBackend.kind`;缺席 = 没装出后端 ⇒ `memory`。 */
|
|
36
36
|
export declare function buildStorePosture(a: {
|
|
37
|
-
readonly declared:
|
|
37
|
+
readonly declared: StoreBackendWord;
|
|
38
38
|
readonly assembled: StoreBackend["kind"] | undefined;
|
|
39
39
|
readonly degradedReason: StoreDegradedReason | undefined;
|
|
40
40
|
}): StorePosture;
|
|
41
41
|
/** caps `store` 的形。 */
|
|
42
42
|
export interface StoreCapability {
|
|
43
|
-
readonly declared:
|
|
44
|
-
readonly backend:
|
|
43
|
+
readonly declared: StoreBackendWord;
|
|
44
|
+
readonly backend: StoreBackendWord;
|
|
45
45
|
readonly durable: boolean;
|
|
46
46
|
readonly degradedReason?: StoreDegradedReason;
|
|
47
47
|
/** 7.105.0 及以前的顶层 `sql` 对象,原样搬入(`{engine, isolation, txnMode}`)。present-iff `backend ∈ {mysql, pg}` ∧ 驱动此刻给得出
|
package/dist/store-posture.js
CHANGED
package/dist/task-settings.d.ts
CHANGED
|
@@ -298,6 +298,19 @@ export declare const TASK_SETTINGS_UNSUPPORTED_PERMISSION_KEYS: readonly ["addit
|
|
|
298
298
|
* · `permissions: null` 是既有钉住的「容器缺席」形,照旧不咬。
|
|
299
299
|
*/
|
|
300
300
|
export declare function taskSettingsKeyIssue(raw: unknown): KeyClosureIssue | null;
|
|
301
|
+
/**
|
|
302
|
+
* 🔒 **`settings.hooks` 会不会被采纳** —— 全仓**唯一**的车道谓词(S-995,主会话裁):hook 命令跑在 WORKER HOST([ref] class ② 能力授予),所以只有单用户车道
|
|
303
|
+
* (`requirePrincipal !== true`,与 scheduler / backgroundShell / lspHost / MCP 注入同一姿势)采纳;多租户车道收到 hooks **整只忽略**(`task_hooks_ignored`),绝不执行。
|
|
304
|
+
* 三处读它,谁也不许内联 `requirePrincipal`(各写各的 = 迟早漂):
|
|
305
|
+
* · `boot/resolve-spec.ts` —— 装配:不采纳 ⇒ 不装 `TaskSpec.hooks` + 一条 `task_hooks_ignored`;
|
|
306
|
+
* · `http/routes/capabilities.ts` —— 能力位 `taskSettings.hooks`(「says yes ⟺ route works」);
|
|
307
|
+
* · `http/admission.ts` —— fresh 受理面的「别名 matcher 永不命中」判据的**求值深度**:采纳 ⇒ `"regex"`(运行期同一只匹配函数,带资源闸);不采纳 ⇒ `"literal"`
|
|
308
|
+
* (7.112.0 的字面判:零调用方正则执行 —— 受理面是所有车道,租户不能借一只灾难回溯的 matcher 占满事件循环)。
|
|
309
|
+
* `test/hooks-alias-matcher-s995.test.ts` R5g 钉「三处都调它、没有内联判据」。
|
|
310
|
+
*/
|
|
311
|
+
export declare function taskHooksHonored(config: {
|
|
312
|
+
readonly requirePrincipal?: boolean;
|
|
313
|
+
}): boolean;
|
|
301
314
|
/**
|
|
302
315
|
* Validate an untrusted `body.settings` (raw `unknown` off the wire) into the service-trusted {@link
|
|
303
316
|
* ParsedTaskSettings}. NEVER trusts the wire type — every field is shape-checked. `defaultMode` accepts the FIVE
|
package/dist/task-settings.js
CHANGED
|
@@ -158,6 +158,9 @@ export function taskSettingsKeyIssue(raw) {
|
|
|
158
158
|
unsupportedKeys: unsupported.slice(0, MAX_KEY_ECHO),
|
|
159
159
|
};
|
|
160
160
|
}
|
|
161
|
+
export function taskHooksHonored(config) {
|
|
162
|
+
return config.requirePrincipal !== true;
|
|
163
|
+
}
|
|
161
164
|
export function parseTaskSettings(raw) {
|
|
162
165
|
if (raw === null || raw === undefined || typeof raw !== "object")
|
|
163
166
|
return { deferred: [] };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.114.0",
|
|
4
4
|
"description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -62,8 +62,8 @@
|
|
|
62
62
|
"build:binary:run-local:darwin-arm64": "bun build --compile --target=bun-darwin-arm64 src/run-local.ts --outfile dist/run-local-darwin-arm64"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@sema-agent/core": "7.39.
|
|
66
|
-
"@sema-agent/settings-schema": "
|
|
65
|
+
"@sema-agent/core": "7.39.2",
|
|
66
|
+
"@sema-agent/settings-schema": "7.0.1",
|
|
67
67
|
"e2b": "^2.28.0",
|
|
68
68
|
"libsodium-wrappers": "^0.8.4",
|
|
69
69
|
"mysql2": "^3.22.4",
|