@sema-agent/server 7.121.0 → 7.123.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 +175 -0
- package/README.md +6 -1
- package/README.zh-CN.md +6 -1
- package/USAGE.md +57 -18
- package/dist/approval-reconciler.d.ts +8 -0
- package/dist/approval-reconciler.js +72 -52
- package/dist/audit.d.ts +1 -1
- package/dist/audit.js +52 -22
- package/dist/auto-mode-face.d.ts +16 -5
- package/dist/auto-mode-face.js +7 -4
- package/dist/bench/s1/live-deps.js +1 -0
- package/dist/boot/config-center.js +3 -0
- package/dist/boot/leader.js +1 -0
- package/dist/boot/reapers.d.ts +3 -0
- package/dist/boot/reapers.js +2 -1
- package/dist/boot/resolve-spec.js +1 -4
- package/dist/boot/shutdown.js +6 -1
- package/dist/boot/stage-08-reapers.js +2 -2
- package/dist/capabilities/memory-notice.d.ts +1 -1
- package/dist/config-catalog.js +4 -4
- package/dist/config-center/apply-effective.d.ts +1 -1
- package/dist/config-center/apply-effective.js +5 -3
- package/dist/config-center/center-request.d.ts +6 -9
- package/dist/config-center/center-request.js +11 -27
- package/dist/config-center/http-client.js +3 -3
- package/dist/config-center/read-warnings.d.ts +56 -35
- package/dist/config-center/read-warnings.js +67 -19
- package/dist/config-center/types.d.ts +7 -8
- package/dist/config-lkg.d.ts +12 -9
- package/dist/config-lkg.js +38 -39
- package/dist/config-provider.d.ts +3 -2
- package/dist/config-provider.js +2 -2
- package/dist/config-types.d.ts +20 -9
- package/dist/config-types.js +1 -1
- package/dist/config.d.ts +10 -10
- package/dist/config.js +92 -127
- package/dist/fleet/drain-entry.d.ts +28 -3
- package/dist/fleet/drain-entry.js +3 -0
- package/dist/fleet/engine-signal.d.ts +54 -23
- package/dist/fleet/engine-signal.js +29 -5
- package/dist/fleet/serving-lease.js +13 -15
- package/dist/fleet-client.d.ts +8 -6
- package/dist/fleet-client.js +4 -3
- package/dist/governance-overrides.d.ts +7 -2
- package/dist/http/admission.js +7 -5
- package/dist/http/approval-sweeps.js +2 -2
- package/dist/http/checkpoint-ctx.d.ts +60 -11
- package/dist/http/checkpoint-ctx.js +23 -9
- package/dist/http/dispatch.js +22 -19
- package/dist/http/health-self-report.d.ts +3 -0
- package/dist/http/resume-legs.js +8 -8
- package/dist/http/route-ctx.d.ts +19 -3
- package/dist/http/route-table.d.ts +3 -0
- package/dist/http/route-table.js +4 -0
- package/dist/http/routes/admin-drain.d.ts +5 -2
- package/dist/http/routes/admin-drain.js +14 -2
- package/dist/http/routes/approvals-assistant.d.ts +1 -0
- package/dist/http/routes/approvals-assistant.js +6 -6
- package/dist/http/routes/capabilities.js +7 -5
- package/dist/http/routes/fleet.d.ts +1 -0
- package/dist/http/routes/fleet.js +1 -1
- package/dist/http/routes/images.d.ts +1 -0
- package/dist/http/routes/images.js +1 -1
- package/dist/http/routes/leader.js +6 -0
- package/dist/http/routes/runs.d.ts +2 -0
- package/dist/http/routes/runs.js +5 -4
- package/dist/http/routes/sessions.d.ts +1 -0
- package/dist/http/routes/sessions.js +2 -5
- package/dist/http/routes/trace-usage.d.ts +1 -0
- package/dist/http/routes/trace-usage.js +1 -1
- package/dist/http/routes/workflows.d.ts +1 -0
- package/dist/http/routes/workflows.js +1 -1
- package/dist/http/send.d.ts +13 -0
- package/dist/http/send.js +9 -0
- package/dist/http/server.d.ts +3 -0
- package/dist/http/stored-body-replay.d.ts +28 -0
- package/dist/http/stored-body-replay.js +22 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/leader/leader.d.ts +23 -1
- package/dist/leader/leader.js +6 -1
- package/dist/leader/repair-wire.d.ts +1 -1
- package/dist/leader/repair-wire.js +2 -2
- package/dist/leader/run-entry.d.ts +27 -0
- package/dist/leader/run-entry.js +29 -0
- package/dist/leader/wire.d.ts +10 -0
- package/dist/leader/wire.js +15 -10
- package/dist/model-entry-refusal.d.ts +18 -9
- package/dist/model-entry-refusal.js +19 -14
- package/dist/model-route-endpoint.d.ts +21 -1
- package/dist/model-route-endpoint.js +9 -3
- package/dist/model-route-gap.d.ts +103 -0
- package/dist/model-route-gap.js +80 -0
- package/dist/model-route-url.d.ts +13 -20
- package/dist/model-route-url.js +28 -24
- package/dist/namespaced-rule-name.d.ts +16 -30
- package/dist/namespaced-rule-name.js +2 -20
- package/dist/observability/fail-open.d.ts +23 -10
- package/dist/observability/fail-open.js +8 -8
- package/dist/plugins/approval-ask-store-file.d.ts +1 -0
- package/dist/plugins/approval-ask-store-file.js +3 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +1 -0
- package/dist/plugins/approval-ask-store-memory.js +6 -0
- package/dist/plugins/approval-ask-store-sql.d.ts +12 -0
- package/dist/plugins/approval-ask-store-sql.js +4 -0
- package/dist/plugins/one-time-migrations.js +40 -0
- package/dist/plugins/park-commit-watch.d.ts +19 -5
- package/dist/plugins/park-commit-watch.js +24 -17
- package/dist/plugins/session-policy-store-sql.js +1 -2
- package/dist/plugins/workflow-run-store-sql.d.ts +4 -2
- package/dist/router/route-orchestration.d.ts +4 -2
- package/dist/router/route-orchestration.js +2 -1
- package/dist/rule-entry-list.d.ts +3 -13
- package/dist/rule-entry-list.js +1 -12
- package/dist/run-local.js +4 -2
- package/dist/runtime-governance.d.ts +67 -32
- package/dist/runtime-governance.js +28 -9
- package/dist/session-policy-wire.d.ts +47 -49
- package/dist/session-policy-wire.js +41 -36
- package/dist/session-sync.d.ts +5 -1
- package/dist/session-sync.js +2 -1
- package/dist/sighup-idle.d.ts +7 -1
- package/dist/sighup-idle.js +4 -4
- package/dist/spec-fields.d.ts +4 -4
- package/dist/spec-fields.js +1 -4
- package/dist/task-settings.d.ts +36 -14
- package/dist/task-settings.js +20 -23
- package/dist/tool-approval.d.ts +6 -0
- package/dist/tool-approval.js +13 -1
- package/dist/trace/redact.d.ts +3 -3
- package/dist/unattended-permission-mode.d.ts +17 -13
- package/dist/unattended-permission-mode.js +3 -1
- package/dist/url-shape.d.ts +31 -0
- package/dist/url-shape.js +41 -0
- package/package.json +3 -3
- package/scripts/drain-check-local.mjs +1 -1
|
@@ -12,28 +12,16 @@ export function createParkCommitWatch(inner) {
|
|
|
12
12
|
let inFlight = 0;
|
|
13
13
|
const idleWaiters = new Set();
|
|
14
14
|
const unconfirmed = new Set();
|
|
15
|
-
const notify = (row) => {
|
|
15
|
+
const notify = async (row) => {
|
|
16
16
|
const calls = committedParkCallsOf(row);
|
|
17
17
|
if (calls !== undefined)
|
|
18
18
|
for (const l of listeners)
|
|
19
|
-
l(calls);
|
|
19
|
+
await l(calls);
|
|
20
20
|
};
|
|
21
|
-
const
|
|
21
|
+
const counted = async (work) => {
|
|
22
22
|
inFlight += 1;
|
|
23
23
|
try {
|
|
24
|
-
|
|
25
|
-
await inner.put(token, cp);
|
|
26
|
-
}
|
|
27
|
-
catch (err) {
|
|
28
|
-
unconfirmed.add(token);
|
|
29
|
-
if (unconfirmed.size > UNCONFIRMED_PUT_TOKENS_MAX) {
|
|
30
|
-
const oldest = unconfirmed.values().next();
|
|
31
|
-
if (oldest.done !== true)
|
|
32
|
-
unconfirmed.delete(oldest.value);
|
|
33
|
-
}
|
|
34
|
-
throw err;
|
|
35
|
-
}
|
|
36
|
-
notify(cp);
|
|
24
|
+
return await work();
|
|
37
25
|
}
|
|
38
26
|
finally {
|
|
39
27
|
inFlight -= 1;
|
|
@@ -42,13 +30,28 @@ export function createParkCommitWatch(inner) {
|
|
|
42
30
|
wake();
|
|
43
31
|
}
|
|
44
32
|
};
|
|
33
|
+
const put = (token, cp) => counted(async () => {
|
|
34
|
+
try {
|
|
35
|
+
await inner.put(token, cp);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
unconfirmed.add(token);
|
|
39
|
+
if (unconfirmed.size > UNCONFIRMED_PUT_TOKENS_MAX) {
|
|
40
|
+
const oldest = unconfirmed.values().next();
|
|
41
|
+
if (oldest.done !== true)
|
|
42
|
+
unconfirmed.delete(oldest.value);
|
|
43
|
+
}
|
|
44
|
+
throw err;
|
|
45
|
+
}
|
|
46
|
+
await notify(cp);
|
|
47
|
+
});
|
|
45
48
|
const get = async (token) => {
|
|
46
49
|
if (!unconfirmed.has(token))
|
|
47
50
|
return inner.get(token);
|
|
48
51
|
unconfirmed.delete(token);
|
|
49
52
|
const row = await inner.get(token);
|
|
50
53
|
if (row !== null)
|
|
51
|
-
notify(row);
|
|
54
|
+
await counted(() => notify(row));
|
|
52
55
|
return row;
|
|
53
56
|
};
|
|
54
57
|
const store = new Proxy(inner, {
|
|
@@ -65,6 +68,10 @@ export function createParkCommitWatch(inner) {
|
|
|
65
68
|
onParkCommitted(listener) {
|
|
66
69
|
listeners.push(listener);
|
|
67
70
|
},
|
|
71
|
+
track(work) {
|
|
72
|
+
void counted(() => work).catch(() => undefined);
|
|
73
|
+
return work;
|
|
74
|
+
},
|
|
68
75
|
quiesce(deadlineMs) {
|
|
69
76
|
if (inFlight === 0 || Date.now() >= deadlineMs)
|
|
70
77
|
return Promise.resolve({ inFlight });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { assertSessionRuleNamesCurrent, loosenReasons, SessionPolicyError, normalizeRules, stripRev } from "@sema-agent/core";
|
|
3
|
-
import {
|
|
3
|
+
import { readSessionPermissionRules, describeSessionPermissionRulesProblem } from "../session-policy-wire.js";
|
|
4
4
|
import { decodeJsonColumn, isJsonColumnBodyProblem, jsonColumnText } from "./sql-json-column.js";
|
|
5
5
|
import { pgHasUnstorable } from "./pg-safe-json.js";
|
|
6
6
|
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
@@ -105,7 +105,6 @@ export class SqlSessionPolicyStore {
|
|
|
105
105
|
}
|
|
106
106
|
const clean = normalizeRules(stripRev(rules));
|
|
107
107
|
assertSessionRuleNamesCurrent(clean);
|
|
108
|
-
assertSessionRuleNamesReachable(clean);
|
|
109
108
|
if (prior && opts?.operator !== true) {
|
|
110
109
|
const loosens = loosenReasons(prior, clean);
|
|
111
110
|
if (loosens.length > 0) {
|
|
@@ -68,8 +68,10 @@ type WorkflowRun = Parameters<WorkflowRunStore["put"]>[1];
|
|
|
68
68
|
export declare const MAX_RUN_BLOB_BYTES: number;
|
|
69
69
|
declare const onWarnType: (msg: string, meta: Record<string, unknown>) => void;
|
|
70
70
|
export type InboxWarn = typeof onWarnType;
|
|
71
|
-
/** S-107:workflow_run 行的 `error`(脚本抛出的 `err.message
|
|
72
|
-
* 同一口脱;`result` 由 core 完成时源头脱。幂等,不抛(超大 ⇒ 占位)。
|
|
71
|
+
/** S-107:workflow_run 行的 `error`(脚本抛出的 `err.message`)在**两条写腿**(put / update 常态 + oversize 降级)
|
|
72
|
+
* 同一口脱;`result` 由 core 完成时源头脱。幂等,不抛(超大 ⇒ 占位)。
|
|
73
|
+
* core 7.48.0 起 `error` 在铸点也过 core 的 `redactSecrets`,但两张脱敏表互不为超集(本仓的表多认几种记号,core 的表多认另几种)⇒ 这一口暂留;
|
|
74
|
+
* 退役条件 = core 的脱敏表成为两表的并集(本仓的每一种记号它都认)—— 到那时这一口与它的两条调用一起删。 */
|
|
73
75
|
export declare function withRedactedError<T extends {
|
|
74
76
|
error?: string | undefined;
|
|
75
77
|
}>(run: T): T;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Autonomy } from "../runtime-governance.js";
|
|
2
2
|
import type { GovernanceOverrides } from "../governance-overrides.js";
|
|
3
3
|
export type OrchestrationMode = "single" | "supervisor" | "team";
|
|
4
4
|
/** The DETERMINISTIC signals the routing decision reads (extracted by {@link deriveRoutingSignals} from the
|
|
@@ -54,7 +54,9 @@ export declare function deriveRoutingSignals(ctx: RoutingContext): RoutingSignal
|
|
|
54
54
|
* derivation + {@link deriveRoutingSignals} + {@link routeOrchestration} in one tested call; main.ts's resolveSpec
|
|
55
55
|
* calls this). Signal mapping:
|
|
56
56
|
* - `isolatedExecEnv` ⟺ a sandboxed remote-exec backend (`e2b`/`k8s` Kata) — `ssh`/`host`/`adb` are NOT isolated.
|
|
57
|
-
* - hand tools (write_file/bash) are mounted only when an executionEnv exists AND
|
|
57
|
+
* - hand tools (write_file/bash) are mounted only when an executionEnv exists AND the autonomy does not clamp the hands
|
|
58
|
+
* read-only (read off {@link autonomyOverrides} — the one place an autonomy word becomes a hands clamp; `plan` is the
|
|
59
|
+
* engine's plan mode, whose hands stay mounted and are gated at call time, so it is not read-only here).
|
|
58
60
|
* - `canEgress` is left false in v1 (per-tool egress detection deferred; danger derives from non-isolation).
|
|
59
61
|
* - `delegatedOrLongHorizon` is false on the single-task path (the (b) value lives on the /v1/leader path).
|
|
60
62
|
*/
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { COARSE_SHELL_TOOLS } from "@sema-agent/core";
|
|
2
|
+
import { autonomyOverrides } from "../runtime-governance.js";
|
|
2
3
|
import { operatorAlwaysHumanAsk } from "../operator-ask.js";
|
|
3
4
|
import { heldRuleName } from "../tool-rule-names.js";
|
|
4
5
|
export function routeOrchestration(s) {
|
|
@@ -20,7 +21,7 @@ export function deriveRoutingSignals(ctx) {
|
|
|
20
21
|
};
|
|
21
22
|
}
|
|
22
23
|
export function routeServiceTask(input) {
|
|
23
|
-
const readOnlyHands = input.autonomy
|
|
24
|
+
const readOnlyHands = autonomyOverrides(input.autonomy).handsReadOnly === true;
|
|
24
25
|
return routeOrchestration(deriveRoutingSignals({
|
|
25
26
|
...(input.explicitTeam ? { explicitMode: "team" } : {}),
|
|
26
27
|
...(input.autonomy ? { autonomy: input.autonomy } : {}),
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
* ① **形**({@link ruleEntryListShapeOk}):字符串数组。引擎逐条迭代;一个裸字符串会被逐字符读成几条(deny 侧 = 意图静默落空)。
|
|
10
10
|
* 本谓词不带条数帽(引擎的 `screenAllowedToolsValue` 只判形);条数帽是另一只函数 {@link allowLayerEntryCountIssue}(`MAX_ALLOW_LAYER_ENTRIES`,
|
|
11
11
|
* 请求 allow 桶、技能两只清单、运维表四张表同一只、同一句)。
|
|
12
|
-
* ②
|
|
13
|
-
*
|
|
12
|
+
* ② (7.116.0–7.122.x 的「引擎读不到的一形」—— 工具段为空的协议名 `mcp__srv__` 四张表都拒 —— 随 core 7.48.0 #1400 退役:引擎判官给它判词
|
|
13
|
+
* `unmintable_segment`,条目由下面 ③ 读成 `tool_name`,与对端段铸不出的名字同一条路。)
|
|
14
14
|
* ③ **引擎读成空的条目**({@link inertRuleEntriesOf},判据 = 引擎包根 `compileAllowList(entries, behavior, 名册)`,名册 = 空集 `NO_DECLARED_TOOL_NAMES`
|
|
15
|
-
* —— 条目的工具名也过引擎判官:退役名 / 无前缀短名 /
|
|
15
|
+
* —— 条目的工具名也过引擎判官:退役名 / 无前缀短名 / 对端段或工具段铸不出、为空读成 `tool_name`,带判词与 1:1 现名;原因词是引擎的 `ALLOW_ENTRY_INERT_REASONS`):
|
|
16
16
|
* 运维的 allow 表拒启(运维自己的配置,写错了当场改);技能的拒侧(`disallowedTools`)拒 —— 提交 400、存量体重放 409(下面的 {@link skillDenyInertEntryOf});
|
|
17
17
|
* 请求的 allow 侧(`settings.permissions.allow`、技能 `allowedTools`)不拒:引擎在开跑时发 `permission.allowed_tool_not_granted`(受众 = 用户)。
|
|
18
18
|
* 技能拒侧那一拒与引擎 #1372(core 7.43.0:那只技能不激活)是同一件事的两处 —— 7.118.0 重估过、本版**不退**:本仓的续跑腿把引擎的
|
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
* (重估读数与退役条件在下面那只函数头注)。
|
|
21
21
|
*/
|
|
22
22
|
import { compileAllowList } from "@sema-agent/core";
|
|
23
|
-
import { emptyToolSegmentOf } from "./namespaced-rule-name.js";
|
|
24
23
|
/** ① 形:字符串数组(空串也是字符串 —— 引擎把它读成 `grammar` 并通告 / 拒,见 ③)。 */
|
|
25
24
|
export declare const ruleEntryListShapeOk: (v: unknown) => v is readonly string[];
|
|
26
25
|
/**
|
|
@@ -42,15 +41,6 @@ export declare function allowLayerEntryCountIssue(field: string, list: readonly
|
|
|
42
41
|
readonly count: number;
|
|
43
42
|
readonly cap: number;
|
|
44
43
|
} | undefined;
|
|
45
|
-
/** 一张表里第一条「引擎读不到、却永远匹配不到」的条目(工具段为空的协议名)。 */
|
|
46
|
-
export interface EmptyToolSegmentEntry {
|
|
47
|
-
readonly entry: string;
|
|
48
|
-
readonly ns: NonNullable<ReturnType<typeof emptyToolSegmentOf>>;
|
|
49
|
-
}
|
|
50
|
-
/** ② 第一条工具段为空的协议名条目(没有 ⇒ `undefined`)。纯字符串运算。 */
|
|
51
|
-
export declare function emptyToolSegmentEntryOf(list: readonly string[]): EmptyToolSegmentEntry | undefined;
|
|
52
|
-
/** ② 的句子(`<field> names "<entry>", which can never match a tool (…)`;后半与配置表 / 会话规则写口同一只)。 */
|
|
53
|
-
export declare function describeEmptyToolSegmentEntry(field: string, u: EmptyToolSegmentEntry): string;
|
|
54
44
|
/** 一条引擎读成空的条目(引擎的形:原因词,名字层另带判词 / 1:1 现名;包根不导出这只具名型 ⇒ 从 `compileAllowList` 的返回型取,不手抄)。 */
|
|
55
45
|
export type InertRuleEntry = ReturnType<typeof compileAllowList>["inert"][number];
|
|
56
46
|
/** ③ 引擎按 `behavior` 读这张表时读成空的条目(引擎的整表读法:allow 表里的 `*` 让整表读成空;deny 表里的 `*` 只读空它自己);条目名过引擎判官(名册 = 空集)。 */
|
package/dist/rule-entry-list.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { compileAllowList } from "@sema-agent/core";
|
|
2
2
|
import { displayListEntry } from "./list-entry-display.js";
|
|
3
|
-
import {
|
|
3
|
+
import { NO_DECLARED_TOOL_NAMES } from "./namespaced-rule-name.js";
|
|
4
4
|
export const ruleEntryListShapeOk = (v) => Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
5
5
|
export const MAX_ALLOW_LAYER_ENTRIES = 1_000;
|
|
6
6
|
export function describeAllowLayerOverCap(field, count) {
|
|
@@ -12,17 +12,6 @@ export function allowLayerEntryCountIssue(field, list) {
|
|
|
12
12
|
return undefined;
|
|
13
13
|
return { message: describeAllowLayerOverCap(field, list.length), count: list.length, cap: MAX_ALLOW_LAYER_ENTRIES };
|
|
14
14
|
}
|
|
15
|
-
export function emptyToolSegmentEntryOf(list) {
|
|
16
|
-
for (const entry of list) {
|
|
17
|
-
const ns = emptyToolSegmentOf(ruleEntryToolName(entry));
|
|
18
|
-
if (ns !== undefined)
|
|
19
|
-
return { entry, ns };
|
|
20
|
-
}
|
|
21
|
-
return undefined;
|
|
22
|
-
}
|
|
23
|
-
export function describeEmptyToolSegmentEntry(field, u) {
|
|
24
|
-
return `${field} names ${displayListEntry(u.entry)}, which can never match a tool (${describeEmptyToolSegment(u.ns)})`;
|
|
25
|
-
}
|
|
26
15
|
export function inertRuleEntriesOf(list, behavior) {
|
|
27
16
|
return compileAllowList(list, behavior, NO_DECLARED_TOOL_NAMES).inert;
|
|
28
17
|
}
|
package/dist/run-local.js
CHANGED
|
@@ -14,7 +14,7 @@ import { withUnattendedLeg } from "./unattended-permission-mode.js";
|
|
|
14
14
|
import { buildUnattendedPolicyRefusal } from "./tool-approval.js";
|
|
15
15
|
import { createBrain } from "./brain.js";
|
|
16
16
|
import { assertGuardPatternsUsable, buildOnlySensitiveBaselineWarning, buildApprovalGrantNotice, createApprovalBaselinePolicy, createDeploymentGovernanceInputs, } from "./deployment-governance.js";
|
|
17
|
-
import { applyRuntimeGovernance } from "./runtime-governance.js";
|
|
17
|
+
import { applyRuntimeGovernance, autonomyModeFloor, entryPermissionMode } from "./runtime-governance.js";
|
|
18
18
|
import { ForkRoutingSessionStore } from "./plugins/fork-routing-session-store.js";
|
|
19
19
|
import { taskWallClockSec } from "./task-workflow.js";
|
|
20
20
|
import { applyCatalogToSource } from "./capabilities/center-prompts.js";
|
|
@@ -344,7 +344,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
344
344
|
for (const de of r.domainErrors ?? [])
|
|
345
345
|
logger.warn("config_domain_invalid", { domain: de.domain, error: de.error, note: "domain fell to schema defaults; other domains applied normally" });
|
|
346
346
|
for (const d of r.droppedEntries ?? [])
|
|
347
|
-
logger.warn("config_entry_dropped", { domain: d.domain, entry: d.entry, error: d.error, version: r.effective.version, phase: "boot", note: "this one entry failed its domain schema and was dropped on its own — the rest of the candidate applies (per-entry tolerance); fix the named entry to bring it back" });
|
|
347
|
+
logger.warn("config_entry_dropped", { domain: d.domain, entry: d.entry, reasonCode: d.reasonCode, error: d.error, version: r.effective.version, phase: "boot", note: "this one entry failed its domain schema and was dropped on its own — the rest of the candidate applies (per-entry tolerance); fix the named entry to bring it back" });
|
|
348
348
|
effective = r.effective;
|
|
349
349
|
applyEffective(config, r.effective, logger, { leg: "boot" });
|
|
350
350
|
if (effective?.mcp)
|
|
@@ -633,7 +633,9 @@ export async function runLocal(argv, deps = {}) {
|
|
|
633
633
|
...(maxCostUsd !== undefined ? { maxCostUsd } : {}),
|
|
634
634
|
...(maxTokens !== undefined ? { maxTokens } : {}),
|
|
635
635
|
};
|
|
636
|
+
const entryWord = entryPermissionMode(undefined, autonomyModeFloor(config.autonomy)).effective;
|
|
636
637
|
const spec = withUnattendedLeg(applyLocalGovernance({
|
|
638
|
+
...(entryWord !== undefined ? { permissionMode: entryWord } : {}),
|
|
637
639
|
objective: mention.cleanedObjective,
|
|
638
640
|
sessionId,
|
|
639
641
|
backgroundScope: "session",
|
|
@@ -14,16 +14,16 @@
|
|
|
14
14
|
* toolPolicy rule is `combinePolicies(base, override)` (deny-wins) — the command gate can only ADD denies/asks
|
|
15
15
|
* on top of the baseline, never clear it.
|
|
16
16
|
*
|
|
17
|
-
* TRAP #2 — `autonomy` must EXPAND INTO
|
|
18
|
-
* `toolPolicy`), NOT become a parallel enum on the spec (design-research §10: a parallel `autonomy`
|
|
19
|
-
* `handsReadOnly:true` would conflict). The expansion is tighten-only and routed through `tightenTaskSpec`, so
|
|
17
|
+
* TRAP #2 — `autonomy` must EXPAND INTO core primitives (`handsReadOnly` + `writeFace` / `permissionMode` /
|
|
18
|
+
* `shellGate` / a `toolPolicy`), NOT become a parallel enum on the spec (design-research §10: a parallel `autonomy`
|
|
19
|
+
* field + `handsReadOnly:true` would conflict). The expansion is tighten-only and routed through `tightenTaskSpec`, so
|
|
20
20
|
* `auto` (the loosest mode) can never LOOSEN a stricter base — `tightenTaskSpec` THROWS instead.
|
|
21
21
|
*
|
|
22
22
|
* ⚠️ `commandPolicy` is a COARSE name-level filter, NOT a sandbox. `sh -c …` / `$(…)` / pipes / `env X=…` /
|
|
23
23
|
* path-prefixed commands all bypass the argv[0] match (they route to `ask`, fail-closed, but are not blocked by
|
|
24
24
|
* name). Real isolation is the `executionEnv` sandbox. Same caveat core documents on `createCoarseCommandNamePolicy`.
|
|
25
25
|
*/
|
|
26
|
-
import { type ShellGateDoctrine, type TaskSpec, type ToolPolicy } from "@sema-agent/core";
|
|
26
|
+
import { type PermissionMode, type ShellGateDoctrine, type TaskSpec, type ToolPolicy } from "@sema-agent/core";
|
|
27
27
|
import { GovernanceConfig } from "@sema-agent/settings-schema";
|
|
28
28
|
import { type GovernanceAskMarks } from "./governance-ask-marks.js";
|
|
29
29
|
import { type GovernanceOverrides } from "./governance-overrides.js";
|
|
@@ -33,26 +33,6 @@ export declare const AUTONOMY_MODES: ["read-only", "ask", "plan", "auto"];
|
|
|
33
33
|
* 契约给 `command` 加一条 refine / 给 `decision` 加一个判词,本仓的型当场跟上(或在下面的
|
|
34
34
|
* `RANK` / 穷尽 switch 上编译红),而不是靠人记得把手写联合改一遍。 */
|
|
35
35
|
export type CommandRule = NonNullable<GovernanceConfig["commandPolicy"]>[number];
|
|
36
|
-
/** True when `name` names a DELEGATION tool — the subagent tool(CC-187 canonical `"Agent"`,core
|
|
37
|
-
* `DEFAULT_SUBAGENT_TOOL_NAME`)。5.0.0 [ref]:折叠面退役,RAW 比对 core 单源常量——旧名("Task")
|
|
38
|
-
* 在 core roster 层响亮 miss,不再需要归一;单源常量本身就防了当年 1.202 改名时裸字符串失配的
|
|
39
|
-
* fail-open(那个教训的解=引用常量,不是折叠)。 */
|
|
40
|
-
export declare function isDelegationToolName(name: string): boolean;
|
|
41
|
-
/** The hands-read-only DELEGATION STRIP (the main.ts governed-spec site, extracted here so the actual filter —
|
|
42
|
-
* not just the name predicate — is pinned by tests): a read-only (plan-mode) run does not mount a delegation
|
|
43
|
-
* tool at all.
|
|
44
|
-
*
|
|
45
|
-
* 🔴 原始理由已被上游收编(2026-08-05 亲验安装包 core 5.13.x):子任务 spec 现在**继承** `handsReadOnly`
|
|
46
|
-
* (`dist/agents/subagent.js` 的 `...(ctx.handsReadOnly === true ? { handsReadOnly: true } : {})`,resume
|
|
47
|
-
* resume 腿同款)与 `shellGate`(`dist/core/runner/prepare-task.js` 的 `inheritedGateForChildren`),所以
|
|
48
|
-
* 「子任务会拿到可写的手」这个当年的缺口在 core 侧已经补上。本 strip 因此是**同向的第二层**(更严,不更松):
|
|
49
|
-
* 它保证 read-only 一档下连委派工具面都不铸,与继承是否存在无关。拆掉它是一次行为变更(read-only 任务将
|
|
50
|
-
* 重新能委派),需要单独决定——不要因为「上游修好了」就顺手删。
|
|
51
|
-
*
|
|
52
|
-
* Identity (the same array) when nothing matches, so the caller's spec-object churn stays minimal. */
|
|
53
|
-
export declare function stripDelegationTools<T extends {
|
|
54
|
-
name: string;
|
|
55
|
-
}>(tools: readonly T[]): T[];
|
|
56
36
|
/** The operator autonomy mode (registry `governance.autonomy`) —— 契约派生(见 {@link AUTONOMY_MODES})。 */
|
|
57
37
|
export type Autonomy = NonNullable<GovernanceConfig["autonomy"]>;
|
|
58
38
|
/** 导出给 `test/coarse-shell-tools-mirror.test.ts` 的同源钉用(见那里的顶注)。 */
|
|
@@ -99,20 +79,33 @@ export declare function validateCommandRules(rules: CommandRule[]): string[];
|
|
|
99
79
|
*/
|
|
100
80
|
export declare function compileCommandPolicyLayers(rules: CommandRule[] | undefined): ToolPolicy[];
|
|
101
81
|
/**
|
|
102
|
-
* Expand an autonomy mode into TIGHTEN-ONLY `TaskSpec`
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
82
|
+
* Expand an autonomy mode into the TIGHTEN-ONLY `TaskSpec` FENCES that are re-folded on EVERY leg (first run and every
|
|
83
|
+
* resume rebuild) — the primitives, TRAP #2:
|
|
84
|
+
* - `read-only` → `handsReadOnly: true` **+ `writeFace: "roots"`** (read, never mutate; the §6 verifier read-only
|
|
85
|
+
* boundary). The hands clamp travels to every delegated child by core itself (`childClampSeats` — tighten-only), so the
|
|
86
|
+
* delegation tool stays mounted: a read-only run can delegate, and its children are read-only too.
|
|
106
87
|
* - `ask` → `shellGate: "always"` (every `Bash` command tightens to an `irreversible_ask` durable suspend —
|
|
107
88
|
* the fail-closed default for an unattended deployment with no parsed classifier).
|
|
108
|
-
* - `auto` / `undefined` → `{}` (
|
|
89
|
+
* - `plan` / `auto` / `undefined` → `{}` (nothing re-folded per leg; still subject to the deployment baseline + commandPolicy).
|
|
109
90
|
*
|
|
110
|
-
* 🔴
|
|
91
|
+
* 🔴 **规则:按段重折的只有静态围栏,模式词不在这里。** 只读手 + roots、shell 地板没有出口 —— 每一段续跑都该在,所以每一段都折。
|
|
92
|
+
* `plan` 是有出口的**生命周期词**(`plan_review` 把计划带出 plan),不是围栏:它是**提交入口的模式词地板**({@link autonomyModeFloor}),
|
|
93
|
+
* 盖在每一个 spec 出生点一次(词 = {@link entryPermissionMode} 的 `effective`):受理门盖进存储的提交体(`task-settings.ts` `stampEntryPermissionMode`)、run-local 盖在它自建的 spec 上、
|
|
94
|
+
* leader run 盖在它出生时的入口体上(`leader/run-entry.ts`;入口词是 `plan` 的 leader run 进不了那条车道 —— worker 会话没有计划审阅通道 ⇒ 受理口响亮拒)。此后一条 run 的模式词
|
|
95
|
+
* 只有存储体一个源,写者是每一个出生点(新的持久化写都经 `http/checkpoint-ctx.ts` `buildCheckpointCtx`)与 `plan_review` 写回。按段折一个模式词 = 第二个源:被批准的计划会在下一次挂起→续跑
|
|
96
|
+
* 被拉回 plan。收紧入参的型上因此没有 `permissionMode`(`governance-overrides.ts`)。(上一个发布版本里本档不碰模式词,按段折的是只读手 +
|
|
97
|
+
* 写面 `roots`、与 `read-only` 同形 —— 运维 plan 那时根本不进 plan 模式,见下一段。)
|
|
98
|
+
*
|
|
99
|
+
* 🔴 **为什么 `plan` 不再与 `read-only` 同形**:修前两档同映「只读手 + roots」,外加 `resolve-spec.ts` 见只读手就剥委派工具 —— 于是 `plan` 有三套
|
|
100
|
+
* 实现(运维 plan = 只读手、用户选 plan = 模式词 + 只读手 + 剥委派、模型自进 plan = 只有模式词),而剥委派撞上请求体的 `agents` 就是 core 的
|
|
101
|
+
* `config.agents.no_delegation_tool` 整轮拒。现在 plan 只有引擎一个判官,`read-only` 只是只读手这一个词。
|
|
102
|
+
*
|
|
103
|
+
* 🔴 **为什么 `read-only` 同时说 `writeFace:"roots"`**([ref] 随批;不是补丁,是把这一档的表态说全):
|
|
111
104
|
* core 7.18.0 的 `resolveContainmentFace` 第 1 行把「只读(verifier)挂载 × 任务层 `writeFace:"open"`」
|
|
112
105
|
* 判成**真实矛盾**并**抛** `config.write_face_readonly_conflict`(理由逐字:那种挂载根本不带写面,
|
|
113
106
|
* 声明一个开放写面 = 为一次调用没有的工具表态)。而 `permissionMode:"bypassPermissions"` 自 [ref] 起
|
|
114
107
|
* 会在阶段④铸出 `writeFace:"open"` —— 于是「`AUTONOMY=read-only` 的部署 × 一个 bypass 请求」这一格
|
|
115
|
-
* 不修就是**整条 run
|
|
108
|
+
* 不修就是**整条 run 当场配置错误失败**(`plan` 一档不需要它:提交入口把模式词钳成 `plan`,bypass 那一列根本不生效)。
|
|
116
109
|
*
|
|
117
110
|
* 修在这里而不是在写点加一条「若 handsReadOnly 则别写」的判据:两个字段**在这一档里是同一件事**
|
|
118
111
|
* (「这条 run 没有写面」),而 `tightenTaskSpec` 本来就把 `writeFace` 当 tighten-only 安全字段收
|
|
@@ -122,6 +115,48 @@ export declare function compileCommandPolicyLayers(rules: CommandRule[] | undefi
|
|
|
122
115
|
* 它照样把键写出来(不是「只在有 open 时才压」——那又是一条判据)。
|
|
123
116
|
*/
|
|
124
117
|
export declare function autonomyOverrides(autonomy: Autonomy | undefined): GovernanceOverrides;
|
|
118
|
+
/**
|
|
119
|
+
* 运维 autonomy 给**提交入口**的模式词地板 —— 唯一属主(规则全文见 {@link autonomyOverrides} 顶注)。`plan` ⇒ `"plan"`:每个新提交的 run
|
|
120
|
+
* 以 plan 起步,请求的词按 core 的模式秩钳到它之下;其余三档与缺席 ⇒ 无地板(它们的表态全在按段的围栏里)。
|
|
121
|
+
* 「一个词在这块地板下变成什么」只有一只函数答:{@link entryPermissionMode}(读者全表在它的头注)。
|
|
122
|
+
* 闭集**穷尽** switch:契约给 autonomy 加一档 ⇒ 这里编译红([ref])。
|
|
123
|
+
*/
|
|
124
|
+
export declare function autonomyModeFloor(autonomy: Autonomy | undefined): PermissionMode | undefined;
|
|
125
|
+
/**
|
|
126
|
+
* 一个模式词进入一块地板之后的**读数** —— {@link entryPermissionMode} 的答。四键:调用方声明的词(`requested`,缺席 = 没声明)、地板(`floor`,
|
|
127
|
+
* 缺席 = 这里没有地板)、生效词(`effective`)与「地板改了调用方的词」(`clamped`,core 的同名位:只在声明了词、且那个词被折回地板时为真 ——
|
|
128
|
+
* 没声明词而取地板本身不算改词)。
|
|
129
|
+
*/
|
|
130
|
+
export interface EntryPermissionMode<W extends PermissionMode | undefined = PermissionMode | undefined> {
|
|
131
|
+
readonly requested: W;
|
|
132
|
+
readonly floor: PermissionMode | undefined;
|
|
133
|
+
readonly effective: W | PermissionMode;
|
|
134
|
+
readonly clamped: boolean;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 「请求词 `word` 在地板 `floor` 下变成什么」—— **唯一**的一只函数(本仓对 core `clampPermissionMode` 的唯一调用点;秩只在 core 一处)。
|
|
138
|
+
*
|
|
139
|
+
* 规则:地板缺席 ⇒ 词原样、`clamped:false`;地板在场 ⇒ core `clampPermissionMode(word, floor)` 的答(词缺席 ⇒ 地板本身;比地板松的词 ⇒ 折回地板、`clamped:true`)。
|
|
140
|
+
* 本仓「地板」即 core 的天花板(`plan` 秩最低:比它松的词都折成它)。
|
|
141
|
+
*
|
|
142
|
+
* 读者(都调这一只,不另写「缺席原样否则钳」):
|
|
143
|
+
* · spec 出生点写 `effective` —— 受理门(`task-settings.ts` `stampEntryPermissionMode` 盖进存储体)、run-local 自建的 spec、
|
|
144
|
+
* leader run 的入口词(`leader/run-entry.ts` `leaderRunEntry`)、leader 车道合并口(`leader/leader.ts` `withLeaderLeg`:spec 的词钳在入口词之下)、
|
|
145
|
+
* 无人值守腿(`unattended-permission-mode.ts` `withUnattendedLeg`:`default` 钳在 spec 的词之下);
|
|
146
|
+
* · 「这个词会变成什么」的读者 —— 能力面写面开不开({@link autonomyKeepsOpenWriteFace}:bypass 声明过地板)、能力面 auto 读面
|
|
147
|
+
* (`auto-mode-face.ts` `judgeAutoModeArming`:意图过地板;`clamped` 且不是 auto ⇒ 未武装原因 `operator_floor`)、
|
|
148
|
+
* 存量停驻体能否按此刻的地板重放(`http/stored-body-replay.ts`:体的词经钳会变 ⇒ 拒)。
|
|
149
|
+
* `test/entry-permission-mode.test.ts` 按语法树钉:core 钳在 `src/` 只此一处调用,上面八处各调本函数一次。
|
|
150
|
+
*/
|
|
151
|
+
export declare function entryPermissionMode<W extends PermissionMode | undefined>(word: W, floor: PermissionMode | undefined): EntryPermissionMode<W>;
|
|
152
|
+
/**
|
|
153
|
+
* 运维 autonomy 留不留得住请求层「掀开写围栏」那条声明(`permissionMode:"bypassPermissions"`,引擎模式表那一列 `writeFace:"open"`)——
|
|
154
|
+
* 能力面 `taskWriteFaceOpen` 的治理那一半。判据读**两只属主本身**,与一条真提交走的路同序:bypass 声明先过入口地板({@link entryPermissionMode},
|
|
155
|
+
* 与受理门的盖章同一只函数),再交给本档的按段围栏(`tightenGoverned` → core `tightenTaskSpec`),
|
|
156
|
+
* 看折出来的 spec 还开不开写面 —— 显式席 `"roots"` 胜过模式列(`read-only`),入口词被钳成别的词时读那个词的模式列(`plan`)。
|
|
157
|
+
* 本仓不另写一张「哪几档钳写面」的表:档位怎么钳 / 怎么折由两只属主说,写面开不开由引擎的模式表说。
|
|
158
|
+
*/
|
|
159
|
+
export declare function autonomyKeepsOpenWriteFace(autonomy: Autonomy | undefined): boolean;
|
|
125
160
|
/**
|
|
126
161
|
* [ref]:**部署级**判据 —— 本部署的治理层自己是否把 shellGate 抬到 `"always"`。
|
|
127
162
|
*
|
|
@@ -207,7 +242,7 @@ export declare function governanceOriginOf(gate: {
|
|
|
207
242
|
* Apply the operator's runtime governance (autonomy + commandPolicy + manualModeShellGate + the guard set) onto a base
|
|
208
243
|
* `TaskSpec`, TIGHTEN-ONLY, in a SINGLE {@link tightenTaskSpec} call: commandPolicy compiles to a `toolPolicy`
|
|
209
244
|
* override (combined onto the baseline by `tightenTaskSpec` — TRAP #1), and autonomy expands to
|
|
210
|
-
* `handsReadOnly` / `shellGate` overrides (TRAP #2). `tightenTaskSpec` THROWS `TaskSpecTightenError` if any
|
|
245
|
+
* `handsReadOnly` + `writeFace` / `shellGate` overrides (TRAP #2; the plan floor is the submission entrance's, never a per-leg fold). `tightenTaskSpec` THROWS `TaskSpecTightenError` if any
|
|
211
246
|
* override would LOOSEN a stricter base safety field — surfacing a misconfiguration loudly instead of silently
|
|
212
247
|
* weakening the gate. Returns `base` untouched when there is nothing to apply.
|
|
213
248
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { combinePolicies, COARSE_SHELL_TOOLS as CORE_COARSE_SHELL_TOOLS, createCoarseCommandNamePolicy,
|
|
1
|
+
import { clampPermissionMode, combinePolicies, COARSE_SHELL_TOOLS as CORE_COARSE_SHELL_TOOLS, createCoarseCommandNamePolicy, modePolicyOf, parseLeadingCommandName, wrapToolPolicy, } from "@sema-agent/core";
|
|
2
2
|
import { GovernanceConfig } from "@sema-agent/settings-schema";
|
|
3
3
|
import { currentGovernanceAskMarks } from "./governance-ask-marks.js";
|
|
4
4
|
import { tightenGoverned } from "./governance-overrides.js";
|
|
@@ -7,13 +7,6 @@ import { heldRuleName } from "./tool-rule-names.js";
|
|
|
7
7
|
const COMMAND_RULE_SHAPE = GovernanceConfig.shape.commandPolicy.unwrap().element.shape;
|
|
8
8
|
const COMMAND_DECISIONS = COMMAND_RULE_SHAPE.decision.options;
|
|
9
9
|
export const AUTONOMY_MODES = GovernanceConfig.shape.autonomy.unwrap().options;
|
|
10
|
-
export function isDelegationToolName(name) {
|
|
11
|
-
return name === DEFAULT_SUBAGENT_TOOL_NAME;
|
|
12
|
-
}
|
|
13
|
-
export function stripDelegationTools(tools) {
|
|
14
|
-
const kept = tools.filter((t) => !isDelegationToolName(t.name));
|
|
15
|
-
return kept.length === tools.length ? tools : kept;
|
|
16
|
-
}
|
|
17
10
|
const COARSE_SHELL_TOOLS = CORE_COARSE_SHELL_TOOLS;
|
|
18
11
|
export const COARSE_SHELL_TOOLS_MIRROR = COARSE_SHELL_TOOLS;
|
|
19
12
|
function createCommandAskListPolicy(askCommands, tools = COARSE_SHELL_TOOLS) {
|
|
@@ -104,15 +97,41 @@ export function compileCommandPolicyLayers(rules) {
|
|
|
104
97
|
export function autonomyOverrides(autonomy) {
|
|
105
98
|
switch (autonomy) {
|
|
106
99
|
case "read-only":
|
|
107
|
-
case "plan":
|
|
108
100
|
return { handsReadOnly: true, writeFace: "roots" };
|
|
109
101
|
case "ask":
|
|
110
102
|
return { shellGate: "always" };
|
|
103
|
+
case "plan":
|
|
111
104
|
case "auto":
|
|
112
105
|
case undefined:
|
|
113
106
|
return {};
|
|
114
107
|
}
|
|
115
108
|
}
|
|
109
|
+
export function autonomyModeFloor(autonomy) {
|
|
110
|
+
switch (autonomy) {
|
|
111
|
+
case "plan":
|
|
112
|
+
return "plan";
|
|
113
|
+
case "read-only":
|
|
114
|
+
case "ask":
|
|
115
|
+
case "auto":
|
|
116
|
+
case undefined:
|
|
117
|
+
return undefined;
|
|
118
|
+
default: {
|
|
119
|
+
const unhandled = autonomy;
|
|
120
|
+
throw new Error(`autonomy "${String(unhandled)}" has no entry-floor arm`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
export function entryPermissionMode(word, floor) {
|
|
125
|
+
if (floor === undefined)
|
|
126
|
+
return { requested: word, floor, effective: word, clamped: false };
|
|
127
|
+
const { effective, clamped } = clampPermissionMode(word, floor);
|
|
128
|
+
return { requested: word, floor, effective, clamped };
|
|
129
|
+
}
|
|
130
|
+
export function autonomyKeepsOpenWriteFace(autonomy) {
|
|
131
|
+
const entry = entryPermissionMode("bypassPermissions", autonomyModeFloor(autonomy)).effective;
|
|
132
|
+
const folded = tightenGoverned({ objective: "", permissionMode: entry }, autonomyOverrides(autonomy));
|
|
133
|
+
return folded.writeFace !== "roots" && folded.permissionMode !== undefined && modePolicyOf(folded.permissionMode).writeFace === "open";
|
|
134
|
+
}
|
|
116
135
|
export function governanceMandatesShellGateAlways(governance) {
|
|
117
136
|
return autonomyOverrides(governance.autonomy).shellGate === "always" || governance.manualModeShellGate === "always";
|
|
118
137
|
}
|
|
@@ -29,13 +29,14 @@
|
|
|
29
29
|
* 里同点同调它;准备阶段的名审计与本仓配置表读的是同一只 `judgeRuleToolName`)—— 本仓手写的那一只(`legacyToolNameOf`)删除,零双读;
|
|
30
30
|
* 「哪几桶装工具名」归 core(它的 `HOLDS_TOOL_NAMES`)。
|
|
31
31
|
*
|
|
32
|
-
* ── 7.
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
32
|
+
* ── 7.123.0(core 7.48.0):两问都只剩引擎的判 ──────────────────────────────────────────────────────────────────────
|
|
33
|
+
* ① **桶**能不能写:core 7.48.0 #1471 起 `normalizeRules` 对「不是一列非空字符串」的桶抛 `TypeError`(裸字符串、`null`、混入非字符串、`""` /
|
|
34
|
+
* 纯空白项),句子点名桶与值类型。写口读者逐桶问它({@link engineRuleListOf})⇒ 两条写口是唯一拒点(`400 request.field_invalid`,句子原样),
|
|
35
|
+
* 店里的 `normalizeRules` 对这两条写口的输入不再可能抛。修前本仓的形读者只判「字符串数组」,`[""]` / `[" "]` 过它、在店里抛 ⇒ 500。
|
|
36
|
+
* 本仓自己的「字符串数组」判只留在**读回**({@link readSessionPermissionRules}:SQL 店读出的行,读不出形 ⇒ `corrupt`)—— 读回与写口是两个问题,
|
|
37
|
+
* 读回不许判「引擎还肯不肯写」:7.47.x 及以前写下的空串项存量行必须读得出来(运营方清它、准备阶段照常执行它都要先读到它)。
|
|
38
|
+
* ② **名字**够不够得着工具:core 7.48.0 #1400 起工具段为空的协议名(`mcp__srv__`)也是引擎判官的判词 `unmintable_segment` ⇒ 本仓 7.116.0 起
|
|
39
|
+
* 自答的那一问(「引擎判官放行、却一只工具都够不着」)整只退役,写口拒码随之由 `request.field_invalid` 变 `rules.legacy_tool_name`。
|
|
39
40
|
*
|
|
40
41
|
* ── 文案也在这里(codex 一轮追加:两条腿各铸一份句子 = 第二份手抄)───────────────────────────────
|
|
41
42
|
* 初版把 wire 文案留在两个路由各自铸,理由是错误文案冻结门(`test/api-error-text-freeze.test.ts`)锚的是
|
|
@@ -46,24 +47,9 @@
|
|
|
46
47
|
*/
|
|
47
48
|
import { SessionPolicyError } from "@sema-agent/core";
|
|
48
49
|
import type { SessionPermissionRules, SessionRuleNameRefusal, SessionRulesRecord } from "@sema-agent/core";
|
|
49
|
-
import { emptyToolSegmentOf } from "./namespaced-rule-name.js";
|
|
50
50
|
/** core `SessionPermissionRules` 的桶名全集(派生,不是手抄)。 */
|
|
51
51
|
export declare const SESSION_PERMISSION_RULE_FIELDS: readonly (keyof SessionPermissionRules)[];
|
|
52
|
-
/**
|
|
53
|
-
export interface SessionRuleUnreachableName {
|
|
54
|
-
readonly field: SessionRuleNameRefusal["field"];
|
|
55
|
-
readonly name: string;
|
|
56
|
-
readonly ns: NonNullable<ReturnType<typeof emptyToolSegmentOf>>;
|
|
57
|
-
}
|
|
58
|
-
/** 逐桶逐条问引擎读不到的那一形;第一条交回,全部够得着 ⇒ `undefined`。调用方先过引擎判官({@link legacyToolNameRefusalOf} / `assertSessionRuleNamesCurrent`)。 */
|
|
59
|
-
export declare function unreachableSessionRuleNameOf(rules: SessionPermissionRules): SessionRuleUnreachableName | undefined;
|
|
60
|
-
/**
|
|
61
|
-
* SQL 会话规则店写口的同一问(core 判官之后、收紧门之前,与 `assertSessionRuleNamesCurrent` 同点):够不着 ⇒ 抛,一条写语句都不发。
|
|
62
|
-
* 抛的是普通 `Error`(不是 `SessionPolicyError`:core 那张闭集里没有这个词,也不往里塞)—— 本服务的两条 HTTP 写口在读者那一层先拒,
|
|
63
|
-
* 走到这里 = 有一个绕过写口读者的写者,HTTP 上落 500(服务端自身的失误,响亮)。
|
|
64
|
-
*/
|
|
65
|
-
export declare function assertSessionRuleNamesReachable(rules: SessionPermissionRules): void;
|
|
66
|
-
/** 一条规则体的**形**问题。判词而非文案(见文件头注的「文案属主」一段)。 */
|
|
52
|
+
/** **读回**的形问题(SQL 店读出的行;判词而非文案,见文件头注的「文案属主」一段)。 */
|
|
67
53
|
export type SessionPermissionRulesShapeProblem =
|
|
68
54
|
/** 整体不是一只普通对象(`null` / 数组 / 标量 / 缺席)。 */
|
|
69
55
|
{
|
|
@@ -74,18 +60,23 @@ export type SessionPermissionRulesShapeProblem =
|
|
|
74
60
|
kind: "field-not-string-array";
|
|
75
61
|
field: keyof SessionPermissionRules;
|
|
76
62
|
};
|
|
77
|
-
/** 一条**要写进店**的规则体的问题
|
|
78
|
-
export type
|
|
79
|
-
|
|
63
|
+
/** 一条**要写进店**的规则体的问题 —— 三问都只有一个判官:容器形(本仓)、桶(引擎 `normalizeRules`)、名字(引擎 `assertSessionRuleNamesCurrent`)。 */
|
|
64
|
+
export type SessionPermissionRulesWriteProblem = {
|
|
65
|
+
kind: "not-an-object";
|
|
66
|
+
}
|
|
67
|
+
/** 引擎不肯把这一桶写成规则表(core 7.48.0 #1471);`refusal` = 引擎的句子(点名桶与值类型),原样上 wire。 */
|
|
80
68
|
| {
|
|
81
|
-
kind: "
|
|
82
|
-
|
|
69
|
+
kind: "field-not-rule-list";
|
|
70
|
+
field: keyof SessionPermissionRules;
|
|
71
|
+
refusal: string;
|
|
83
72
|
}
|
|
84
|
-
/**
|
|
73
|
+
/** S-800 / S-1047:桶形对,但工具名桶里有一条引擎判官判非现役的名字;`refusal` = core 判官交回的 detail(原样上 wire)。 */
|
|
85
74
|
| {
|
|
86
|
-
kind: "
|
|
87
|
-
|
|
75
|
+
kind: "legacy-tool-name";
|
|
76
|
+
refusal: SessionRuleNameRefusal;
|
|
88
77
|
};
|
|
78
|
+
/** 两只读者的判词并集(句子铸点 {@link describeSessionPermissionRulesProblem} 读它)。 */
|
|
79
|
+
export type SessionPermissionRulesProblem = SessionPermissionRulesShapeProblem | SessionPermissionRulesWriteProblem;
|
|
89
80
|
/** 两只读者的读数:有问题给判词,没问题给**已收窄**的规则体(调用方零 cast)。`P` = 这只读者会给的判词集。 */
|
|
90
81
|
export type SessionPermissionRulesRead<P extends SessionPermissionRulesProblem = SessionPermissionRulesProblem> = {
|
|
91
82
|
problem: P;
|
|
@@ -95,27 +86,34 @@ export type SessionPermissionRulesRead<P extends SessionPermissionRulesProblem =
|
|
|
95
86
|
rules: SessionPermissionRules;
|
|
96
87
|
};
|
|
97
88
|
/**
|
|
98
|
-
*
|
|
99
|
-
* (
|
|
100
|
-
* `readSessionRulesBody`:读不出形的行判 `corrupt`)。
|
|
89
|
+
* **读回**的形读者:读一只 `SessionPermissionRules`,形不对给判词,形对给**只含五桶**的规则体。消费者 = SQL 店的读回
|
|
90
|
+
* (`session-policy-store-sql.ts` 的 `readSessionRulesBody`:读不出形的行判 `corrupt`)。
|
|
101
91
|
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* 本读者与它同口径。
|
|
92
|
+
* 认识面窄得刻意:**只判「五桶各是字符串数组」**,不判引擎还肯不肯写(空串 / 纯空白项)、不判名字 —— 那是写口的问题
|
|
93
|
+
* ({@link readWritableSessionPermissionRules})。7.47.x 及以前写下的空串项存量行必须读得出:读回把它判坏 ⇒ 运营方清它的 PUT 在写前快照上 500、
|
|
94
|
+
* 准备阶段也读不到它(同 S-800 的「判官只在写口」)。未知键**照旧忽略**:core 的 `normalizeRules` 本来就只搬这五桶,本读者与它同口径。
|
|
106
95
|
*/
|
|
107
96
|
export declare function readSessionPermissionRules(raw: unknown): SessionPermissionRulesRead<SessionPermissionRulesShapeProblem>;
|
|
108
97
|
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
98
|
+
* 一份规则体(写口的入参,或店里读出来的一行)里**引擎不肯写**的第一只桶 —— 没有 ⇒ `undefined`。判官同 {@link engineRuleListOf}。
|
|
99
|
+
* 两个读者:写口读者(拒)与 2c 重放的同内容判据(`session-sync.ts` 的 `sameRulesContent`:目的端存量行是这一形 ⇒ 答「不同内容」,写交给店的收紧门)。
|
|
100
|
+
*/
|
|
101
|
+
export declare function unwritableRuleListOf(rules: Readonly<Partial<Record<keyof SessionPermissionRules, unknown>>>): {
|
|
102
|
+
field: keyof SessionPermissionRules;
|
|
103
|
+
refusal: string;
|
|
104
|
+
} | undefined;
|
|
105
|
+
/**
|
|
106
|
+
* **写口读者**(S-800)—— 两条写入腿(`PUT /v1/sessions/:id/policy`、sync/import 的 `policy` 面经 {@link readSessionRulesRecords})往会话规则店
|
|
107
|
+
* 写之前的**唯一**判据,三问按序、每问一个判官:
|
|
108
|
+
* ① 容器形 —— 一只普通对象(本仓);
|
|
109
|
+
* ② 每一只桶 —— 引擎肯不肯把它写成规则表({@link engineRuleListOf},core 7.48.0 #1471;与引擎店同序:先 `normalizeRules` 再判名);
|
|
110
|
+
* ③ 工具名桶里的每一条 —— 引擎判官({@link legacyToolNameRefusalOf}:退役名 / 无前缀 `__` 名 / 对端段或工具段铸不出、为空)。
|
|
111
|
+
* 拿到规则体 = 店里的 `normalizeRules` 与 `assertSessionRuleNamesCurrent` 对它都不会再抛(同一只判官先问过)。
|
|
112
|
+
* 🔴 **只在写口,不在读回**:SQL 店读回同一行时只判形({@link readSessionPermissionRules})。早先写进去的退役名行 / 空串项行必须**读得出来** ——
|
|
113
|
+
* 那是运营方清掉它的唯一路径(operator 的 PUT 先读写前快照),也是 core 准备阶段报 `config.legacy_tool_name` 而不是一只 500 `corrupt` 的前提
|
|
114
|
+
* (7.105.0 车QQ 双库套件实抓,修在同批)。
|
|
117
115
|
*/
|
|
118
|
-
export declare function readWritableSessionPermissionRules(raw: unknown): SessionPermissionRulesRead
|
|
116
|
+
export declare function readWritableSessionPermissionRules(raw: unknown): SessionPermissionRulesRead<SessionPermissionRulesWriteProblem>;
|
|
119
117
|
/** 一条 `SessionRulesRecord` 的形问题(`policy[]` 的元素级;桶级问题原样转载在 `rules` 位)。 */
|
|
120
118
|
export type SessionRulesRecordProblem =
|
|
121
119
|
/** `policy` 本身不是数组。 */
|
|
@@ -133,11 +131,11 @@ export type SessionRulesRecordProblem =
|
|
|
133
131
|
kind: "principal-not-a-string";
|
|
134
132
|
index: number;
|
|
135
133
|
}
|
|
136
|
-
/** 第 `index` 条的 `rules`
|
|
134
|
+
/** 第 `index` 条的 `rules` 写不进(转载写口读者的判词)。 */
|
|
137
135
|
| {
|
|
138
136
|
kind: "rules";
|
|
139
137
|
index: number;
|
|
140
|
-
problem:
|
|
138
|
+
problem: SessionPermissionRulesWriteProblem;
|
|
141
139
|
};
|
|
142
140
|
/** {@link readSessionRulesRecords} 的读数。 */
|
|
143
141
|
export type SessionRulesRecordsRead = {
|