@sema-agent/server 7.138.0 → 7.139.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 +276 -7
- package/README.md +7 -4
- package/README.zh-CN.md +6 -4
- package/USAGE.md +73 -29
- package/deploy/sema-up/README.md +1 -1
- package/deploy/sema-up/lib/lanes.json +3 -2
- package/dist/adoption/wire.d.ts +3 -3
- package/dist/approval-card.d.ts +20 -20
- package/dist/bench/s1/arms.d.ts +1 -1
- package/dist/bench/s1/arms.js +1 -3
- package/dist/bench/s1/run-firm.js +2 -2
- package/dist/boot/device-lane.d.ts +8 -3
- package/dist/boot/device-lane.js +7 -2
- package/dist/boot/execution-env.d.ts +4 -2
- package/dist/boot/execution-env.js +16 -24
- package/dist/boot/execution-lane.d.ts +1 -1
- package/dist/boot/governance-seams.d.ts +3 -2
- package/dist/boot/governance-seams.js +9 -11
- package/dist/boot/memory-consolidation.js +2 -2
- package/dist/boot/resolve-spec.d.ts +1 -1
- package/dist/boot/resolve-spec.js +8 -9
- package/dist/boot/retention-lane.d.ts +5 -5
- package/dist/boot/retention-lane.js +0 -6
- package/dist/boot/runner-deps.d.ts +3 -1
- package/dist/boot/runner-deps.js +2 -1
- package/dist/boot/session-faces.d.ts +0 -3
- package/dist/boot/session-faces.js +15 -8
- package/dist/boot/stage-02-web-admin-pull.js +2 -2
- package/dist/boot/stage-05-execution-env.js +2 -2
- package/dist/boot/stage-06-runners.js +0 -1
- package/dist/boot/stage-09-leader.js +1 -2
- package/dist/boot/stage-10-http-server.js +2 -2
- package/dist/boot/stores.js +4 -4
- package/dist/boot/web-admin.js +2 -2
- package/dist/capabilities/mcp-probe.js +1 -1
- package/dist/capabilities/scenarios.d.ts +1 -1
- package/dist/capability-facts.d.ts +15 -7
- package/dist/capability-facts.js +8 -3
- package/dist/config-catalog.js +7 -7
- package/dist/config-invariants.d.ts +23 -3
- package/dist/config-invariants.js +42 -0
- package/dist/config-provider.d.ts +19 -24
- package/dist/config-provider.js +27 -12
- package/dist/config-types.d.ts +10 -5
- package/dist/config.d.ts +33 -0
- package/dist/config.js +44 -15
- package/dist/delegated-session-owner.d.ts +2 -2
- package/dist/deployment-shape.d.ts +1 -1
- package/dist/device-enrollment.d.ts +41 -2
- package/dist/device-enrollment.js +27 -12
- package/dist/device-store.d.ts +58 -85
- package/dist/device-store.js +19 -49
- package/dist/device-ws-hub.js +2 -1
- package/dist/env-name-allowlist-knobs.d.ts +5 -4
- package/dist/exec-cut.d.ts +60 -0
- package/dist/exec-cut.js +36 -0
- package/dist/execution-lane-caps.d.ts +19 -0
- package/dist/execution-lane-caps.js +10 -0
- package/dist/execution-placement.d.ts +259 -0
- package/dist/execution-placement.js +199 -0
- package/dist/field-conflict.d.ts +3 -2
- package/dist/field-conflict.js +1 -0
- package/dist/hosted-posture.d.ts +2 -2
- package/dist/http/admission.js +8 -19
- package/dist/http/checkpoint-ctx.d.ts +21 -12
- package/dist/http/checkpoint-ctx.js +4 -2
- package/dist/http/claimed-run.d.ts +1 -1
- package/dist/http/durable-run-leg.js +9 -5
- package/dist/http/execution-context.d.ts +218 -0
- package/dist/http/execution-context.js +330 -0
- package/dist/http/resume-legs.js +50 -20
- package/dist/http/route-ctx.d.ts +14 -8
- package/dist/http/routes/background.d.ts +29 -1
- package/dist/http/routes/background.js +29 -2
- package/dist/http/routes/devices.d.ts +13 -19
- package/dist/http/routes/devices.js +21 -26
- package/dist/http/routes/session-sync.js +110 -15
- package/dist/http/routes/sessions.d.ts +6 -0
- package/dist/http/routes/sessions.js +115 -11
- package/dist/http/routes/side-query.d.ts +12 -4
- package/dist/http/routes/side-query.js +4 -5
- package/dist/http/routes/tasks.js +37 -31
- package/dist/http/server.d.ts +35 -19
- package/dist/http/server.js +11 -0
- package/dist/http/session-inherit-env.d.ts +3 -4
- package/dist/http/session-inherit-env.js +1 -1
- package/dist/http/wire-types.d.ts +8 -12
- package/dist/index.d.ts +1 -0
- package/dist/memory-sync-client.d.ts +53 -14
- package/dist/memory-sync-client.js +196 -116
- package/dist/memory-sync.d.ts +1 -1
- package/dist/memory-sync.js +3 -2
- package/dist/observability/cost-taxonomy.d.ts +2 -2
- package/dist/observability/fail-open.d.ts +12 -0
- package/dist/observability/fail-open.js +12 -0
- package/dist/observability/run-terminal.js +0 -1
- package/dist/orchestration/workflow-notify-journal.js +4 -4
- package/dist/org-memory-admission.d.ts +4 -3
- package/dist/org-memory-admission.js +6 -6
- package/dist/parked-decide.d.ts +34 -9
- package/dist/parked-decide.js +12 -19
- package/dist/parked-revive-surface.d.ts +12 -15
- package/dist/parked-revive-surface.js +3 -6
- package/dist/peer-seat.d.ts +13 -7
- package/dist/peer-seat.js +37 -3
- package/dist/plugins/approval-ask-store-file.d.ts +7 -0
- package/dist/plugins/approval-ask-store-file.js +11 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +6 -0
- package/dist/plugins/approval-ask-store-memory.js +12 -0
- package/dist/plugins/approval-ask-store-sql.d.ts +22 -5
- package/dist/plugins/approval-ask-store-sql.js +30 -12
- package/dist/plugins/caching-session-store.d.ts +6 -2
- package/dist/plugins/caching-session-store.js +9 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +6 -5
- package/dist/plugins/checkpoint-store-sql.js +5 -7
- package/dist/plugins/device-store-sql.d.ts +25 -51
- package/dist/plugins/device-store-sql.js +49 -103
- package/dist/plugins/execution-placement-sql.d.ts +151 -0
- package/dist/plugins/execution-placement-sql.js +223 -0
- package/dist/plugins/file-history-store-sql.d.ts +7 -6
- package/dist/plugins/file-history-store-sql.js +5 -5
- package/dist/plugins/file-run-store.d.ts +6 -1
- package/dist/plugins/file-run-store.js +4 -0
- package/dist/plugins/fork-routing-session-store.d.ts +6 -0
- package/dist/plugins/fork-routing-session-store.js +16 -0
- package/dist/plugins/local-session-store.d.ts +149 -50
- package/dist/plugins/local-session-store.js +382 -107
- package/dist/plugins/memory-key-guards.d.ts +4 -0
- package/dist/plugins/memory-key-guards.js +1 -0
- package/dist/plugins/memory-run-store.d.ts +1 -1
- package/dist/plugins/one-time-migrations.d.ts +111 -3
- package/dist/plugins/one-time-migrations.js +380 -10
- package/dist/plugins/outcome-ledger-sql.d.ts +2 -2
- package/dist/plugins/permission-rule-store-sql.d.ts +13 -15
- package/dist/plugins/permission-rule-store-sql.js +13 -16
- package/dist/plugins/pg-pool.d.ts +7 -3
- package/dist/plugins/pg-session-storage.d.ts +31 -20
- package/dist/plugins/pg-session-storage.js +120 -52
- package/dist/plugins/remote-env-adb.d.ts +20 -4
- package/dist/plugins/remote-env-adb.js +70 -62
- package/dist/plugins/remote-env-device.d.ts +30 -12
- package/dist/plugins/remote-env-device.js +18 -17
- package/dist/plugins/remote-env-e2b.d.ts +18 -4
- package/dist/plugins/remote-env-e2b.js +41 -42
- package/dist/plugins/remote-env-file-error.d.ts +2 -0
- package/dist/plugins/remote-env-file-error.js +1 -1
- package/dist/plugins/remote-env-host.d.ts +21 -8
- package/dist/plugins/remote-env-host.js +66 -70
- package/dist/plugins/remote-env-k8s.d.ts +11 -16
- package/dist/plugins/remote-env-k8s.js +61 -54
- package/dist/plugins/remote-env-local-docker.d.ts +29 -6
- package/dist/plugins/remote-env-local-docker.js +46 -59
- package/dist/plugins/remote-env-ssh.d.ts +16 -4
- package/dist/plugins/remote-env-ssh.js +72 -56
- package/dist/plugins/remote-shell.d.ts +100 -3
- package/dist/plugins/remote-shell.js +166 -2
- package/dist/plugins/retention-store-sql.d.ts +9 -6
- package/dist/plugins/retention-store-sql.js +7 -1
- package/dist/plugins/run-store-sql.d.ts +13 -3
- package/dist/plugins/run-store-sql.js +5 -0
- package/dist/plugins/schema-consistency.d.ts +15 -5
- package/dist/plugins/schema-consistency.js +14 -8
- package/dist/plugins/schema-drift-sentence.d.ts +27 -0
- package/dist/plugins/schema-drift-sentence.js +38 -0
- package/dist/plugins/session-fork.d.ts +54 -0
- package/dist/plugins/session-fork.js +38 -1
- package/dist/plugins/session-row-sql.d.ts +22 -0
- package/dist/plugins/session-row-sql.js +81 -0
- package/dist/plugins/session-store.d.ts +1 -1
- package/dist/plugins/store-backend.d.ts +1 -1
- package/dist/plugins/store-backend.js +19 -7
- package/dist/plugins/store-contracts.d.ts +25 -5
- package/dist/plugins/tidb-pool.d.ts +12 -7
- package/dist/plugins/tidb-pool.js +9 -3
- package/dist/plugins/tidb-session-store.d.ts +57 -26
- package/dist/plugins/tidb-session-store.js +118 -50
- package/dist/plugins/tool-result-store-sql.d.ts +3 -2
- package/dist/plugins/tool-result-store-sql.js +18 -10
- package/dist/project-memory.d.ts +9 -15
- package/dist/project-memory.js +5 -4
- package/dist/request-key-closure.js +1 -1
- package/dist/request-value-closure.d.ts +6 -7
- package/dist/request-value-closure.js +1 -3
- package/dist/run-local.d.ts +6 -0
- package/dist/run-local.js +24 -20
- package/dist/runs.d.ts +2 -1
- package/dist/runs.js +3 -1
- package/dist/security.d.ts +38 -12
- package/dist/security.js +8 -2
- package/dist/server-secret-env.d.ts +9 -5
- package/dist/session-children-retire.d.ts +27 -9
- package/dist/session-children-retire.js +9 -5
- package/dist/session-incarnation.d.ts +19 -9
- package/dist/session-incarnation.js +4 -2
- package/dist/session-row.d.ts +114 -5
- package/dist/session-row.js +55 -5
- package/dist/session-titler.d.ts +2 -2
- package/dist/task-a2a.d.ts +1 -1
- package/dist/task-cwd.d.ts +6 -1
- package/dist/task-mcp.d.ts +3 -3
- package/dist/tool-approval.d.ts +6 -0
- package/dist/tool-approval.js +8 -1
- package/dist/trace/project.d.ts +1 -0
- package/dist/trace/project.js +1 -0
- package/dist/trace/self-minted-wire.d.ts +111 -2
- package/dist/trace/self-minted-wire.js +38 -1
- package/dist/url-refusal.d.ts +1 -1
- package/dist/web-admin-client/apply-effective.d.ts +17 -3
- package/dist/web-admin-client/apply-effective.js +20 -10
- package/dist/web-admin-client/skills-mcp.js +3 -2
- package/dist/web-admin-client/types.d.ts +6 -0
- package/package.json +3 -3
- package/wire-changes/server@7.139.0.json +348 -0
- package/dist/http/device-placement.d.ts +0 -175
- package/dist/http/device-placement.js +0 -145
package/dist/parked-decide.d.ts
CHANGED
|
@@ -15,8 +15,15 @@ import type { ParkedReviveSurfaceFactory } from "./parked-revive-surface.js";
|
|
|
15
15
|
import type { RebuiltInheritedGate } from "./boot/parked-revive-gate.js";
|
|
16
16
|
import { type WorkflowRunParkKey, type WorkflowAgentSessionIndex } from "./orchestration/workflow-agent-session-index.js";
|
|
17
17
|
import type { WorkflowParkDecisionStore, WorkflowParkOutcome } from "./orchestration/workflow-park-decision.js";
|
|
18
|
-
import {
|
|
18
|
+
import type { SessionFace } from "./session-incarnation.js";
|
|
19
|
+
import { type ExecutionCommitDeps, type RootedExecutionContext } from "./http/execution-context.js";
|
|
19
20
|
import type { GateIdentity } from "./checkpoint-identity.js";
|
|
21
|
+
/**
|
|
22
|
+
* 本腿的**装配前提**(`createHttpServer` 起服拒的拒句;规则见 {@link ParkedDecideDeps} 的 `execution`):接了本腿(`parkedReviveSurface` 在场)的部署,
|
|
23
|
+
* 会话店必须带执行席 —— 认领之后对根会话住客的唯一判官是执行席上的写口,无席 ⇒ 认领之后没有任何判官(安全轴上不留静默放开的臂,所以拒启,
|
|
24
|
+
* 不留「无席时照旧两读」的臂)。
|
|
25
|
+
*/
|
|
26
|
+
export declare const PARKED_REVIVE_EXECUTION_SEAT_REQUIRED = "the parked-agent revive leg (parkedReviveSurface) needs a session store with an execution placement seat (sessionStorage.executionPlacement) \u2014 after the claim, the root session's occupant is judged only by the write on that seat; every built-in session store carries one (the MySQL-protocol and PostgreSQL stores and the local store, i.e. the session store a StoreBackend hands out; the caching and fork-routing wrappers forward it), and an in-memory test or embedding passes InMemoryExecutionPlacementSeat; refusing to start rather than revive parked agents with nobody judging the root session's occupant after the claim";
|
|
20
27
|
export interface ParkedAgentMatch {
|
|
21
28
|
handle: string;
|
|
22
29
|
row: BackgroundAgentRecord;
|
|
@@ -151,14 +158,6 @@ export interface ParkedDecideDeps {
|
|
|
151
158
|
* checkpointStore 同实例,[ref] 设计稿 §4),以及与它同出一张列表的继承座 `mountsCallerTool`(一层行才有;属主与全部判据
|
|
152
159
|
* 在 `parked-revive-surface.ts`)。修前是 boot 期的一只裸 Agent 工具、从不交座 ⇒ 7.32.0 起停在继承工具上的 allow 恒 422。 */
|
|
153
160
|
reviveSurface: ParkedReviveSurfaceFactory;
|
|
154
|
-
/**
|
|
155
|
-
* [ref]([ref] §4)—— 会话店按 id 读一行的唯一读口(`sessionStorage.headOf`:属主 + 化身 + 叶子一次读;活住客经 `sessionFaceOfHead`
|
|
156
|
-
* 投影,空化身响亮拒)。**必填**:本腿对根会话读两次 ——
|
|
157
|
-
* claim 之前一次、claim 成功之后一次 —— 会话不在或两次不是同一位住客 ⇒ 回滚 claim、答既有的 `409 decide.not_parked`;赎回面只在
|
|
158
|
-
* 属主相等且两次是同一位住客时交继承座。读抛 ⇒ claim 前原样上抛(没有东西要回滚),claim 后先回滚再上抛。
|
|
159
|
-
* ([ref] 那只可选的 `sessionOwnerOf` 退役:可选 ⇒ 缺席时静默不复核,正是 [ref] 那个窗的形。)
|
|
160
|
-
*/
|
|
161
|
-
headOf: HeadRead;
|
|
162
161
|
/** revive args 的 `subagent_type` 成员性判定集([ref] Q1 修正:非 roster 成员必须省略,否则
|
|
163
162
|
* unknown subagent_type 拒;字面比对 general-purpose 在 roster 撞名时会错省)。缺省空集=恒省略。 */
|
|
164
163
|
knownAgentTypes?: ReadonlySet<string>;
|
|
@@ -187,7 +186,33 @@ export interface ParkedDecideDeps {
|
|
|
187
186
|
owner: string | null;
|
|
188
187
|
taskId: string;
|
|
189
188
|
sessionId: string;
|
|
189
|
+
executionLane: import("./execution-lane-caps.js").ExecutionLane;
|
|
190
190
|
}) => NonNullable<ToolExecuteContext["onAsk"]>;
|
|
191
|
+
/**
|
|
192
|
+
* 边界 9 的执行上下文(设计 §3.2;与提交腿 / 续跑家族同一只解析器、同一只写口)—— 本腿对根会话的住客**一读一判**:
|
|
193
|
+
* · `resolve` —— **认领之前**按行的根取上下文:解析器那一次行读(执行席:住客 + 格)就是本腿对根会话的**唯一一读**;根行不在 ⇒ `gone`
|
|
194
|
+
* (本腿答既有的 `decide.not_parked`);其余拒(mirror / 车道不再服务 / 主体的车道裁定)按续跑家族的折叠律答好交回(`answer`)—— 不认领、不驱动。
|
|
195
|
+
* 成功形的 `occupant` **必有**:接本腿的部署必须带执行席(无席的会话店交不出这一读;`createHttpServer` 在装配时拒无席形);
|
|
196
|
+
* · `commitDeps` —— 认领之后 `commitExecutionContext` 读的部署件:唯一写口按那一读的住客定址写 / 再读(化身 ∧ 属主),这就是认领之后对根住客的
|
|
197
|
+
* **唯一一判**(拒 ⇒ 先回滚认领)。写口放行的那一位就是赎回面的根住客见证;写口之后本腿不再读根。
|
|
198
|
+
* 复活子代的审批席带这只上下文的车道(`approverSeatFor` 的 `executionLane`)。
|
|
199
|
+
*/
|
|
200
|
+
execution: {
|
|
201
|
+
resolve: (root: string) => Promise<{
|
|
202
|
+
readonly ok: true;
|
|
203
|
+
readonly ctx: RootedExecutionContext & {
|
|
204
|
+
readonly occupant: SessionFace;
|
|
205
|
+
};
|
|
206
|
+
} | {
|
|
207
|
+
readonly ok: false;
|
|
208
|
+
readonly gone: true;
|
|
209
|
+
} | {
|
|
210
|
+
readonly ok: false;
|
|
211
|
+
readonly gone: false;
|
|
212
|
+
readonly answer: ParkedDecideResult;
|
|
213
|
+
}>;
|
|
214
|
+
commitDeps: ExecutionCommitDeps;
|
|
215
|
+
};
|
|
191
216
|
warn?: (event: string, fields: Record<string, unknown>) => void;
|
|
192
217
|
}
|
|
193
218
|
export interface ParkedDecideRequest {
|
package/dist/parked-decide.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { compareOccupants } from "@sema-agent/core";
|
|
2
1
|
import { readClosedWordSeat } from "./approval-card.js";
|
|
3
2
|
import { routeWorkflowPark } from "./orchestration/workflow-agent-session-index.js";
|
|
4
3
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
5
4
|
import { defaultSubagentTailBus } from "./fleet/subagent-tail-bus.js";
|
|
6
5
|
import { backgroundScopesForCheckpointScope, decodeCheckpointScope, ownerOfCheckpointScope } from "./security.js";
|
|
7
6
|
import { ASK_USER_QUESTION_TOOL_NAME, isEngineQuestionRow } from "./approval-content-kind.js";
|
|
8
|
-
import { sessionFaceOfHead } from "./session-incarnation.js";
|
|
9
7
|
import { refusalBody, resumeRefusalFacts } from "./http/refusal-body.js";
|
|
10
8
|
import { backgroundAgentNameOf } from "./background-agent-name.js";
|
|
11
9
|
import { buildSettlementEvidence } from "./gate-settlement.js";
|
|
10
|
+
import { commitExecutionContext } from "./http/execution-context.js";
|
|
11
|
+
import { EXECUTION_PLACEMENT_SEAT_CARRIERS } from "./execution-placement.js";
|
|
12
|
+
export const PARKED_REVIVE_EXECUTION_SEAT_REQUIRED = `the parked-agent revive leg (parkedReviveSurface) needs a session store with an execution placement seat (sessionStorage.executionPlacement) — after the claim, the root session's occupant is judged only by the write on that seat; ${EXECUTION_PLACEMENT_SEAT_CARRIERS}; refusing to start rather than revive parked agents with nobody judging the root session's occupant after the claim`;
|
|
12
13
|
export async function findParkOrigin(sources, cp) {
|
|
13
14
|
if (sources.agentStore !== undefined) {
|
|
14
15
|
let match;
|
|
@@ -270,9 +271,10 @@ export async function decideParkedAgent(deps, req, match) {
|
|
|
270
271
|
if (revivability.kind === "unrevivable")
|
|
271
272
|
return req.decision === "deny" ? voidUnrevivableCard(deps, req, match) : revivability.approveRefusal;
|
|
272
273
|
const root = revivability.root;
|
|
273
|
-
const
|
|
274
|
-
if (
|
|
275
|
-
return sessionGone(match.handle);
|
|
274
|
+
const resolved = await deps.execution.resolve(root);
|
|
275
|
+
if (!resolved.ok)
|
|
276
|
+
return resolved.gone ? sessionGone(match.handle) : resolved.answer;
|
|
277
|
+
const execution = resolved.ctx;
|
|
276
278
|
const inheritedGate = deps.rebuildInheritedGate ? await deps.rebuildInheritedGate(row, { principal: decodeCheckpointScope(req.scope) }) : undefined;
|
|
277
279
|
const stores = { agentStore: deps.agentStore, checkpointStore: deps.checkpointStore };
|
|
278
280
|
const claim = await deps.registry.claimParkedAgent(stores, match.handle, { scope: row.scope, owner: row.owner });
|
|
@@ -312,21 +314,12 @@ export async function decideParkedAgent(deps, req, match) {
|
|
|
312
314
|
await rollback("claimed_token_moved");
|
|
313
315
|
return gateMoved(match.handle);
|
|
314
316
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
catch (e) {
|
|
320
|
-
await rollback("session_face_unreadable");
|
|
321
|
-
throw e;
|
|
322
|
-
}
|
|
323
|
-
if (compareOccupants(faceBefore, faceAfter) !== "same") {
|
|
324
|
-
await rollback("session_occupant_changed");
|
|
325
|
-
return sessionGone(match.handle);
|
|
326
|
-
}
|
|
317
|
+
const placementRefusal = await commitExecutionContext(deps.execution.commitDeps, { kind: "parked", taskId: match.handle, rollback, goneAnswer: sessionGone(match.handle) }, execution);
|
|
318
|
+
if (placementRefusal)
|
|
319
|
+
return placementRefusal;
|
|
327
320
|
let surface;
|
|
328
321
|
try {
|
|
329
|
-
surface = deps.reviveSurface(row, { principal: decodeCheckpointScope(req.scope),
|
|
322
|
+
surface = deps.reviveSurface(row, { principal: decodeCheckpointScope(req.scope), rootOccupant: execution.occupant });
|
|
330
323
|
}
|
|
331
324
|
catch (e) {
|
|
332
325
|
await rollback("revive_surface_threw");
|
|
@@ -350,7 +343,7 @@ export async function decideParkedAgent(deps, req, match) {
|
|
|
350
343
|
toolCallId: `drv-${ticket.claimId}`,
|
|
351
344
|
...(surface.mountsCallerTool !== undefined ? { mountsCallerTool: surface.mountsCallerTool } : {}),
|
|
352
345
|
...(deps.approverSeatFor !== undefined && seatRoot !== undefined
|
|
353
|
-
? { onAsk: deps.approverSeatFor({ owner: ownerOfCheckpointScope(req.scope), taskId: match.handle, sessionId: seatRoot }) }
|
|
346
|
+
? { onAsk: deps.approverSeatFor({ owner: ownerOfCheckpointScope(req.scope), taskId: match.handle, sessionId: seatRoot, executionLane: execution.lane }) }
|
|
354
347
|
: {}),
|
|
355
348
|
reviveClaim: {
|
|
356
349
|
row: { ...ticket.row, parkClaimId: ticket.claimId },
|
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
* 给那个会话的 run 组 `spec.tools` 的同一只工厂、同一个会话分区(任务清单店 `taskListStoreFor(root)` 与领队那一张是同一张;
|
|
12
12
|
* 多租户的 WebFetch 过滤同样生效 ⇒ 子代同样拿不到)。座 = `(t) => tools.includes(t)`(身份判:同名副本不算)。
|
|
13
13
|
* · **禁**用 boot 期单例填座:那只 bundle 没有会话,它的任务清单是一张私有表 —— 填了座,每个会话 / 租户的复活队友都会共用它。
|
|
14
|
-
* ·
|
|
15
|
-
*
|
|
16
|
-
* ①
|
|
17
|
-
*
|
|
18
|
-
* (
|
|
19
|
-
*
|
|
14
|
+
* · **根会话属于这张卡的租户**才填座(codex r1 [high]):任务清单店只按会话 id 分区,而会话 id 由调用方自选、删后可被别人(或同一个人)重领 ——
|
|
15
|
+
* 根会话删了(或删了重建)之后赎回一张旧卡,按 id 重解出来的是**下一位住客**的清单。两件事,两处判,各判一次:
|
|
16
|
+
* ① **住客判**(根会话现在还是这次决定判过的那一位:化身 ∧ 属主)只在赎回腿认领之后的唯一写口(执行放置,按解析器那一读的住客定址)——
|
|
17
|
+
* 不是那一位 ⇒ 赎回腿整条拒(`409 decide.not_parked`),走不到这里;只比属主比不出「同一个人删了重建」,所以住客判比化身;
|
|
18
|
+
* ② **租户判**在这里:写口放行的那一位(`rootOccupant`)的属主与卡的权威 principal 逐字相等(匿名会话 `null` ≡ 无 principal)。
|
|
19
|
+
* 不成立 ⇒ 不交座(fail-closed:子代不继承,继承工具上的 allow 回 422、审批待决,deny 照结)。根删除时把子孙的 parked 卡一并作废 = [ref] 件 B
|
|
20
20
|
* (`boot/session-faces.ts` 的删除协调器)。
|
|
21
21
|
* · 只对**一层**行填座(`rootSessionId` 在场且 = `parentSessionId`:派它的 run 就是根会话的 run,列表正是那个 run 的挂载)。更深的行
|
|
22
22
|
* (孙代:派它的是一个子代,它的挂载是继承裁剪后的子集,行上读不出来)不填 —— 无座 = core 的 fail-closed 臂;
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
* 座只判 bundle 实例的成员性,继承面是 `[...web, ...scaffolds]`,那两项差异不改变谁在面内。
|
|
26
26
|
*
|
|
27
27
|
* **属主来源([ref] / [ref])与交座是两个独立判据**:赎回工具的停驻挂点(复活的子代再要审批时停驻,`plugins/session-store.ts`)按委派继承律定宿主行属主,
|
|
28
|
-
* 律要的是**委派树根的见证**:根 id(行上本来就带 `rootSessionId`)+
|
|
29
|
-
*
|
|
30
|
-
* `{ sessionId: root, delegationRoot }` 重解,不交座的行(
|
|
28
|
+
* 律要的是**委派树根的见证**:根 id(行上本来就带 `rootSessionId`)+ 判断那一刻的根行住客 = 写口放行的那一位(赎回腿解析器那一读;不是那一位时
|
|
29
|
+
* 写口整条拒,走不到这里)—— 见证恒带住客。所以**每一行**都交出它({@link ScenarioContext} 的 `delegationRoot`):交座的一层行以
|
|
30
|
+
* `{ sessionId: root, delegationRoot }` 重解,不交座的行(更深的行、根属主不是卡的租户的一层行)以 `{ delegationRoot }` 重解 —— 只喂停驻挂点,不挂任何会话级设施、
|
|
31
31
|
* 不带 principal,交座判据(上面三条)一个字不动。行上没有根 ⇒ boot 期那只裸 Agent(没有根可交 ⇒ 律给哨兵)。挂点那一刻根行已不在 ⇒ 律给哨兵;在而不是见证的那一位
|
|
32
32
|
* (批准之后删了、同 id 被重新登记)⇒ 停驻否决 —— 两件都在挂点那一侧判,与这里无关。
|
|
33
33
|
*/
|
|
@@ -41,15 +41,12 @@ export interface ParkedReviveSurface {
|
|
|
41
41
|
readonly mountsCallerTool?: (tool: ToolSpec) => boolean;
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
44
|
-
* 按行解析赎回面的入参身份:`principal` = 权威 principal(取 checkpoint 的 scope 列,不取行上有损的 `row.scope`);`
|
|
45
|
-
*
|
|
44
|
+
* 按行解析赎回面的入参身份:`principal` = 权威 principal(取 checkpoint 的 scope 列,不取行上有损的 `row.scope`);`rootOccupant` = 写口放行的那一位
|
|
45
|
+
* 根会话住客(赎回腿认领之前解析器那一次行读;属主 `null` = 匿名会话)—— 型上恒在:接赎回腿的部署必须带执行席。
|
|
46
46
|
*/
|
|
47
47
|
export interface ParkedReviveSurfaceIdentity {
|
|
48
48
|
readonly principal: string | undefined;
|
|
49
|
-
readonly
|
|
50
|
-
readonly before: SessionFace;
|
|
51
|
-
readonly after: SessionFace;
|
|
52
|
-
};
|
|
49
|
+
readonly rootOccupant: SessionFace;
|
|
53
50
|
}
|
|
54
51
|
/** 按行解析赎回面。 */
|
|
55
52
|
export type ParkedReviveSurfaceFactory = (row: BackgroundAgentRecord, identity: ParkedReviveSurfaceIdentity) => ParkedReviveSurface;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEFAULT_SUBAGENT_TOOL_NAME
|
|
1
|
+
import { DEFAULT_SUBAGENT_TOOL_NAME } from "@sema-agent/core";
|
|
2
2
|
const agentOf = (tools) => tools.find((t) => t.name === DEFAULT_SUBAGENT_TOOL_NAME);
|
|
3
3
|
export function createParkedReviveSurface(defaultScenario) {
|
|
4
4
|
const bootTool = agentOf(defaultScenario({}).tools);
|
|
@@ -9,7 +9,7 @@ export function createParkedReviveSurface(defaultScenario) {
|
|
|
9
9
|
if (root === undefined)
|
|
10
10
|
return { reviveTool: bootTool };
|
|
11
11
|
const seated = holdsSeat(row.parentSessionId === root, identity);
|
|
12
|
-
const delegationRoot = { sessionId: root, incarnation: identity.
|
|
12
|
+
const delegationRoot = { sessionId: root, incarnation: identity.rootOccupant.incarnation };
|
|
13
13
|
const tools = (seated ? defaultScenario({}, identity.principal, { sessionId: root, delegationRoot }) : defaultScenario({}, undefined, { delegationRoot })).tools;
|
|
14
14
|
const reviveTool = agentOf(tools);
|
|
15
15
|
if (reviveTool === undefined)
|
|
@@ -18,9 +18,6 @@ export function createParkedReviveSurface(defaultScenario) {
|
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
20
|
function holdsSeat(oneLevel, identity) {
|
|
21
|
-
|
|
22
|
-
if (!oneLevel || face === undefined || compareOccupants(face.before, face.after) !== "same")
|
|
23
|
-
return false;
|
|
24
|
-
return (face.before.owner ?? undefined) === identity.principal && (face.after.owner ?? undefined) === identity.principal;
|
|
21
|
+
return oneLevel && (identity.rootOccupant.owner ?? undefined) === identity.principal;
|
|
25
22
|
}
|
|
26
23
|
//# sourceMappingURL=parked-revive-surface.js.map
|
package/dist/peer-seat.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { A2aServerSpec, McpServerSpec, PeerAnswer, PeerQuestion, PeerSeatTarget } from "@sema-agent/core";
|
|
1
|
+
import type { A2aServerSpec, McpServerSpec, PeerAnswer, PeerQuestion, PeerSeat, PeerSeatTarget } from "@sema-agent/core";
|
|
2
2
|
import { type Logger } from "./observability/logger.js";
|
|
3
3
|
import { type SafeText } from "./trace/exit-seat.js";
|
|
4
4
|
type StdioArm = Extract<McpServerSpec["transport"], {
|
|
@@ -95,16 +95,22 @@ export interface PeerLease {
|
|
|
95
95
|
/** Stop holding. Entries no other holder holds are dropped now. A second call does nothing. */
|
|
96
96
|
release(): void;
|
|
97
97
|
}
|
|
98
|
-
/**
|
|
99
|
-
export interface PeerDeskSeat {
|
|
100
|
-
/** The pieces for one send, or a throw (refused: logged as one `peer_seat_refused` row naming protocol, peer and reason only). */
|
|
101
|
-
answer(question: PeerQuestion): Promise<PeerAnswer>;
|
|
102
|
-
}
|
|
103
|
-
/** Live entries and the host leases holding them, summed across entries — the registry's whole observable state. */
|
|
98
|
+
/** Live entries and their holders — host leases and engine holds — summed across entries: the registry's whole observable state. */
|
|
104
99
|
export interface PeerDeskSize {
|
|
105
100
|
readonly entries: number;
|
|
106
101
|
readonly holders: number;
|
|
107
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* The engine's seat as the desk is it (`RunnerDeps.peerSeat`, the probe's and the panels' `peerSeat`): `answer` the pieces for one send, or a
|
|
105
|
+
* throw (refused: logged as one `peer_seat_refused` row naming protocol, peer and reason only) — it reads no signal (the desk does no IO);
|
|
106
|
+
* `hold` / `release` one engine holder of a reference this desk minted. Every hold the desk cannot count throws — a reference this desk did not
|
|
107
|
+
* mint (any kind but `handle`), a handle whose entry is gone — and so does a release with no counted hold: the engine reads a hold that returns
|
|
108
|
+
* as held, so a quiet return would be a hold nobody keeps (security axis, fail-closed). The engine reports each throw once as `peer-seat-hold`;
|
|
109
|
+
* a refused hold holds nothing and gets no release, a refused release changes nothing.
|
|
110
|
+
*/
|
|
111
|
+
export interface PeerDeskSeat extends PeerSeat {
|
|
112
|
+
answer(question: PeerQuestion, signal?: AbortSignal): Promise<PeerAnswer>;
|
|
113
|
+
}
|
|
108
114
|
export interface PeerDesk {
|
|
109
115
|
readonly seat: PeerDeskSeat;
|
|
110
116
|
/**
|
package/dist/peer-seat.js
CHANGED
|
@@ -104,7 +104,7 @@ export function createPeerDesk(opts = {}) {
|
|
|
104
104
|
return entry;
|
|
105
105
|
};
|
|
106
106
|
const dropIfUnheld = (id, entry) => {
|
|
107
|
-
if (entry.hosts.size === 0)
|
|
107
|
+
if (entry.hosts.size === 0 && entry.engineHolds === 0)
|
|
108
108
|
table.delete(id);
|
|
109
109
|
};
|
|
110
110
|
const liveState = (lease) => {
|
|
@@ -151,7 +151,41 @@ export function createPeerDesk(opts = {}) {
|
|
|
151
151
|
return { kind: "carry", env: entry.pieces };
|
|
152
152
|
}
|
|
153
153
|
};
|
|
154
|
+
const handleIdOf = (ref) => {
|
|
155
|
+
switch (ref.kind) {
|
|
156
|
+
case "handle":
|
|
157
|
+
return ref.id;
|
|
158
|
+
case "env":
|
|
159
|
+
case "vault":
|
|
160
|
+
case "assertion":
|
|
161
|
+
return undefined;
|
|
162
|
+
default: {
|
|
163
|
+
const unknownKind = ref;
|
|
164
|
+
void unknownKind;
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
};
|
|
154
169
|
const seat = Object.freeze({
|
|
170
|
+
hold: (ref) => {
|
|
171
|
+
const id = handleIdOf(ref);
|
|
172
|
+
if (id === undefined)
|
|
173
|
+
throw new Error("peer desk: hold of a reference this desk did not mint — not held");
|
|
174
|
+
const entry = table.get(id);
|
|
175
|
+
if (entry === undefined)
|
|
176
|
+
throw new Error("peer desk: hold of a handle whose entry is gone — not held (a hold never revives an entry)");
|
|
177
|
+
entry.engineHolds += 1;
|
|
178
|
+
},
|
|
179
|
+
release: (ref) => {
|
|
180
|
+
const id = handleIdOf(ref);
|
|
181
|
+
if (id === undefined)
|
|
182
|
+
throw new Error("peer desk: release of a reference this desk did not mint — nothing released");
|
|
183
|
+
const entry = table.get(id);
|
|
184
|
+
if (entry === undefined || entry.engineHolds === 0)
|
|
185
|
+
throw new Error("peer desk: release of a handle with no counted engine hold — nothing released");
|
|
186
|
+
entry.engineHolds -= 1;
|
|
187
|
+
dropIfUnheld(id, entry);
|
|
188
|
+
},
|
|
155
189
|
answer: async (question) => {
|
|
156
190
|
const target = question.peer;
|
|
157
191
|
const ref = target.credentials;
|
|
@@ -193,7 +227,7 @@ export function createPeerDesk(opts = {}) {
|
|
|
193
227
|
const id = mintId(canonical);
|
|
194
228
|
const known = table.get(id);
|
|
195
229
|
if (known === undefined) {
|
|
196
|
-
table.set(id, { binding, lane, pieces, hosts: new Set([holder.id]) });
|
|
230
|
+
table.set(id, { binding, lane, pieces, hosts: new Set([holder.id]), engineHolds: 0 });
|
|
197
231
|
}
|
|
198
232
|
else if (canonicalOf(known.binding, known.lane, known.pieces) !== canonical) {
|
|
199
233
|
throw new PeerDeskError("peer_binding_conflict", "peer desk: two different declarations were minted one id; the registered one stands");
|
|
@@ -212,7 +246,7 @@ export function createPeerDesk(opts = {}) {
|
|
|
212
246
|
size: () => {
|
|
213
247
|
let holders = 0;
|
|
214
248
|
for (const entry of table.values())
|
|
215
|
-
holders += entry.hosts.size;
|
|
249
|
+
holders += entry.hosts.size + entry.engineHolds;
|
|
216
250
|
return { entries: table.size, holders };
|
|
217
251
|
},
|
|
218
252
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ExecutionLane } from "../execution-lane-caps.js";
|
|
1
2
|
import type { AskState } from "../approval-ask-machine.js";
|
|
2
3
|
import type { SettleCause } from "../approval-deny-reasons.js";
|
|
3
4
|
import type { AskCallCoordinate, AskRow, AskTerminalClaimIntent, AskTerminalClaimOpts, AskTerminalClaimOutcome, AskTransitionPatch, ApprovalAskStore, BatchRow, BindGateInput, BindResult, DecideAskInput, DecideResult, EnsureAskResult, ExpireResult, NewAskRow } from "./approval-ask-store-sql.js";
|
|
@@ -209,6 +210,12 @@ export declare class FileApprovalAskStore implements ApprovalAskStore {
|
|
|
209
210
|
};
|
|
210
211
|
/** 释放追加 fd(`LocalBackend.close` → 优雅重启重开)。不包 try/catch 的理由同
|
|
211
212
|
* {@link FileApprovalNonceStore.dispose}:core 的 `AppendLog.close()` 自己就是全函数。 */
|
|
213
|
+
/**
|
|
214
|
+
* 执行放置切换 ④b 的 local 腿(一次性迁移登记表 M32;`LocalBackend.runOneTimeMigrations` 在任何读者之前调):升级前写下的问(日志 `ensureAsk` 记录行 /
|
|
215
|
+
* 压实快照行没有 `executionLane`)记成本部署车道表的唯一项,然后**一次压实**把整本账重写成带车道的快照(压实 = 既有整本原子重写)。
|
|
216
|
+
* 没有缺车道的行 ⇒ 零写。车道表不是一元而还有缺车道的行 ⇒ 抛与 SQL 同一只拒(`backfill_ambiguous`,由调用方铸),一个字节没写。
|
|
217
|
+
*/
|
|
218
|
+
backfillExecutionLanes(lanes: readonly ExecutionLane[], refuse: (pending: number) => never): number;
|
|
212
219
|
dispose(): void;
|
|
213
220
|
}
|
|
214
221
|
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { closeSync, copyFileSync, openSync, readSync, statSync, existsSync } from "node:fs";
|
|
2
|
+
import { isExecutionLane } from "../execution-lane-caps.js";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import { AppendLog, atomicWriteFile, ensureDir } from "@sema-agent/core";
|
|
4
5
|
import { replayJsonlSkipping } from "../observability/corrupt-read-seat.js";
|
|
@@ -295,6 +296,16 @@ export class FileApprovalAskStore {
|
|
|
295
296
|
replayStats() {
|
|
296
297
|
return { rejected: this.replayRejectedCount, corruptLines: this.corruptLineCount, records: this.records };
|
|
297
298
|
}
|
|
299
|
+
backfillExecutionLanes(lanes, refuse) {
|
|
300
|
+
const pending = this.core.exportRows().asks.filter((r) => !isExecutionLane(String(r.executionLane ?? ""))).length;
|
|
301
|
+
if (pending === 0)
|
|
302
|
+
return 0;
|
|
303
|
+
if (lanes.length !== 1)
|
|
304
|
+
refuse(pending);
|
|
305
|
+
const filled = this.core.fillMissingExecutionLanes(lanes[0]);
|
|
306
|
+
this.compact();
|
|
307
|
+
return filled;
|
|
308
|
+
}
|
|
298
309
|
dispose() {
|
|
299
310
|
this.log?.close();
|
|
300
311
|
this.log = undefined;
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
* - resolveProvisional 故意不经 canAskTransition(同 SQL twin 头注:这是版本化补偿的例外通道)。
|
|
30
30
|
*/
|
|
31
31
|
import { type AskState, type BatchState } from "../approval-ask-machine.js";
|
|
32
|
+
import { type ExecutionLane } from "../execution-lane-caps.js";
|
|
32
33
|
import type { AskCallCoordinate, AskDecision, AskRow, AskTerminalClaimIntent, AskTerminalClaimOpts, AskTerminalClaimOutcome, AskTransitionPatch, ApprovalAskStore, BatchRow, BindGateInput, BindResult, DecideAskInput, EnsureAskResult, DecideResult, ExpireResult, NewAskRow } from "./approval-ask-store-sql.js";
|
|
33
34
|
/**
|
|
34
35
|
* [ref] 的 `updatedInput` **入店编码**——两 twin 共用的那一只(本函数是唯一真源)。
|
|
@@ -72,6 +73,11 @@ export declare class InMemoryApprovalAskStore implements ApprovalAskStore {
|
|
|
72
73
|
* 两种可变性)。
|
|
73
74
|
*/
|
|
74
75
|
exportRows(): ApprovalAskRowsSnapshot;
|
|
76
|
+
/**
|
|
77
|
+
* 执行放置切换 ④b 的 local 腿(File 形的内核口;`FileApprovalAskStore.backfillExecutionLanes` 调):出处车道还没记下的行(升级前写下的日志 / 快照行
|
|
78
|
+
* 装回后这一格缺席)记成 `lane`(一元车道表的唯一项 —— 调用方判过)。返回改了的行数;只碰缺这一格的行。
|
|
79
|
+
*/
|
|
80
|
+
fillMissingExecutionLanes(lane: ExecutionLane): number;
|
|
75
81
|
/** [ref]:一行的**同步**点读(拷贝;纯读,零判定)—— File 形在写口的同步前缀里用它(见类顶注)。 */
|
|
76
82
|
peekAsk(askId: string): AskRow | null;
|
|
77
83
|
/** [ref]:把快照里的行**原样**装回两张 Map(同键覆盖)。零谓词、零状态机 —— 它不是一条转移,是重放
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ASK_TERMINAL, canAskTransition } from "../approval-ask-machine.js";
|
|
2
|
+
import { isExecutionLane } from "../execution-lane-caps.js";
|
|
2
3
|
import { APPROVAL_ENDING_CAUSES } from "../approval-deny-reasons.js";
|
|
3
4
|
import { askRowCarriesOwner, askRowOnCall, assertDecideSettledBy, assertIdempotencyKeyShape, assertIntegralEpochMs, assertReapBounds, ApprovalAskReadAbortedError, claimTargetState, POLICY_REFUSAL_DECISION, projectAskTerminalClaimTruth, projectDecideReading } from "./approval-ask-store-sql.js";
|
|
4
5
|
function applyPatch(row, patch) {
|
|
@@ -41,6 +42,16 @@ export class InMemoryApprovalAskStore {
|
|
|
41
42
|
exportRows() {
|
|
42
43
|
return { asks: [...this.asks.values()].map((r) => ({ ...r })), batches: [...this.batches.values()].map((b) => ({ ...b })) };
|
|
43
44
|
}
|
|
45
|
+
fillMissingExecutionLanes(lane) {
|
|
46
|
+
let filled = 0;
|
|
47
|
+
for (const [askId, row] of this.asks) {
|
|
48
|
+
if (isExecutionLane(String(row.executionLane ?? "")))
|
|
49
|
+
continue;
|
|
50
|
+
this.asks.set(askId, { ...row, executionLane: lane });
|
|
51
|
+
filled += 1;
|
|
52
|
+
}
|
|
53
|
+
return filled;
|
|
54
|
+
}
|
|
44
55
|
peekAsk(askId) {
|
|
45
56
|
const row = this.asks.get(askId);
|
|
46
57
|
return row ? { ...row } : null;
|
|
@@ -74,6 +85,7 @@ export class InMemoryApprovalAskStore {
|
|
|
74
85
|
sourceTaskId: row.sourceTaskId,
|
|
75
86
|
sessionId: row.sessionId,
|
|
76
87
|
owner: row.owner ?? null,
|
|
88
|
+
executionLane: row.executionLane,
|
|
77
89
|
batchId: row.batchId,
|
|
78
90
|
toolCallId: row.toolCallId,
|
|
79
91
|
legKey: row.legKey,
|
|
@@ -44,6 +44,7 @@ import { type AskState, type BatchState } from "../approval-ask-machine.js";
|
|
|
44
44
|
import type { HostSettledBy } from "../host-decision.js";
|
|
45
45
|
import { type AskRowCause, type SettleCause } from "../approval-deny-reasons.js";
|
|
46
46
|
import { type IndexSpec } from "./ensure-index.js";
|
|
47
|
+
import { type ExecutionLane } from "../execution-lane-caps.js";
|
|
47
48
|
export type AskDecision = "approve" | "deny";
|
|
48
49
|
export interface AskRow {
|
|
49
50
|
askId: string;
|
|
@@ -52,6 +53,11 @@ export interface AskRow {
|
|
|
52
53
|
sessionId: string;
|
|
53
54
|
/** 租户 scope——哨兵归一在消费层(车2+),店里存原值(设计定稿 §3)。 */
|
|
54
55
|
owner: string | null;
|
|
56
|
+
/**
|
|
57
|
+
* 造这只问的那条执行的车道(审批出处带车道:问行唯一铸点 `ensureAsk` 与 `taskId` / `sessionId` 同处写;既有行命中一个字节不改)。
|
|
58
|
+
* 读根授权只读问自己记下的车道。存量行随执行放置切换的一次性迁移回填(SQL `approval_ask.execution_lane`、local 问账日志压实)。
|
|
59
|
+
*/
|
|
60
|
+
executionLane: ExecutionLane;
|
|
55
61
|
batchId: string;
|
|
56
62
|
toolCallId: string;
|
|
57
63
|
/** 车3 §3.2′ 换轴:数值 `leg` → `legKey`(= `sha256(resume token)` 的 hex,首腿空串)。语义与派生
|
|
@@ -174,6 +180,8 @@ export interface NewAskRow {
|
|
|
174
180
|
sourceTaskId: string;
|
|
175
181
|
sessionId: string;
|
|
176
182
|
owner?: string | null;
|
|
183
|
+
/** 出处的执行车道(必填;语义见 {@link AskRow.executionLane})。 */
|
|
184
|
+
executionLane: ExecutionLane;
|
|
177
185
|
batchId: string;
|
|
178
186
|
toolCallId: string;
|
|
179
187
|
legKey: string;
|
|
@@ -562,6 +570,14 @@ export interface ApprovalAskStore {
|
|
|
562
570
|
getBatch(batchId: string): Promise<BatchRow | null>;
|
|
563
571
|
}
|
|
564
572
|
export declare const APPROVAL_ASK_TABLE = "approval_ask";
|
|
573
|
+
/**
|
|
574
|
+
* `approval_ask.execution_lane` 的列定义(两方言;唯一属主)—— 建表 DDL 内联它,存量库的加列只由执行放置切换的一次性迁移做(`one-time-migrations.ts` M32 步 ②),
|
|
575
|
+
* 那一步也读这里的定义。`DEFAULT ''` 只为存量库一句加列;回填(同迁移步 ④b)之后没有 '' 行,读口对 '' 与词表外的值一律响亮拒({@link executionLaneColumnOf})。
|
|
576
|
+
*/
|
|
577
|
+
export declare const APPROVAL_ASK_EXECUTION_LANE_COLUMN: {
|
|
578
|
+
readonly mysql: "execution_lane VARCHAR(32) NOT NULL DEFAULT ''";
|
|
579
|
+
readonly pg: "execution_lane VARCHAR(32) COLLATE \"C\" NOT NULL DEFAULT ''";
|
|
580
|
+
};
|
|
565
581
|
export declare const APPROVAL_BATCH_TABLE = "approval_batch";
|
|
566
582
|
/**
|
|
567
583
|
* TiDB/MySQL-protocol 侧的两条建表语句,**抽成导出数组**(车3 刀 3a):`tidb-pool.ts` 的
|
|
@@ -571,7 +587,8 @@ export declare const APPROVAL_BATCH_TABLE = "approval_batch";
|
|
|
571
587
|
* 下面的 `ensureTiDBApprovalAskSchema` 保留为遍历这个数组的薄壳(集成测试直接调它建表,
|
|
572
588
|
* 不必为了建两张表去拉起整套中央 schema)。
|
|
573
589
|
*
|
|
574
|
-
* SCHEMA POLICY 同 `tidb-pool.ts` 头注:纯 CREATE,禁 ALTER 增量 seam
|
|
590
|
+
* SCHEMA POLICY 同 `tidb-pool.ts` 头注:纯 CREATE,禁 ALTER 增量 seam,加列直接改这里(新建库随它到货;带数据的存量库由列集对账拒启并打印 `ALTER`,部署者自己执行;
|
|
591
|
+
* 改列类型 / 可空性列集对账看不见,另走一次性迁移;只有空库 / 开发机可以整库重建〔销毁全部数据〕)
|
|
575
592
|
* (`docs/schema/baseline-mysql.sql` 是由这些文本机器录制出的**产物**,永不手改)。
|
|
576
593
|
*
|
|
577
594
|
* ── 列宽的**测量依据**([ref] A10;U2 逐列取证在档)──────────────────────────────────────────────────
|
|
@@ -619,18 +636,18 @@ export declare function ensurePgApprovalAskSchema(q: PgQueryFn): Promise<void>;
|
|
|
619
636
|
* [ref] 升级前置断言 —— **拒启**,不是 warn(形照 [ref] 的 `assertPermissionRuleApprovalSchema` /
|
|
620
637
|
* [ref] 的 `assertToolResultProvenanceSchema` 两条先例)。
|
|
621
638
|
*
|
|
622
|
-
* 病:本仓不发 `ALTER TABLE`(SCHEMA POLICY
|
|
639
|
+
* 病:本仓不发 `ALTER TABLE`(SCHEMA POLICY:代码是唯一真源,存量库缺列由部署者按拒启句打印的 ALTER 自己执行)。于是一台**没跟上**就升上来
|
|
623
640
|
* 的部署,`CREATE TABLE IF NOT EXISTS` 对存量 `approval_ask` 是空操作 —— 本车新加的
|
|
624
641
|
* `rule_command` / `rule_scope_root` 两列不在,而 `ensureAsk` 的 INSERT 与 `askCols` 的每一条读
|
|
625
642
|
* **无条件**引用它们。后果不是「少一个新功能」,是**整条流内审批协议**在那台机器上是坏的:
|
|
626
643
|
* · 每一只 ask 的 `ensureAsk` 撞 unknown column ⇒ 被 `noteStoreError` 吞掉 ⇒ 整只 ask 退回 park 路由
|
|
627
644
|
* (卡不落库、`GET /v1/approvals` 的重放面恒空、迟到决议腿恒 404);
|
|
628
645
|
* · 而 `/v1/capabilities` 的 `streamApproval` 仍然报 active ⇒「says yes ⟺ route works」整句为假。
|
|
629
|
-
*
|
|
646
|
+
* 没有这道断言,运维得不到**任何**「这次升级必须先给表加列」的信号 —— 正是 [ref]「禁静默降级」要挡的形。
|
|
630
647
|
*
|
|
631
648
|
* 判据 = **能力探测**(恒空的 `WHERE 1=0` 读)+ **方言的缺列错误码**(判据属主 `sql-errors.ts` 的
|
|
632
|
-
* {@link isMissingColumnError});其余错误**原样 rethrow**,一个字都不加工 ——
|
|
633
|
-
*
|
|
649
|
+
* {@link isMissingColumnError});其余错误**原样 rethrow**,一个字都不加工 —— 把一次瞬时故障诊断成「缺列」的拒因
|
|
650
|
+
* 比它要挡的缺陷更贵([ref] 那两轮收窄的结论逐字继承)。拒因的出路是**闭集三条**(与整库对账同一份,{@link describeProbedTableDrift}),没有销毁性的一条。
|
|
634
651
|
*
|
|
635
652
|
* 🔴 位置与两条先例同款:**不在** `ensureSchema` 里(那条通道的契约是「只发 CREATE」,有运行时门看着),
|
|
636
653
|
* 放在 DDL 之后、任何路由装配之前;且**只在流内审批协议真会上场时**跑(判据 = 与协调器同一个符号
|
|
@@ -2,8 +2,10 @@ import { mysqlDriver, pgDriver, dialectJsonEncoder } from "./sql-driver.js";
|
|
|
2
2
|
import { ASK_TERMINAL, canAskTransition } from "../approval-ask-machine.js";
|
|
3
3
|
import { APPROVAL_ENDING_CAUSES, causeOfAskRow } from "../approval-deny-reasons.js";
|
|
4
4
|
import { isDupKeyError, isMissingColumnError } from "./sql-errors.js";
|
|
5
|
+
import { describeProbedTableDrift } from "./schema-drift-sentence.js";
|
|
5
6
|
import { decodeJsonColumn, foldJsonColumn, jsonColumnText, readJsonObjectColumn, readTriStateJsonValueColumn } from "./sql-json-column.js";
|
|
6
7
|
import { ensureIndexes } from "./ensure-index.js";
|
|
8
|
+
import { isExecutionLane } from "../execution-lane-caps.js";
|
|
7
9
|
export function assertIntegralEpochMs(field, value) {
|
|
8
10
|
if (!Number.isSafeInteger(value)) {
|
|
9
11
|
throw new TypeError(`approval-ask store: ${field} must be an integer epoch-ms (BIGINT column), got ${String(value)}`);
|
|
@@ -99,6 +101,17 @@ export function projectDecideReading(row, owner) {
|
|
|
99
101
|
return askRowCarriesOwner(row, owner) ? row : null;
|
|
100
102
|
}
|
|
101
103
|
export const APPROVAL_ASK_TABLE = "approval_ask";
|
|
104
|
+
export const APPROVAL_ASK_EXECUTION_LANE_COLUMN = {
|
|
105
|
+
mysql: "execution_lane VARCHAR(32) NOT NULL DEFAULT ''",
|
|
106
|
+
pg: `execution_lane VARCHAR(32) COLLATE "C" NOT NULL DEFAULT ''`,
|
|
107
|
+
};
|
|
108
|
+
function executionLaneColumnOf(raw, askId) {
|
|
109
|
+
const lane = typeof raw === "string" ? raw : "";
|
|
110
|
+
if (!isExecutionLane(lane)) {
|
|
111
|
+
throw new Error(`approval-ask-store: ask ${askId} carries execution_lane ${JSON.stringify(lane)}, which is not an execution lane — the execution placement switch migration backfills every row; refusing rather than guess the lane this ask was raised on`);
|
|
112
|
+
}
|
|
113
|
+
return lane;
|
|
114
|
+
}
|
|
102
115
|
export const APPROVAL_BATCH_TABLE = "approval_batch";
|
|
103
116
|
export const TIDB_APPROVAL_ASK_STATEMENTS = [
|
|
104
117
|
`CREATE TABLE IF NOT EXISTS ${APPROVAL_BATCH_TABLE} (
|
|
@@ -118,6 +131,9 @@ export const TIDB_APPROVAL_ASK_STATEMENTS = [
|
|
|
118
131
|
source_task_id VARCHAR(255) NOT NULL,
|
|
119
132
|
session_id VARCHAR(64) NOT NULL,
|
|
120
133
|
owner VARCHAR(190) NULL,
|
|
134
|
+
-- execution_lane:造这只问的那条执行的车道(审批出处带车道;ensureAsk 铸行时写,既有行命中不改)。DEFAULT '' 只为存量库一句加列
|
|
135
|
+
-- (一次性迁移加列并按根会话放置回填,迁移后没有 '' 行)。
|
|
136
|
+
${APPROVAL_ASK_EXECUTION_LANE_COLUMN.mysql},
|
|
121
137
|
batch_id VARCHAR(64) NOT NULL,
|
|
122
138
|
tool_call_id VARCHAR(255) NOT NULL,
|
|
123
139
|
-- leg_key: 车3 §3.2′ 身份轴——sha256(resume checkpoint token) 的 hex(64 字符),首腿存空串。
|
|
@@ -172,7 +188,7 @@ export const TIDB_APPROVAL_ASK_STATEMENTS = [
|
|
|
172
188
|
UNIQUE KEY uq_approval_ask_idem (task_id, idempotency_key),
|
|
173
189
|
KEY idx_ask_task_state (task_id, state),
|
|
174
190
|
-- idx_ask_session_state: 车3 §5.1 的 sync 腿开流重放读口(listPendingBySession)的支撑索引。
|
|
175
|
-
-- 表尚未进生产
|
|
191
|
+
-- 表尚未进生产 ⇒ 此刻加索引零迁移成本,晚一车加就要动生产表。
|
|
176
192
|
KEY idx_ask_session_state (session_id, state),
|
|
177
193
|
KEY idx_ask_batch (batch_id),
|
|
178
194
|
KEY idx_ask_state (state)
|
|
@@ -206,6 +222,8 @@ export async function ensurePgApprovalAskSchema(q) {
|
|
|
206
222
|
source_task_id VARCHAR(255) COLLATE "C" NOT NULL,
|
|
207
223
|
session_id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
208
224
|
owner VARCHAR(190) COLLATE "C",
|
|
225
|
+
-- execution_lane —— MySQL twin 逐字同形(语义见那侧注)。
|
|
226
|
+
${APPROVAL_ASK_EXECUTION_LANE_COLUMN.pg},
|
|
209
227
|
batch_id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
210
228
|
tool_call_id VARCHAR(255) COLLATE "C" NOT NULL,
|
|
211
229
|
-- leg_key: 车3 §3.2′ 身份轴——sha256(resume checkpoint token) 的 hex(64 字符),首腿存空串
|
|
@@ -258,18 +276,16 @@ export async function assertApprovalAskRuleMaterialSchema(query, dialect) {
|
|
|
258
276
|
`persisted on the row too, so a replica that does not hold the waiting leg replays the approved bytes; every ask-row ` +
|
|
259
277
|
`write and read names these columns unconditionally, so on this table the whole in-stream approval protocol degrades silently (no card ` +
|
|
260
278
|
`is persisted, the replay list stays empty, late decisions 404) while /v1/capabilities still reports it active. ` +
|
|
261
|
-
`
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
`
|
|
265
|
-
|
|
266
|
-
`in-flight card falls back to the durable gate (\`POST /v1/approvals/:sessionId/decide\`). ` +
|
|
267
|
-
`Underlying probe error: ${err instanceof Error ? err.message : String(err)}`);
|
|
279
|
+
`The table holds IN-FLIGHT approval asks (cards awaiting an answer) plus settled bookkeeping; the durable park face ` +
|
|
280
|
+
`(checkpoint rows) and the persisted rules themselves are untouched, so an in-flight card falls back to the durable gate ` +
|
|
281
|
+
`(\`POST /v1/approvals/:sessionId/decide\`) until the columns exist.\n` +
|
|
282
|
+
`Underlying probe error: ${err instanceof Error ? err.message : String(err)}\n\n` +
|
|
283
|
+
(await describeProbedTableDrift(query, dialect, [APPROVAL_ASK_TABLE])));
|
|
268
284
|
}
|
|
269
285
|
}
|
|
270
286
|
function askCols(dialect) {
|
|
271
287
|
return [
|
|
272
|
-
"ask_id, task_id, source_task_id, session_id, owner, batch_id, tool_call_id, leg_key, parent_tool_call_id, bound_input_hash, state",
|
|
288
|
+
"ask_id, task_id, source_task_id, session_id, owner, execution_lane, batch_id, tool_call_id, leg_key, parent_tool_call_id, bound_input_hash, state",
|
|
273
289
|
"provisional, rev, decision",
|
|
274
290
|
jsonColumnText(dialect, "decision_actor"),
|
|
275
291
|
"decision_note, settled_by",
|
|
@@ -307,6 +323,7 @@ function mapAskRowBody(r, askId) {
|
|
|
307
323
|
sourceTaskId: String(r.source_task_id),
|
|
308
324
|
sessionId: String(r.session_id),
|
|
309
325
|
owner: strOrNull(r.owner),
|
|
326
|
+
executionLane: executionLaneColumnOf(r.execution_lane, askId),
|
|
310
327
|
batchId: String(r.batch_id),
|
|
311
328
|
toolCallId: String(r.tool_call_id),
|
|
312
329
|
legKey: String(r.leg_key),
|
|
@@ -410,14 +427,15 @@ export class SqlApprovalAskStore {
|
|
|
410
427
|
try {
|
|
411
428
|
await conn.begin();
|
|
412
429
|
await conn.query(this.q(`INSERT IGNORE INTO ${APPROVAL_BATCH_TABLE} (batch_id, task_id, state, rev, created_at_ms, updated_at_ms) VALUES (?, ?, 'OPEN', 0, ?, ?)`, `INSERT INTO ${APPROVAL_BATCH_TABLE} (batch_id, task_id, state, rev, created_at_ms, updated_at_ms) VALUES ($1, $2, 'OPEN', 0, $3, $4) ON CONFLICT (batch_id) DO NOTHING`), [row.batchId, row.taskId, now, now]);
|
|
413
|
-
const askInsert = await conn.query(this.q(`INSERT IGNORE INTO ${APPROVAL_ASK_TABLE} (ask_id, task_id, source_task_id, session_id, owner, batch_id, tool_call_id, leg_key, parent_tool_call_id, bound_input_hash, state, provisional, rev, decision, decision_actor, decision_note, settled_by, decided_at_ms, denied_reason, gate_token, gate_bound_call_id, gate_bound_input_hash, idempotency_key, card_json, rule_command, rule_scope_root, schema_version, expires_at_ms, created_at_ms, updated_at_ms) ` +
|
|
414
|
-
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'STREAM_PENDING', 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, ?, ?, ?, ?, ?, ?, ?)", `INSERT INTO ${APPROVAL_ASK_TABLE} (ask_id, task_id, source_task_id, session_id, owner, batch_id, tool_call_id, leg_key, parent_tool_call_id, bound_input_hash, state, provisional, rev, decision, decision_actor, decision_note, settled_by, decided_at_ms, denied_reason, gate_token, gate_bound_call_id, gate_bound_input_hash, idempotency_key, card_json, rule_command, rule_scope_root, schema_version, expires_at_ms, created_at_ms, updated_at_ms) ` +
|
|
415
|
-
"VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, 'STREAM_PENDING', 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, $
|
|
430
|
+
const askInsert = await conn.query(this.q(`INSERT IGNORE INTO ${APPROVAL_ASK_TABLE} (ask_id, task_id, source_task_id, session_id, owner, execution_lane, batch_id, tool_call_id, leg_key, parent_tool_call_id, bound_input_hash, state, provisional, rev, decision, decision_actor, decision_note, settled_by, decided_at_ms, denied_reason, gate_token, gate_bound_call_id, gate_bound_input_hash, idempotency_key, card_json, rule_command, rule_scope_root, schema_version, expires_at_ms, created_at_ms, updated_at_ms) ` +
|
|
431
|
+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'STREAM_PENDING', 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, ?, ?, ?, ?, ?, ?, ?)", `INSERT INTO ${APPROVAL_ASK_TABLE} (ask_id, task_id, source_task_id, session_id, owner, execution_lane, batch_id, tool_call_id, leg_key, parent_tool_call_id, bound_input_hash, state, provisional, rev, decision, decision_actor, decision_note, settled_by, decided_at_ms, denied_reason, gate_token, gate_bound_call_id, gate_bound_input_hash, idempotency_key, card_json, rule_command, rule_scope_root, schema_version, expires_at_ms, created_at_ms, updated_at_ms) ` +
|
|
432
|
+
"VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, 'STREAM_PENDING', 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, $12, $13, $14, $15, $16, $17, $18) ON CONFLICT (ask_id) DO NOTHING"), [
|
|
416
433
|
row.askId,
|
|
417
434
|
row.taskId,
|
|
418
435
|
row.sourceTaskId,
|
|
419
436
|
row.sessionId,
|
|
420
437
|
row.owner ?? null,
|
|
438
|
+
row.executionLane,
|
|
421
439
|
row.batchId,
|
|
422
440
|
row.toolCallId,
|
|
423
441
|
row.legKey,
|