@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/security.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { type SessionStore, type SessionTreeEntry } from "@sema-agent/core";
|
|
|
3
3
|
import type { ServiceConfig } from "./config-types.js";
|
|
4
4
|
import type { TaskRequestBody } from "./http/wire-types.js";
|
|
5
5
|
import type { StagingHandle } from "./session-sync-kernel.js";
|
|
6
|
+
import type { SessionFaceReader } from "./session-incarnation.js";
|
|
6
7
|
/** One row of the session-list projection (CC /resume picker) — the durable session abstraction's enumeration
|
|
7
8
|
* output (§0.5). Mirrors {@link RunStore}'s shipped `SessionSummary` shape so the wire response is unchanged. */
|
|
8
9
|
export interface SessionListItem {
|
|
@@ -16,7 +17,7 @@ export interface SessionListItem {
|
|
|
16
17
|
/** K-5c (shell §K): the latest run's task_id — the shell's one-hop live-tail re-attach anchor on resume. */
|
|
17
18
|
lastRunId: string | null;
|
|
18
19
|
/** [ref]([ref]/[ref]):the LATEST run's **engine run id**(`TaskResult.runId`)when known —— OMIT-when-absent,
|
|
19
|
-
* 恒不铸 null。runs-ledger listers 从 latest run
|
|
20
|
+
* 恒不铸 null。runs-ledger listers 从 latest run 的 `result` JSON 提取(终局与 park 时写、认领续跑即清空 —— S-173 ⇒ 在跑 latest 键诚实缺席);
|
|
20
21
|
* local lister 走 `noteTaskRun` 席实时记录(⇒ 在跑也在场)——两面缺席集合不完全相等,读法一致:
|
|
21
22
|
* 缺席 = 「此行无已知 engine runId」,禁读成其它含义。⚠️ 与 `lastRunId`(= latest run 的 **task_id**)
|
|
22
23
|
* 是两条身份轴,命名对齐 core `SessionStoreSummary.lastTaskRunId`。wire:additive 键零投影透传
|
|
@@ -94,6 +95,9 @@ export declare function legacyUnownedSessionOk(requirePrincipal: boolean, gateOw
|
|
|
94
95
|
export declare function legacyUnownedAmnestyOpen(requirePrincipal: boolean, gateOwner: string | null): boolean;
|
|
95
96
|
export type OwnerAwareSessionStore = SessionStore & {
|
|
96
97
|
ownerOf?: (sessionId: string) => Promise<string | null | undefined>;
|
|
98
|
+
/** S-790 / S-506([ref] §2)—— 属主 + **化身**一次读(`undefined` = 无此会话;行在而化身为空 ⇒ 响亮拒)。规则的唯一属主
|
|
99
|
+
* 是 `src/session-incarnation.ts`。只关心属主的消费点照旧读 `ownerOf`;判「这还是不是同一位住客」的读它。 */
|
|
100
|
+
sessionFaceOf?: SessionFaceReader;
|
|
97
101
|
register?: (sessionId: string, owner: string | null) => Promise<void>;
|
|
98
102
|
/** E16 — enumerate the caller's sessions (owner-filtered on the session store's OWN owner column).
|
|
99
103
|
* `includeUnowned` = [ref] 窄互认第二臂的**第三条件**(`owner IS NULL`)的 SQL 承载位:置真时
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S-790 件B([ref] §3)—— **删会话带走以它为根的后台子代**:一只会话被删,以它为根的子代跟着结束 —— parked 的作废
|
|
3
|
+
* (行转终态,审批卡随之离开待决面),在跑的停掉。**这件事做完,才删会话元数据**(调用方:`boot/session-faces.ts` 的删除协调器)。
|
|
4
|
+
*
|
|
5
|
+
* ## 两步、两种失败,类型上分开(合并复审 gpt-6 腿 [high] 之后的形,裁定 2026-09-28)
|
|
6
|
+
* 1. {@link probeSessionChildren} —— **可达性预检,零写**。在协调器的任何破坏性步骤**之前**跑:有本进程结束不了的子代(在别的副本上
|
|
7
|
+
* 跑的 running 行:本进程没有它的句柄、耐久行的写者不是本进程 —— 本进程 `stopTask` 对它只答 `not_local`)⇒
|
|
8
|
+
* `{ reachable: false, unreachableHandles }`,协调器立刻答 409 `conflict.session_children_alive`,一行不删。409 的语义因此只有一句:**拒在删之前**。
|
|
9
|
+
* (修前的形是「先删账本、再结束子代、结束不了再答 409」—— 409 说「什么都没删」,而 run / event 账本已经提交删除了。)
|
|
10
|
+
* 2. {@link retireSessionChildren} —— 结束子代(预检之后、账本删除之后)。停不净的只可能是**时序**失败(本进程发出的停止还没落盘 /
|
|
11
|
+
* 一只在预检与停止之间冒出来的子代),不是可达性 ⇒ `{ stopped: "incomplete", remainingHandles }`,协调器按既有腿的失败契约**抛**
|
|
12
|
+
* {@link SessionChildrenStopIncompleteError}(路由通用 500),不删 session_meta,幂等重试收敛。
|
|
13
|
+
* 「一个 `remaining > 0` 同时表示两件事」的双关由此消失:预检的失败是 409,结束的失败是 500,两只函数、两个返回型。
|
|
14
|
+
*
|
|
15
|
+
* ## 动词(core 板 [ref] ⑧⑨ 给定;本车第零步探针 `test/core-probe-parked-stop-root-access.test.ts` 真跑证实)
|
|
16
|
+
* · 枚举 = `agentStore.listBySession(scope, 根会话)`(耐久行,锚 = 行的 session / parent / root 任一),并上本进程登记表里的
|
|
17
|
+
* `activeDelegationHandles(scope, 根会话)`(没接耐久店的部署只有这一半)。scope 轴由调用方给(协调器:会话行的 `owner ?? "default"`)。
|
|
18
|
+
* · **parked** 行 ⇒ `checkpointStore.expire(token, 卡自己的 scope)`(与 core 自己的本进程仲裁器同一只 CAS)+
|
|
19
|
+
* `registry.reconcileParkedAgents(stores, scope)` ⇒ 行 `failed`(stoppedBy `system`)、卡离开待决面。任何副本上都做得到 ⇒ 预检把它
|
|
20
|
+
* 算作可达(前提是本部署有 checkpoint 店)。
|
|
21
|
+
* ⚠️ **不对 parked 行调 `stopTask`**:只在耐久店里的行(park 它的进程不在 / 别的副本 / 重启后 —— parked 行活过重启是常态)
|
|
22
|
+
* `stopTask` 什么都不改;本进程里的活 handle 先 expire 再 stopTask,core 答误导的 `park_resume_won`(core [ref] 原文)。
|
|
23
|
+
* · **running** 行 ⇒ `stopTask(handle, { scope, sessionId: 根会话 })` ⇒ `killed`。可达与否**只有一个判据**,与 core `stopTask` 答 `not_local`
|
|
24
|
+
* 的那一条同源({@link runsOnAnotherReplica}):本进程**没有**这只句柄(不论状态)**且**耐久行 running、写者(`writerId`)不是本进程
|
|
25
|
+
* (`registry.writerId`)⇒ 不可达(别的副本 / 写它的进程已不在)。本进程有句柄 —— 哪怕已经 killed、终态写还在飞 —— 或耐久行的写者就是
|
|
26
|
+
* 本进程 ⇒ 可达,交给有上界的结束循环(已停的句柄上 `stopTask` 是 no-op,循环等耐久写落地;等不到就是时序失败 500)。
|
|
27
|
+
* (修复验证轮两条 opus 腿同命中:修前预检看的是内存句柄的**状态**(不是 running / pending ⇒ 够不着),本副本刚停、写在飞的子代因此被答成
|
|
28
|
+
* 「在别的副本上」的 409 —— 同一个「这只子代在不在本进程」的判断有两个写者。)
|
|
29
|
+
*
|
|
30
|
+
* ## 循环(有上界)
|
|
31
|
+
* 作废 / 停止与一次并发的 `/decide` 可能交错:expire 的 CAS 输给了决定腿的 resolve ⇒ 那只子代被唤醒成 running ⇒ 下一轮枚举看见
|
|
32
|
+
* 它、停掉它。循环到「枚举不出活的子代」为止,轮数有上界({@link RETIRE_MAX_ROUNDS});本进程 kill 的耐久写是 core 的异步写链,
|
|
33
|
+
* 两轮之间留一小段退避让它落盘。
|
|
34
|
+
*/
|
|
35
|
+
import type { BackgroundAgentStore, CheckpointStore, TaskRegistry } from "@sema-agent/core";
|
|
36
|
+
/** 重新枚举的轮数上界。 */
|
|
37
|
+
export declare const RETIRE_MAX_ROUNDS = 6;
|
|
38
|
+
/** 两步共用的枚举口。 */
|
|
39
|
+
interface ChildrenSource {
|
|
40
|
+
agentStore: BackgroundAgentStore | undefined;
|
|
41
|
+
registry: Pick<TaskRegistry, "activeDelegationHandles">;
|
|
42
|
+
}
|
|
43
|
+
export interface ProbeSessionChildrenDeps extends ChildrenSource {
|
|
44
|
+
/** 缺席 = 本部署没有耐久审批;撞见 parked 行却没有它 ⇒ 那只作废不了 ⇒ 不可达(拒在删之前)。 */
|
|
45
|
+
checkpointStore: Pick<CheckpointStore, "get" | "expire"> | undefined;
|
|
46
|
+
registry: Pick<TaskRegistry, "activeDelegationHandles" | "getAccessibleTask" | "writerId">;
|
|
47
|
+
}
|
|
48
|
+
export interface RetireSessionChildrenDeps extends ChildrenSource {
|
|
49
|
+
/** 缺席 = 本部署没有耐久审批(不可能有 parked 行);撞见 parked 行却没有它 ⇒ 那只作废不了,留到剩余(预检本该已经拒掉它)。 */
|
|
50
|
+
checkpointStore: Pick<CheckpointStore, "get" | "expire"> | undefined;
|
|
51
|
+
registry: Pick<TaskRegistry, "stopTask" | "reconcileParkedAgents" | "activeDelegationHandles">;
|
|
52
|
+
sleep?: (ms: number) => Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
/** 可达性预检的结果(零写)。`unreachableHandles` 只含本进程结束不了的那几只;可达的子代不在这里。 */
|
|
55
|
+
export type SessionChildrenReachability = {
|
|
56
|
+
readonly reachable: true;
|
|
57
|
+
readonly live: number;
|
|
58
|
+
} | {
|
|
59
|
+
readonly reachable: false;
|
|
60
|
+
readonly live: number;
|
|
61
|
+
readonly unreachableHandles: readonly string[];
|
|
62
|
+
};
|
|
63
|
+
/** 结束子代的结果。`incomplete` = 时序失败(本进程的停止还没落盘 / 预检之后冒出来的子代),不是可达性。 */
|
|
64
|
+
export type RetireSessionChildrenOutcome = {
|
|
65
|
+
readonly stopped: "all";
|
|
66
|
+
readonly found: number;
|
|
67
|
+
readonly rounds: number;
|
|
68
|
+
} | {
|
|
69
|
+
readonly stopped: "incomplete";
|
|
70
|
+
readonly found: number;
|
|
71
|
+
readonly rounds: number;
|
|
72
|
+
readonly remainingHandles: readonly string[];
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* **可达性预检**(零写;协调器在任何破坏性步骤之前调)。parked 行在有 checkpoint 店时可达(expire + reconcile 在任何副本上都成);
|
|
76
|
+
* running 行除非 {@link runsOnAnotherReplica} 都可达(`getAccessibleTask` / `agentStore.get` 都是纯读,不动任何东西)。
|
|
77
|
+
*/
|
|
78
|
+
export declare function probeSessionChildren(deps: ProbeSessionChildrenDeps, rootSessionId: string, scope: string): Promise<SessionChildrenReachability>;
|
|
79
|
+
/** 以 `rootSessionId` 为根、在 `scope` 分区里的后台子代:作废 parked、停掉 running,重新枚举到没有活的为止(有上界)。 */
|
|
80
|
+
export declare function retireSessionChildren(deps: RetireSessionChildrenDeps, rootSessionId: string, scope: string): Promise<RetireSessionChildrenOutcome>;
|
|
81
|
+
/**
|
|
82
|
+
* 结束子代这一步的**时序**失败(可达性已在预检过了):本进程发出的停止在上界内没有全部落地。协调器抛它 ⇒ 路由通用 500;
|
|
83
|
+
* session_meta 不删(E21「子腿失败 ⇒ 不删 meta ⇒ 幂等重试收敛」的既有契约),账本那一腿此时已经删了 —— 与任何一条子腿中途失败同形。
|
|
84
|
+
* 文案不带句柄(句柄进运维 warn `session_delete_children_stop_incomplete`)。
|
|
85
|
+
*/
|
|
86
|
+
export declare class SessionChildrenStopIncompleteError extends Error {
|
|
87
|
+
readonly sessionId: string;
|
|
88
|
+
readonly remaining: number;
|
|
89
|
+
constructor(sessionId: string, remaining: number);
|
|
90
|
+
}
|
|
91
|
+
export {};
|
|
92
|
+
//# sourceMappingURL=session-children-retire.d.ts.map
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export const RETIRE_MAX_ROUNDS = 6;
|
|
2
|
+
const RETIRE_BACKOFF_MS = [0, 25, 50, 100, 200, 400];
|
|
3
|
+
async function liveChildren(deps, rootSessionId, scope) {
|
|
4
|
+
const out = new Map();
|
|
5
|
+
if (deps.agentStore !== undefined) {
|
|
6
|
+
for (const s of await deps.agentStore.listBySession(scope, rootSessionId)) {
|
|
7
|
+
if (s.status === "running" || s.status === "parked")
|
|
8
|
+
out.set(s.handle, { handle: s.handle, status: s.status });
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
for (const handle of deps.registry.activeDelegationHandles(scope, rootSessionId)) {
|
|
12
|
+
if (!out.has(handle))
|
|
13
|
+
out.set(handle, { handle, status: "running" });
|
|
14
|
+
}
|
|
15
|
+
return [...out.values()];
|
|
16
|
+
}
|
|
17
|
+
export async function probeSessionChildren(deps, rootSessionId, scope) {
|
|
18
|
+
const live = await liveChildren(deps, rootSessionId, scope);
|
|
19
|
+
const unreachableHandles = [];
|
|
20
|
+
for (const child of live) {
|
|
21
|
+
const unreachable = child.status === "parked" ? deps.checkpointStore === undefined : await runsOnAnotherReplica(deps, child.handle, rootSessionId, scope);
|
|
22
|
+
if (unreachable)
|
|
23
|
+
unreachableHandles.push(child.handle);
|
|
24
|
+
}
|
|
25
|
+
return unreachableHandles.length === 0 ? { reachable: true, live: live.length } : { reachable: false, live: live.length, unreachableHandles };
|
|
26
|
+
}
|
|
27
|
+
async function runsOnAnotherReplica(deps, handle, rootSessionId, scope) {
|
|
28
|
+
if (deps.registry.getAccessibleTask(handle, { scope, sessionId: rootSessionId }) !== undefined)
|
|
29
|
+
return false;
|
|
30
|
+
if (deps.agentStore === undefined)
|
|
31
|
+
return false;
|
|
32
|
+
const row = await deps.agentStore.get(handle, scope);
|
|
33
|
+
return row?.status === "running" && row.writerId !== deps.registry.writerId;
|
|
34
|
+
}
|
|
35
|
+
export async function retireSessionChildren(deps, rootSessionId, scope) {
|
|
36
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
37
|
+
let live = await liveChildren(deps, rootSessionId, scope);
|
|
38
|
+
const found = live.length;
|
|
39
|
+
let rounds = 0;
|
|
40
|
+
while (live.length > 0 && rounds < RETIRE_MAX_ROUNDS) {
|
|
41
|
+
rounds++;
|
|
42
|
+
let parkedSeen = false;
|
|
43
|
+
for (const child of live) {
|
|
44
|
+
if (child.status === "parked") {
|
|
45
|
+
parkedSeen = true;
|
|
46
|
+
await expireParkedCard(deps, child.handle, scope);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
await deps.registry.stopTask(child.handle, { scope, sessionId: rootSessionId }, deps.agentStore !== undefined ? { agentStore: deps.agentStore } : {});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (parkedSeen && deps.agentStore !== undefined && deps.checkpointStore !== undefined) {
|
|
53
|
+
await deps.registry.reconcileParkedAgents({ agentStore: deps.agentStore, checkpointStore: deps.checkpointStore }, scope, Date.now());
|
|
54
|
+
}
|
|
55
|
+
await sleep(RETIRE_BACKOFF_MS[Math.min(rounds, RETIRE_BACKOFF_MS.length - 1)]);
|
|
56
|
+
live = await liveChildren(deps, rootSessionId, scope);
|
|
57
|
+
}
|
|
58
|
+
return live.length === 0
|
|
59
|
+
? { stopped: "all", found, rounds }
|
|
60
|
+
: { stopped: "incomplete", found, rounds, remainingHandles: live.map((c) => c.handle) };
|
|
61
|
+
}
|
|
62
|
+
async function expireParkedCard(deps, handle, scope) {
|
|
63
|
+
if (deps.agentStore === undefined || deps.checkpointStore === undefined)
|
|
64
|
+
return;
|
|
65
|
+
const row = await deps.agentStore.get(handle, scope);
|
|
66
|
+
if (row === null || row.status !== "parked" || row.parkedCheckpointToken === undefined)
|
|
67
|
+
return;
|
|
68
|
+
const token = row.parkedCheckpointToken;
|
|
69
|
+
const cp = await deps.checkpointStore.get(token);
|
|
70
|
+
if (cp === null || cp.status !== "pending")
|
|
71
|
+
return;
|
|
72
|
+
await deps.checkpointStore.expire(token, cp.scope);
|
|
73
|
+
}
|
|
74
|
+
export class SessionChildrenStopIncompleteError extends Error {
|
|
75
|
+
sessionId;
|
|
76
|
+
remaining;
|
|
77
|
+
constructor(sessionId, remaining) {
|
|
78
|
+
super(`session ${sessionId}: ${remaining} background agent(s) this replica stopped had not settled within ${RETIRE_MAX_ROUNDS} rounds — ` +
|
|
79
|
+
`the session record was kept so the delete can be retried (it converges once they settle)`);
|
|
80
|
+
this.sessionId = sessionId;
|
|
81
|
+
this.remaining = remaining;
|
|
82
|
+
this.name = "SessionChildrenStopIncompleteError";
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=session-children-retire.js.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🔴 **core `SessionError` 闭集 → HTTP 的唯一映射**(S-612,server 7.104.0;设计稿 [ref] §2.1)。
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么要一只属主
|
|
5
|
+
* 修前 wire 上只有**一处**认得 `SessionError` 的码:`http/routes/session-sync.ts` 的暂存导入腿把 `invalid_session`
|
|
6
|
+
* 手写成 422。其余每一条路都把它交给全局兜底 ⇒ `500 internal.error "internal error"`,码丢。core 7.30.0(#1041)
|
|
7
|
+
* 给文件会话店铸了 `corrupt_session`(存档没有会话头 ⇒ 这只会话唤不醒),而它在 wire 上一个名字都没有
|
|
8
|
+
* (真跑读数见 S-612 收车档 §0:冷启动时三条提交口与每一条经归属探针 / 读存档碰到它的口都答 500 `internal.error`;
|
|
9
|
+
* owner 已知时同步口是 200 + failed 体、并照常走计费收尾)。
|
|
10
|
+
*
|
|
11
|
+
* ## 为什么多一个 `face` 参数(而不是单参)
|
|
12
|
+
* core 的 `invalid_session` 是**同词两义**(亲读 dist:`engine/session/import-validate.js` 的导入门拒调用方交来的
|
|
13
|
+
* 日志,`engine/session/storage-base.js` 的建树 / 走根拒**已存**日志里指向缺席条目的叶子或祖先 —— 后者即 core #1077)。
|
|
14
|
+
* 本仓对它的成文义是前者(调用方的日志没过校验 ⇒ 422),而后者今天在打开会话的各口上答的是通用 500 / 同步口
|
|
15
|
+
* 200 + failed(S-612 的 3b 负控格钉住这一形,#1077 落地后按 core 的新码再对一次)。单参签名装不下这件事:要么
|
|
16
|
+
* 打开面的每个消费点都写一句 `code === "corrupt_session"` 特判,要么把 #1077 形从今天的应答改成「你交的日志坏了」。
|
|
17
|
+
* ⇒ 面是映射的一维,写进属主,不散在消费点。
|
|
18
|
+
* · `"open"` —— 为某只会话**打开它已存的日志**(提交各腿的引擎 prepare、鉴权门的归属探针、直接读会话的口);
|
|
19
|
+
* · `"import"` —— 校验 / 写入**调用方交来的**日志(2c session-sync 的暂存导入)。
|
|
20
|
+
*
|
|
21
|
+
* ## 逐词(`undefined` = 登记过的留白:这个词在这一面上没有自己的 wire 答复,站点的既有处置照旧)
|
|
22
|
+
* · `corrupt_session` —— 两面同答 **500 `corrupt_session`**。这只会话的存档读不出、唤不醒。不是 4xx:调用方没做错
|
|
23
|
+
* 任何事;不是 503:重试改不了判(修复在运维手里)—— 与会话规则店的 `corrupt` 同一档同一理由。码不另铸(core 的词
|
|
24
|
+
* 原样上 wire,契约既有立场:不为上游码铸孪生码)。三种会话后端同词:文件店由 core 铸,两只 SQL 店由
|
|
25
|
+
* `plugins/session-event-payload.ts` 铸(坏的 `session_event.payload` 行)。
|
|
26
|
+
* · `invalid_session` —— `import` 面 **422 `invalid_entries`**(修前 session-sync 的那一行,码位 / 状态逐字不变);
|
|
27
|
+
* `open` 面留白(#1077 形,见上)。
|
|
28
|
+
* · `not_found` —— 留白:每一面各有自己的「不存在」答法(404 `not_found.session` / 同步口的 `resume.session_not_found` /
|
|
29
|
+
* 列表里的缺席),码位各不相同,不由本表统一。
|
|
30
|
+
* · `invalid_entry` / `invalid_fork_target` / `placement_refused` —— 留白:本仓今天没有一条 wire 口把它们作为自己的
|
|
31
|
+
* 应答(它们在 core 的追加 / 分叉 / 放置面上抛,落进各站点既有的处置或通用 500)。给它们起名是另一件事、另一张票。
|
|
32
|
+
* · `storage` / `conflict` / `unknown` —— 留白:`conflict` 由提交腿的 CAS 冲突臂按结果处置(`runs.ts`
|
|
33
|
+
* `isSessionConflictResult`),`storage` / `unknown` 是故障类,通用 500 就是它们今天的应答。
|
|
34
|
+
*
|
|
35
|
+
* core 给闭集加一个词 ⇒ 下面的 `switch` 编译红,由人裁定它在两面上的答复,而不是让它静默落进某个恰好存在的臂
|
|
36
|
+
* ([ref] 词表纪律;同形先例 `session-policy-wire.ts` 的 `sessionPolicyErrorHttp`)。
|
|
37
|
+
*/
|
|
38
|
+
import type { SessionError } from "@sema-agent/core";
|
|
39
|
+
/** core `SessionError` 的码闭集(core 不从包根导出这个型名 ⇒ 从导出类的 `code` 键派生,不手抄)。 */
|
|
40
|
+
export type SessionErrorCode = SessionError["code"];
|
|
41
|
+
/** 映射的面(见文件头注)。 */
|
|
42
|
+
export type SessionErrorFace = "open" | "import";
|
|
43
|
+
/** 一个词在一面上的 wire 答复。 */
|
|
44
|
+
export interface SessionErrorWire {
|
|
45
|
+
readonly status: number;
|
|
46
|
+
readonly errorCode: string;
|
|
47
|
+
/**
|
|
48
|
+
* 固定句(缺席 = 发 core 的原句,过既有脱敏)。只有 import 面的 `invalid_session` 用它:core 导入门的原句点名的是
|
|
49
|
+
* 调用方日志里的条目 id / 父子关系,修前这一口就只回一句固定话(冻结在 `test/api-error-text-freeze.test.ts` 的
|
|
50
|
+
* 常量锚格),本批逐字不改。
|
|
51
|
+
*/
|
|
52
|
+
readonly sentence?: string;
|
|
53
|
+
}
|
|
54
|
+
/** import 面 `invalid_session` 的固定句(修前 session-sync 暂存导入腿那一行的原文)。 */
|
|
55
|
+
export declare const STAGED_IMPORT_INVALID_SENTENCE = "staged session log failed validation";
|
|
56
|
+
export declare function sessionErrorHttp(code: SessionErrorCode, face: SessionErrorFace): SessionErrorWire | undefined;
|
|
57
|
+
export declare function isSessionErrorCode(code: string): code is SessionErrorCode;
|
|
58
|
+
//# sourceMappingURL=session-error-wire.d.ts.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export const STAGED_IMPORT_INVALID_SENTENCE = "staged session log failed validation";
|
|
2
|
+
const CORRUPT_SESSION = { status: 500, errorCode: "corrupt_session" };
|
|
3
|
+
const INVALID_ENTRIES = { status: 422, errorCode: "invalid_entries", sentence: STAGED_IMPORT_INVALID_SENTENCE };
|
|
4
|
+
export function sessionErrorHttp(code, face) {
|
|
5
|
+
switch (code) {
|
|
6
|
+
case "corrupt_session":
|
|
7
|
+
return CORRUPT_SESSION;
|
|
8
|
+
case "invalid_session":
|
|
9
|
+
return face === "import" ? INVALID_ENTRIES : undefined;
|
|
10
|
+
case "not_found":
|
|
11
|
+
case "invalid_entry":
|
|
12
|
+
case "invalid_fork_target":
|
|
13
|
+
case "placement_refused":
|
|
14
|
+
case "storage":
|
|
15
|
+
case "conflict":
|
|
16
|
+
case "unknown":
|
|
17
|
+
return undefined;
|
|
18
|
+
default: {
|
|
19
|
+
const unhandled = code;
|
|
20
|
+
throw new Error(`unhandled SessionError code: ${JSON.stringify(unhandled)}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const SESSION_ERROR_CODE_PRESENCE = {
|
|
25
|
+
not_found: true,
|
|
26
|
+
invalid_session: true,
|
|
27
|
+
invalid_entry: true,
|
|
28
|
+
invalid_fork_target: true,
|
|
29
|
+
storage: true,
|
|
30
|
+
conflict: true,
|
|
31
|
+
placement_refused: true,
|
|
32
|
+
corrupt_session: true,
|
|
33
|
+
unknown: true,
|
|
34
|
+
};
|
|
35
|
+
export function isSessionErrorCode(code) {
|
|
36
|
+
return Object.hasOwn(SESSION_ERROR_CODE_PRESENCE, code);
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=session-error-wire.js.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** 会话元数据的一次读:属主 + 化身(同一行、同一次读)。 */
|
|
2
|
+
export interface SessionFace {
|
|
3
|
+
/** `null` = 登记为无主的会话。 */
|
|
4
|
+
readonly owner: string | null;
|
|
5
|
+
/** 这一位住客的化身令牌(非空;空值在读口就响亮拒了)。 */
|
|
6
|
+
readonly incarnation: string;
|
|
7
|
+
}
|
|
8
|
+
/** 读口的形(三只会话店 + 缓存包装层同一个签名)。 */
|
|
9
|
+
export type SessionFaceReader = (sessionId: string) => Promise<SessionFace | undefined>;
|
|
10
|
+
/** 铸一只新化身(铸点见头注,只在新建行)。 */
|
|
11
|
+
export declare function mintSessionIncarnation(): string;
|
|
12
|
+
/** 日志里点名一只化身时唯一允许的形:前 8 位。完整令牌不进任何日志正文。 */
|
|
13
|
+
export declare function incarnationLogPrefix(incarnation: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* 把一段**不归本仓撰写**的文本(典型:core 记忆引擎的事故披露句 —— `memory.session_incarnation_mismatch` 逐字带着两只令牌)落日志之前,
|
|
16
|
+
* 把其中每一只化身形的串截成 {@link incarnationLogPrefix}(前 8 位 + `…`)。规则只有一条:完整令牌不进日志正文;前 8 位留着对账。
|
|
17
|
+
*/
|
|
18
|
+
export declare function redactIncarnations(text: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* 两次读是不是**同一位住客**:都在、化身逐字相等。任一次读不到(会话不在)⇒ 不是 —— 没有「读不到就当相同」的臂。
|
|
21
|
+
* 属主是否等于某个身份是调用方自己的判据(赎回面比的是卡的权威 principal),不在这里。
|
|
22
|
+
*/
|
|
23
|
+
export declare function sameSessionOccupant(before: SessionFace | undefined, after: SessionFace | undefined): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* 会话行在、化身却是空的 —— 响亮拒(头注「读到空化身」一条)。具名类型:调用方按类型认它,不按文案。
|
|
26
|
+
* 文案不带任何令牌(本来就是空的),只点名会话 id 与出路。
|
|
27
|
+
*/
|
|
28
|
+
export declare class SessionIncarnationMissingError extends Error {
|
|
29
|
+
readonly sessionId: string;
|
|
30
|
+
readonly code = "session.incarnation_missing";
|
|
31
|
+
constructor(sessionId: string);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 店把一行原始列读成 {@link SessionFace} 的唯一口(三只店共用 —— 空值判据只写这一处)。
|
|
35
|
+
* `incarnation` 不是非空字符串 ⇒ {@link SessionIncarnationMissingError}。
|
|
36
|
+
*/
|
|
37
|
+
export declare function sessionFaceFromRow(sessionId: string, owner: string | null, incarnation: unknown): SessionFace;
|
|
38
|
+
/**
|
|
39
|
+
* session-sync **整段换装**(replace)时化身的去留 —— 三只店共用的一句判据:换装删掉再插回的是**同一位住客**的那一行
|
|
40
|
+
* (删除带属主门),所以行在、属主相同、化身非空 ⇒ 沿用;行不在(这是一次新建)或还是空的存量形 ⇒ 铸一只新的。
|
|
41
|
+
*/
|
|
42
|
+
export declare function carriedIncarnation(prior: {
|
|
43
|
+
owner?: unknown;
|
|
44
|
+
incarnation?: unknown;
|
|
45
|
+
} | undefined, owner: string | null): string;
|
|
46
|
+
/**
|
|
47
|
+
* `session_meta.incarnation` 的列定义 —— 两方言各一句,**唯一属主**:中央 DDL(`tidb-pool.ts` / `pg-session-storage.ts` 的
|
|
48
|
+
* `CREATE TABLE session_meta`)与一次性迁移 M5 的 `ADD COLUMN`(`plugins/one-time-migrations.ts`)都内插这同一串,
|
|
49
|
+
* 于是「新库建出来的列」与「存量库补上的列」逐字同形(S-547 起服列册探针比的是列名,形由这里保证)。
|
|
50
|
+
*
|
|
51
|
+
* `NOT NULL DEFAULT ''`:空串就是「没铸过」的唯一表示 —— 读口对它响亮拒({@link SessionIncarnationMissingError})。
|
|
52
|
+
* 缺省值只为两件事存在:①存量表 `ADD COLUMN` 在两方言上一句成立(PG 对非空表加无缺省的 NOT NULL 列直接拒);
|
|
53
|
+
* ②一条漏铸的写腿落下的是**读得出来的错**(读口拒),而不是一条插入期的 500 —— 两形都 fail-closed,前者还能被迁移补上。
|
|
54
|
+
* 宽度 64:`randomUUID()` 是 36 字符,留出余量但不给任意长串留位。
|
|
55
|
+
*/
|
|
56
|
+
export declare const SESSION_META_INCARNATION_COLUMN: {
|
|
57
|
+
readonly name: "incarnation";
|
|
58
|
+
readonly tidb: "incarnation VARCHAR(64) NOT NULL DEFAULT ''";
|
|
59
|
+
readonly pg: "incarnation VARCHAR(64) COLLATE \"C\" NOT NULL DEFAULT ''";
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=session-incarnation.d.ts.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
export function mintSessionIncarnation() {
|
|
3
|
+
return randomUUID();
|
|
4
|
+
}
|
|
5
|
+
export function incarnationLogPrefix(incarnation) {
|
|
6
|
+
return incarnation.slice(0, 8);
|
|
7
|
+
}
|
|
8
|
+
const INCARNATION_TOKEN_SHAPE = /[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/gi;
|
|
9
|
+
export function redactIncarnations(text) {
|
|
10
|
+
return text.replace(INCARNATION_TOKEN_SHAPE, (token) => `${incarnationLogPrefix(token)}…`);
|
|
11
|
+
}
|
|
12
|
+
export function sameSessionOccupant(before, after) {
|
|
13
|
+
return before !== undefined && after !== undefined && before.incarnation === after.incarnation;
|
|
14
|
+
}
|
|
15
|
+
export class SessionIncarnationMissingError extends Error {
|
|
16
|
+
sessionId;
|
|
17
|
+
code = "session.incarnation_missing";
|
|
18
|
+
constructor(sessionId) {
|
|
19
|
+
super(`session ${sessionId} has no incarnation token: every session row is minted one when it is created, and rows written before ` +
|
|
20
|
+
`7.104.0 get one from the one-time migration that runs at boot (plugins/one-time-migrations.ts, entry M5). An empty value ` +
|
|
21
|
+
`after that means a write path that did not mint one, or a hand-edited row — it is refused rather than read as "matches anyone". ` +
|
|
22
|
+
`Restart a 7.104.0+ replica so the migration backfills it, or delete the session.`);
|
|
23
|
+
this.sessionId = sessionId;
|
|
24
|
+
this.name = "SessionIncarnationMissingError";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function sessionFaceFromRow(sessionId, owner, incarnation) {
|
|
28
|
+
if (typeof incarnation !== "string" || incarnation === "")
|
|
29
|
+
throw new SessionIncarnationMissingError(sessionId);
|
|
30
|
+
return { owner, incarnation };
|
|
31
|
+
}
|
|
32
|
+
export function carriedIncarnation(prior, owner) {
|
|
33
|
+
if (prior !== undefined && (prior.owner ?? null) === owner && typeof prior.incarnation === "string" && prior.incarnation !== "") {
|
|
34
|
+
return prior.incarnation;
|
|
35
|
+
}
|
|
36
|
+
return mintSessionIncarnation();
|
|
37
|
+
}
|
|
38
|
+
export const SESSION_META_INCARNATION_COLUMN = {
|
|
39
|
+
name: "incarnation",
|
|
40
|
+
tidb: "incarnation VARCHAR(64) NOT NULL DEFAULT ''",
|
|
41
|
+
pg: `incarnation VARCHAR(64) COLLATE "C" NOT NULL DEFAULT ''`,
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=session-incarnation.js.map
|
|
@@ -16,10 +16,16 @@
|
|
|
16
16
|
* 自陈「Malformed shape → 400(never a downstream TypeError)」,这一半没守);
|
|
17
17
|
* · `loosenReasons` 的每一臂都从 `prior` 出发,**prior 为空时恒回空集** ⇒ tighten-only 门对一条
|
|
18
18
|
* 全新的会话放行任何内容,坏形因此连「收紧门」这一层都碰不到。
|
|
19
|
-
* ⇒ 类修:一只读者,键集由 `keyof SessionPermissionRules` **派生**(下方 `
|
|
20
|
-
* `satisfies Record<keyof SessionPermissionRules,
|
|
19
|
+
* ⇒ 类修:一只读者,键集由 `keyof SessionPermissionRules` **派生**(下方 `RULE_FIELD_ROLE` 的
|
|
20
|
+
* `satisfies Record<keyof SessionPermissionRules, RuleFieldRole>`:core 加桶 ⇒ 缺键**编译红**;core 删桶 ⇒
|
|
21
21
|
* 多键**编译红**),两条腿同调。
|
|
22
22
|
*
|
|
23
|
+
* ── S-800(7.105.0):同一只读者多判一问 —— 写得进却跑不了的工具名 ─────────────────────────────────
|
|
24
|
+
* 两条写入腿是 server 往会话规则店写的**全部**写口,所以「写口 fail-closed」收成一只**写口读者**
|
|
25
|
+
* ({@link readWritableSessionPermissionRules} = 形读者 + {@link legacyToolNameOf}),两腿同调,将来第三条写入腿
|
|
26
|
+
* 调同一只也同得。判词多一种(`legacy-tool-name`),两腿各把它映射成 `400 rules.legacy_tool_name`(映射点仍在各自
|
|
27
|
+
* 站点:码位只放字面量)。形读者本身不变 —— SQL 店的读回也用它,读回不许判名字(理由见写口读者头注)。
|
|
28
|
+
*
|
|
23
29
|
* ── 文案也在这里(codex 一轮追加:两条腿各铸一份句子 = 第二份手抄)───────────────────────────────
|
|
24
30
|
* 初版把 wire 文案留在两个路由各自铸,理由是错误文案冻结门(`test/api-error-text-freeze.test.ts`)锚的是
|
|
25
31
|
* `src/http/` 桶里的 `sendError` 站点。codex 一轮指出那等于**把同一句话写了两遍**(`sessions.ts` 与
|
|
@@ -28,10 +34,52 @@
|
|
|
28
34
|
* 渲染器对每一种判词的输出字节,且对判词闭集穷尽(加一种判词 ⇒ 那一格编译红)。文案一个字节未改。
|
|
29
35
|
*/
|
|
30
36
|
import type { SessionPermissionRules, SessionPolicyError, SessionRulesRecord } from "@sema-agent/core";
|
|
37
|
+
declare const RULE_FIELD_ROLE: {
|
|
38
|
+
readonly toolAllow: "tool-name";
|
|
39
|
+
readonly toolDeny: "tool-name";
|
|
40
|
+
readonly allowDirs: "path";
|
|
41
|
+
readonly commandAllow: "command";
|
|
42
|
+
readonly commandDeny: "command";
|
|
43
|
+
};
|
|
31
44
|
/** core `SessionPermissionRules` 的桶名全集(派生,不是手抄)。 */
|
|
32
45
|
export declare const SESSION_PERMISSION_RULE_FIELDS: readonly (keyof SessionPermissionRules)[];
|
|
33
|
-
/**
|
|
34
|
-
export type
|
|
46
|
+
/** 装工具名的那几桶(由 {@link RULE_FIELD_ROLE} 派生的型)。 */
|
|
47
|
+
export type ToolNameRuleField = {
|
|
48
|
+
[K in keyof typeof RULE_FIELD_ROLE]: (typeof RULE_FIELD_ROLE)[K] extends "tool-name" ? K : never;
|
|
49
|
+
}[keyof typeof RULE_FIELD_ROLE];
|
|
50
|
+
/**
|
|
51
|
+
* S-800(client-core [ref] CC-226;server [ref];core [ref])—— 「写得进、跑不了、撤不掉」的工具名。
|
|
52
|
+
*
|
|
53
|
+
* 两臂,逐字对应 core 7.33.8 准备阶段名审计抛 `config.legacy_tool_name` 的那两臂(判据只用 core **导出**的
|
|
54
|
+
* 两件,零镜像表 —— 表随 core 钉版走):
|
|
55
|
+
* · `"retired"`:名字在 core 的 `RETIRED_TOOL_NAMES` 里;`guidance` = 那张表给它的一句现役指引(原样);
|
|
56
|
+
* · `"unprefixed-namespace"`:名字里有 `"__"` 分隔符,而 `protocolOf(name)` 认不出它属于哪个协议。
|
|
57
|
+
*
|
|
58
|
+
* 为什么在**写口**判:会话规则店只准非 operator 收紧,删掉一条 deny 算放宽 ⇒ 写进去的这种名字属主自己删不掉,
|
|
59
|
+
* 而准备阶段见它就整只拒启 ⇒ 这个会话之后的每一跑都起不来。写口是唯一还来得及说「不」的地方。
|
|
60
|
+
*
|
|
61
|
+
* ⚠️ **比准备阶段严一格**(方向安全,与 client-core 0.83.6 的 KL-157 同向):准备阶段对**本 run 名册里挂着**的
|
|
62
|
+
* 同名工具放行(部署自己挂了一只叫 `Task` 的工具就不审计它),写口看不到将来哪一跑的名册 ⇒ 一律拒。
|
|
63
|
+
* 受伤的是「部署自带与退役名同名的工具、又要在会话规则里点它」的形;出路 = 部署级工具策略(不经会话规则店)。
|
|
64
|
+
* ⚠️ **过渡形**:core 已表态(#1156,7.34.1)把这两臂抽成一只导出纯函数并在会话规则店写口即拒 —— 到货后本函数
|
|
65
|
+
* 换成调 core 那只(拒因原样透传),三份判定收成一份。
|
|
66
|
+
*
|
|
67
|
+
* 🔴 **为什么不复用 `config.ts` 的 `findUnmatchableToolNames`**(本仓另一只按名字判的单点;读过实现后裁):两只回答的
|
|
68
|
+
* 不是同一个问题。那一只问「部署名单里这条名字会不会**永远匹配不上**活工具」—— 它刻意豁免 `allow`(白名单匹配不上 =
|
|
69
|
+
* fail-closed 且当场可见),并且拒两段 MCP 名 `mcp__srv`(一名两义)与 core 文法编不出的串。本函数问的是「引擎见到这条
|
|
70
|
+
* 会话规则会不会**拒启**」:引擎准备阶段的名审计对 `allow` 桶照样抛(豁免它 = 放过一条砖化路径),对 `mcp__srv` 却
|
|
71
|
+
* **不**抛(会话层自 core 5.55 / 5.56 起把它当整对端覆盖形真命中)。判据只能逐字跟 core 那两臂走,不能借另一只的口径。
|
|
72
|
+
* 两只在「退役名」「无前缀 `__` 名」两臂上同源(都读 core 导出的同两件),7.34.1 提货时一并换调 core 的导出函数。
|
|
73
|
+
*/
|
|
74
|
+
export type LegacyToolName = {
|
|
75
|
+
why: "retired";
|
|
76
|
+
guidance: string;
|
|
77
|
+
} | {
|
|
78
|
+
why: "unprefixed-namespace";
|
|
79
|
+
};
|
|
80
|
+
export declare function legacyToolNameOf(name: string): LegacyToolName | undefined;
|
|
81
|
+
/** 一条规则体的**形**问题。判词而非文案(见文件头注的「文案属主」一段)。 */
|
|
82
|
+
export type SessionPermissionRulesShapeProblem =
|
|
35
83
|
/** 整体不是一只普通对象(`null` / 数组 / 标量 / 缺席)。 */
|
|
36
84
|
{
|
|
37
85
|
kind: "not-an-object";
|
|
@@ -41,22 +89,45 @@ export type SessionPermissionRulesProblem =
|
|
|
41
89
|
kind: "field-not-string-array";
|
|
42
90
|
field: keyof SessionPermissionRules;
|
|
43
91
|
};
|
|
44
|
-
/** {@link
|
|
45
|
-
export type
|
|
46
|
-
|
|
92
|
+
/** 一条**要写进店**的规则体的问题 = 形问题 ∪ S-800 的名字问题(判据 {@link legacyToolNameOf})。 */
|
|
93
|
+
export type SessionPermissionRulesProblem = SessionPermissionRulesShapeProblem
|
|
94
|
+
/** S-800:形对,但工具名桶里有一条写进去就跑不了的名字。 */
|
|
95
|
+
| {
|
|
96
|
+
kind: "legacy-tool-name";
|
|
97
|
+
field: ToolNameRuleField;
|
|
98
|
+
name: string;
|
|
99
|
+
legacy: LegacyToolName;
|
|
100
|
+
};
|
|
101
|
+
/** 两只读者的读数:有问题给判词,没问题给**已收窄**的规则体(调用方零 cast)。`P` = 这只读者会给的判词集。 */
|
|
102
|
+
export type SessionPermissionRulesRead<P extends SessionPermissionRulesProblem = SessionPermissionRulesProblem> = {
|
|
103
|
+
problem: P;
|
|
47
104
|
rules?: undefined;
|
|
48
105
|
} | {
|
|
49
106
|
problem?: undefined;
|
|
50
107
|
rules: SessionPermissionRules;
|
|
51
108
|
};
|
|
52
109
|
/**
|
|
53
|
-
*
|
|
110
|
+
* **形读者**:读一只 `SessionPermissionRules`,形不对给判词,形对给**只含五桶**的规则体。三个消费者:两条写入腿
|
|
111
|
+
* (经 {@link readWritableSessionPermissionRules})与 SQL 店的**读回**(`session-policy-store-sql.ts` 的
|
|
112
|
+
* `readSessionRulesBody`:读不出形的行判 `corrupt`)。
|
|
54
113
|
*
|
|
55
114
|
* 认识面窄得刻意:**只判形**,不判内容(桶内字符串的 pattern 文法、路径是否可达、tighten-only 是否成立
|
|
56
|
-
* 都不在这里 —— 前两者归 core 的 rule 文法与 `normalizeRules`,后者归 `putRules`
|
|
57
|
-
*
|
|
115
|
+
* 都不在这里 —— 前两者归 core 的 rule 文法与 `normalizeRules`,后者归 `putRules` 的收紧门;S-800 的名字判官
|
|
116
|
+
* 只在**写口**那一只读者里,见下)。未知键**照旧忽略**(不是新的拒因):core 的 `normalizeRules` 本来就只搬这五桶,
|
|
117
|
+
* 本读者与它同口径。
|
|
58
118
|
*/
|
|
59
|
-
export declare function readSessionPermissionRules(raw: unknown): SessionPermissionRulesRead
|
|
119
|
+
export declare function readSessionPermissionRules(raw: unknown): SessionPermissionRulesRead<SessionPermissionRulesShapeProblem>;
|
|
120
|
+
/**
|
|
121
|
+
* **写口读者**(S-800)= 形读者 + 名字判官:两条写入腿(`PUT /v1/sessions/:id/policy`、sync/import 的 `policy`
|
|
122
|
+
* 面经 {@link readSessionRulesRecords})往会话规则店写之前的**唯一**判据。两问,按序:
|
|
123
|
+
* ① 形(同 {@link readSessionPermissionRules};一份坏形的体答形的 400);
|
|
124
|
+
* ② 工具名桶里的每一条过 {@link legacyToolNameOf} —— 写进去之后属主撤不掉、会话之后每一跑都起不来的名字,
|
|
125
|
+
* 写口是唯一来得及拒的地方。
|
|
126
|
+
* 🔴 **只在写口,不在读回**:SQL 店读回同一行时只判形(形读者)。一条早先写进去的退役名行必须**读得出来** ——
|
|
127
|
+
* 那是运营方清掉它的唯一路径(operator 的 PUT 先读写前快照),也是 core 准备阶段报 `config.legacy_tool_name`
|
|
128
|
+
* 而不是一只 500 `corrupt` 的前提。把判官放进形读者会让读回把它判坏(7.105.0 车QQ 双库套件实抓,修在同批)。
|
|
129
|
+
*/
|
|
130
|
+
export declare function readWritableSessionPermissionRules(raw: unknown): SessionPermissionRulesRead;
|
|
60
131
|
/** 一条 `SessionRulesRecord` 的形问题(`policy[]` 的元素级;桶级问题原样转载在 `rules` 位)。 */
|
|
61
132
|
export type SessionRulesRecordProblem =
|
|
62
133
|
/** `policy` 本身不是数组。 */
|
|
@@ -90,6 +161,8 @@ export type SessionRulesRecordsRead = {
|
|
|
90
161
|
};
|
|
91
162
|
/**
|
|
92
163
|
* 读 sync/import 的 `policy` 面(`SessionRulesRecord[]`)。好形返回**已收窄**的记录数组,坏形给判词。
|
|
164
|
+
* 它只服务那一条**写入**腿(导入的记录随后 `putRules` 进店),所以每条记录的规则体走**写口读者**
|
|
165
|
+
* {@link readWritableSessionPermissionRules}(与 PUT 腿同一只判官)。
|
|
93
166
|
*
|
|
94
167
|
* `rev`:记录里带的 `rev` 是**导出端的**乐观锁读数,`putRules` 侧一进门就 `stripRev` 掉(`sameRulesContent`
|
|
95
168
|
* 也比的是去 rev 的字节形),所以它在本读者里只是过境位 —— 非数字即当缺席(不是拒因:一个坏 rev 改变不了
|
|
@@ -98,6 +171,15 @@ export type SessionRulesRecordsRead = {
|
|
|
98
171
|
export declare function readSessionRulesRecords(raw: unknown): SessionRulesRecordsRead;
|
|
99
172
|
/** 桶级判词 → 人话。闭集穷尽:{@link SessionPermissionRulesProblem} 加一种判词 ⇒ 本 switch 编译红。 */
|
|
100
173
|
export declare function describeSessionPermissionRulesProblem(p: SessionPermissionRulesProblem): string;
|
|
174
|
+
/**
|
|
175
|
+
* S-800:一只 `policy[]` 判词**是不是**「写得进却跑不了的名字」那一类 —— 是就交回那一格(桶 + 名字),
|
|
176
|
+
* 调用点据此发 `rules.legacy_tool_name`,否则发形的码。闭集穷尽两层:记录级判词或桶级判词加一种 ⇒ 编译红,
|
|
177
|
+
* 新判词不会静默落进某一个码。
|
|
178
|
+
*/
|
|
179
|
+
export declare function legacyToolNameProblemOf(p: SessionRulesRecordProblem): {
|
|
180
|
+
field: ToolNameRuleField;
|
|
181
|
+
name: string;
|
|
182
|
+
} | undefined;
|
|
101
183
|
/** `policy[]` 判词 → 人话(sync/import 腿在它前面拼 `policy: `)。闭集穷尽同上。 */
|
|
102
184
|
export declare function describeSessionRulesRecordProblem(p: SessionRulesRecordProblem): string;
|
|
103
185
|
/**
|
|
@@ -115,7 +197,10 @@ export declare function describeSessionRulesRecordProblem(p: SessionRulesRecordP
|
|
|
115
197
|
*
|
|
116
198
|
* 逐词:
|
|
117
199
|
* · `conflict`(409)—— CAS 输了,重读 `rev` 再来。**码位与状态逐字不变**。
|
|
118
|
-
* · `loosen_forbidden`(403)—— 非 operator
|
|
200
|
+
* · `loosen_forbidden`(403)—— 非 operator 想放松。**码位与状态逐字不变**。⚠️ 这个词还有**第二个铸点**(S-546 B5,
|
|
201
|
+
* 如实登记、未改):PG 后端的 SQL 双生对含 NUL / 孤代理字节的规则拒收时借用了它
|
|
202
|
+
* (`plugins/session-policy-store-sql.ts` 的 `pgHasUnstorable` 臂)—— 那一形**既非放宽也非越权**,operator 写同样撞上;
|
|
203
|
+
* core 的闭集里没有「输入不可存」这个词,改码位要先裁定新码与 PUT / 2c 导入两条入口同判(DEBTS S-546 B5)。
|
|
119
204
|
* · `corrupt`(**500**)—— 店读不出它自己该读的那份字节(记号/行被列外篡改或撕裂)。
|
|
120
205
|
* 不是 4xx:调用方没做错任何事,它也改不了这件事;不是 503:重试改不了判(core 成文的修复路径是
|
|
121
206
|
* **人**去改回或删掉那一行,而删记号要交出它握着的那个号)。
|
|
@@ -124,4 +209,5 @@ export declare function sessionPolicyErrorHttp(code: SessionPolicyError["code"])
|
|
|
124
209
|
status: number;
|
|
125
210
|
errorCode: string;
|
|
126
211
|
};
|
|
212
|
+
export {};
|
|
127
213
|
//# sourceMappingURL=session-policy-wire.d.ts.map
|