@sema-agent/server 7.117.0 → 7.119.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 +182 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +58 -28
- package/deploy/sema-up/README.md +2 -0
- package/deploy/sema-up/chart/templates/minio.yaml +2 -1
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +1 -0
- package/deploy/sema-up/chart/templates/server.yaml +1 -0
- package/deploy/sema-up/chart/values.yaml +7 -1
- package/deploy/sema-up/cluster-up.sh +20 -8
- package/deploy/sema-up/kube-up.sh +29 -16
- package/deploy/sema-up/lib/buckets.sh +21 -0
- package/deploy/sema-up/lib/health.sh +33 -0
- package/deploy/sema-up/sema-up.sh +14 -9
- package/deploy/sema-up/smoke.sh +16 -6
- package/deploy/sema-up/stack/compose.yaml +1 -1
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -1
- package/dist/approval-ask-audit-store.js +2 -7
- package/dist/approval-card.d.ts +40 -10
- package/dist/approval-card.js +11 -1
- package/dist/approval.d.ts +55 -17
- package/dist/approval.js +74 -33
- package/dist/audit.d.ts +12 -1
- package/dist/audit.js +62 -2
- package/dist/auto-mode-face.d.ts +2 -3
- package/dist/bake-runner/main.js +1 -6
- package/dist/bake-runner/protocol.d.ts +18 -0
- package/dist/bake-runner/protocol.js +22 -2
- package/dist/bake-runner/runner.d.ts +12 -8
- package/dist/bake-runner/runner.js +5 -9
- package/dist/boot/config-center.js +1 -1
- package/dist/boot/coordinators.js +4 -1
- package/dist/boot/device-lane.d.ts +3 -3
- package/dist/boot/device-lane.js +1 -2
- package/dist/boot/leader.d.ts +4 -4
- package/dist/boot/leader.js +1 -2
- package/dist/boot/parked-revive-gate.d.ts +4 -6
- package/dist/boot/parked-revive-gate.js +2 -6
- package/dist/boot/resolve-spec.js +9 -11
- package/dist/boot/runner-deps.js +2 -2
- package/dist/boot/stage-07-capability-layer.js +7 -3
- package/dist/boot/stage-10-http-server.js +3 -2
- package/dist/boot/stores.js +12 -7
- package/dist/capabilities/center-plugins.d.ts +2 -0
- package/dist/capabilities/center-plugins.js +24 -6
- package/dist/capabilities/code-review-council.js +8 -11
- package/dist/capabilities/hands-lane.d.ts +25 -0
- package/dist/capabilities/hands-lane.js +1 -1
- package/dist/capabilities/mcp-probe.d.ts +2 -2
- package/dist/capabilities/scenarios.d.ts +34 -7
- package/dist/capabilities/scenarios.js +33 -6
- package/dist/config-catalog.js +3 -3
- package/dist/config.d.ts +14 -18
- package/dist/config.js +15 -26
- package/dist/deployment-governance.d.ts +5 -7
- package/dist/deployment-governance.js +4 -5
- package/dist/host-decision.d.ts +114 -31
- package/dist/host-decision.js +39 -2
- package/dist/http/admission.js +12 -6
- package/dist/http/approval-sweeps.js +1 -1
- package/dist/http/mcp-dial-deadline.d.ts +35 -0
- package/dist/http/mcp-dial-deadline.js +21 -0
- package/dist/http/memory-compliance-failure-reply.d.ts +11 -5
- package/dist/http/memory-compliance-failure-reply.js +17 -8
- package/dist/http/memory-store-refusal-reply.d.ts +14 -11
- package/dist/http/memory-store-refusal-reply.js +16 -5
- package/dist/http/resume-legs.js +8 -3
- package/dist/http/routes/approvals-assistant.d.ts +25 -3
- package/dist/http/routes/approvals-assistant.js +27 -26
- package/dist/http/routes/capabilities.js +6 -3
- package/dist/http/routes/mcp-reconnect.d.ts +17 -0
- package/dist/http/routes/mcp-reconnect.js +6 -1
- package/dist/http/routes/memory-bundle.js +1 -1
- package/dist/http/routes/memory-compliance.js +1 -1
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/memory-origin.js +1 -1
- package/dist/http/routes/runs.js +47 -33
- package/dist/http/routes/sessions.d.ts +1 -1
- package/dist/http/routes/sessions.js +23 -22
- package/dist/http/routes/workflows.js +2 -0
- package/dist/http/wire-types.d.ts +17 -13
- package/dist/images/manifest.d.ts +7 -1
- package/dist/images/manifest.js +18 -16
- package/dist/memory-store-refusal.d.ts +38 -18
- package/dist/memory-store-refusal.js +25 -7
- package/dist/model-unreachable-notice.d.ts +1 -1
- package/dist/namespaced-rule-name.d.ts +46 -41
- package/dist/namespaced-rule-name.js +36 -22
- package/dist/observability/corrupt-read-seat.d.ts +4 -0
- package/dist/observability/corrupt-read-seat.js +1 -0
- package/dist/observability/fail-open.d.ts +19 -3
- package/dist/observability/fail-open.js +19 -3
- package/dist/operator-ask.d.ts +6 -2
- package/dist/orchestration/workflow-agent-session-index.d.ts +82 -59
- package/dist/orchestration/workflow-agent-session-index.js +80 -14
- package/dist/orchestration/workflow-completion-inbox.d.ts +5 -0
- package/dist/orchestration/workflow-completion-inbox.js +39 -17
- package/dist/orchestration/workflow-notify-journal.d.ts +1 -1
- package/dist/orchestration/workflow-notify-journal.js +7 -2
- package/dist/outbound-dispatcher.d.ts +29 -20
- package/dist/outbound-dispatcher.js +105 -16
- package/dist/parked-decide.d.ts +43 -48
- package/dist/parked-decide.js +54 -16
- package/dist/permission-rule-vocab.d.ts +4 -3
- package/dist/permission-rule-vocab.js +1 -1
- package/dist/plugins/approval-ask-store-sql.d.ts +17 -20
- package/dist/plugins/approval-ask-store-sql.js +0 -3
- package/dist/plugins/approval-exemption-store.d.ts +4 -0
- package/dist/plugins/approval-exemption-store.js +1 -0
- package/dist/plugins/blob-backend.d.ts +13 -0
- package/dist/plugins/blob-backend.js +26 -0
- package/dist/plugins/local-session-store.d.ts +78 -47
- package/dist/plugins/local-session-store.js +152 -122
- package/dist/plugins/one-time-migrations.d.ts +11 -0
- package/dist/plugins/one-time-migrations.js +128 -4
- package/dist/plugins/pg-pool.js +1 -1
- package/dist/plugins/snapshot-bucket-presence.d.ts +36 -0
- package/dist/plugins/snapshot-bucket-presence.js +40 -0
- package/dist/plugins/store-backend.d.ts +17 -6
- package/dist/plugins/store-backend.js +46 -16
- package/dist/plugins/store-open-wait.d.ts +33 -0
- package/dist/plugins/store-open-wait.js +73 -0
- package/dist/plugins/tidb-pool.js +1 -1
- package/dist/plugins/web-search.d.ts +20 -1
- package/dist/plugins/web-search.js +39 -10
- package/dist/plugins/workflow-run-store-sql.d.ts +7 -6
- package/dist/plugins/workflow-run-store-sql.js +5 -7
- package/dist/request-key-closure.js +1 -1
- package/dist/request-value-closure.d.ts +1 -1
- package/dist/rule-entry-list.d.ts +35 -46
- package/dist/rule-entry-list.js +18 -26
- package/dist/rules-consent.js +3 -0
- package/dist/run-local.js +5 -3
- package/dist/runs.d.ts +1 -1
- package/dist/security.d.ts +5 -3
- package/dist/security.js +4 -0
- package/dist/session-policy-wire.d.ts +13 -12
- package/dist/session-policy-wire.js +11 -11
- package/dist/spec-fields.d.ts +13 -11
- package/dist/spec-fields.js +4 -7
- package/dist/standing-answer-grant.d.ts +14 -2
- package/dist/standing-answer-grant.js +8 -1
- package/dist/store-posture.d.ts +14 -6
- package/dist/store-posture.js +7 -4
- package/dist/task-settings.d.ts +27 -55
- package/dist/task-settings.js +13 -21
- package/dist/tool-approval.d.ts +133 -138
- package/dist/tool-approval.js +175 -66
- package/dist/trace/core-keyset-guard.d.ts +5 -5
- package/dist/trace/redact.d.ts +6 -0
- package/dist/trace/redact.js +9 -9
- package/dist/trace/self-minted-wire.d.ts +16 -2
- package/dist/trace/self-minted-wire.js +7 -0
- package/dist/user-input-ledger.d.ts +30 -14
- package/dist/user-input-ledger.js +25 -14
- package/package.json +2 -2
- package/scripts/drain-check-local.mjs +74 -3
- package/scripts/drain-check.mysql.sql +43 -17
- package/scripts/drain-check.pg.sql +46 -20
package/dist/parked-decide.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { routeWorkflowPark } from "./orchestration/workflow-agent-session-index.js";
|
|
2
2
|
import { defaultSubagentTailBus } from "./fleet/subagent-tail-bus.js";
|
|
3
3
|
import { backgroundScopesForCheckpointScope, decodeCheckpointScope } from "./security.js";
|
|
4
4
|
import { ASK_USER_QUESTION_TOOL_NAME } from "./approval-content-kind.js";
|
|
@@ -20,19 +20,30 @@ export async function findParkOrigin(sources, cp) {
|
|
|
20
20
|
if (index === undefined || runStore === undefined)
|
|
21
21
|
return undefined;
|
|
22
22
|
try {
|
|
23
|
-
const
|
|
24
|
-
if (
|
|
23
|
+
const pointers = await index.listByAgentSession(cp.sessionId);
|
|
24
|
+
if (pointers.length === 0)
|
|
25
25
|
return undefined;
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
26
|
+
const runs = [];
|
|
27
|
+
for (const runId of new Set(pointers.map((p) => p.runId))) {
|
|
28
|
+
const run = await runStore.get(runId);
|
|
29
|
+
if (run !== null)
|
|
30
|
+
runs.push(run);
|
|
31
|
+
}
|
|
32
|
+
const route = routeWorkflowPark(cp.sessionId, runs);
|
|
33
|
+
switch (route.kind) {
|
|
34
|
+
case "held":
|
|
35
|
+
return route.park.token === cp.token ? { kind: "workflow", runId: route.runId, run: route.run, park: route.park } : undefined;
|
|
36
|
+
case "forked":
|
|
37
|
+
return { kind: "workflow_park_forked", runIds: route.runIds };
|
|
38
|
+
case "unproven":
|
|
39
|
+
return { kind: "workflow_parks_unreadable", runId: route.runId, run: route.run, cause: route.cause, ...(route.readableHolderRunId !== undefined ? { readableHolderRunId: route.readableHolderRunId } : {}) };
|
|
40
|
+
case "none":
|
|
41
|
+
return undefined;
|
|
42
|
+
default: {
|
|
43
|
+
const unhandled = route;
|
|
44
|
+
throw new Error(`unhandled workflow park route: ${JSON.stringify(unhandled)}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
36
47
|
}
|
|
37
48
|
catch (e) {
|
|
38
49
|
quiet(sources.warn, "workflow_park_probe_failed", { error: e instanceof Error ? e.message : String(e) });
|
|
@@ -90,12 +101,39 @@ export function buildParkedResumeOutcome(req) {
|
|
|
90
101
|
};
|
|
91
102
|
}
|
|
92
103
|
export function denyWorkflowParksUnreadable(origin) {
|
|
104
|
+
switch (origin.cause) {
|
|
105
|
+
case "older_engine":
|
|
106
|
+
return {
|
|
107
|
+
status: 409,
|
|
108
|
+
body: {
|
|
109
|
+
error: "this decision cannot be matched to the pending step it belongs to: the run's record was written by an earlier engine version, which did not record which run is waiting on each pending step — and that record is the only proof that the approval you are deciding belongs to this run. The decision is therefore NOT delivered, rather than answered with a receipt nothing can redeem: the approval card stays pending and nothing is forged. Resume the run itself instead — `Workflow({resumeFromRunId})` with the runId below — and once the resumed run is waiting on this step again it records itself as the one waiting, after which deciding here works normally. Only if the engine also refuses that resume is the run truly unrecoverable",
|
|
110
|
+
errorCode: "decide.workflow_parks_unreadable",
|
|
111
|
+
runId: origin.runId,
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
case "unreadable":
|
|
115
|
+
return {
|
|
116
|
+
status: 409,
|
|
117
|
+
body: {
|
|
118
|
+
error: "this decision cannot be matched to the pending step it belongs to: this run's record cannot be read (the list of pending steps the run owns is damaged: one of its entries is malformed, or it is not a list at all) — and that list is the only proof that the approval you are deciding belongs to this run. The decision is therefore NOT delivered, rather than answered with a receipt nothing can redeem: the approval card stays pending and nothing is forged. Resuming does not clear this: the damaged record keeps naming this step whatever runs after it, so the decision stays undeliverable until whoever operates this deployment repairs that record (the runId below); after that, deciding here works normally. When the response also carries readableHolderRunId, that run's record is readable and is the latest one waiting on this step — it is where the work can be carried on, but the damaged record above still keeps this decision from being delivered until that record is fixed",
|
|
119
|
+
errorCode: "decide.workflow_parks_unreadable",
|
|
120
|
+
runId: origin.runId,
|
|
121
|
+
...(origin.readableHolderRunId !== undefined ? { readableHolderRunId: origin.readableHolderRunId } : {}),
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
default: {
|
|
125
|
+
const unhandled = origin.cause;
|
|
126
|
+
throw new Error(`unhandled workflow parks-unreadable cause: ${String(unhandled)}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
export function denyWorkflowParkForked(origin) {
|
|
93
131
|
return {
|
|
94
132
|
status: 409,
|
|
95
133
|
body: {
|
|
96
|
-
error: "this decision cannot be
|
|
97
|
-
errorCode: "decide.
|
|
98
|
-
|
|
134
|
+
error: "this decision cannot be delivered: the same run was resumed more than once and every resumed copy listed in runIds is waiting on this same step, so there is no single run to hand the decision to. Nothing was delivered and the approval card stays pending. Resume one of the runs listed in runIds; once that run is waiting on this step again, deciding here works normally",
|
|
135
|
+
errorCode: "decide.workflow_park_forked",
|
|
136
|
+
runIds: [...origin.runIds],
|
|
99
137
|
},
|
|
100
138
|
};
|
|
101
139
|
}
|
|
@@ -10,12 +10,13 @@ import type { RuleApprovalRecord, RuleBehavior } from "@sema-agent/core";
|
|
|
10
10
|
*/
|
|
11
11
|
export declare function isPersistedRuleTool(name: string): boolean;
|
|
12
12
|
/**
|
|
13
|
-
* `PersistedRuleMatch`
|
|
13
|
+
* `PersistedRuleMatch` 七员(core 顶注逐字):`exact` / `prefix`(历史 colon-star,永久兼容读、复合前缀形的
|
|
14
14
|
* 唯一拼写)/ `wildcard`([ref] 尾 space-star,单命令体上与 prefix **同一谓词**,建议面自此铸它)/
|
|
15
15
|
* `subpath`(Read 目录形,永不容纳任何命令)/ `path`(core 7.9.0 [ref]:路径族 deny/ask/allow 的模式文法
|
|
16
|
-
* —— `//abs`、`~/`、`/root-relative`、cwd 相对,段内 `*`、跨段 `**`)
|
|
16
|
+
* —— `//abs`、`~/`、`/root-relative`、cwd 相对,段内 `*`、跨段 `**`)/ `name` / `name_prefix`(core 7.43.0 #1318:`Skill` 的名字文法 ——
|
|
17
|
+
* `Skill(<名>)` 精确、`Skill(<前缀>:*)` 名字前缀、无词界;技能卡的建议行 `match: "name"`)。
|
|
17
18
|
*/
|
|
18
|
-
export declare const PERSISTED_RULE_MATCHES: readonly ["exact", "prefix", "wildcard", "subpath", "path"];
|
|
19
|
+
export declare const PERSISTED_RULE_MATCHES: readonly ["exact", "prefix", "wildcard", "subpath", "path", "name", "name_prefix"];
|
|
19
20
|
/**
|
|
20
21
|
* S-511 —— `RuleBehavior` 每一员在**无同意仪式的直接写口**(`POST /v1/rules`)上的逐员表态。
|
|
21
22
|
*
|
|
@@ -2,7 +2,7 @@ import { ruleToolGrammarOf } from "@sema-agent/core";
|
|
|
2
2
|
export function isPersistedRuleTool(name) {
|
|
3
3
|
return ruleToolGrammarOf(name) !== undefined;
|
|
4
4
|
}
|
|
5
|
-
export const PERSISTED_RULE_MATCHES = ["exact", "prefix", "wildcard", "subpath", "path"];
|
|
5
|
+
export const PERSISTED_RULE_MATCHES = ["exact", "prefix", "wildcard", "subpath", "path", "name", "name_prefix"];
|
|
6
6
|
const MATCHES_PIN = true;
|
|
7
7
|
void MATCHES_PIN;
|
|
8
8
|
export const DIRECT_RULE_WRITE_ADMITS = {
|
|
@@ -82,8 +82,8 @@ export interface AskRow {
|
|
|
82
82
|
decisionNote: string | null;
|
|
83
83
|
/**
|
|
84
84
|
* 🔴 S-558(#1016 的持久半场)—— 这条决议的**结算来源**:`null` = 一个人答的(= 缺席律,core 对
|
|
85
|
-
* 不带这一格的 outcome 逐字按 `"human"` 读),非 null = 宿主自报的那个词(
|
|
86
|
-
* `"policy"
|
|
85
|
+
* 不带这一格的 outcome 逐字按 `"human"` 读),非 null = 宿主自报的那个词(S-1154 起写进来的只有决定体出处位
|
|
86
|
+
* `decidedBy:"policy"` 经 `liveSettledBy` 映射来的 `"policy"`,allow 与 deny 两向都写;词表属主恒是 core —— 见 {@link HostSettledBy})。
|
|
87
87
|
*
|
|
88
88
|
* **为什么必须是行上的一格**(病灶,7.93.3 合并树 codex ① 验真):`settledBy` 此前只到达**当次**回决的
|
|
89
89
|
* 即时回调,行上不留痕 ⇒ 重放 / 再入 / 同 askId 的兄弟等待者 / 歧义提交恢复读回的是裸 `false`,
|
|
@@ -230,8 +230,8 @@ export interface DecideAskInput {
|
|
|
230
230
|
* 🔴 S-558:本次回决的**结算来源**,与决议同一条 UPDATE 落列 `settled_by`(语义/两态读法/为什么是
|
|
231
231
|
* 自己一列,全在 {@link AskRow.settledBy})。缺席 ⇒ 列 NULL ⇒ 读回是「一个人答的」。
|
|
232
232
|
*
|
|
233
|
-
* 店**不判词**(词表属主是 core):写什么存什么。店只判**形** —— `assertDecideSettledBy`
|
|
234
|
-
* (长度 ≤
|
|
233
|
+
* 店**不判词**(词表属主是 core):写什么存什么。店只判**形** —— `assertDecideSettledBy` 的那一条
|
|
234
|
+
* (长度 ≤ 列宽;S-1154 起 approve 行也可以带来源位),理由见那只门的顶注;三 twin 同判。
|
|
235
235
|
*/
|
|
236
236
|
settledBy?: HostSettledBy;
|
|
237
237
|
/** [ref]:ctrl+g 编辑实参,与决议同一条 UPDATE 落列(语义三态见 {@link AskRow.updatedInput})。
|
|
@@ -270,26 +270,23 @@ export declare function assertIntegralEpochMs(field: string, value: number): voi
|
|
|
270
270
|
export declare function assertIdempotencyKeyShape(key: string): void;
|
|
271
271
|
/** `settled_by` 列宽 = 这一格的唯一尺(两方言 DDL 与下面的形门读同一个常量,不手抄第二份)。 */
|
|
272
272
|
export declare const MAX_SETTLED_BY_CHARS = 16;
|
|
273
|
-
/** `settled_by` 这一列装得下这个词吗(1..{@link MAX_SETTLED_BY_CHARS})——
|
|
274
|
-
*
|
|
275
|
-
* 读同一只,尺不抄第二份。 */
|
|
273
|
+
/** `settled_by` 这一列装得下这个词吗(1..{@link MAX_SETTLED_BY_CHARS})—— 店门口形门({@link assertDecideSettledBy})的**谓词本身**。
|
|
274
|
+
* S-1154 起决定口不再读它:wire 上的出处位是闭集(`host-decision.ts` 的 `APPROVAL_DECIDED_BY`),映射到这一列的词恒在尺内。 */
|
|
276
275
|
export declare function settledByFitsColumn(v: string): boolean;
|
|
277
276
|
/**
|
|
278
277
|
* S-558 的**入店卫生门**(三 twin 同判,与 {@link assertIdempotencyKeyShape} / {@link assertIntegralEpochMs} 同族)。
|
|
279
|
-
* 判的是**形**,不是**词** —— 词表属主恒是 core
|
|
280
|
-
* 本层一个词都不认识。两条:
|
|
278
|
+
* 判的是**形**,不是**词** —— 词表属主恒是 core,本层一个词都不认识。一条:
|
|
281
279
|
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
* 因此**证得出**不会丢掉任何真事实,而不是靠记性。
|
|
280
|
+
* **长度 ≤ 列宽**。`settled_by` 是 `VARCHAR(16)` 的闭集词列。不拦的话:MySQL 协议侧非严格模式**静默截断**、PG 侧直接
|
|
281
|
+
* 报错 —— 同一份调用方代码在两个后端拿到两种答案(`assertIdempotencyKeyShape` 的 PAD SPACE 论证逐字同源),而**被截断过的
|
|
282
|
+
* 词与原词是两个词**:一条决议的来源在库里变成了另一件事。空串同拒(core 那边它不是词,存进去只会读成一个假的「非人」)。
|
|
283
|
+
*
|
|
284
|
+
* 🔴 S-1154:此前的第二条「只与 `deny` 同行」**删了** —— 它的前提(「只有人能批准一个要执行的动作,core 对 `{allow:true,settledBy}`
|
|
285
|
+
* 一律 fail-closed 拒」)自 core 7.39.0 起不成立:策略的放行铸 `policy_allowed`,决定体的出处位 `decidedBy:"policy"` 在 allow 与 deny
|
|
286
|
+
* 上都落这一列,approve 行带来源位是一条合法的行(读回铸点 `outcomeOfDecidedRow` 两向都读它)。今天写这一列的只有两条路:决定口经
|
|
287
|
+
* `liveSettledBy` 映射来的 `"policy"`,与 `claimTerminal(refuse)` 的 {@link POLICY_REFUSAL_DECISION} —— 都是 core 在两向上都收的词。
|
|
291
288
|
*/
|
|
292
|
-
export declare function assertDecideSettledBy(decision: Pick<DecideAskInput, "
|
|
289
|
+
export declare function assertDecideSettledBy(decision: Pick<DecideAskInput, "settledBy">): void;
|
|
293
290
|
/**
|
|
294
291
|
* [ref] 车3 刀 3b(§14.2 遗留记账):两条**重放读口**被调用方的 deadline 掐断时抛的具名错误。
|
|
295
292
|
*
|
|
@@ -324,7 +321,7 @@ export type AskTerminalClaimIntent = "expire" | "cancel" | "refuse";
|
|
|
324
321
|
* `refuse` 意图落行的决议(NP-2)—— **部署政策拒**:deny + 结算来源 `policy`。来源词的型取 core 的 `HostSettledBy`
|
|
325
322
|
* (core 改词 / 退词 ⇒ 这里 tsc 红);协调器交给 core 的那一只(`tool-approval.ts` 的 `UnattendedPolicyRefusal`)读的是
|
|
326
323
|
* **同一个**常量,于是「行上记的来源」与「首判交出去的来源」在构造上是一个词。三 twin 落行前都过 {@link assertDecideSettledBy}
|
|
327
|
-
* (settled_by 的唯一形门:长度 ≤
|
|
324
|
+
* (settled_by 的唯一形门:长度 ≤ 列宽)。
|
|
328
325
|
*/
|
|
329
326
|
export declare const POLICY_REFUSAL_DECISION: {
|
|
330
327
|
readonly decision: "deny";
|
|
@@ -24,9 +24,6 @@ export function assertDecideSettledBy(decision) {
|
|
|
24
24
|
if (!settledByFitsColumn(v)) {
|
|
25
25
|
throw new Error(`invalid settledBy: the settlement source is one word from the engine's closed set and lands in a VARCHAR(${MAX_SETTLED_BY_CHARS}) column — must be 1-${MAX_SETTLED_BY_CHARS} chars (got ${JSON.stringify(v.slice(0, 32))}${v.length > 32 ? "…" : ""}, length ${v.length})`);
|
|
26
26
|
}
|
|
27
|
-
if (decision.decision !== "deny") {
|
|
28
|
-
throw new Error(`invalid settledBy: a settlement source is only representable on a deny row (got decision="${decision.decision}") — only a person approves an action that EXECUTES, so an approve row carrying a non-human settlement source is a row no replay could honestly hand back to the engine`);
|
|
29
|
-
}
|
|
30
27
|
}
|
|
31
28
|
export class ApprovalAskReadAbortedError extends Error {
|
|
32
29
|
constructor(where) {
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { Pool as MysqlPool } from "mysql2/promise";
|
|
2
2
|
import type { Pool as PgPool } from "pg";
|
|
3
3
|
import type { Logger } from "../observability/logger.js";
|
|
4
|
+
/** The server's own audit event for a question a row answered without asking anyone — one name for both reading positions
|
|
5
|
+
* (the list gate: field `seat: "deployment_list"`; the approval seat: `seat: "approval_seat"`). Not a settlement record: nobody
|
|
6
|
+
* was asked, so it names no human / person / policy and carries no `remember`. */
|
|
7
|
+
export declare const APPROVAL_EXEMPTED_EVENT = "approval_exempted";
|
|
4
8
|
export interface ApprovalExemptionRow {
|
|
5
9
|
toolName: string;
|
|
6
10
|
grantedBy: string | null;
|
|
@@ -2,6 +2,7 @@ import { join } from "node:path";
|
|
|
2
2
|
import { existsSync, readdirSync, rmSync } from "node:fs";
|
|
3
3
|
import { AppendLog, ensureDir, sanitizePathComponent } from "@sema-agent/core";
|
|
4
4
|
import { replayJsonlGuarded } from "../observability/corrupt-read-seat.js";
|
|
5
|
+
export const APPROVAL_EXEMPTED_EVENT = "approval_exempted";
|
|
5
6
|
export class TiDBApprovalExemptionStore {
|
|
6
7
|
pool;
|
|
7
8
|
constructor(pool) {
|
|
@@ -108,6 +108,15 @@ export interface MinioBlobBackendConfig {
|
|
|
108
108
|
* 同步路径,SendUserFile 工具调用永不返回。120s 对慢链路 ~64MiB 单 PUT/GET 仍宽裕(presignTtlSec
|
|
109
109
|
* 注释同口径),远小于病形。send-user-file 与 leader/diffup 同 import 此常量。 */
|
|
110
110
|
export declare const OBJECT_STORE_IO_TIMEOUT_MS = 120000;
|
|
111
|
+
/** 桶存在性探针的结果(闭集):`present` = 列举 2xx;`missing` = 404 且体里是 `NoSuchBucket`(**唯一**能证明缺桶的形);其余一切 = `unknown`(带一句原因,不回显体)。 */
|
|
112
|
+
export type BucketProbe = {
|
|
113
|
+
readonly state: "present";
|
|
114
|
+
} | {
|
|
115
|
+
readonly state: "missing";
|
|
116
|
+
} | {
|
|
117
|
+
readonly state: "unknown";
|
|
118
|
+
readonly detail: string;
|
|
119
|
+
};
|
|
111
120
|
export declare class MinioBlobBackend implements BlobBackend {
|
|
112
121
|
private readonly cfg;
|
|
113
122
|
private readonly keyPrefix;
|
|
@@ -160,6 +169,10 @@ export declare class MinioBlobBackend implements BlobBackend {
|
|
|
160
169
|
headObject(hash: string): Promise<{
|
|
161
170
|
lastModifiedMs: number;
|
|
162
171
|
} | undefined>;
|
|
172
|
+
/** 桶存在性探针(boot 期核**一次**,`plugins/snapshot-bucket-presence.ts`):presigned ListObjectsV2 `max-keys=1`(与 sweep 的列举同一条签名路径,零新依赖)。
|
|
173
|
+
* 🔴 只有「404 且体里是 `NoSuchBucket`」才算缺桶;网络不通 / 403(凭据不能列举 ≠ 桶不在)/ 5xx / 没有 NoSuchBucket 的 404(代理 / 路径错)
|
|
174
|
+
* 都**证明不了**缺桶 ⇒ `unknown`,调用方不拒启、只报一次。总函数:不抛。`timeoutMs`:boot 期调用方给的单次上界(黑洞端点不许把起服拖到 120s)。 */
|
|
175
|
+
probeBucket(timeoutMs?: number): Promise<BucketProbe>;
|
|
163
176
|
/** Byte-delete the given hashes — called ONLY from the async sweepOrphanBlobs reaper (NEVER the synchronous purge
|
|
164
177
|
* path). Honors the SAME created_at grace window as the SQL backend: with an index, DELETE the grace-passed index
|
|
165
178
|
* rows first (the index's created_at is the authoritative age — an S3 object carries no service-controlled timestamp
|
|
@@ -253,6 +253,32 @@ export class MinioBlobBackend {
|
|
|
253
253
|
const ms = lm ? Date.parse(lm) : NaN;
|
|
254
254
|
return { lastModifiedMs: Number.isFinite(ms) ? ms : Date.now() };
|
|
255
255
|
}
|
|
256
|
+
async probeBucket(timeoutMs = OBJECT_STORE_IO_TIMEOUT_MS) {
|
|
257
|
+
const f = this.cfg.fetchImpl ?? fetch;
|
|
258
|
+
const url = presignS3ListUrl({
|
|
259
|
+
endpoint: this.cfg.endpoint,
|
|
260
|
+
bucket: this.cfg.bucket,
|
|
261
|
+
prefix: this.keyPrefix,
|
|
262
|
+
maxKeys: 1,
|
|
263
|
+
accessKey: this.cfg.accessKey,
|
|
264
|
+
secretKey: this.cfg.secretKey,
|
|
265
|
+
...(this.cfg.region ? { region: this.cfg.region } : {}),
|
|
266
|
+
expiresSec: this.ttl,
|
|
267
|
+
});
|
|
268
|
+
let res;
|
|
269
|
+
try {
|
|
270
|
+
res = await f(url, { method: "GET", signal: AbortSignal.timeout(timeoutMs) });
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
return { state: "unknown", detail: `request failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
274
|
+
}
|
|
275
|
+
if (res.ok)
|
|
276
|
+
return { state: "present" };
|
|
277
|
+
const body = await res.text().catch(() => "");
|
|
278
|
+
if (res.status === 404 && /<Code>NoSuchBucket<\/Code>/.test(body))
|
|
279
|
+
return { state: "missing" };
|
|
280
|
+
return { state: "unknown", detail: `HTTP ${res.status}` };
|
|
281
|
+
}
|
|
256
282
|
async deleteBlobs(hashes) {
|
|
257
283
|
if (hashes.length === 0)
|
|
258
284
|
return 0;
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
import { type AcquiredSession, type SessionStore, type SessionRepo, type SessionTreeEntry } from "@sema-agent/core";
|
|
1
|
+
import { FileSessionRepo, type AcquiredSession, type SessionStore, type SessionRepo, type SessionTreeEntry } from "@sema-agent/core";
|
|
2
2
|
import type { SessionSummary } from "./store-contracts.js";
|
|
3
|
+
import { type Logger } from "../observability/logger.js";
|
|
3
4
|
import { type SessionFace } from "../session-incarnation.js";
|
|
4
5
|
import { type InstructionSourceBaseline } from "../instruction-source-notice.js";
|
|
5
6
|
import { type StagingHandle } from "../session-sync-kernel.js";
|
|
7
|
+
/**
|
|
8
|
+
* local 车道会话存档的**唯一 repo 出口**:本仓里每一只读 `<root>/sessions/*.jsonl` 的 `FileSessionRepo` 都从这里取(`LocalSessionStore` 的底座与会话视图口
|
|
9
|
+
* `audit.ts#buildSessionAuditLocal` 的读旁路),所以坏记录的披露座只有这一份接线 —— 修前视图口自建一只不带座的 repo,同一份坏档在这里读到有计数 / 探针,
|
|
10
|
+
* 在视图口读到一个字都没有。座 = F 类 `server.session-repo.corrupt-record-skipped`(会话存档里读不出的记录丢的是可用性:历史少一截 / 列表少一行,不开任何门;
|
|
11
|
+
* 计数单位 = 一条被跳过的记录,逐命中探针行,warn 每进程一次),外加会话打开作用域的计数腿(`noteReplayInSessionOpenScope`:只在 `openSessionForItself`
|
|
12
|
+
* 的作用域里才记,作用域外 —— 列表 / 导出 / 视图口 —— 是空操作,那些读只进运维面)。
|
|
13
|
+
*/
|
|
14
|
+
export declare function createLocalSessionRepo(root: string): FileSessionRepo;
|
|
6
15
|
export declare class LocalSessionStore implements SessionStore {
|
|
7
16
|
private readonly repo;
|
|
8
17
|
/** [ref] 车1:托管留存声明 —— **诚实的 none**。本店的行没有任何按期删除的腿(留存 lane 的三方法只实现在
|
|
@@ -12,8 +21,6 @@ export declare class LocalSessionStore implements SessionStore {
|
|
|
12
21
|
readonly retention: import("@sema-agent/core").RetentionDeclaration;
|
|
13
22
|
/** In-flight acquisitions keyed by id, so concurrent acquire(sameId) in one process share one (mirrors TiDBSessionStore). */
|
|
14
23
|
private readonly pending;
|
|
15
|
-
/** Service-side owner scope per session (core's repo has no owner column). Registered-ownerless ⇒ null; absent ⇒ undefined. */
|
|
16
|
-
private readonly owners;
|
|
17
24
|
/** Last-activity marker (touch) for ordering — the keyset sort key for {@link listSessions}. PERSISTED to a JSON
|
|
18
25
|
* sidecar under {@link dataDir} (when given) so the /resume picker's newest-first order SURVIVES a service restart:
|
|
19
26
|
* without persistence the map starts EMPTY on boot and every session degrades to `createdAt` ordering (the picker
|
|
@@ -54,35 +61,52 @@ export declare class LocalSessionStore implements SessionStore {
|
|
|
54
61
|
private readonly sidecarPath?;
|
|
55
62
|
/** Auto-title map (`session-titles.json`). Separate file from the lastActivity sidecar ON PURPOSE (that format is a
|
|
56
63
|
* #10 clobber area); titles are write-once + ultra-low-rate (one per session, ever). Cross-instance sharing: see
|
|
57
|
-
* {@link
|
|
64
|
+
* {@link SidecarMap}. */
|
|
58
65
|
private readonly titles;
|
|
59
66
|
/** S-690 — instruction-source baselines (`session-instruction-sources.json`): the chain member the session's previous
|
|
60
67
|
* run selected, encoded like the SQL twins' `session_meta.instruction_source` column ('' = none; missing key = never
|
|
61
68
|
* recorded). Its own file for the same reason titles have theirs: one small map per concern, no format coupling. */
|
|
62
69
|
private readonly instructionSources;
|
|
63
|
-
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
|
|
70
|
+
/**
|
|
71
|
+
* 会话元数据(`session-incarnations.json`,一格的形与两种读法见 {@link SessionMetaCell}):**属主与化身的唯一真源**,SQL 孪生
|
|
72
|
+
* `session_meta` 行上 `owner` / `incarnation` 两列在本店的对位。两样都**必须落盘**:
|
|
73
|
+
* · 化身(S-790 / S-506,[ref] §2)的口径是「一位住客一生稳定」,记忆引擎按它绑定会话级记录(opt-out / 污染标记)——
|
|
74
|
+
* 重启后换一只值 = 同一位住客的 opt-out 不再绑它 = 静默恢复采集;
|
|
75
|
+
* · 属主(7.119.0)是会话策略规则的存储键:规则按「会话 × 属主」存、按 run 的 principal 读 —— 重启后属主丢了,规则照样在跑,
|
|
76
|
+
* 属主自己却读不到也删不掉(`409 conflict.session_ownerless`)。7.118.x 及更早属主只活在一只进程内存 Map 里,就是这个病。
|
|
77
|
+
* 没有第二份:进程里的读视图就是这只边档按路径共享的那张表({@link SidecarMap},同写链;只发布已落盘的值 —— 写失败什么都不发布)。铸点只在新建会话;
|
|
78
|
+
* 盘上有会话而边档里没有它那一格 ⇒ 第一次读补铸一格只有化身的(属主没有事实可写)并落盘。严格读(读不出 ⇒ 整只拒,见 `SidecarMap.strict`)。
|
|
79
|
+
*/
|
|
80
|
+
private readonly meta;
|
|
81
|
+
/** 认领行(`session_owner_claimed`)的出口 —— 一次性迁移 M10 的认领臂专用(窗口见 `plugins/one-time-migrations.ts` 登记表)。 */
|
|
82
|
+
private readonly log;
|
|
69
83
|
/** @param repo the core SessionRepo this store projects into the §0.5 SessionStore shape.
|
|
70
84
|
* @param dataDir the SAME data dir the file-backed repo persists under (LocalBackend passes `fileBackend.root`).
|
|
71
85
|
* When given, lastActivity is persisted to `<dataDir>/session-last-activity.json` so it survives a
|
|
72
|
-
* restart (the file-backed deployment); omit it for the in-memory deployment (nothing survives).
|
|
73
|
-
|
|
86
|
+
* restart (the file-backed deployment); omit it for the in-memory deployment (nothing survives).
|
|
87
|
+
* @param opts.log the `session_owner_claimed` line's sink (LocalBackend passes a logger at the deployment's LOG_LEVEL;
|
|
88
|
+
* absent ⇒ an info-level JSON-line logger — a claim is never silent). */
|
|
89
|
+
constructor(repo: SessionRepo, dataDir?: string, opts?: {
|
|
90
|
+
log?: Pick<Logger, "info">;
|
|
91
|
+
});
|
|
92
|
+
/**
|
|
93
|
+
* 把 `sessionId` 那一格写成 `next(before)` 的结果并**落盘**(写链里在草稿上判、写、落盘后才发布给读者 —— 与 instruction-source 同一只 `commit`;
|
|
94
|
+
* 判据建立在每一节之前的盘上状态上,所以防 clobber 的「已记下就不动」写在 `next` 里,不写在链外)。`next` 返回 `undefined` = 不改。
|
|
95
|
+
* 落不了盘 ⇒ 抛(属主与化身都是正确性事实,不做「内存里先记着」的降级:一只没落盘的化身重启后会被换掉 —— 那是另一位住客;
|
|
96
|
+
* 一个没落盘的属主重启后就丢了 —— 那正是 7.119.0 修的病)。返回这一次是否真改了那一格。
|
|
97
|
+
*/
|
|
98
|
+
private writeMeta;
|
|
99
|
+
/** 删一只会话的那一格(落盘;落不了 ⇒ 抛 —— 留下的格会让一只删掉的会话在重启后「还在」)。 */
|
|
100
|
+
private dropMeta;
|
|
101
|
+
/** 盘上有没有这只会话(repo 的唯一探针;`open` 读整只存档,所以只在边档里没有那一格时才走到这里)。 */
|
|
102
|
+
private existsInRepo;
|
|
74
103
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
104
|
+
* 一只会话的元数据 —— 属主与化身的**唯一读口**(`ownerOf` / `sessionFaceOf` / `getHead` / 删除与换装的属主门都经它)。`undefined` = 无此会话。
|
|
105
|
+
* 边档里有那一格 ⇒ 读它(一次同步读,两样同出一格,不会拼出混世代组合);没有那一格而盘上有会话(7.104.0 之前的会话从没被铸过化身)
|
|
106
|
+
* ⇒ 补铸一格只有化身的(属主没有事实可写,不伪造)并落盘后再答 —— 之后的读都命中那一格,不再回 repo。
|
|
78
107
|
*/
|
|
79
|
-
private
|
|
80
|
-
/** S-790
|
|
81
|
-
private mintIncarnation;
|
|
82
|
-
/** S-790:已存在的会话缺一只化身(盘上 7.104.0 之前的会话)⇒ 补铸;已有的一个字节不动。 */
|
|
83
|
-
private ensureIncarnation;
|
|
84
|
-
/** S-790:属主 + 化身一次读(SQL 孪生同义;`undefined` = 无此会话)。盘上有会话而边档里没有它的化身 ⇒ 补铸并落盘后再答;
|
|
85
|
-
* 读的过程中会话被删 / 换了主 ⇒ `undefined`(与 {@link getHead} 同一条「读间被删」判据)。 */
|
|
108
|
+
private metaOf;
|
|
109
|
+
/** S-790:属主 + 化身一次读(SQL 孪生同义;`undefined` = 无此会话)—— 两样出自 {@link metaOf} 读到的同一格。 */
|
|
86
110
|
sessionFaceOf(sessionId: string): Promise<SessionFace | undefined>;
|
|
87
111
|
/** Cheap pre-LLM probe (local twin). The map write needs no session_meta row, so there is
|
|
88
112
|
* no "none" (register-lag) dimension here — two states only. */
|
|
@@ -94,10 +118,10 @@ export declare class LocalSessionStore implements SessionStore {
|
|
|
94
118
|
/** S-690 —— 换源通告的基线读(local 孪生;三态读法与 SQL 两孪生的 `session_meta.instruction_source` 同一只编解码)。 */
|
|
95
119
|
readInstructionSource(sessionId: string): Promise<InstructionSourceBaseline>;
|
|
96
120
|
/**
|
|
97
|
-
* S-690 —— CAS 覆写基线(契约见 `InstructionSourceStore`:期望值不符 ⇒ 不写;**没落盘 ⇒
|
|
121
|
+
* S-690 —— CAS 覆写基线(契约见 `InstructionSourceStore`:期望值不符 ⇒ 不写;**没落盘 ⇒ 内存不动、抛**,与 SQL 孪生
|
|
98
122
|
* 「要么提交要么抛」同形 —— 修前写失败只 warn、内存照样前进,同来源的后续 run 在相等比较上短路、再也不重写,重启后
|
|
99
|
-
* 比的是一份过期的盘,codex che-NM r1 [medium])。CAS
|
|
100
|
-
*
|
|
123
|
+
* 比的是一份过期的盘,codex che-NM r1 [medium])。CAS 判定(在草稿上)、落盘、发布**整段在写链的一节里**(`commit`):下一节
|
|
124
|
+
* 开始之前共享的那张表只含落过盘的值,判定永远不会建立在一个没落过盘的值上(codex che-NM r2 [medium])。
|
|
101
125
|
*/
|
|
102
126
|
recordInstructionSource(sessionId: string, expected: InstructionSourceBaseline, source: string | null): Promise<void>;
|
|
103
127
|
/** Best-effort, one-shot hydrate of the persisted lastActivity map (no-op when not persisting / no sidecar yet). A
|
|
@@ -141,28 +165,35 @@ export declare class LocalSessionStore implements SessionStore {
|
|
|
141
165
|
requireExisting?: boolean;
|
|
142
166
|
}): Promise<AcquiredSession>;
|
|
143
167
|
private load;
|
|
144
|
-
/**
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
|
|
149
|
-
/** Owner of a session: `null` if registered ownerless, `undefined` if no such session (mirrors TiDBSessionStore.ownerOf).
|
|
168
|
+
/**
|
|
169
|
+
* Idempotently record a session's owner — never overwrites a RECORDED owner (incl. a recorded `null`, "registered ownerless";
|
|
170
|
+
* mirrors TiDBSessionStore.register's first-write rule). TiDB parity: register INSERTs a session_meta row so the session EXISTS
|
|
171
|
+
* (forkable) even before its first acquire/run — so we also ensure an EMPTY repo session exists (open if present → never
|
|
172
|
+
* overwrite history; create if not), else `ownerOf` would say a registered-but-never-acquired session exists while `fork` 404s it.
|
|
150
173
|
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
174
|
+
* 首写判据在写链里判(两只并发登记:先落盘的那一只赢,后一只不改、也不建会话)。一只**盘上已有、属主从没被记下**的会话
|
|
175
|
+
* (边档那一格只有化身 —— 7.118.x 及更早写下的,或更早到连化身都没有)是一次性迁移 M10 的对象(窗口见 `plugins/one-time-migrations.ts`
|
|
176
|
+
* 登记表):窗开着、且这次登记带着 principal(`owner !== null`)⇒ **认领**(记下属主,化身一个字节不动 —— 同一位住客),一行 info
|
|
177
|
+
* `session_owner_claimed`;否则只补化身(缺就补),属主仍是「没被记下」,读作无主。认领的安全前提是单用户 —— local 后端起服门拒
|
|
178
|
+
* `REQUIRE_PRINCIPAL=true`,授权口也只在那时才把 principal 递过来认领(`security.ts` `createAuthorizer`)。
|
|
179
|
+
*/
|
|
180
|
+
register(sessionId: string, owner: string | null): Promise<void>;
|
|
181
|
+
/** Owner of a session: `null` if ownerless (registered ownerless, or an owner that was never recorded — a session written by
|
|
182
|
+
* an earlier version), `undefined` if no such session (mirrors TiDBSessionStore.ownerOf). Reads {@link metaOf}: the persisted
|
|
183
|
+
* cell, so the answer survives a restart exactly like the SQL twins' `session_meta.owner` column does. (Before 7.119.0 the
|
|
184
|
+
* owner lived in a process-local Map and this method answered `null` for every session after a restart — the S3 fallback
|
|
185
|
+
* "on disk ⇒ ownerless" kept the E21 delete working, but froze every owner-keyed session-policy rule.) */
|
|
156
186
|
ownerOf(sessionId: string): Promise<string | null | undefined>;
|
|
157
|
-
/** [ref] 六轮复审:owner+leafId
|
|
158
|
-
* (
|
|
187
|
+
/** [ref] 六轮复审:owner+leafId 原子快照 —— 先读那一格,读 leaf,再同步复读那一格:格没了(读间被删)或换了主 / 换了住客 ⇒
|
|
188
|
+
* undefined(调用方按删处理)。 */
|
|
159
189
|
getHead(sessionId: string): Promise<{
|
|
160
190
|
owner: string | null;
|
|
161
191
|
leafId: string | null;
|
|
162
192
|
} | undefined>;
|
|
163
|
-
/** 2c P1d-β — re-stamp the
|
|
164
|
-
*
|
|
165
|
-
|
|
193
|
+
/** 2c P1d-β — re-stamp the session's owner WITHOUT rewriting entries (the staged-commit `identical` path: the log is
|
|
194
|
+
* already present & equal, so only the owner needs re-stamping to the importing principal, §9). Persisted like every
|
|
195
|
+
* owner write; the incarnation is untouched (same occupant). No cell (no such session) ⇒ no-op. */
|
|
196
|
+
restampOwner(sessionId: string, owner: string | null): Promise<void>;
|
|
166
197
|
touch(sessionId: string): Promise<void>;
|
|
167
198
|
/** E18 — the session's current leaf SessionTreeEntry.id (@see TiDBSessionStore.getLeafId). Reads the repo's storage
|
|
168
199
|
* leaf (no acquire-lock; InMemory/File repo reads don't lock). null when the session doesn't exist / has no leaf. */
|
|
@@ -174,10 +205,9 @@ export declare class LocalSessionStore implements SessionStore {
|
|
|
174
205
|
* abstraction (`repo.list()`), keyset-paginated newest-first by last activity. Session IDENTITY comes from the
|
|
175
206
|
* repo (one entry per persisted session); a {@link SessionSummary} is SYNTHESIZED per id so the wire shape is
|
|
176
207
|
* byte-identical to {@link TiDBSessionStore.listSessions} (the run picker doesn't drift):
|
|
177
|
-
* - `owner` — the
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
* null-owner session lists). The route already owner-gates at the HTTP layer.
|
|
208
|
+
* - `owner` — the session's persisted session-meta cell (null when ownerless / never recorded). A
|
|
209
|
+
* single-user local deployment runs REQUIRE_PRINCIPAL=off; the route already owner-gates
|
|
210
|
+
* at the HTTP layer.
|
|
181
211
|
* - `firstActivityAt` — the session's `createdAt` (repo metadata).
|
|
182
212
|
* - `lastActivityAt` — the touch marker (falls back to `createdAt`); the keyset sort key.
|
|
183
213
|
* - `objectivePreview`— the last USER-text entry, synthesized from the session tree (no run ledger).
|
|
@@ -240,9 +270,10 @@ export declare class LocalSessionStore implements SessionStore {
|
|
|
240
270
|
/** 2c session-sync — IMPORT a verbatim entry log into `sessionId` via core `SessionRepo.importEntries`
|
|
241
271
|
* (which runs the `validateEntriesForImport` fail-closed gate before writing). `owner` is the authenticated
|
|
242
272
|
* importing principal (re-stamped into the service-side owner map; core's local repo has no owner column, §9).
|
|
243
|
-
* `owner=null` (single-user local) is passed as `undefined` to the core seam (its owner param is `string | undefined`).
|
|
273
|
+
* `owner=null` (single-user local) is passed as `undefined` to the core seam (its owner param is `string | undefined`).
|
|
274
|
+
* The owner is recorded in the session-meta cell (persisted), together with the incarnation. */
|
|
244
275
|
importEntries(sessionId: string, owner: string | null, entries: SessionTreeEntry[]): Promise<void>;
|
|
245
|
-
/** importEntries / replaceEntries 的共用写半场:导日志 +
|
|
276
|
+
/** importEntries / replaceEntries 的共用写半场:导日志 + 一格写全属主与化身(新建铸的那只,或换装沿用的那只)。 */
|
|
246
277
|
private importEntriesAs;
|
|
247
278
|
/** 2c session-sync (§7/§8) — IDEMPOTENT REPLACE. The local twin's replace is DELETE-then-IMPORT: core's
|
|
248
279
|
* InMemorySessionRepo / FileSessionRepo expose no single replace primitive, but the local backend is single-writer
|