@sema-agent/server 7.103.2 → 7.105.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 +378 -2
- package/README.md +6 -1
- package/README.zh-CN.md +5 -1
- package/USAGE.md +320 -16
- package/deploy/sema-up/README.md +57 -7
- package/deploy/sema-up/cluster-up.sh +18 -4
- package/deploy/sema-up/kube-up.sh +16 -0
- package/deploy/sema-up/lib/dataroot.sh +142 -0
- package/deploy/sema-up/lib/flagshape.sh +69 -0
- package/deploy/sema-up/lib/lanes.py +14 -4
- package/deploy/sema-up/lib/shell-access.sh +41 -0
- package/deploy/sema-up/preflight.sh +14 -1
- package/deploy/sema-up/sema-up.sh +134 -37
- package/deploy/sema-up/smoke.sh +5 -0
- package/dist/auto-mode-face.js +3 -4
- package/dist/boot/budget-tracing.js +1 -1
- package/dist/boot/config-center.d.ts +6 -2
- package/dist/boot/config-center.js +5 -59
- package/dist/boot/execution-env.d.ts +11 -0
- package/dist/boot/execution-env.js +14 -0
- package/dist/boot/execution-lane.d.ts +25 -0
- package/dist/boot/execution-lane.js +95 -0
- package/dist/boot/memory-consolidation.d.ts +185 -9
- package/dist/boot/memory-consolidation.js +192 -32
- package/dist/boot/resolve-spec.d.ts +2 -0
- package/dist/boot/resolve-spec.js +28 -24
- package/dist/boot/runner-deps.d.ts +6 -2
- package/dist/boot/runner-deps.js +6 -1
- package/dist/boot/session-faces.d.ts +5 -0
- package/dist/boot/session-faces.js +18 -2
- package/dist/boot/session-incarnation-seat.d.ts +41 -0
- package/dist/boot/session-incarnation-seat.js +69 -0
- package/dist/boot/stage-01-config.js +2 -2
- package/dist/boot/stage-02-registry-pull.js +3 -1
- package/dist/boot/stage-04-stores.d.ts +2 -0
- package/dist/boot/stage-04-stores.js +3 -1
- package/dist/boot/stage-05-execution-env.d.ts +4 -0
- package/dist/boot/stage-05-execution-env.js +2 -2
- package/dist/boot/stage-06-runners.d.ts +4 -1
- package/dist/boot/stage-06-runners.js +7 -14
- package/dist/boot/stage-07-capability-layer.d.ts +4 -1
- package/dist/boot/stage-08-reapers.d.ts +4 -1
- package/dist/boot/stage-09-leader.d.ts +6 -1
- package/dist/boot/stage-09-leader.js +2 -1
- package/dist/boot/stage-10-http-server.d.ts +6 -1
- package/dist/boot/stage-10-http-server.js +4 -1
- package/dist/brain.d.ts +1 -1
- package/dist/config-catalog.d.ts +16 -1
- package/dist/config-catalog.js +9 -9
- package/dist/config-center/restart-signal.d.ts +4 -4
- package/dist/config-invariants.d.ts +7 -1
- package/dist/config-invariants.js +37 -6
- package/dist/config-provider.d.ts +42 -10
- package/dist/config-provider.js +18 -16
- package/dist/config-types.d.ts +37 -16
- package/dist/config-types.js +1 -0
- package/dist/config.d.ts +28 -30
- package/dist/config.js +95 -77
- package/dist/env-facts.d.ts +13 -3
- package/dist/env-facts.js +15 -3
- package/dist/execution-lane-caps.d.ts +15 -0
- package/dist/execution-lane-caps.js +8 -0
- package/dist/file-history-capture.d.ts +42 -35
- package/dist/file-history-capture.js +15 -10
- package/dist/file-history-disclosure.d.ts +7 -31
- package/dist/file-history-disclosure.js +12 -0
- package/dist/fleet/fleet-bus.d.ts +5 -1
- package/dist/http/admission.d.ts +12 -5
- package/dist/http/admission.js +5 -1
- package/dist/http/dispatch.d.ts +18 -0
- package/dist/http/dispatch.js +19 -6
- package/dist/http/principal-gate.d.ts +22 -0
- package/dist/http/principal-gate.js +20 -0
- package/dist/http/resume-legs.d.ts +1 -1
- package/dist/http/resume-legs.js +9 -5
- package/dist/http/route-table.d.ts +15 -4
- package/dist/http/route-table.js +6 -5
- package/dist/http/routes/agents-roster.d.ts +7 -0
- package/dist/http/routes/agents-roster.js +6 -2
- package/dist/http/routes/approvals-assistant.d.ts +48 -19
- package/dist/http/routes/approvals-assistant.js +117 -50
- package/dist/http/routes/capabilities.js +3 -2
- package/dist/http/routes/config-catalog.js +1 -0
- package/dist/http/routes/fleet.d.ts +3 -2
- package/dist/http/routes/images.js +0 -1
- package/dist/http/routes/memory-consolidation.d.ts +3 -2
- package/dist/http/routes/memory-policy.js +3 -3
- package/dist/http/routes/rules.js +2 -2
- package/dist/http/routes/runs.js +29 -10
- package/dist/http/routes/session-sync.js +14 -15
- package/dist/http/routes/sessions-list.js +3 -2
- package/dist/http/routes/sessions.js +49 -25
- package/dist/http/routes/shared-memory.js +2 -2
- package/dist/http/routes/side-query.js +8 -1
- package/dist/http/routes/tasks.js +22 -19
- package/dist/http/routes/trace-usage.d.ts +2 -0
- package/dist/http/routes/trace-usage.js +8 -13
- package/dist/http/send.d.ts +12 -0
- package/dist/http/send.js +12 -1
- package/dist/http/server.d.ts +30 -8
- package/dist/http/server.js +11 -5
- package/dist/http/sse-lifecycle.d.ts +1 -1
- package/dist/http/sse-log.d.ts +12 -9
- package/dist/http/sse-log.js +1 -8
- package/dist/http/sync-submit-body.d.ts +43 -0
- package/dist/http/sync-submit-body.js +4 -0
- package/dist/http/wire-types.d.ts +23 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/leader/merge.d.ts +8 -3
- package/dist/memory-incident-sink.js +2 -1
- package/dist/model-select.d.ts +9 -0
- package/dist/model-select.js +5 -1
- package/dist/observability/corrupt-read-seat.d.ts +151 -16
- package/dist/observability/corrupt-read-seat.js +158 -15
- package/dist/observability/fail-open.d.ts +17 -1
- package/dist/observability/fail-open.js +17 -1
- package/dist/observability/metrics.js +1 -1
- package/dist/observability/run-terminal.d.ts +60 -10
- package/dist/observability/run-terminal.js +119 -9
- package/dist/parked-decide.d.ts +8 -3
- package/dist/parked-decide.js +56 -20
- package/dist/parked-revive-surface.d.ts +14 -7
- package/dist/parked-revive-surface.js +5 -1
- package/dist/plugins/approval-ask-store-file.d.ts +4 -1
- package/dist/plugins/approval-ask-store-file.js +9 -12
- package/dist/plugins/approval-ask-store-sql.d.ts +4 -0
- package/dist/plugins/approval-ask-store-sql.js +4 -1
- package/dist/plugins/approval-exemption-store.d.ts +21 -1
- package/dist/plugins/approval-exemption-store.js +36 -7
- package/dist/plugins/approval-nonce-store.d.ts +16 -2
- package/dist/plugins/approval-nonce-store.js +17 -7
- package/dist/plugins/caching-session-store.d.ts +5 -0
- package/dist/plugins/caching-session-store.js +3 -0
- package/dist/plugins/file-resume-anchor-store.d.ts +7 -2
- package/dist/plugins/file-resume-anchor-store.js +19 -5
- package/dist/plugins/file-run-store.d.ts +21 -8
- package/dist/plugins/file-run-store.js +60 -38
- package/dist/plugins/image-bake-store-sql.d.ts +0 -2
- package/dist/plugins/image-bake-store-sql.js +0 -5
- package/dist/plugins/local-session-store.d.ts +22 -0
- package/dist/plugins/local-session-store.js +74 -11
- package/dist/plugins/memory-run-store.d.ts +7 -5
- package/dist/plugins/memory-run-store.js +11 -4
- package/dist/plugins/one-time-migrations.d.ts +38 -1
- package/dist/plugins/one-time-migrations.js +143 -15
- package/dist/plugins/permission-rule-store-file.d.ts +9 -2
- package/dist/plugins/permission-rule-store-file.js +37 -51
- package/dist/plugins/pg-session-storage.d.ts +3 -0
- package/dist/plugins/pg-session-storage.js +19 -10
- package/dist/plugins/retention-store-sql.js +1 -0
- package/dist/plugins/run-store-sql.d.ts +15 -6
- package/dist/plugins/run-store-sql.js +15 -13
- package/dist/plugins/schema-indexes.js +1 -0
- package/dist/plugins/send-file-ledger.d.ts +5 -1
- package/dist/plugins/send-file-ledger.js +11 -6
- package/dist/plugins/session-event-payload.d.ts +14 -4
- package/dist/plugins/session-event-payload.js +5 -2
- package/dist/plugins/session-policy-store-sql.js +18 -5
- package/dist/plugins/store-backend.d.ts +13 -7
- package/dist/plugins/store-backend.js +10 -6
- package/dist/plugins/store-contracts.d.ts +7 -2
- package/dist/plugins/tidb-pool.js +12 -5
- package/dist/plugins/tidb-session-store.d.ts +6 -1
- package/dist/plugins/tidb-session-store.js +17 -10
- package/dist/project-identity.d.ts +4 -6
- package/dist/read-face-posture.d.ts +21 -0
- package/dist/read-face-posture.js +9 -0
- package/dist/request-key-closure.js +1 -1
- package/dist/request-value-closure.d.ts +72 -3
- package/dist/request-value-closure.js +12 -4
- package/dist/roster-bound-http-status.d.ts +6 -0
- package/dist/roster-bound-http-status.js +6 -0
- package/dist/rules-consent.js +0 -11
- package/dist/run-local.js +22 -27
- package/dist/runs.d.ts +22 -2
- package/dist/runs.js +29 -7
- package/dist/sealed-key.d.ts +31 -4
- package/dist/sealed-key.js +9 -0
- package/dist/security.d.ts +5 -1
- package/dist/session-children-retire.d.ts +92 -0
- package/dist/session-children-retire.js +85 -0
- package/dist/session-error-wire.d.ts +58 -0
- package/dist/session-error-wire.js +38 -0
- package/dist/session-incarnation.d.ts +61 -0
- package/dist/session-incarnation.js +43 -0
- package/dist/session-policy-wire.d.ts +98 -12
- package/dist/session-policy-wire.js +71 -8
- package/dist/session-records-skipped-notice.d.ts +20 -0
- package/dist/session-records-skipped-notice.js +27 -0
- package/dist/tool-approval.d.ts +65 -0
- package/dist/tool-approval.js +36 -9
- package/dist/trace/core-keyset-guard.d.ts +8 -7
- package/dist/trace/engine-notice-wire.d.ts +3 -1
- package/dist/trace/engine-notice-wire.js +2 -0
- package/dist/trace/project.d.ts +11 -6
- package/dist/trace/project.js +12 -3
- package/dist/trace/redact.d.ts +7 -0
- package/dist/trace/redact.js +153 -61
- package/dist/trace/wire-projection-faces.d.ts +10 -3
- package/dist/trace/wire-projection-faces.js +8 -2
- package/package.json +2 -2
- package/dist/plugins/memory-session-policy-store.d.ts +0 -46
- package/dist/plugins/memory-session-policy-store.js +0 -52
package/dist/env-facts.d.ts
CHANGED
|
@@ -30,6 +30,14 @@ export interface SandboxEnvFacts {
|
|
|
30
30
|
note?: string;
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* 一条 scratchpad 路径能不能被**原样**宣告(core 的渲染上限 {@link MAX_PATH} 之内)—— scratchpad 的**铸造条件**之一。
|
|
35
|
+
* 两个铸点(server 自算 {@link ensureScratchpadDir}、壳供 {@link screenShellScratchpadDir})读这同一只:渲染不下的路径
|
|
36
|
+
* **根本不铸**(不建目录、不进写门豁免、不宣告 —— 宣告是根围栏那个洞的唯一写者,S-842,于是也不开洞)。
|
|
37
|
+
* 🔴 S-842 codex r1 [medium]:删 [ref] 折叠之后,只在宣告那一步丢长路径会留下「建了、豁免了、却不宣告也不开洞」的目录;
|
|
38
|
+
* 规则挪到铸点,四件同有同无。(远程车道的 `/tmp/scratchpad/<sessionId>` 恒在上限内:sessionId ≤ 64,见 prepareSpec。)
|
|
39
|
+
*/
|
|
40
|
+
export declare function scratchpadPathRenderable(path: string): boolean;
|
|
33
41
|
/** Compose envFacts from what the deployment knows at spec-prep. Returns undefined when nothing is known
|
|
34
42
|
* (no fact beats a vacuous one — an empty block would still spend prompt tokens). */
|
|
35
43
|
export declare function buildEnvFacts(opts: {
|
|
@@ -68,12 +76,14 @@ export declare function scratchpadDirFor(localDataRoot: string, sessionId: strin
|
|
|
68
76
|
* egress/pkgSource "don't declare what you don't know"). Lifecycle: the reaper primitives live below —
|
|
69
77
|
* {@link purgeScratchpadDir}(session delete)+ {@link sweepStaleScratchpads}(mtime 周期扫),keyed by the
|
|
70
78
|
* same scratchpadDirFor convention;接线(E21 DELETE purge + 周期 sweep)在 main.ts 侧(D 组)。 */
|
|
71
|
-
export declare function ensureScratchpadDir(localDataRoot: string, sessionId: string
|
|
79
|
+
export declare function ensureScratchpadDir(localDataRoot: string, sessionId: string,
|
|
80
|
+
/** 渲染不下(铸造条件不成立)时的那一行 —— 调用方交日志口(S-842 codex r1:不铸必须说出来,否则「本会话没有 scratchpad」无从排查)。 */
|
|
81
|
+
warn: (msg: string, meta?: object) => void): Promise<string | undefined>;
|
|
72
82
|
/** G3([ref] 壳侧承诺③/[ref]③,[ref]§四接单):**壳供** scratchpad 路径的验收门。
|
|
73
83
|
*
|
|
74
84
|
* 壳(cli)vendored getScratchpadDir 算出 per-session scratchpad 路径随 `TaskRequest.scratchpadDir` 发来;
|
|
75
|
-
* 接受后它**优先于** server 自算(ensureScratchpadDir),喂
|
|
76
|
-
* fs-write gate exemptDirs
|
|
85
|
+
* 接受后它**优先于** server 自算(ensureScratchpadDir),喂 resolve-spec 的同一单点(envFacts 宣告 ——
|
|
86
|
+
* core 据它开根围栏的洞,S-842 起是那个洞的唯一写者 —— + fs-write gate exemptDirs)。「没到货引擎忽略 = 无害 additive」。
|
|
77
87
|
*
|
|
78
88
|
* 信任边界:这是 exemptDirs **放宽**输入,验收 fail-closed(任何拒收形回落自算,不 fault 任务):
|
|
79
89
|
* - 多租户(requirePrincipal)一律拒——共享 worker 盘上租户自选豁免目录=超范围面;
|
package/dist/env-facts.js
CHANGED
|
@@ -5,6 +5,9 @@ import { dirCoversPath, isValidCwd, normalizeAbsolutePathLexically } from "./tas
|
|
|
5
5
|
const MAX_STR = 64;
|
|
6
6
|
const MAX_CAPS = 16;
|
|
7
7
|
const MAX_PATH = 160;
|
|
8
|
+
export function scratchpadPathRenderable(path) {
|
|
9
|
+
return path.length <= MAX_PATH;
|
|
10
|
+
}
|
|
8
11
|
const bound = (s) => (s.length > MAX_STR ? s.slice(0, MAX_STR) : s);
|
|
9
12
|
const bound2 = (s) => (s.length > MAX_PATH ? s.slice(0, MAX_PATH) : s);
|
|
10
13
|
export function buildEnvFacts(opts) {
|
|
@@ -17,7 +20,7 @@ export function buildEnvFacts(opts) {
|
|
|
17
20
|
...(caps.length > 0 ? { capabilities: caps } : {}),
|
|
18
21
|
...(opts.pkgSource ? { pkgSource: bound(opts.pkgSource) } : {}),
|
|
19
22
|
...(opts.egress ? { egress: opts.egress } : {}),
|
|
20
|
-
...(opts.scratchpadDir && opts.scratchpadDir
|
|
23
|
+
...(opts.scratchpadDir && scratchpadPathRenderable(opts.scratchpadDir) ? { scratchpadDir: opts.scratchpadDir } : {}),
|
|
21
24
|
...(opts.resumeFacts && Object.keys(opts.resumeFacts).length > 0
|
|
22
25
|
? { resumeFacts: { ...opts.resumeFacts, ...(opts.resumeFacts.note ? { note: bound2(opts.resumeFacts.note) } : {}) } }
|
|
23
26
|
: {}),
|
|
@@ -51,8 +54,17 @@ export function scratchpadSessionSegment(sessionId) {
|
|
|
51
54
|
export function scratchpadDirFor(localDataRoot, sessionId) {
|
|
52
55
|
return join(localDataRoot, "scratchpad", scratchpadSessionSegment(sessionId));
|
|
53
56
|
}
|
|
54
|
-
export async function ensureScratchpadDir(localDataRoot, sessionId) {
|
|
57
|
+
export async function ensureScratchpadDir(localDataRoot, sessionId, warn) {
|
|
55
58
|
const dir = scratchpadDirFor(localDataRoot, sessionId);
|
|
59
|
+
if (!scratchpadPathRenderable(dir)) {
|
|
60
|
+
warn("scratchpad_not_minted", {
|
|
61
|
+
reason: "too_long",
|
|
62
|
+
length: dir.length,
|
|
63
|
+
max: MAX_PATH,
|
|
64
|
+
note: "the session scratchpad path is longer than the engine renders verbatim, so it is not created, not exempted and not declared (a truncated path would be a fake one) — shorten LOCAL_DATA_ROOT to get a scratchpad back",
|
|
65
|
+
});
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
56
68
|
try {
|
|
57
69
|
await mkdir(dir, { recursive: true });
|
|
58
70
|
return dir;
|
|
@@ -89,7 +101,7 @@ export function screenShellScratchpadDir(raw, opts) {
|
|
|
89
101
|
opts.warn("shell_scratchpad_ignored", { reason: "not_absolute" });
|
|
90
102
|
return undefined;
|
|
91
103
|
}
|
|
92
|
-
if (raw
|
|
104
|
+
if (!scratchpadPathRenderable(raw)) {
|
|
93
105
|
opts.warn("shell_scratchpad_ignored", { reason: "too_long", length: raw.length, max: MAX_PATH });
|
|
94
106
|
return undefined;
|
|
95
107
|
}
|
|
@@ -70,6 +70,21 @@ export interface ExecutionLaneCaps {
|
|
|
70
70
|
* 静默继承一个没人表过态的缺席。
|
|
71
71
|
*/
|
|
72
72
|
readonly canonicalPathAuthority: LaneCapability;
|
|
73
|
+
/**
|
|
74
|
+
* 车PI 修复轮 ②(S-775 / clay C-R91A ②,主会话裁「按表不按词」;裁定语义 = **租户不共宿主**)—— 这条车道**分不分租户**。
|
|
75
|
+
* · `supported` = 每个租户的命令落在各自的目标上(每任务一只沙箱:e2b / k8s / local-docker;发起者自己的设备:device,
|
|
76
|
+
* 归属由 deviceId↔principal 绑定验过),或根本没有手(in-process:没有可共享的执行目标);
|
|
77
|
+
* · `single-user-only` = 所有任务落在**同一个共享目标**上(worker 宿主:host;一台常驻 SSH 对端:ssh;一台 ADB 设备:adb)
|
|
78
|
+
* ⇒ 只许单用户部署用。
|
|
79
|
+
* 本位**没有** `unsupported` 这一档(那会读成「谁都不许用」,不是车道事实)—— 型上收窄掉,每条车道必须在两档里表态。
|
|
80
|
+
* 🔴 **本位即执法源**(车PI 验修轮 F8,如实登记):唯一消费点是 `config-invariants.ts` 的 `multi-tenant-shared-lane`(经
|
|
81
|
+
* {@link laneCapabilityHolds} 落成布尔),能力表对账格里本位的「判别式」读的就是这张表 —— 自指,只证明消费点读的是本位,不证明本位
|
|
82
|
+
* 的值对。本位值的**独立作者** = 部署面 `deploy/sema-up/lib/lanes.json` 的 users 列(只有 host / e2b / k8s 三行)+ 行为格
|
|
83
|
+
* (`test/multi-tenant-shared-lane.test.ts`:ssh / adb 的工厂给两个租户的任务铸出的 env 报同一个 `workspaceHandle().sandboxId`)。
|
|
84
|
+
* local-docker / device / in-process 的 `supported` 今天没有独立预言机(e2b / local-docker 的句柄要连上才有,k8s 连前恒 `(unconnected)`,
|
|
85
|
+
* 都不是零成本格)—— 改这几行时按适配器事实自查。
|
|
86
|
+
*/
|
|
87
|
+
readonly tenantSeparation: Exclude<LaneCapability, "unsupported">;
|
|
73
88
|
}
|
|
74
89
|
/**
|
|
75
90
|
* per-lane 能力表。**穷举 switch,禁 default 臂**(hands-lane.ts:39-46 先例)。
|
|
@@ -20,6 +20,7 @@ export function executionLaneCaps(lane) {
|
|
|
20
20
|
hostFilePlane: YES,
|
|
21
21
|
coreRemoteExecutionEnv: NO,
|
|
22
22
|
canonicalPathAuthority: NO,
|
|
23
|
+
tenantSeparation: YES,
|
|
23
24
|
};
|
|
24
25
|
case "host":
|
|
25
26
|
return {
|
|
@@ -37,6 +38,7 @@ export function executionLaneCaps(lane) {
|
|
|
37
38
|
hostFilePlane: YES,
|
|
38
39
|
coreRemoteExecutionEnv: YES,
|
|
39
40
|
canonicalPathAuthority: YES,
|
|
41
|
+
tenantSeparation: SU,
|
|
40
42
|
};
|
|
41
43
|
case "e2b":
|
|
42
44
|
return {
|
|
@@ -54,6 +56,7 @@ export function executionLaneCaps(lane) {
|
|
|
54
56
|
hostFilePlane: NO,
|
|
55
57
|
coreRemoteExecutionEnv: YES,
|
|
56
58
|
canonicalPathAuthority: NO,
|
|
59
|
+
tenantSeparation: YES,
|
|
57
60
|
};
|
|
58
61
|
case "k8s":
|
|
59
62
|
return {
|
|
@@ -71,6 +74,7 @@ export function executionLaneCaps(lane) {
|
|
|
71
74
|
hostFilePlane: NO,
|
|
72
75
|
coreRemoteExecutionEnv: YES,
|
|
73
76
|
canonicalPathAuthority: YES,
|
|
77
|
+
tenantSeparation: YES,
|
|
74
78
|
};
|
|
75
79
|
case "ssh":
|
|
76
80
|
return {
|
|
@@ -88,6 +92,7 @@ export function executionLaneCaps(lane) {
|
|
|
88
92
|
hostFilePlane: NO,
|
|
89
93
|
coreRemoteExecutionEnv: YES,
|
|
90
94
|
canonicalPathAuthority: YES,
|
|
95
|
+
tenantSeparation: SU,
|
|
91
96
|
};
|
|
92
97
|
case "adb":
|
|
93
98
|
return {
|
|
@@ -105,6 +110,7 @@ export function executionLaneCaps(lane) {
|
|
|
105
110
|
hostFilePlane: NO,
|
|
106
111
|
coreRemoteExecutionEnv: YES,
|
|
107
112
|
canonicalPathAuthority: YES,
|
|
113
|
+
tenantSeparation: SU,
|
|
108
114
|
};
|
|
109
115
|
case "local-docker":
|
|
110
116
|
return {
|
|
@@ -122,6 +128,7 @@ export function executionLaneCaps(lane) {
|
|
|
122
128
|
hostFilePlane: NO,
|
|
123
129
|
coreRemoteExecutionEnv: YES,
|
|
124
130
|
canonicalPathAuthority: NO,
|
|
131
|
+
tenantSeparation: YES,
|
|
125
132
|
};
|
|
126
133
|
case "device":
|
|
127
134
|
return {
|
|
@@ -139,6 +146,7 @@ export function executionLaneCaps(lane) {
|
|
|
139
146
|
hostFilePlane: NO,
|
|
140
147
|
coreRemoteExecutionEnv: YES,
|
|
141
148
|
canonicalPathAuthority: YES,
|
|
149
|
+
tenantSeparation: YES,
|
|
142
150
|
};
|
|
143
151
|
}
|
|
144
152
|
}
|
|
@@ -1,35 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* S-589 —— 文件历史的**装配单点**与能力词 `fileHistoryCapture` 的闭集(叶模块:只 type-import store-backend)。
|
|
2
|
+
* S-589 → S-724 —— 文件历史的**装配单点**与能力词 `fileHistoryCapture` 的闭集(叶模块:只 type-import store-backend 与 core 型)。
|
|
3
3
|
*
|
|
4
|
-
* ##
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* `
|
|
8
|
-
*
|
|
4
|
+
* ## 病史
|
|
5
|
+
* S-589(7.97.0):`boot/budget-tracing.ts` 只要后端有文件历史店就无条件交给 core(`RunnerDeps.fileHistoryStore`)。core 的首次
|
|
6
|
+
* 触碰捕获当时是**进程级**的:接了店就恒捕获,没有任何单会话 / 单 run 的杠杆。于是用户在客户端关掉「文件检查点」关掉的只是
|
|
7
|
+
* 提示,文件字节照旧进 `file_history_*`。S-589 给了部署旋钮 `FILE_HISTORY_CAPTURE`,而 core 那时只有一种写法表达「不捕获」——
|
|
8
|
+
* **不接店** ⇒ 旋钮关 = 摘店,代价是已存历史也一起读不了(restore / rewind / 2c sync 一并关)。
|
|
9
|
+
* S-724(7.104.0):core 7.31.0 收了两件 —— 部署的**捕获位** `RunnerDeps.fileHistoryCapture: "off"`(店照接,只停新捕获,
|
|
10
|
+
* 已存历史照常服务 `restoreFiles` / `rewindFilesTo`)与请求级关断词 `TaskSpec.fileHistory: "disabled"`。旋钮改落 core 的同名座:
|
|
11
|
+
* **名字说的是「捕获」,就只关捕获** —— 一条旋钮一个意思。
|
|
9
12
|
*
|
|
10
|
-
* ##
|
|
11
|
-
* 「这台部署有没有文件历史店」只在 {@link assembleFileHistory}
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
13
|
+
* ## 两件事,两个单点
|
|
14
|
+
* · 「这台部署有没有文件历史店」只在 {@link assembleFileHistory} 判一次(后端在即在,**与旋钮无关**),产出两张脸:
|
|
15
|
+
* **店** `store` 喂 core 的 `RunnerDeps.fileHistoryStore`(还原族)、rewind 族能力位(`rewindFiles` / `restoreFiles` /
|
|
16
|
+
* `rewindFilesTo` / `sessionSync`)与 2c sync 路由族;**擦除脸** `erasure` = 店的**只有删除类动词**的收窄视图,喂 E21 在线删除
|
|
17
|
+
* (`boot/session-faces.ts`)与孤儿 blob GC(`boot/reapers.ts`)。旋钮从不管擦除:关掉捕获的部署照样要能删掉已捕获的文件字节。
|
|
18
|
+
* · 「捕不捕」只在 {@link fileHistoryCaptureOf} 判一次(纯函数,输入 = 店在不在 + 旋钮)。两个消费点喂它同一组输入:
|
|
19
|
+
* core 的部署捕获位({@link coreFileHistoryCaptureSeat},`boot/runner-deps.ts`)与能力词(`http/routes/capabilities.ts`)——
|
|
20
|
+
* core 收到的与能力面说的不可能各说各话。捕获位 `"off"` ⟺ 店在 ∧ 旋钮关;**没有店的部署形不下发**该位:core 对
|
|
21
|
+
* 「off 但没店」拒 prepare(`config.invalid_file_history_capture`,「nothing to switch off」),而旋钮的意思「不捕获」在没店的
|
|
22
|
+
* 部署上本来就成立 —— 旋钮照样无条件生效,只是那一形无事可关(选这一臂而不是拒启的理由见 CHANGELOG 7.104.0 S-724 条:
|
|
23
|
+
* 拒启会把「形合法、意思成立」的配置判成错,且会与 auto 后端的降级臂耦合)。
|
|
24
|
+
* ⚠️ 关掉 ≠ 删掉:翻旋钮**不**做任何批量删除;已捕获的历史照旧被 restore / rewind / sync 读用,按既有的会话删除 /
|
|
25
|
+
* 留存规则清理。要让已存历史也不可用,部署今天的出路只有逐会话 `DELETE /v1/sessions/:id` 与留存清扫(见 USAGE)。
|
|
21
26
|
*
|
|
22
|
-
* ##
|
|
23
|
-
* cli 要的是「`/config`
|
|
24
|
-
* (
|
|
25
|
-
* `on-
|
|
26
|
-
*
|
|
27
|
+
* ## 能力词为什么是 `off` / `on-optional` 两词
|
|
28
|
+
* cli 要的是「`/config` 那个开关该不该出现」。三词闭集的设计(S-589)是 `off` / `on-always` / `on-optional`;S-589 时第三词
|
|
29
|
+
* 结构上不可达(没有单 run 不捕获位)。S-724 起请求词 `fileHistory: "disabled"` 受理 ⇒ 捕获中的部署恒可按 run 关掉 ⇒
|
|
30
|
+
* `on-optional` 成真,而 `on-always`(「捕获中且关不掉」)**结构上不可达** —— 预先声明一个到不了的词等于给客户端一个
|
|
31
|
+
* 永远等不到的分支,所以删词不留(7.97.0–7.103.x 的 worker 仍会发它,消费端读法在契约附录 B)。
|
|
27
32
|
*/
|
|
33
|
+
import type { RunnerDeps } from "@sema-agent/core";
|
|
28
34
|
import type { ServiceFileHistoryStore, StoreBackend } from "./plugins/store-backend.js";
|
|
29
35
|
/** 能力词闭集的单点属主(`Record` 锁步:联合加词而这里不补 ⇒ 编译红;词表数组由它派生,不手抄)。 */
|
|
30
36
|
declare const FILE_HISTORY_CAPTURE_WORD_SET: {
|
|
31
37
|
readonly off: true;
|
|
32
|
-
readonly "on-
|
|
38
|
+
readonly "on-optional": true;
|
|
33
39
|
};
|
|
34
40
|
/** `GET /v1/capabilities` 的 `fileHistoryCapture` 词。 */
|
|
35
41
|
export type FileHistoryCapture = keyof typeof FILE_HISTORY_CAPTURE_WORD_SET;
|
|
@@ -41,25 +47,26 @@ export declare const FILE_HISTORY_CAPTURE_WORDS: readonly FileHistoryCapture[];
|
|
|
41
47
|
export type FileHistoryErasureFace = Pick<ServiceFileHistoryStore, "reap" | "deleteBySession" | "sweepOrphanBlobs">;
|
|
42
48
|
/** 装配结果:两张脸。 */
|
|
43
49
|
export interface FileHistoryAssembly {
|
|
44
|
-
/**
|
|
50
|
+
/** 店:在场 ⟺ 后端在(与旋钮无关)。 */
|
|
45
51
|
readonly store: ServiceFileHistoryStore | undefined;
|
|
46
|
-
/** 擦除脸:在场 ⟺ 后端在(
|
|
52
|
+
/** 擦除脸:在场 ⟺ 后端在(与旋钮无关;是 {@link store} 的删除类动词视图)。 */
|
|
47
53
|
readonly erasure: FileHistoryErasureFace | undefined;
|
|
48
54
|
}
|
|
55
|
+
/** 装配:「这台部署有没有文件历史店」的唯一判定。旋钮不在这里 —— 它只关捕获,不摘店(S-724)。 */
|
|
56
|
+
export declare function assembleFileHistory(backend: StoreBackend | undefined): FileHistoryAssembly;
|
|
49
57
|
/**
|
|
50
|
-
*
|
|
58
|
+
* 「捕不捕」的**唯一**判定(纯函数):能力词。`store` = 装配出来的店(在不在),`knob` = 部署旋钮 `FILE_HISTORY_CAPTURE`
|
|
59
|
+
* (`config.fileHistoryCapture`)。从**装配结果**派生,不照旋钮字面回声:后端本来就没有这只店的部署同样是 `off`。
|
|
51
60
|
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
61
|
+
* 与 `rewindFiles` 分叉(S-589 当时写下的分叉条件,自本版成真):本词答「这台部署捕不捕、能不能按 run 关掉」,
|
|
62
|
+
* `rewindFiles` 答「这台部署能不能还原」—— 旋钮关的部署本词 `off` 而 `rewindFiles` 仍为真。
|
|
54
63
|
*/
|
|
55
|
-
export declare function
|
|
64
|
+
export declare function fileHistoryCaptureOf(store: object | undefined, knob: boolean): FileHistoryCapture;
|
|
56
65
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* 将来有人把两位当成一回事):core 收了单会话不捕获位之后,本词在捕获脸在场时会变成 `on-optional`,而
|
|
61
|
-
* `rewindFiles` 仍是 true —— 它回答的是「这台部署能不能还原」,本词回答的是「捕获关不关得掉」。
|
|
66
|
+
* core 的部署捕获位 `RunnerDeps.fileHistoryCapture`,由 {@link fileHistoryCaptureOf} 派生(同一组输入、同一只判定)。
|
|
67
|
+
* 形照 `execution-lane-caps.ts` 的穷举 switch(无 default 臂):能力词加一个而这里不加臂 ⇒ 编译红 —— 加臂的那一刻就是
|
|
68
|
+
* 决定「这一词下 core 的捕获位怎么递」的地方。没有店 ⇒ 恒 `undefined`(core 对「off 但没店」拒 prepare)。
|
|
62
69
|
*/
|
|
63
|
-
export declare function
|
|
70
|
+
export declare function coreFileHistoryCaptureSeat(store: object | undefined, knob: boolean): RunnerDeps["fileHistoryCapture"];
|
|
64
71
|
export {};
|
|
65
72
|
//# sourceMappingURL=file-history-capture.d.ts.map
|
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
const FILE_HISTORY_CAPTURE_WORD_SET = {
|
|
2
2
|
off: true,
|
|
3
|
-
"on-
|
|
3
|
+
"on-optional": true,
|
|
4
4
|
};
|
|
5
5
|
export const FILE_HISTORY_CAPTURE_WORDS = Object.keys(FILE_HISTORY_CAPTURE_WORD_SET);
|
|
6
|
-
export function assembleFileHistory(backend
|
|
6
|
+
export function assembleFileHistory(backend) {
|
|
7
7
|
if (backend === undefined)
|
|
8
8
|
return { store: undefined, erasure: undefined };
|
|
9
9
|
const physical = backend.fileHistory();
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
return { store: physical, erasure: physical };
|
|
11
|
+
}
|
|
12
|
+
export function fileHistoryCaptureOf(store, knob) {
|
|
13
|
+
return store !== undefined && knob ? "on-optional" : "off";
|
|
14
|
+
}
|
|
15
|
+
export function coreFileHistoryCaptureSeat(store, knob) {
|
|
16
|
+
if (store === undefined)
|
|
17
|
+
return undefined;
|
|
18
|
+
const word = fileHistoryCaptureOf(store, knob);
|
|
19
|
+
switch (word) {
|
|
12
20
|
case "off":
|
|
13
|
-
return
|
|
14
|
-
case "on-
|
|
15
|
-
return
|
|
21
|
+
return "off";
|
|
22
|
+
case "on-optional":
|
|
23
|
+
return undefined;
|
|
16
24
|
}
|
|
17
25
|
}
|
|
18
|
-
export function fileHistoryCaptureOf(captureFace) {
|
|
19
|
-
return captureFace === undefined ? "off" : "on-always";
|
|
20
|
-
}
|
|
21
26
|
//# sourceMappingURL=file-history-capture.js.map
|
|
@@ -1,34 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* S-157④ —— SQL file-history 店的 **typed 永久失败**披露座:散文一个铸点 + 机器面一只 sink。
|
|
3
|
-
*
|
|
4
|
-
* ## 病(基线 v7.79.0 逐行亲核 + 亲跑)
|
|
5
|
-
* `plugins/blob-backend.ts:55` 的 `BlobTooLargeError` 带 typed 判别码 `blob_too_large_for_sql`,头注
|
|
6
|
-
* 原话「绝不能与 transient 写失败混在一类(那类是可重试的 502;这类重试永远不会成功)」。可它一旦被
|
|
7
|
-
* `plugins/file-history-store-sql.ts` 的任何一条 `catch` 折进 `FileHistoryResult.error.message`,判别码
|
|
8
|
-
* 就只剩散文,**机器一位都读不到**。两条腿各自的后果:
|
|
9
|
-
*
|
|
10
|
-
* · **snapshot(边界捕获)腿**:引擎把失败包成一句散文交给 `RunnerDeps.onError({phase:"rewind"})`
|
|
11
|
-
* (core 7.20.0 `dist/core/runner/prepare-file-history.js` 的 `wrapFileHistoryEntry`),服务那条臂从文本里抠**第一个**
|
|
12
|
-
* 括号词当 metric label —— 而第一个括号词恒是引擎自己的闭集码。实测:「这个文件永远存不进这口店」
|
|
13
|
-
* 与「连接被重置」在 `rewind_snapshot_failed_total` 上**同形** `code="snapshot_failed"`。
|
|
14
|
-
* · **trackEdit(首触备份)腿**:本服务用引擎默认的 `onTrackFailure:"refuse"`(`boot/runner-deps.ts` 亲读),
|
|
15
|
-
* 那条路 **完全不走 onError** —— 引擎把 `(track_failed: <本店 message>)` 念给模型,末句是
|
|
16
|
-
* 「Retry when the file-history store is healthy.」(`prepare-file-history.js`)。对这枚码那是永远
|
|
17
|
-
* 不会到来的一天,而**运维面一个计数都没有**:这条腿今天在遥测上完全不可见。
|
|
18
|
-
*
|
|
19
|
-
* ## 修法(源头留痕,不走文本通道)
|
|
20
|
-
* · **散文面**({@link discloseCaughtFailure} 的返回值):把 caught error 的 typed `code` 写成一个括号词
|
|
21
|
-
* 前置到它自己的 message 前 —— **不 `instanceof` 单挑任何一个类**(身上有字符串 `code` 就折),该店
|
|
22
|
-
* 11 处折点全部改走这一只函数。人与模型因此看得到「这是哪一类失败」。
|
|
23
|
-
* · **机器面**(同一只函数的副作用):码取自 typed 错**对象**、对闭集词表判定,**绝不从散文里抠**。
|
|
24
|
-
* 🔴 这一条是 codex R1-[high] 验真后的定形:文本通道**天然可伪造** —— 一个名叫
|
|
25
|
-
* `(blob_too_large_for_sql).txt` 的文件读不出来时,它的路径就在 `snapshot_failed` 的散文里,任何
|
|
26
|
-
* 「从散文里取在册括号词」的读法都会把它当成一枚永久码(继而抑制掉真故障的告警)。闭集只约束基数,
|
|
27
|
-
* 不能证明来源。所以机读判定搬回**唯一知道真相的那一层**=铸错的地方,`rewind` 臂的读法一字不动。
|
|
28
|
-
* · 留痕三件套与 `observability/fail-open.ts` / `observability/secret-env-scrub.ts` 同律:**计数逐次**
|
|
29
|
-
* (`file_history_permanent_failure_total{code}`)、**每进程每码一条 warn**(第二条起零新增内容,而
|
|
30
|
-
* 20G 树的用户每 turn 撞一次)、装配前累计的次数在 install 时补进 metrics。
|
|
31
|
-
*/
|
|
32
1
|
import type { Logger } from "./observability/logger.js";
|
|
33
2
|
import type { Metrics } from "./observability/metrics.js";
|
|
34
3
|
interface Sink {
|
|
@@ -49,5 +18,12 @@ export declare function installFileHistoryFailureRecorder(deps: Sink): void;
|
|
|
49
18
|
export declare function fileHistoryPermanentFailureCounts(): ReadonlyMap<string, number>;
|
|
50
19
|
/** 测试用重置(进程级状态在同一个 vitest worker 里跨用例存活)。 */
|
|
51
20
|
export declare function resetFileHistoryFailureRecorderForTest(deps?: Sink): void;
|
|
21
|
+
/** core `FileHistoryError.code` 闭集的值投影(两道差集钉在下面;`satisfies` 让它进 core 读表的 `literals` 段 ——
|
|
22
|
+
* core 退役其中一词时,提货前的求交门就能点名这张表)。 */
|
|
23
|
+
declare const FILE_HISTORY_ERROR_CODE_WORDS: readonly ["track_failed", "snapshot_failed", "restore_failed", "adopt_failed", "not_found", "aborted", "unsupported_format", "conflict"];
|
|
24
|
+
/** rewind 粗码臂的码词 = core 的闭集。 */
|
|
25
|
+
export type FileHistoryErrorCode = (typeof FILE_HISTORY_ERROR_CODE_WORDS)[number];
|
|
26
|
+
/** rewind 臂一次失败的粗码:文本里**第一个**在册的 `(码)` / `(码:`;没有 ⇒ `unknown`。纯函数、不抛。 */
|
|
27
|
+
export declare function rewindFailureCode(err: unknown): FileHistoryErrorCode | "unknown";
|
|
52
28
|
export {};
|
|
53
29
|
//# sourceMappingURL=file-history-disclosure.d.ts.map
|
|
@@ -86,4 +86,16 @@ export function resetFileHistoryFailureRecorderForTest(deps = {}) {
|
|
|
86
86
|
sink = { ...deps };
|
|
87
87
|
deps.metrics?.counter(PERMANENT_FAILURE_COUNTER, PERMANENT_FAILURE_HELP);
|
|
88
88
|
}
|
|
89
|
+
const FILE_HISTORY_ERROR_CODE_WORDS = ["track_failed", "snapshot_failed", "restore_failed", "adopt_failed", "not_found", "aborted", "unsupported_format", "conflict"];
|
|
90
|
+
const FILE_HISTORY_ERROR_CODE_SET = new Set(FILE_HISTORY_ERROR_CODE_WORDS);
|
|
91
|
+
const isFileHistoryErrorCode = (w) => FILE_HISTORY_ERROR_CODE_SET.has(w);
|
|
92
|
+
export function rewindFailureCode(err) {
|
|
93
|
+
const text = err instanceof Error ? err.message : String(err);
|
|
94
|
+
for (const m of text.matchAll(/\(([a-z_]+)[):]/g)) {
|
|
95
|
+
const word = m[1];
|
|
96
|
+
if (isFileHistoryErrorCode(word))
|
|
97
|
+
return word;
|
|
98
|
+
}
|
|
99
|
+
return "unknown";
|
|
100
|
+
}
|
|
89
101
|
//# sourceMappingURL=file-history-disclosure.js.map
|
|
@@ -54,7 +54,11 @@ export interface FleetTaskRow {
|
|
|
54
54
|
* 🔴 与 `name` 分域:`name` 是**显示名**(源 `description ?? name`,模型自由文本);本键是**身份键**,
|
|
55
55
|
* 消费端按串相等把「@某个名字」绑到这一行(同类型两个子代在 `agentType` 上不可分,在本键上可分)。
|
|
56
56
|
* ADDITIVE / tolerate-absent:**缺席 = 一次匿名委派**(不是「不知道它叫什么」)—— 7.25.0 之前本键在本仓
|
|
57
|
-
*
|
|
57
|
+
* 没有任何源,恒缺席。**VERBATIM,不过 `redactSecrets`**(S-546 B6①:本注此前写「与 `name` 同一道边界,emit 前
|
|
58
|
+
* redact」,而三处铸点从本键到货起就是原样投 —— 见本文件 spawn / tick / terminal 三臂的行注与
|
|
59
|
+
* `trace/core-keyset-guard.ts` 的 `spawnName` 记账):它是**身份键**,消费端按串相等绑行,脱敏会把两个合法队友名
|
|
60
|
+
* 改写成同一串;出生点校验过的字符集里也没有可脱敏的形。残余(已披露)= 有人把真凭据当队友名写进
|
|
61
|
+
* `Agent({name})`,那一串原样上 fleet 面。展示名 `name` 照旧脱敏。 */
|
|
58
62
|
agentName?: string;
|
|
59
63
|
/** Owning principal (= the run's scope). The `/v1/fleet/stream` route filters to the caller's scope (owner-gated)
|
|
60
64
|
* unless the caller is fleet-wide (operator / trace-token). NOT rendered — a tenant-isolation field. */
|
package/dist/http/admission.d.ts
CHANGED
|
@@ -29,11 +29,18 @@ export declare const MAX_OUTPUT_SCHEMA_CHARS = 32768;
|
|
|
29
29
|
* · `skill.description must be a string of at most 1024 characters`(描述长度帽);
|
|
30
30
|
* · `skill.name must be a non-empty string of at most 64 characters` 里的 **长度**那一半。
|
|
31
31
|
*
|
|
32
|
-
* 退役的判据是**上游两侧都没有同形门**:CC
|
|
33
|
-
*
|
|
34
|
-
*
|
|
32
|
+
* 退役的判据是**上游两侧都没有同形门**:CC 的技能清单渲染**从不**因为条数多或描述长而拒掉一条技能 —— 超预算时每一条
|
|
33
|
+
* 只有两种结局:全文,或退成 name-only(`- name`);bundled 技能恒为全文,其余按优先级在剩余预算内贪心升回全文
|
|
34
|
+
* (cli280.js:217940-217942 `JBt`;预算 = `max(1, floor((窗口 ?? 200000) × bytesPerToken(缺省 4)× fraction))`,
|
|
35
|
+
* env `SLASH_COMMAND_TOOL_CHAR_BUDGET` 优先,cli280.js:215920 `nUe`)。单条描述超过 `skillListingMaxDescChars`(默认 1536)
|
|
36
|
+
* 截成前 1535 字符加「…」,这一截与预算无关、恒生效(cli280.js:217940 `NFr`)。旧引的 `cli259.js:23195` 是 `skillListingBudgetFraction` 的 settings describe 文案,
|
|
37
|
+
* 不是算法本体。CC 的技能名校验(2.1.259 `Ir()` / 2.1.280 `Rs()`,cli280.js:236381;校验的是 Agent SDK `skills` 选项里的
|
|
38
|
+
* 技能名)判非空、无未配对代理、无首尾空白、无括号逗号控制字符、不是 `'*'`、无通配后缀、不以 `/` 开头、无连续反斜杠、
|
|
39
|
+
* 不以反斜杠结尾 —— **没有长度这一条**;CC 的名字长度界在别的面上且都不是 64:forked skill 作用域持久化 256
|
|
40
|
+
* (cli280.js:214671 `BV`)、transcript 载入(resume)时 `skill_listing.names` 每条 ≤ 512 / 至多 4096 条,不合格整条附件丢弃
|
|
41
|
+
* (cli280.js:217865)。
|
|
35
42
|
* 引擎侧同理:唯一整拒的是 1 MiB 的单技能加载门。于是那三条帽是「装了 11 个技能就 400」这种
|
|
36
|
-
* 纯本仓面的伤——收益是零(
|
|
43
|
+
* 纯本仓面的伤——收益是零(预算由渲染侧兜),代价是用户装不进自己的技能库。
|
|
37
44
|
*
|
|
38
45
|
* **补偿**(三问的第三问):`settings.skillListingBudgetFraction` / `settings.skillListingMaxDescChars`
|
|
39
46
|
* 两个旋钮直达引擎的渲染预算(per-turn 成本的真属主)。
|
|
@@ -47,7 +54,7 @@ export declare const MAX_OUTPUT_SCHEMA_CHARS = 32768;
|
|
|
47
54
|
export declare function validateUserSkills(skills: unknown): string | null;
|
|
48
55
|
export declare function createAdmission(ctx: {
|
|
49
56
|
deps: FlatServiceDeps;
|
|
50
|
-
runnerFor:
|
|
57
|
+
runnerFor: FlatServiceDeps["runnerFor"];
|
|
51
58
|
} & Pick<RouteHelpers, "readJson">): {
|
|
52
59
|
prepareSpec: (req: IncomingMessage, res: ServerResponse, bodyIn?: TaskRequestBody, onTypedFailure?: (err: HttpError) => void, legFacts?: {
|
|
53
60
|
liveStream?: boolean;
|
package/dist/http/admission.js
CHANGED
|
@@ -4,7 +4,7 @@ import { modelUnavailableMessage } from "../model-entry-refusal.js";
|
|
|
4
4
|
import { HttpError } from "../security.js";
|
|
5
5
|
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_PERMISSION_RULES, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS, SETTINGS_PERMISSION_MODE_WORDS, isSettingsPermissionMode, parseTaskSettings, skillListingBudgetFractionOk, skillListingMaxDescOk, taskSettingsKeyIssue } from "../task-settings.js";
|
|
6
6
|
import { taskBodyKeyIssue } from "../request-key-closure.js";
|
|
7
|
-
import { singleMemberOffFieldIssue } from "../request-value-closure.js";
|
|
7
|
+
import { FILE_HISTORY_DISABLED_NO_RESTORE, fileHistoryRestoreConflict, singleMemberOffFieldIssue } from "../request-value-closure.js";
|
|
8
8
|
import { selfOrchestrationDenial } from "../task-workflow.js";
|
|
9
9
|
import { centerEntitlementSourceWired } from "../runtime-caps-resolver.js";
|
|
10
10
|
import { parseHooksConfig } from "../hooks/hook-runner.js";
|
|
@@ -333,6 +333,10 @@ export function createAdmission(ctx) {
|
|
|
333
333
|
return null;
|
|
334
334
|
}
|
|
335
335
|
}
|
|
336
|
+
if (fileHistoryRestoreConflict(body)) {
|
|
337
|
+
sendError(res, 400, "request.field_conflict", FILE_HISTORY_DISABLED_NO_RESTORE);
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
336
340
|
if (body.requireExistingSession !== undefined && typeof body.requireExistingSession !== "boolean") {
|
|
337
341
|
sendError(res, 400, "request.field_invalid", "requireExistingSession must be a boolean");
|
|
338
342
|
return null;
|
package/dist/http/dispatch.d.ts
CHANGED
|
@@ -22,6 +22,24 @@ import type { FlatServiceDeps } from "./server.js";
|
|
|
22
22
|
* 凭证门、[ref] 的 parked 赎回席、以及 [ref] 的 consolidation 阀门启动期告警读同一句(手抄迟早
|
|
23
23
|
* 只改一处,而这条判据决定的是「无凭证部署上写口开不开」)。导出仅为最后一个消费点(main.ts)。 */
|
|
24
24
|
export declare function hasServiceAuth(config: ServiceConfig): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* 🔴 S-672(test T-72 [ref];主会话裁 ①,分诊 [ref])—— 全局服务凭据门**之前**那几族只读路
|
|
27
|
+
* (`route-table.ts` 里 `position: "read-credential"` 的行)的**唯一**鉴权谓词:「服务凭据 ∨ 已验 principal」。
|
|
28
|
+
*
|
|
29
|
+
* 病:这几族(trace-usage / workflows / fleet)排在全局门之前,理由只有一条 —— 只读 `TRACE_TOKEN` 单凭据的调用方
|
|
30
|
+
* (registry 用量 / 轨迹中继)在全局门会被 401。可排在门前之后,每一族只剩**块内**自己那道门:fleet / workflows
|
|
31
|
+
* 只在 `REQUIRE_PRINCIPAL` 时要身份,trace 认 `gatedPrincipal`(BFF 形上就是调用方自己写的 principal 头)。于是
|
|
32
|
+
* 配了 service token、没开 REQUIRE_PRINCIPAL 的部署上,`POST /v1/tasks` 吃凭据,这三族读路对端口全开。
|
|
33
|
+
*
|
|
34
|
+
* 规则(一条,三族同读;fail-closed):
|
|
35
|
+
* · 部署**没配**任何 service 凭据(`hasServiceAuth` 假)⇒ 放行 —— 与全局门对 `/v1/tasks` 同判(run-local 形);
|
|
36
|
+
* · 否则要么出示了**服务凭据**(service token 单 / 多,或只读 `TRACE_TOKEN` —— 后者正是这几族在门前的理由),
|
|
37
|
+
* 要么带着**验过的** principal(registry 登录令牌 / direct-door 签名 principal 令牌;判据 = `verifiedIdentitySource`,
|
|
38
|
+
* 与 off-box operator 读面同一只)。调用方自己写的 principal 头不是凭据。
|
|
39
|
+
* 过了本门,各族块内的门(属主 scope / fleet-wide 上限 / `REQUIRE_PRINCIPAL` 的身份门)照旧 —— 本谓词只回答
|
|
40
|
+
* 「这个请求带没带这台部署认的凭据」,不回答「它能看谁的行」。
|
|
41
|
+
*/
|
|
42
|
+
export declare function readCredentialOk(req: IncomingMessage, config: ServiceConfig): boolean;
|
|
25
43
|
export declare function createDispatch(ctx: {
|
|
26
44
|
deps: FlatServiceDeps;
|
|
27
45
|
noteActivity: (url: string) => void;
|
package/dist/http/dispatch.js
CHANGED
|
@@ -3,14 +3,21 @@ import { setSsoPrincipal, ssoVerifiedPrincipal, setSsoScope, isDestructiveSessio
|
|
|
3
3
|
import { publicStoreProbeError } from "../store-live-probe.js";
|
|
4
4
|
import { looksLikeJwt } from "@sema-agent/settings-schema/api/auth-bridge";
|
|
5
5
|
import { ROUTE_METHOD_ORDER } from "./route-ctx.js";
|
|
6
|
-
import {
|
|
6
|
+
import { READ_CREDENTIAL_ROUTES, PUBLIC_ROUTES, POST_CREDENTIAL_ROUTES, routeLabel, isBillableSubmitPath, isCredentialGatedRewrite, isMethodDispatchedSubmitPath, lookupRoute, isProbePath } from "./route-table.js";
|
|
7
7
|
import { sendJson, sendError, isSseResponse } from "./send.js";
|
|
8
8
|
import { readinessOf, healthReadinessKeys, sendReadyz, billableRefusalOf, sendBillableRefusal } from "./readiness.js";
|
|
9
|
-
import { authorized, systemFor, gatedPrincipal, sendPrincipalRefusal } from "./principal-gate.js";
|
|
9
|
+
import { authorized, systemFor, gatedPrincipal, sendPrincipalRefusal, sendUnauthorized, verifiedIdentitySource } from "./principal-gate.js";
|
|
10
10
|
const PROCESS_STARTED_AT_MS = Date.now() - Math.round(process.uptime() * 1000);
|
|
11
11
|
export function hasServiceAuth(config) {
|
|
12
12
|
return Boolean(config.authToken) || Object.keys(config.authTokens ?? {}).length > 0;
|
|
13
13
|
}
|
|
14
|
+
export function readCredentialOk(req, config) {
|
|
15
|
+
if (!hasServiceAuth(config))
|
|
16
|
+
return true;
|
|
17
|
+
if (verifiedIdentitySource(req, config) !== undefined)
|
|
18
|
+
return true;
|
|
19
|
+
return config.traceToken !== undefined && config.traceToken !== "" && authorized(req, config.traceToken);
|
|
20
|
+
}
|
|
14
21
|
function cors(res, req, origins, principalHeader) {
|
|
15
22
|
if (!origins || origins.length === 0)
|
|
16
23
|
return;
|
|
@@ -44,7 +51,7 @@ export function createDispatch(ctx) {
|
|
|
44
51
|
};
|
|
45
52
|
deps.drainState.lastAttachEndedAt = () => lastAttachEndedAt;
|
|
46
53
|
}
|
|
47
|
-
async function dispatchSegment(rows, req, res, url, search, method, allow, ctx) {
|
|
54
|
+
async function dispatchSegment(rows, req, res, url, search, method, allow, ctx, admit) {
|
|
48
55
|
for (const row of rows) {
|
|
49
56
|
if (row.enabled !== undefined && !row.enabled(deps))
|
|
50
57
|
continue;
|
|
@@ -54,6 +61,10 @@ export function createDispatch(ctx) {
|
|
|
54
61
|
allow.push(m);
|
|
55
62
|
if (found.decl === null)
|
|
56
63
|
continue;
|
|
64
|
+
if (admit !== undefined && !admit(req)) {
|
|
65
|
+
sendUnauthorized(res, "credential");
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
57
68
|
await row.handler(req, res, { decl: found.decl, params: found.params, search }, ctx);
|
|
58
69
|
return true;
|
|
59
70
|
}
|
|
@@ -175,7 +186,7 @@ export function createDispatch(ctx) {
|
|
|
175
186
|
const devOpen = !anyT && !mt && !deps.config.requirePrincipal;
|
|
176
187
|
const ok = devOpen || (anyT && systemFor(req, deps.config) !== undefined) || (!!mt && authorized(req, mt));
|
|
177
188
|
if (!ok) {
|
|
178
|
-
|
|
189
|
+
sendUnauthorized(res, "operator");
|
|
179
190
|
return;
|
|
180
191
|
}
|
|
181
192
|
if (url === "/metrics/summary") {
|
|
@@ -204,7 +215,9 @@ export function createDispatch(ctx) {
|
|
|
204
215
|
}
|
|
205
216
|
}
|
|
206
217
|
}
|
|
207
|
-
if (await dispatchSegment(
|
|
218
|
+
if (await dispatchSegment(READ_CREDENTIAL_ROUTES, req, res, url, search, method, allow.pre, ctx, (r) => readCredentialOk(r, deps.config)))
|
|
219
|
+
return;
|
|
220
|
+
if (await dispatchSegment(PUBLIC_ROUTES, req, res, url, search, method, allow.pre, ctx))
|
|
208
221
|
return;
|
|
209
222
|
let source = null;
|
|
210
223
|
const anyServiceAuth = hasServiceAuth(deps.config);
|
|
@@ -215,7 +228,7 @@ export function createDispatch(ctx) {
|
|
|
215
228
|
source = "sso";
|
|
216
229
|
}
|
|
217
230
|
else {
|
|
218
|
-
|
|
231
|
+
sendUnauthorized(res, "credential");
|
|
219
232
|
return;
|
|
220
233
|
}
|
|
221
234
|
}
|
|
@@ -44,6 +44,28 @@ export declare function principalTokenPresentedButUnverified(req: IncomingMessag
|
|
|
44
44
|
* 调用链上的**证明三件套**,与本门(身份面)是两道门。
|
|
45
45
|
*/
|
|
46
46
|
export declare function sendPrincipalRefusal(res: ServerResponse, req: IncomingMessage, config: ServiceConfig): void;
|
|
47
|
+
/**
|
|
48
|
+
* 🔴 **`401 auth.unauthorized` 的唯一铸点**(S-850,7.104.0;test [ref] QA 7a C15)。
|
|
49
|
+
*
|
|
50
|
+
* 病:十六处路由(9 个文件)各自手抄同一个 401 —— 码 `auth.unauthorized`、message 就一个词 `unauthorized`。照 sema-up 部署、壳接上
|
|
51
|
+
* 引擎后,第一句就撞上它,不说要哪份凭据、放哪、去哪拿。
|
|
52
|
+
*
|
|
53
|
+
* 规则:码不变;message 按**这道门收什么**分三面(闭集 {@link UnauthorizedFace},穷举 switch —— 加一面不接句子 = 编译红),
|
|
54
|
+
* 每面一句模块级常量(冻结门按常量解引用逐字钉)。三句都**不含任何值**(令牌 / 头名 / 路径实参一概不回显),且**不区分**
|
|
55
|
+
* 「没带」与「带错」—— 同一道门上两者逐字同体,区分就是探测面。
|
|
56
|
+
* · `credential` —— 全局凭据门(`dispatch.ts`):部署配了 service token,请求没带或带的不认(registry 登录令牌验不过同属此面);
|
|
57
|
+
* · `operator` —— 只给运维凭据开的口(`/metrics*` 与 `GET /v1/tasks/source-summary`):service token 开得了,指标口另认只读的
|
|
58
|
+
* `METRICS_TOKEN`(它在全局门之前判);
|
|
59
|
+
* · `identity` —— 过了凭据门(或这台部署没配 service token)、但这一口要一个**验过的调用方身份**:属主寻址的读 / 写面、规则面、
|
|
60
|
+
* 记忆面。在配了 service token 的部署上,「带 service token 却没身份」只会落在只认身份的口(规则 / 记忆 / 会话策略)上 ——
|
|
61
|
+
* 这时再叫调用方「去带 service token」是假出路,所以这一面的句子只讲身份。
|
|
62
|
+
* `auth.principal_required` / `principal_unverified`(上面 {@link sendPrincipalRefusal})是另一只码、另一只铸点,不在此列。
|
|
63
|
+
*/
|
|
64
|
+
export type UnauthorizedFace = "credential" | "operator" | "identity";
|
|
65
|
+
export declare const UNAUTHORIZED_CREDENTIAL = "unauthorized \u2014 the request carries no credential this deployment accepts: send the deployment's service token as a Bearer token in the Authorization header (its operator set it as SERVICE_AUTH_TOKEN or SERVICE_AUTH_TOKENS; a sema-up deployment keeps SERVICE_AUTH_TOKEN in ~/.sema-up/<project>/.env on the machine that ran sema-up)";
|
|
66
|
+
export declare const UNAUTHORIZED_OPERATOR = "unauthorized \u2014 this endpoint is for the deployment's operators and the request carries no operator credential: send the deployment's service token as a Bearer token in the Authorization header (the metrics endpoints also accept the read-only METRICS_TOKEN)";
|
|
67
|
+
export declare const UNAUTHORIZED_IDENTITY = "unauthorized \u2014 this endpoint acts for a named caller and the request carries no caller identity this deployment verifies: identify the caller the way this deployment does (a registry sign-in token as the Bearer token, a signed principal token on a direct-door deployment, or the principal header set by a trusted front end)";
|
|
68
|
+
export declare function sendUnauthorized(res: ServerResponse, face: UnauthorizedFace): void;
|
|
47
69
|
/**
|
|
48
70
|
* 🔴 **「这个调用方读得到这一行 run 吗」的唯一谓词**(S-297 抽取;此前只活在 `server.ts runOwnerOk` 的
|
|
49
71
|
* 函数体里,而 `runOwnerOk` 把**判断**与**404 怎么发**焊在一起 —— 于是任何需要同一条判断但不想 404 的
|
|
@@ -65,6 +65,26 @@ export function sendPrincipalRefusal(res, req, config) {
|
|
|
65
65
|
}
|
|
66
66
|
sendError(res, 401, "auth.principal_required", `missing principal header '${config.principalHeader}'`);
|
|
67
67
|
}
|
|
68
|
+
export const UNAUTHORIZED_CREDENTIAL = "unauthorized — the request carries no credential this deployment accepts: send the deployment's service token as a Bearer token in the Authorization header (its operator set it as SERVICE_AUTH_TOKEN or SERVICE_AUTH_TOKENS; a sema-up deployment keeps SERVICE_AUTH_TOKEN in ~/.sema-up/<project>/.env on the machine that ran sema-up)";
|
|
69
|
+
export const UNAUTHORIZED_OPERATOR = "unauthorized — this endpoint is for the deployment's operators and the request carries no operator credential: send the deployment's service token as a Bearer token in the Authorization header (the metrics endpoints also accept the read-only METRICS_TOKEN)";
|
|
70
|
+
export const UNAUTHORIZED_IDENTITY = "unauthorized — this endpoint acts for a named caller and the request carries no caller identity this deployment verifies: identify the caller the way this deployment does (a registry sign-in token as the Bearer token, a signed principal token on a direct-door deployment, or the principal header set by a trusted front end)";
|
|
71
|
+
export function sendUnauthorized(res, face) {
|
|
72
|
+
switch (face) {
|
|
73
|
+
case "credential":
|
|
74
|
+
sendError(res, 401, "auth.unauthorized", UNAUTHORIZED_CREDENTIAL);
|
|
75
|
+
return;
|
|
76
|
+
case "operator":
|
|
77
|
+
sendError(res, 401, "auth.unauthorized", UNAUTHORIZED_OPERATOR);
|
|
78
|
+
return;
|
|
79
|
+
case "identity":
|
|
80
|
+
sendError(res, 401, "auth.unauthorized", UNAUTHORIZED_IDENTITY);
|
|
81
|
+
return;
|
|
82
|
+
default: {
|
|
83
|
+
const unknownFace = face;
|
|
84
|
+
throw new Error(`sendUnauthorized: unknown face ${String(unknownFace)}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
68
88
|
export function runOwnerReadable(principal, owner, config) {
|
|
69
89
|
if (owner === null)
|
|
70
90
|
return true;
|
|
@@ -24,7 +24,7 @@ import type { createRequestGuards } from "./request-guards.js";
|
|
|
24
24
|
import type { FlatServiceDeps } from "./server.js";
|
|
25
25
|
export declare function createResumeLegs(ctx: {
|
|
26
26
|
deps: FlatServiceDeps;
|
|
27
|
-
runnerFor:
|
|
27
|
+
runnerFor: FlatServiceDeps["runnerFor"];
|
|
28
28
|
} & RunRegistry & RouteHelpers & Pick<RouteLegs, "prepareSpec" | "finalizeTaskResult"> & Pick<ReturnType<typeof createRequestGuards>, "ownerCostQuotaDenied" | "ownerFleetLeaseDenied" | "usageWindowDenialFor">): {
|
|
29
29
|
routeCtxBase: RouteCtxBase;
|
|
30
30
|
resumeCheckpoint: (sessionId: string, decision: "approve" | "deny", reason: string | undefined, hostDecision: HostDecision, req?: IncomingMessage, answer?: QuestionAnswer, binding?: DecideBinding, onResumeCommitted?: (grant: {
|