@sema-agent/server 7.143.0 → 7.144.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 +166 -0
- package/USAGE.md +24 -14
- package/dist/approval-card.d.ts +31 -14
- package/dist/approval-card.js +8 -8
- package/dist/boot/execution-env.js +1 -1
- package/dist/boot/runner-deps.d.ts +10 -5
- package/dist/boot/runner-deps.js +5 -4
- package/dist/boot/shutdown.d.ts +1 -1
- package/dist/boot/shutdown.js +4 -2
- package/dist/boot/stage-01-config.js +2 -0
- package/dist/boot/stage-07-capability-layer.js +1 -1
- package/dist/boot/stage-10-http-server.js +2 -7
- package/dist/capabilities/scenarios.d.ts +14 -18
- package/dist/capabilities/scenarios.js +1 -2
- package/dist/config-d-migration.d.ts +2 -0
- package/dist/delegated-session-owner.d.ts +33 -151
- package/dist/delegated-session-owner.js +1 -83
- package/dist/entry-mode-clamp-notice.d.ts +1 -1
- package/dist/execution-placement.d.ts +7 -4
- package/dist/hooks/classic-stations.d.ts +3 -1
- package/dist/hooks/classic-stations.js +2 -0
- package/dist/http/principal-gate.d.ts +1 -1
- package/dist/http/refusal-body.d.ts +7 -3
- package/dist/http/refusal-body.js +1 -0
- package/dist/http/resume-legs.js +11 -4
- package/dist/http/routes/approvals-assistant.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lane-home-probe.d.ts +15 -0
- package/dist/lane-home-probe.js +4 -0
- package/dist/observability/corrupt-read-seat.d.ts +5 -9
- package/dist/observability/corrupt-read-seat.js +1 -2
- package/dist/observability/fail-open.d.ts +12 -8
- package/dist/observability/fail-open.js +12 -8
- package/dist/observability/run-terminal.d.ts +1 -1
- package/dist/observability/run-terminal.js +2 -2
- package/dist/parked-revive-surface.d.ts +6 -7
- package/dist/peer-inbound-face.d.ts +29 -34
- package/dist/peer-inbound-face.js +28 -63
- package/dist/plugins/caching-session-store.d.ts +0 -3
- package/dist/plugins/caching-session-store.js +0 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +41 -34
- package/dist/plugins/checkpoint-store-sql.js +99 -38
- package/dist/plugins/fork-routing-session-store.d.ts +8 -1
- package/dist/plugins/fork-routing-session-store.js +6 -0
- package/dist/plugins/local-checkpoint-store.d.ts +6 -8
- package/dist/plugins/local-checkpoint-store.js +3 -2
- package/dist/plugins/local-session-store.d.ts +64 -29
- package/dist/plugins/local-session-store.js +126 -56
- package/dist/plugins/local-session-window.d.ts +4 -2
- package/dist/plugins/one-time-migrations.d.ts +12 -12
- package/dist/plugins/one-time-migrations.js +12 -12
- package/dist/plugins/pg-session-storage.d.ts +1 -9
- package/dist/plugins/pg-session-storage.js +2 -19
- package/dist/plugins/remote-env-host.d.ts +8 -4
- package/dist/plugins/remote-env-host.js +7 -9
- package/dist/plugins/remote-shell.js +12 -2
- package/dist/plugins/schema-consistency.d.ts +5 -1
- package/dist/plugins/session-fork.d.ts +1 -1
- package/dist/plugins/session-ownership.d.ts +9 -0
- package/dist/plugins/session-ownership.js +6 -0
- package/dist/plugins/session-store.d.ts +13 -16
- package/dist/plugins/session-store.js +9 -10
- package/dist/plugins/sql-json-column.d.ts +24 -2
- package/dist/plugins/sql-json-column.js +38 -3
- package/dist/plugins/store-backend.js +3 -2
- package/dist/plugins/tidb-session-store.d.ts +2 -14
- package/dist/plugins/tidb-session-store.js +2 -20
- package/dist/run-local.js +2 -1
- package/dist/runs.js +4 -0
- package/dist/security.d.ts +0 -4
- package/dist/session-error-wire.d.ts +24 -25
- package/dist/session-error-wire.js +4 -5
- package/dist/session-incarnation.d.ts +2 -2
- package/dist/session-notify-door.d.ts +2 -2
- package/dist/session-notify-door.js +5 -1
- package/dist/tool-approval.d.ts +3 -1
- package/dist/tool-approval.js +47 -18
- package/dist/trace/core-keyset-guard.d.ts +1 -1
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +1 -1
- package/dist/trace/ledger-sink.js +12 -4
- package/dist/trace/model-segment.d.ts +25 -6
- package/dist/trace/model-segment.js +47 -6
- package/dist/trace/project.d.ts +8 -2
- package/dist/trace/project.js +6 -4
- package/dist/trace/redact.d.ts +11 -0
- package/dist/trace/redact.js +19 -3
- package/dist/trace/self-minted-wire.d.ts +88 -9
- package/dist/trace/self-minted-wire.js +25 -3
- package/package.json +2 -2
- package/wire-changes/server@7.144.0.json +291 -0
- package/dist/session-records-skipped-notice.d.ts +0 -21
- package/dist/session-records-skipped-notice.js +0 -27
|
@@ -13,7 +13,7 @@ import { IS_WIN32, resolveHostShell, hostShell, pinHostShell, spawnGroupOptions,
|
|
|
13
13
|
import { BackgroundShellManager, seedMemStream, feedMemStream, drainMemStream } from "./background-shell-support.js";
|
|
14
14
|
import { FileError, NodeExecutionEnv, inheritEnvPolicyProblem, ExecutionError, RemoteExecutionError, RollingTailBuffer, walkSymlinkChain, markTruncated, SchedulerError, BackgroundShellError, } from "@sema-agent/core";
|
|
15
15
|
import { capabilitiesFor } from "../execution-lane-caps.js";
|
|
16
|
-
import { LaneHomeSeat } from "../lane-home-probe.js";
|
|
16
|
+
import { LaneHomeSeat, laneHomeRefusal } from "../lane-home-probe.js";
|
|
17
17
|
import { composeHostSubprocessEnv, hostSubprocessEnv, withheldFromHostSubprocess } from "../server-secret-env.js";
|
|
18
18
|
import { nameTooLongCode } from "./remote-env-file-error.js";
|
|
19
19
|
import { trackEphemeralHostWorkspace, untrackEphemeralHostWorkspace } from "./host-workspace-registry.js";
|
|
@@ -64,11 +64,13 @@ export class RemoteHostExecutionEnv {
|
|
|
64
64
|
return this.homeSeat.home;
|
|
65
65
|
}
|
|
66
66
|
homeSeat;
|
|
67
|
-
homeRefusal;
|
|
68
67
|
get cdPath() {
|
|
69
68
|
const value = this.childEnv(undefined, undefined).CDPATH;
|
|
70
69
|
return value === undefined || value === "" ? [] : value.split(":");
|
|
71
70
|
}
|
|
71
|
+
get receivedEnvNames() {
|
|
72
|
+
return Object.entries(this.childEnv(undefined, undefined)).flatMap(([name, value]) => (value === undefined ? [] : [name]));
|
|
73
|
+
}
|
|
72
74
|
get withheldEnv() {
|
|
73
75
|
return withheldFromHostSubprocess({ policy: this.cfg.inheritEnv, allow: [] }, this.cfg.env);
|
|
74
76
|
}
|
|
@@ -148,7 +150,9 @@ export class RemoteHostExecutionEnv {
|
|
|
148
150
|
this.cwd = this.workspaceDir;
|
|
149
151
|
this.handle = { sandboxId: this.workspaceDir, provider: PROVIDER, mountPath: this.workspaceDir };
|
|
150
152
|
this.homeSeat = new LaneHomeSeat(PROVIDER, config, IS_WIN32 ? "win32" : "posix");
|
|
151
|
-
|
|
153
|
+
const homeVerdict = this.homeSeat.settle(this.childHome());
|
|
154
|
+
if (homeVerdict !== undefined)
|
|
155
|
+
throw laneHomeRefusal(homeVerdict);
|
|
152
156
|
}
|
|
153
157
|
workspaceHandle() {
|
|
154
158
|
return this.handle;
|
|
@@ -158,8 +162,6 @@ export class RemoteHostExecutionEnv {
|
|
|
158
162
|
return unsupported("connect(snapshotId)");
|
|
159
163
|
if (this.destroyed)
|
|
160
164
|
return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env already destroyed") };
|
|
161
|
-
if (this.homeRefusal !== undefined)
|
|
162
|
-
return { ok: false, error: new RemoteExecutionError("connect_failed", this.homeRefusal) };
|
|
163
165
|
if (config?.abortSignal?.aborted)
|
|
164
166
|
return { ok: false, error: new RemoteExecutionError("aborted", "connect aborted") };
|
|
165
167
|
try {
|
|
@@ -1262,8 +1264,6 @@ export class RemoteHostExecutionEnv {
|
|
|
1262
1264
|
async shellReady() {
|
|
1263
1265
|
if (this.destroyed)
|
|
1264
1266
|
return { ok: false, error: new ExecutionError("shell_unavailable", "execution env already destroyed") };
|
|
1265
|
-
if (this.homeRefusal !== undefined)
|
|
1266
|
-
return { ok: false, error: new ExecutionError("shell_unavailable", this.homeRefusal) };
|
|
1267
1267
|
try {
|
|
1268
1268
|
try {
|
|
1269
1269
|
await resolveHostShell();
|
|
@@ -1295,8 +1295,6 @@ export class RemoteHostExecutionEnv {
|
|
|
1295
1295
|
return (this.spoolRootDir !== undefined && inside(this.spoolRootDir)) || (this.persistent && inside(this.workspaceDir));
|
|
1296
1296
|
}
|
|
1297
1297
|
async fsReady(p, opts) {
|
|
1298
|
-
if (this.homeRefusal !== undefined)
|
|
1299
|
-
return { ok: false, error: new FileError("target_unavailable", this.homeRefusal, this.resolve(p)) };
|
|
1300
1298
|
if (this.destroyed) {
|
|
1301
1299
|
const lingering = opts?.lingeringBgWrite === true && this._bgManager?.hasShells() === true && this.isLingeringMirrorTarget(p);
|
|
1302
1300
|
if (!lingering)
|
|
@@ -157,7 +157,7 @@ export async function runSymlinkChain(absPath, run) {
|
|
|
157
157
|
const STAGED_FILE = "data";
|
|
158
158
|
const STAGE_DIR_REFUSED_EXIT = 3;
|
|
159
159
|
const STAGE_DIR_UNSEALED_EXIT = 4;
|
|
160
|
-
const STAGE_DIR_UNSEALED_MESSAGE = "sema: the private staging directory could not be made private (chmod 700 failed or did not take effect,
|
|
160
|
+
const STAGE_DIR_UNSEALED_MESSAGE = "sema: the private staging directory could not be made private (chmod 700 failed or did not take effect, the mode or owner read back differently, or an inherited ACL entry could not be removed or read on this platform — the filesystem ignores permission bits or the parent directory carries a default or inheritable ACL); nothing was staged";
|
|
161
161
|
export const STAGE_DIR_LEFT_MARK = "sema: staging directory left behind:";
|
|
162
162
|
export function buildStagingPaths(abs, nonce = randomBytes(6).toString("hex")) {
|
|
163
163
|
const cut = abs.lastIndexOf("/");
|
|
@@ -174,7 +174,17 @@ function privateMkdir(dir) {
|
|
|
174
174
|
}
|
|
175
175
|
function hardenPrivate(dir) {
|
|
176
176
|
const statOf = (gnu, bsd) => `$(stat -c ${gnu} -- ${dir} 2>/dev/null || stat -f ${bsd} -- ${dir} 2>/dev/null)`;
|
|
177
|
-
return `( chmod -- 700 ${dir} 2>/dev/null && m=${statOf("%a", "%Lp")} && u=${statOf("%u", "%u")} && case "$m" in 700|[1-7]700) [ "$u" = "$(id -u)" ];; *) false;; esac )`;
|
|
177
|
+
return `( chmod -- 700 ${dir} 2>/dev/null && m=${statOf("%a", "%Lp")} && u=${statOf("%u", "%u")} && case "$m" in 700|[1-7]700) [ "$u" = "$(id -u)" ];; *) false;; esac && ${aclCleared(dir)} )`;
|
|
178
|
+
}
|
|
179
|
+
function aclCleared(dir) {
|
|
180
|
+
const marker = `ls_out=$(cd ${dir} && ls -ld . 2>&1) && [ -z "$(printf '%s\\n' "$ls_out" | sed -n 2p)" ] && mode_word=\${ls_out%% *} && ` +
|
|
181
|
+
`case "$mode_word" in d?????????*+*) echo +;; d?????????*) ;; *) false;; esac`;
|
|
182
|
+
return (`plat=$(uname -s 2>/dev/null) && case "$plat" in ` +
|
|
183
|
+
`Linux) acl_read() { :; }; acl_strip() { false; };; ` +
|
|
184
|
+
`Darwin) acl_read() { ls_out=$(cd ${dir} && ls -led . 2>&1) && case "$ls_out" in d?????????*) ;; *) false;; esac && printf '%s\\n' "$ls_out" | sed 1d; }; acl_strip() { chmod -N ${dir}; };; ` +
|
|
185
|
+
`FreeBSD) acl_read() { ${marker}; }; acl_strip() { setfacl -b ${dir}; };; ` +
|
|
186
|
+
`SunOS) acl_read() { ${marker}; }; acl_strip() { chmod A- ${dir}; };; ` +
|
|
187
|
+
`*) false;; esac && acl=$(acl_read 2>/dev/null) && { [ -z "$acl" ] || { acl_strip 2>/dev/null && acl=$(acl_read 2>/dev/null) && [ -z "$acl" ]; }; }`);
|
|
178
188
|
}
|
|
179
189
|
function emptyPrivate(dir) {
|
|
180
190
|
return `[ ! -e ${dir}/${STAGED_FILE} ] && [ ! -L ${dir}/${STAGED_FILE} ]`;
|
|
@@ -73,7 +73,11 @@ export interface ParsedTable {
|
|
|
73
73
|
export declare function parseCreateTable(stmt: string): ParsedTable | undefined;
|
|
74
74
|
/** 启动时真会建出来的**全部**表形(录制 → 解析)。同名表二次出现以**先到**为准(`IF NOT EXISTS` 的真语义)。 */
|
|
75
75
|
export declare function expectedTableShapes(dialect: SqlDialect): Promise<Map<string, ParsedTable>>;
|
|
76
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* 拒启码(机读)。附录 A / 拒启码表的登记名。含义 = 「本构建读不出的持久形,开始服务之前判」,一个码三只臂:本文件的列册对账与退役 schema 两道(SQL),
|
|
78
|
+
* 以及 local 数据根的严格边档对账(`local-session-store.ts` 的 `LocalDataRootSchemaMismatchError`,`LocalBackend.ensureSchema()` 调;拒句同形:
|
|
79
|
+
* 码开头的表头 · 逐坐标一段 · 「NEWER than this build」方向句 · 闭集出路)。
|
|
80
|
+
*/
|
|
77
81
|
export declare const SCHEMA_MISMATCH_ERROR_CODE = "config.schema_mismatch";
|
|
78
82
|
/** 一张表的对账结论。 */
|
|
79
83
|
export interface TableMismatch {
|
|
@@ -28,7 +28,7 @@ export interface ForkLogRow {
|
|
|
28
28
|
readonly ts: unknown;
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
|
-
* 两只 SQL 孪生的行怎么交给引擎的 `selectForkRows`(按答案挑行 + 新会话的叶;分叉点不在日志里 ⇒ `invalid_fork_target`、路径中途悬空父 / 父链成环 ⇒ `invalid_session
|
|
31
|
+
* 两只 SQL 孪生的行怎么交给引擎的 `selectForkRows`(按答案挑行 + 新会话的叶;分叉点不在日志里 ⇒ `invalid_fork_target`、路径中途悬空父 / 父链成环 ⇒ `corrupt_session`〔core 7.61.x 及以前 `invalid_session`〕,
|
|
32
32
|
* 引擎的原词原句)。本仓不再自写父链走法 —— 那曾是与引擎 `getPathToRoot` 同义的第二份。
|
|
33
33
|
*/
|
|
34
34
|
export declare const FORK_LOG_ROW_SHAPE: ForkRowShape<ForkLogRow>;
|
|
@@ -22,6 +22,15 @@ export declare function forkOwnerOf(requested: string | null | undefined, source
|
|
|
22
22
|
* 「首写」由调用方判(已记下属主的会话上的调用照旧静默不改,不经这里)。判官 = {@link isSessionOwnerValue}(core 7.56.0 [ref] `principalNameOf`)。
|
|
23
23
|
*/
|
|
24
24
|
export declare function assertFirstOwnerWritable(sessionId: string, owner: unknown): asserts owner is string | null;
|
|
25
|
+
/**
|
|
26
|
+
* placed 行属主格的唯一读法(core 7.62.0 [ref] 盒义务 7;三只持久会话店的 placing write 同调):记下的属主 = 引擎交来的 `placement.owner` **原样** ——
|
|
27
|
+
* 委派树的属主由引擎判(没人派生的 run 读自己那一行、按化身绑一次,原样带给每一层子代;`null` = 根匿名),店只记、不判、不推断,也不进元组
|
|
28
|
+
* (`SessionPlacementRecord` 没有属主)。值不是主体名也不是 `null`(空串、空白、缺席 —— 绕过类型的调用)⇒ 响亮拒(`SessionError("storage")`,一个字节不写):
|
|
29
|
+
* 一只谁也不指的属主会让行要么谁都读得到、要么谁都读不到。判官 = {@link isSessionOwnerValue},与 `register` 首写同一只。
|
|
30
|
+
*/
|
|
31
|
+
export declare function placedSessionOwnerOf(sessionId: string, placement: {
|
|
32
|
+
readonly owner?: unknown;
|
|
33
|
+
}): string | null;
|
|
25
34
|
/**
|
|
26
35
|
* 导入者的属主 —— 换装的写腿(公开 `importEntries` / `replaceEntries` 与暂存提交)把调用方给的属主重戳进会话行之前的唯一读法:
|
|
27
36
|
* 主体名或 `null`(匿名)⇒ 原样交回;别的一切(空串、空白串、带控制 / 分隔 / 非连接符格式字符的串、非串)⇒ `TypeError` 响亮拒 ——
|
|
@@ -22,6 +22,12 @@ export function assertFirstOwnerWritable(sessionId, owner) {
|
|
|
22
22
|
throw new SessionError("storage", `register(${sessionId}): a first ownership write must carry a principal name or null (anonymous), got ${ownerValueFault(owner)} — ` +
|
|
23
23
|
"a value that names nobody would leave the session claimable by anyone or ownable by no one; refusing, nothing written");
|
|
24
24
|
}
|
|
25
|
+
export function placedSessionOwnerOf(sessionId, placement) {
|
|
26
|
+
const owner = placement.owner;
|
|
27
|
+
if (isSessionOwnerValue(owner))
|
|
28
|
+
return owner;
|
|
29
|
+
throw new SessionError("storage", `acquire(${sessionId}): a placed session's owner (placement.owner, the delegation tree's owner the engine judged) must be a principal name or null (an anonymous tree), got ${ownerValueFault(owner)} — refusing, nothing written`);
|
|
30
|
+
}
|
|
25
31
|
export function importerOwnerOf(owner) {
|
|
26
32
|
if (isSessionOwnerValue(owner))
|
|
27
33
|
return owner;
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { type SessionStore } from "@sema-agent/core";
|
|
2
2
|
import type { ServiceConfig } from "../config-types.js";
|
|
3
3
|
import type { StoreBackend } from "./store-backend.js";
|
|
4
|
-
import { type DelegationRootWitness, type OrphanOwnerLog } from "../delegated-session-owner.js";
|
|
5
|
-
import type { OwnerAwareSessionStore } from "../security.js";
|
|
6
4
|
/**
|
|
7
5
|
* Session-store plugin seam.
|
|
8
6
|
*
|
|
@@ -43,23 +41,22 @@ export declare function createSessionStore(config: Pick<ServiceConfig, "sessionB
|
|
|
43
41
|
*/
|
|
44
42
|
export declare function ensureSessionDurable(hostStore: SessionStore, sessionId: string): Promise<void>;
|
|
45
43
|
/**
|
|
46
|
-
* core `SubagentToolOptions.background.ensureChildSessionDurable` 的实现体 —— 停驻挂点的**见证**:这只后台子代的转录此刻是 host 店里一只有真内容、
|
|
47
|
-
*
|
|
44
|
+
* core `SubagentToolOptions.background.ensureChildSessionDurable(sessionId, given)` 的实现体 —— 停驻挂点的**见证**:这只后台子代的转录此刻是 host 店里一只有真内容、
|
|
45
|
+
* 属主恰是引擎交来的委派树属主(`given.owner`)的会话,才放行停驻;任何一步不成立 ⇒ 抛 = 否决(core 收 veto 即 expire checkpoint + settle failed,pre-153 行为,不放大)。
|
|
48
46
|
*
|
|
49
47
|
* 规则(`fork-routing-session-store.ts` 头注):一只后台子代的 durable 行在,它的转录就是 host 店委派分区里的一只 placed 行。挂点只装在声明了
|
|
50
48
|
* durable 委派分区的部署上(stage-07:core `resolveSubagentTranscriptTier(...) === "full"`),那里 core 给每只新起的后台子代铸 placement、
|
|
51
|
-
* 转录生来就在 host 店 ——
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* 缺席(行上没有根)或根行此刻不在 ⇒ 孤行哨兵。
|
|
49
|
+
* 转录生来就在 host 店 —— 所以这里**只验、不搬、不判属主**:属主是引擎判的(core 7.62.0 [ref] L1:没人派生的 run 读自己那一行、按化身绑一次,原样带给每一层子代;
|
|
50
|
+
* 复活的子代 = 它自己那一行的属主),placed 行建行那一笔原样记下了它(盒义务 7,三只持久会话店 + `session-ownership.ts` 的 `placedSessionOwnerOf`)。
|
|
51
|
+
* 挂点不认领、不推断、不收委派树根见证([ref] P-5;本仓的委派继承律在这一写点退役 —— 存量无主 placed 行:SQL 两店由一次性迁移 M23 在窗内回填,本机文件店没有回填臂,见 `local-session-store.ts` 头注)。
|
|
52
|
+
* 0. 宿主店读不出属主(没有 `headOf`,唯一判法 `headReadOf`)⇒ 响亮拒,一次读都不发。
|
|
56
53
|
* 1. 在不在、有没有真内容:{@link ensureSessionDurable}(`requireExisting` 打开 + 叶子非空 + forget 收尾)。任何失败(不在、空壳、读故障)⇒ 原样上抛 = 否决
|
|
57
|
-
* (读故障也否决:说不准转录在不在,就不让一次审批押在它上面)
|
|
58
|
-
*
|
|
59
|
-
* 2.
|
|
60
|
-
*
|
|
61
|
-
* 宿主店必须读得出属主(`delegatedOwnerHostOf`:`headOf`;记录委派树的店另交 `placementOf` + `claimOwnerless`,两只同在或同缺;生产的 SQL / local 店与缓存包装层都满足),
|
|
62
|
-
* 否则响亮拒。
|
|
54
|
+
* (读故障也否决:说不准转录在不在,就不让一次审批押在它上面)。不判它是不是 placed 行:复活的是旧行时 id 取行上的会话(升级前停驻、当年晋升进来的普通行),
|
|
55
|
+
* 它照样是这只子代的转录。
|
|
56
|
+
* 2. 此刻行上的属主 ≠ `given.owner` ⇒ 那是别人的同 id 会话,或这一行在引擎读之后被改写 ⇒ 抛 = 停驻否决(拒句不带任何一方的属主值 —— 它会进停驻否决的错误面)。
|
|
57
|
+
* 契约只承诺属主值相等(化身不比:复活腿的 `given` 只带属主)。
|
|
63
58
|
*/
|
|
64
|
-
export declare function attestChildSessionDurable(hostStore:
|
|
59
|
+
export declare function attestChildSessionDurable(hostStore: SessionStore, sessionId: string, given: {
|
|
60
|
+
readonly owner: string | null;
|
|
61
|
+
}): Promise<void>;
|
|
65
62
|
//# sourceMappingURL=session-store.d.ts.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TtlSessionStore } from "@sema-agent/core";
|
|
2
2
|
import { CachingSessionStore } from "./caching-session-store.js";
|
|
3
|
-
import {
|
|
3
|
+
import { headReadOf } from "../session-incarnation.js";
|
|
4
4
|
export function createSessionStore(config, backend, metrics) {
|
|
5
5
|
if (config.sessionBackend === "tidb") {
|
|
6
6
|
if (!backend)
|
|
@@ -23,16 +23,15 @@ export async function ensureSessionDurable(hostStore, sessionId) {
|
|
|
23
23
|
await hostStore.forget?.(acquired.sessionId);
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
export async function attestChildSessionDurable(hostStore, sessionId,
|
|
27
|
-
const
|
|
28
|
-
if (
|
|
29
|
-
throw new Error(`session "${sessionId}" cannot be parked: the host session store cannot read a session's owner (headOf),
|
|
26
|
+
export async function attestChildSessionDurable(hostStore, sessionId, given) {
|
|
27
|
+
const headOf = headReadOf(hostStore);
|
|
28
|
+
if (headOf === undefined) {
|
|
29
|
+
throw new Error(`session "${sessionId}" cannot be parked: the host session store cannot read a session's owner (headOf), so the delegated transcript's owner cannot be checked`);
|
|
30
30
|
}
|
|
31
|
-
const judged = await readDelegatedOwner(sessionId, root === undefined ? {} : { rootSessionId: root.sessionId }, sourceFacesVia(host.headOf));
|
|
32
|
-
assertRootStillWitnessed(sessionId, root, judged);
|
|
33
31
|
await ensureSessionDurable(hostStore, sessionId);
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
if ((await headOf(sessionId))?.owner !== given.owner) {
|
|
33
|
+
throw new Error(`session "${sessionId}" cannot be parked as this delegated session: the host session store holds a session with this id whose owner is not this delegation tree's owner, ` +
|
|
34
|
+
"so it does not attest this delegated transcript");
|
|
35
|
+
}
|
|
37
36
|
}
|
|
38
37
|
//# sourceMappingURL=session-store.js.map
|
|
@@ -68,12 +68,13 @@ export interface JsonColumnMember {
|
|
|
68
68
|
* 同一只投影的**裸表达式**(不带别名)—— 给 `WHERE` / `UPDATE … WHERE` 这种**没有别名位**的地方用。
|
|
69
69
|
*
|
|
70
70
|
* 🔴 存在的唯一理由是「别名位在那里不合法」,**不是**给读腿留第二条路:读腿一律用 {@link jsonColumnText}
|
|
71
|
-
* (别名是它的产物的一部分,理由见那只函数)
|
|
71
|
+
* (别名是它的产物的一部分,理由见那只函数)。今天整仓**三处**消费方,各自的「别名位不合法」都是真的:
|
|
72
72
|
* · `one-time-migrations.ts` 的几条改写(M8 / M17 / M18 / M21 的候选扫描与按原文 CAS 回写)——
|
|
73
73
|
* 它们要在 `WHERE` 里判这一列的**文本**,而 `WHERE` 没有别名位;
|
|
74
74
|
* · `workflow-journal-store-sql.ts` 的 `loadPage`([ref] 车③a)—— 那一列的投影裹在一条
|
|
75
75
|
* `CASE WHEN LENGTH(result) > ? THEN NULL ELSE <expr> END AS result_json` 里:**别名属于整条 CASE**
|
|
76
76
|
* (`result_json`,因为超字节门的那一支要给 `NULL`),CASE 里面的表达式位上写别名是语法错。
|
|
77
|
+
* · 本模块的 {@link jsonColumnTextSha256} —— 投影裹在摘要函数里,**别名属于整条摘要表达式**(产物是摘要不是体)。
|
|
77
78
|
* 而「怎么把一列 JSON 取成文本」这件事整仓只能有一种写法:两种承载(真 JSON 列 / LONGTEXT 别名)在这条
|
|
78
79
|
* 表达式下合成同一只串,迁移的判等、分页腿的取文本因此与读腿的判形**看同一份字节**。
|
|
79
80
|
*
|
|
@@ -82,6 +83,20 @@ export interface JsonColumnMember {
|
|
|
82
83
|
* 误用的落点是真库上当场的 `SqlJsonColumnError("not-text")` 响亮抛(行上没有那个键 ⇒ `undefined`),不是静默。
|
|
83
84
|
*/
|
|
84
85
|
export declare function jsonColumnTextExpr(dialect: SqlDialect, column: string, memberKey?: string): string;
|
|
86
|
+
/**
|
|
87
|
+
* 一列 JSON 的**投影文本的 sha256**(十六进制小写,引擎内算 —— 整只文档不出库),带别名。输入恰是 {@link jsonColumnTextExpr} 那一只投影:
|
|
88
|
+
* 同一行、同一次读里,本表达式的摘要就是 {@link jsonColumnText} 交给读腿的那串字节的摘要,于是「字节没变 ⇒ 摘要没变」只有一种读法。
|
|
89
|
+
* MySQL 协议 `SHA2(<文本>, 256)`(TiDB / MySQL / MariaDB 都认);PG `encode(sha256(convert_to(<文本>, 'UTF8')), 'hex')`(PG 11+)。
|
|
90
|
+
*
|
|
91
|
+
* 读者今天一处:`checkpoint-store-sql.ts` 的停驻行投影记忆(运维队列 / 调度视图对一份停驻行字节的判决按 (摘要, 引擎构建) 记,
|
|
92
|
+
* 记忆命中就不拉整只文档)。产物是摘要不是体,所以它**不是**体的第二条投影路;回读走 {@link readJsonColumnTextSha256}。
|
|
93
|
+
*/
|
|
94
|
+
export declare function jsonColumnTextSha256(dialect: SqlDialect, column: string, as: string): string;
|
|
95
|
+
/**
|
|
96
|
+
* {@link jsonColumnTextSha256} 那一格的回读:恒是 64 位十六进制小写串。别的一切(键不在 = 读腿漏了投影;串形不对 = 驱动换了行为)都是**码坏**,
|
|
97
|
+
* 响亮抛 —— 不当作「这一行没有摘要」退成每次整行读(那会让记忆静默失效,代价无声地涨回去)。
|
|
98
|
+
*/
|
|
99
|
+
export declare function readJsonColumnTextSha256(cell: unknown, where: string): string;
|
|
85
100
|
/**
|
|
86
101
|
* 一次读回失败的**种别**(店层码,**闭集**;运行期词表在这里,类型从它推导 —— 一张词表只有一个写者):
|
|
87
102
|
* · `not-text` —— 拿到的不是串 = **读腿漏了投影**,那是**码的问题不是数据的问题** ⇒ 响亮的程序错,
|
|
@@ -114,7 +129,12 @@ export declare class SqlJsonColumnError extends Error {
|
|
|
114
129
|
readonly problem: SqlJsonColumnProblem;
|
|
115
130
|
/** 出事的坐标(`<table>.<column> for <identity>`)—— 进错误文案,让运维知道去看哪一行。 */
|
|
116
131
|
readonly where: string;
|
|
117
|
-
|
|
132
|
+
/**
|
|
133
|
+
* `malformed` 时形读者交出的「它不认得的键」({@link JsonColumnShapeBad.unknownKeys};别的种别 / 读者说不出 ⇒ 空)—— 体是更新的写者写下的那一形的判据
|
|
134
|
+
* (例:local 数据根的严格边档据它把拒句点名成「比本构建新」)。判词只有形读者一个写者,本类原样携带。
|
|
135
|
+
*/
|
|
136
|
+
readonly unknownKeys: readonly string[];
|
|
137
|
+
constructor(problem: SqlJsonColumnProblem, where: string, detail: string, unknownKeys?: readonly string[]);
|
|
118
138
|
}
|
|
119
139
|
/** 形读者认了:带上收窄后的值。 */
|
|
120
140
|
export interface JsonColumnShapeOk<T> {
|
|
@@ -125,6 +145,8 @@ export interface JsonColumnShapeOk<T> {
|
|
|
125
145
|
export interface JsonColumnShapeBad {
|
|
126
146
|
readonly value?: undefined;
|
|
127
147
|
readonly problem: string;
|
|
148
|
+
/** 读者因为**不认得**而拒的键(去重、排序;没有 / 读者说不出 ⇒ 缺)—— 更新的写者给这一形加了位。{@link decodeJsonColumn} 原样交给 {@link SqlJsonColumnError.unknownKeys}。 */
|
|
149
|
+
readonly unknownKeys?: readonly string[];
|
|
128
150
|
}
|
|
129
151
|
/** {@link JsonColumnReader} 的答复。 */
|
|
130
152
|
export type JsonColumnShape<T> = JsonColumnShapeOk<T> | JsonColumnShapeBad;
|
|
@@ -20,15 +20,30 @@ export function jsonColumnTextExpr(dialect, column, memberKey) {
|
|
|
20
20
|
}
|
|
21
21
|
return dialect === "pg" ? `(${column}::jsonb -> '${memberKey}')::text` : `CAST(JSON_EXTRACT(${column}, '$.${memberKey}') AS CHAR)`;
|
|
22
22
|
}
|
|
23
|
+
export function jsonColumnTextSha256(dialect, column, as) {
|
|
24
|
+
if (!MEMBER_ALIAS_RE.test(as)) {
|
|
25
|
+
throw new Error(`jsonColumnTextSha256: alias ${JSON.stringify(as)} must be a lower-case bare identifier — PostgreSQL folds unquoted aliases to lower case, so any other spelling would give the two dialects different row keys`);
|
|
26
|
+
}
|
|
27
|
+
const text = jsonColumnTextExpr(dialect, column);
|
|
28
|
+
return dialect === "pg" ? `encode(sha256(convert_to(${text}, 'UTF8')), 'hex') AS ${as}` : `SHA2(${text}, 256) AS ${as}`;
|
|
29
|
+
}
|
|
30
|
+
const SHA256_HEX_RE = /^[0-9a-f]{64}$/;
|
|
31
|
+
export function readJsonColumnTextSha256(cell, where) {
|
|
32
|
+
if (typeof cell === "string" && SHA256_HEX_RE.test(cell))
|
|
33
|
+
return cell;
|
|
34
|
+
throw new Error(`${where}: expected a lower-case sha256 hex digest from the read leg, got ${cell === undefined ? "no such key (the SELECT dropped the projection)" : typeof cell} — refusing rather than guess`);
|
|
35
|
+
}
|
|
23
36
|
export const SQL_JSON_COLUMN_PROBLEMS = ["not-text", "not-json", "malformed"];
|
|
24
37
|
export class SqlJsonColumnError extends Error {
|
|
25
38
|
problem;
|
|
26
39
|
where;
|
|
27
|
-
|
|
40
|
+
unknownKeys;
|
|
41
|
+
constructor(problem, where, detail, unknownKeys = []) {
|
|
28
42
|
super(`${where}: ${detail}`);
|
|
29
43
|
this.name = "SqlJsonColumnError";
|
|
30
44
|
this.problem = problem;
|
|
31
45
|
this.where = where;
|
|
46
|
+
this.unknownKeys = unknownKeys;
|
|
32
47
|
}
|
|
33
48
|
}
|
|
34
49
|
function describeBody(body) {
|
|
@@ -53,7 +68,7 @@ export function decodeJsonColumn(text, read, where) {
|
|
|
53
68
|
}
|
|
54
69
|
const shape = read(body);
|
|
55
70
|
if (shape.problem !== undefined) {
|
|
56
|
-
throw new SqlJsonColumnError("malformed", where, `carries ${describeBody(body)} that this store cannot read (${shape.problem}) — refusing rather than serve it as an empty/partial value
|
|
71
|
+
throw new SqlJsonColumnError("malformed", where, `carries ${describeBody(body)} that this store cannot read (${shape.problem}) — refusing rather than serve it as an empty/partial value`, shape.unknownKeys);
|
|
57
72
|
}
|
|
58
73
|
return shape.value;
|
|
59
74
|
}
|
|
@@ -85,8 +100,28 @@ export function assertJsonColumnWritable(text, read, where) {
|
|
|
85
100
|
export function createZodColumnReader(schema) {
|
|
86
101
|
return (body) => {
|
|
87
102
|
const parsed = schema.safeParse(body);
|
|
88
|
-
|
|
103
|
+
if (parsed.success)
|
|
104
|
+
return { value: parsed.data };
|
|
105
|
+
const unknownKeys = unrecognizedKeysOf(parsed.error.issues);
|
|
106
|
+
return { problem: parsed.error.message, ...(unknownKeys.length > 0 ? { unknownKeys } : {}) };
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function unrecognizedKeysOf(issues) {
|
|
110
|
+
const keys = new Set();
|
|
111
|
+
const walk = (list) => {
|
|
112
|
+
for (const issue of list) {
|
|
113
|
+
if (issue.code === "unrecognized_keys")
|
|
114
|
+
for (const key of issue.keys)
|
|
115
|
+
keys.add(key);
|
|
116
|
+
else if (issue.code === "invalid_union")
|
|
117
|
+
for (const branch of issue.errors)
|
|
118
|
+
walk(branch);
|
|
119
|
+
else if (issue.code === "invalid_key" || issue.code === "invalid_element")
|
|
120
|
+
walk(issue.issues);
|
|
121
|
+
}
|
|
89
122
|
};
|
|
123
|
+
walk(issues);
|
|
124
|
+
return [...keys].sort();
|
|
90
125
|
}
|
|
91
126
|
export function createObjectColumnReader() {
|
|
92
127
|
return (body) => (isJsonObject(body) ? { value: body } : { problem: "expected a JSON object" });
|
|
@@ -165,7 +165,7 @@ class TiDBBackend {
|
|
|
165
165
|
breaker(onWriteFail) { return new TiDBBreakerState(this.pool, onWriteFail); }
|
|
166
166
|
costQuota(limitMicroUsd, windowMs, onDegraded) { return new TiDBCostQuota(this.pool, limitMicroUsd, windowMs, undefined, onDegraded, this.config.dbQueryTimeoutMs); }
|
|
167
167
|
rateLimiter(limit, onDegraded) { return new TiDBRateLimiter(this.pool, limit, undefined, undefined, onDegraded, this.config.dbQueryTimeoutMs); }
|
|
168
|
-
session() { return new TiDBSessionStore(this.pool, {
|
|
168
|
+
session() { return new TiDBSessionStore(this.pool, { retention: sqlStoreRetention(this.config.retentionSweep) }); }
|
|
169
169
|
adoptionLog() { return new TiDBAdoptionLogStore(this.pool); }
|
|
170
170
|
mysqlPool() { return this.pool; }
|
|
171
171
|
pgPool() { return undefined; }
|
|
@@ -219,7 +219,7 @@ class PgBackend {
|
|
|
219
219
|
breaker(onWriteFail) { return new PgBreakerState(this.pool, onWriteFail); }
|
|
220
220
|
costQuota(limitMicroUsd, windowMs, onDegraded) { return new PgCostQuota(this.pool, limitMicroUsd, windowMs, undefined, onDegraded, this.config.dbQueryTimeoutMs); }
|
|
221
221
|
rateLimiter(limit, onDegraded) { return new PgRateLimiter(this.pool, limit, undefined, undefined, onDegraded, this.config.dbQueryTimeoutMs); }
|
|
222
|
-
session() { return new PgSessionStore(this.pool, {
|
|
222
|
+
session() { return new PgSessionStore(this.pool, { retention: sqlStoreRetention(this.config.retentionSweep) }); }
|
|
223
223
|
adoptionLog() { return new PgAdoptionLogStore(this.pool); }
|
|
224
224
|
mysqlPool() { return undefined; }
|
|
225
225
|
pgPool() { return this.pool; }
|
|
@@ -294,6 +294,7 @@ class LocalBackend {
|
|
|
294
294
|
: (plane) => fileSessionCaptureRecordStore(plane?.controlDir ?? ownControlDir);
|
|
295
295
|
}
|
|
296
296
|
async ensureSchema() {
|
|
297
|
+
await this.sessionStore.assertStrictSidecarsReadable();
|
|
297
298
|
await this.sessionStore.settleArchiveSnapshots();
|
|
298
299
|
}
|
|
299
300
|
async runOneTimeMigrations() {
|
|
@@ -17,7 +17,6 @@ import type { SessionWorkspace } from "../session-workspace.js";
|
|
|
17
17
|
import type { RetentionDeclaration } from "@sema-agent/core";
|
|
18
18
|
import type { SessionSummary } from "./store-contracts.js";
|
|
19
19
|
import { type ClientSessionRowWrite, type ReplaySessionRowWrite, type ReplaySessionRowWriteOutcome, type SessionRowPatch, type SessionRowWriteOutcome } from "../session-row.js";
|
|
20
|
-
import { type OrphanOwnerLog } from "../delegated-session-owner.js";
|
|
21
20
|
import { type PlacementAcquireOpts } from "./session-placement.js";
|
|
22
21
|
import { type StagingHandle } from "../session-sync-kernel.js";
|
|
23
22
|
import { type ForkTarget, type ForkToOutcome } from "./session-fork.js";
|
|
@@ -40,8 +39,6 @@ export declare class TiDBSessionStore implements SessionStore {
|
|
|
40
39
|
readonly durability: "durable";
|
|
41
40
|
/** In-flight acquisitions keyed by id, so concurrent acquire(sameId) in one process share one. */
|
|
42
41
|
private readonly pending;
|
|
43
|
-
/** 委派子代孤行那一行 warn 的去处(`delegated-session-owner.ts`;装配给部署的 logger,缺席 ⇒ 缺省 logger,不静默)。 */
|
|
44
|
-
private readonly log;
|
|
45
42
|
/**
|
|
46
43
|
* 执行席 —— 根会话行上这一位住客的那一格的唯一读口与 place / markMirror 两只写口(规则在 `src/execution-placement.ts`;
|
|
47
44
|
* 实现与语句的唯一属主 = `execution-placement-sql.ts`,两孪生同一只实现,本店只交自己的池)。
|
|
@@ -50,7 +47,6 @@ export declare class TiDBSessionStore implements SessionStore {
|
|
|
50
47
|
/** 本店的池包成的 SqlDriver:执行席与会话行格写原语的事务都经它借连接(连接初始化钉住事务读语义,`sql-driver.ts`)。 */
|
|
51
48
|
private readonly db;
|
|
52
49
|
constructor(pool: Pool, opts?: {
|
|
53
|
-
readonly log?: OrphanOwnerLog;
|
|
54
50
|
readonly retention?: RetentionDeclaration;
|
|
55
51
|
});
|
|
56
52
|
acquire(sessionId?: string, opts?: PlacementAcquireOpts): Promise<AcquiredSession>;
|
|
@@ -58,18 +54,10 @@ export declare class TiDBSessionStore implements SessionStore {
|
|
|
58
54
|
/** Idempotently create a session_meta row with an owner (core `SessionStore.register`). Existing owner is never overwritten. */
|
|
59
55
|
register(sessionId: string, owner: string | null): Promise<void>;
|
|
60
56
|
/**
|
|
61
|
-
* acquire 的建行(唯一一处):普通会话 ⇒ 无主(按 id 首建的既有读法);placed 会话 ⇒
|
|
62
|
-
* ([ref]
|
|
63
|
-
* `delegated-session-owner.ts` 的 `createPlacedSessionRow`,本店只交既有读写口,PG 孪生同调)。
|
|
57
|
+
* acquire 的建行(唯一一处):普通会话 ⇒ 无主(按 id 首建的既有读法);placed 会话 ⇒ 属主 = `placement.owner` 原样,与元组同一条 INSERT 落库
|
|
58
|
+
* (core 7.62.0 [ref] 盒义务 7:委派树的属主是引擎判的,店只记、不判、不推断;唯一读法 `placedSessionOwnerOf`,值不是主体名也不是 null ⇒ 响亮拒、不留行。PG 孪生同调)。
|
|
64
59
|
*/
|
|
65
60
|
private createOnAcquire;
|
|
66
|
-
/**
|
|
67
|
-
* [ref] 委派继承律的**认领口**:这只会话的那一位住客(判断时读到的化身)仍是无主的 placed 行 ⇒ 写 `owner`,返回是否写成
|
|
68
|
-
* (句 = `CLAIM_OWNERLESS_PLACED_SQL`,只 CAS 子行自己,与停驻挂点 / M23 同一只;规则在 `delegated-session-owner.ts`)。普通会话行不在射程(placed 判别位)。
|
|
69
|
-
*/
|
|
70
|
-
claimOwnerless(sessionId: string, incarnation: string, owner: string): Promise<boolean>;
|
|
71
|
-
/** 若干来源行的会话面(属主 + 化身)一次读回(委派继承律的来源读;本店没有的 id 不在表里;活住客的投影 `sessionFaceFromRow`,空化身 ⇒ 响亮拒)。 */
|
|
72
|
-
private sourceFaces;
|
|
73
61
|
/** The one session_meta INSERT. Existing owner is never overwritten.
|
|
74
62
|
* [ref]:`placement` 在场时与行同一条 INSERT 落六列;ON DUPLICATE 臂是 no-op ⇒ 已存在的行的元组
|
|
75
63
|
* **永不被重写**(first-write 不可变,义务 5 的写侧半场)。
|
|
@@ -10,11 +10,9 @@ import { carriedRowCells, headFromRow, incarnationFromCell, mintSessionIncarnati
|
|
|
10
10
|
import { sessionRowTagKey } from "../session-row.js";
|
|
11
11
|
import { writeSessionRowCellsSql } from "./session-row-sql.js";
|
|
12
12
|
import { contentForkRelation, fastForwardSharedContentDiverged } from "../session-sync-content.js";
|
|
13
|
-
import { CLAIM_OWNERLESS_PLACED_SQL, claimOwnerlessParams, createPlacedSessionRow } from "../delegated-session-owner.js";
|
|
14
|
-
import { createLogger } from "../observability/logger.js";
|
|
15
13
|
import { placementCapabilityOf, LIST_PLACED_BATCH, PLACEMENT_SELECT_COLUMNS, placedRowFromRow, placementFromRow, placementInsertValues, SESSION_DURABILITY, SESSION_PLACEMENTS, } from "./session-placement.js";
|
|
16
14
|
import { classifySyncRelationshipByIds, SyncConflictError, StagedCommitOwnerChangedError, stagingIdFor, STAGING_ID_MARKER, STAGING_GC_GRACE_MS, } from "../session-sync-kernel.js";
|
|
17
|
-
import { assertFirstOwnerWritable, forkOwnerOf, isSessionOwnerValue } from "./session-ownership.js";
|
|
15
|
+
import { assertFirstOwnerWritable, forkOwnerOf, isSessionOwnerValue, placedSessionOwnerOf } from "./session-ownership.js";
|
|
18
16
|
import { selectForkRows } from "@sema-agent/core";
|
|
19
17
|
import { FORK_LOG_ROW_SHAPE, ForkTargetUnsettledError, classifyForkTarget, forkByFreshId, forkPointOf } from "./session-fork.js";
|
|
20
18
|
import { isMysqlDupKeyError } from "./sql-errors.js";
|
|
@@ -28,12 +26,10 @@ export class TiDBSessionStore {
|
|
|
28
26
|
placements = SESSION_PLACEMENTS;
|
|
29
27
|
durability = SESSION_DURABILITY;
|
|
30
28
|
pending = new Map();
|
|
31
|
-
log;
|
|
32
29
|
executionPlacement;
|
|
33
30
|
db;
|
|
34
31
|
constructor(pool, opts = {}) {
|
|
35
32
|
this.pool = pool;
|
|
36
|
-
this.log = opts.log ?? createLogger();
|
|
37
33
|
this.retention = opts.retention ?? UNMANAGED_RETENTION;
|
|
38
34
|
this.db = mysqlDriver(pool);
|
|
39
35
|
this.executionPlacement = createSqlExecutionPlacementSeat(this.db);
|
|
@@ -81,21 +77,7 @@ export class TiDBSessionStore {
|
|
|
81
77
|
await this.insertMeta(sessionId, owner, undefined);
|
|
82
78
|
}
|
|
83
79
|
async createOnAcquire(sessionId, placement) {
|
|
84
|
-
|
|
85
|
-
return this.insertMeta(sessionId, null, undefined);
|
|
86
|
-
await createPlacedSessionRow(sessionId, placement, {
|
|
87
|
-
readFaces: (ids) => this.sourceFaces(ids),
|
|
88
|
-
insert: (owner) => this.insertMeta(sessionId, owner, placement),
|
|
89
|
-
readOwner: async () => (await this.headOf(sessionId))?.owner,
|
|
90
|
-
}, this.log);
|
|
91
|
-
}
|
|
92
|
-
async claimOwnerless(sessionId, incarnation, owner) {
|
|
93
|
-
const [res] = await this.pool.query(CLAIM_OWNERLESS_PLACED_SQL.mysql, [...claimOwnerlessParams(sessionId, incarnation, owner)]);
|
|
94
|
-
return res.affectedRows > 0;
|
|
95
|
-
}
|
|
96
|
-
async sourceFaces(ids) {
|
|
97
|
-
const [rows] = await this.pool.query(`SELECT session_id, owner, incarnation FROM session_meta WHERE session_id IN (${ids.map(() => "?").join(", ")})`, [...ids]);
|
|
98
|
-
return new Map(rows.map((r) => [String(r.session_id), sessionFaceFromRow(String(r.session_id), r.owner ?? null, r.incarnation)]));
|
|
80
|
+
await this.insertMeta(sessionId, placement === undefined ? null : placedSessionOwnerOf(sessionId, placement), placement);
|
|
99
81
|
}
|
|
100
82
|
async insertMeta(sessionId, owner, placement) {
|
|
101
83
|
const now = new Date();
|
package/dist/run-local.js
CHANGED
|
@@ -51,7 +51,7 @@ import { declaredCredential } from "./model-route-gap.js";
|
|
|
51
51
|
import { judgeServedModelRoute } from "./model-route-endpoint.js";
|
|
52
52
|
import { startMemorySyncRunner } from "./memory-sync-client.js";
|
|
53
53
|
import { buildPricing, cappedCeiling, createTracer } from "./budget.js";
|
|
54
|
-
import { createSharedRunnerDeps } from "./boot/runner-deps.js";
|
|
54
|
+
import { createSharedRunnerDeps, installDefaultEngineNoticeSeat } from "./boot/runner-deps.js";
|
|
55
55
|
import { createPeerDesk } from "./peer-seat.js";
|
|
56
56
|
import { assertGovernanceAssemblable, createGovernanceSeams } from "./boot/governance-seams.js";
|
|
57
57
|
import { assertRetentionLaneWirable } from "./boot/retention-lane.js";
|
|
@@ -352,6 +352,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
352
352
|
const metrics = createMetrics();
|
|
353
353
|
const memoryEngineIncident = createMemoryEngineIncidentSink({ logger, metrics });
|
|
354
354
|
installSecretEnvScrubRecorder({ logger, metrics });
|
|
355
|
+
installDefaultEngineNoticeSeat(logger, metrics);
|
|
355
356
|
logConfigDiagnostics(logger);
|
|
356
357
|
provisionSemaConfigDir(config, logger);
|
|
357
358
|
installOutboundDispatcher(logger);
|
package/dist/runs.js
CHANGED
|
@@ -69,6 +69,9 @@ export function markChildrenStoppedByUserOnAbort(signal, taskId, principal) {
|
|
|
69
69
|
}
|
|
70
70
|
signal.addEventListener("abort", mark, { once: true });
|
|
71
71
|
}
|
|
72
|
+
const EXECUTION_ENV_REFUSAL_HTTP_STATUS = {
|
|
73
|
+
"env.home_unconfirmed": 409,
|
|
74
|
+
};
|
|
72
75
|
const PREPARE_REFUSAL_HTTP_STATUS = {
|
|
73
76
|
"resume.session_not_found": 404,
|
|
74
77
|
"rewind_snapshot.unresolvable": 422,
|
|
@@ -93,6 +96,7 @@ const PREPARE_REFUSAL_HTTP_STATUS = {
|
|
|
93
96
|
"config.memory_project_marker_nested": 409,
|
|
94
97
|
"config.read_deny_floor_unkept": 409,
|
|
95
98
|
"config.mcp_server_name_collision": 409,
|
|
99
|
+
...EXECUTION_ENV_REFUSAL_HTTP_STATUS,
|
|
96
100
|
};
|
|
97
101
|
export function resumeAtHttpStatus(result) {
|
|
98
102
|
const code = failureCodeOf(result.terminal);
|
package/dist/security.d.ts
CHANGED
|
@@ -8,7 +8,6 @@ import type { ClientSessionRowWrite, ReplaySessionRowWrite, ReplaySessionRowWrit
|
|
|
8
8
|
import type { ExecutionPlacementSeat } from "./execution-placement.js";
|
|
9
9
|
import type { SessionUnreadable } from "./session-error-wire.js";
|
|
10
10
|
import type { ForkTarget, ForkToOutcome } from "./plugins/session-fork.js";
|
|
11
|
-
import { type OwnerlessClaim } from "./delegated-session-owner.js";
|
|
12
11
|
/** One row of the session-list projection (CC /resume picker) — the durable session abstraction's enumeration
|
|
13
12
|
* output (§0.5). Mirrors {@link RunStore}'s shipped `SessionSummary` shape so the wire response is unchanged. */
|
|
14
13
|
export interface SessionListItem {
|
|
@@ -183,9 +182,6 @@ export declare function legacyUnownedAmnestyOpen(deployment: DeploymentShapeInpu
|
|
|
183
182
|
export type OwnerAwareSessionStore = SessionStore & {
|
|
184
183
|
/** 属主登记(core `SessionStore.register` 的契约:对没有行的 id 建行并记下属主,首写赢;对已有的行什么都不做)。读回走 `headOf`。 */
|
|
185
184
|
register?: (sessionId: string, owner: string | null) => Promise<void>;
|
|
186
|
-
/** [ref] 委派继承律的**认领口** —— 只在记录委派树的店上(三只持久会话店,只命中 placed 行;不记委派树的店没有):判断时读到的那一位住客仍是无主的 placed 行 ⇒ 写 `owner`,
|
|
187
|
-
* 返回是否写成(只 CAS 子行自己)。唯一调用方 = 停驻挂点的 `settleDelegatedOwner`(`delegated-session-owner.ts`;M23 走同一句 SQL);规则写在那个模块。 */
|
|
188
|
-
claimOwnerless?: OwnerlessClaim;
|
|
189
185
|
/** [ref] placed 分区的元组读口(店的既有读口,一行 PK 读;`undefined` = 普通会话或无此行)。店不交它 = 不实现 placement = 没有 placed 行(无 repo 的内存店)。两个读者:
|
|
190
186
|
* ① 会话面的属主门只在属主为 NULL 时读它一次(无主的 placed 行不是公共行,`http/principal-gate.ts` 的 `sessionOwnerCellOf`);
|
|
191
187
|
* ② 会话视图读它投影体顶层 `agent` 键(`audit.ts` 的 `sessionAgentOf`,只在视图读上付):店没有这一面 ⇒ 键缺席,读面不凭别处另铸子代身份。 */
|