@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
package/dist/boot/runner-deps.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createFileWorkflowScriptStore } from "@sema-agent/core";
|
|
1
|
+
import { createFileWorkflowScriptStore, setDefaultEngineNoticeSeat } from "@sema-agent/core";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { lkgReadDenyRows } from "../config-lkg.js";
|
|
4
4
|
import { auditReadDenyAnchoring } from "./read-deny-anchor-audit.js";
|
|
@@ -14,7 +14,6 @@ import { makeLoadProjectMemory, makeProbeInstructionSources } from "../project-m
|
|
|
14
14
|
import { createInstructionSourceSeat } from "../instruction-source-notice.js";
|
|
15
15
|
import { workflowModelAllowlistFor } from "../task-workflow.js";
|
|
16
16
|
import { buildCrossSessionInboundLayers, crossSessionDialogExpiryOf } from "../cross-session-settings.js";
|
|
17
|
-
import { defaultPeerInboundFace } from "../peer-inbound-face.js";
|
|
18
17
|
import { createPeerDirectory } from "../peer-directory.js";
|
|
19
18
|
import { storeFormRefusalInIncident } from "../memory-store-refusal.js";
|
|
20
19
|
import { coreFileHistoryCaptureSeat } from "../file-history-capture.js";
|
|
@@ -48,7 +47,7 @@ export const PROMPT_CACHE_BREAK_CAUSE_LEVEL = {
|
|
|
48
47
|
};
|
|
49
48
|
const PROMPT_CACHE_BREAK_LEVELS = new Map(Object.entries(PROMPT_CACHE_BREAK_CAUSE_LEVEL));
|
|
50
49
|
const CEILING_ORIGINS = new Set(["walltime", "turns"]);
|
|
51
|
-
export function createEngineNoticeForwarder(logger, router = defaultEngineNoticeRouter, metrics
|
|
50
|
+
export function createEngineNoticeForwarder(logger, router = defaultEngineNoticeRouter, metrics) {
|
|
52
51
|
return (notice) => {
|
|
53
52
|
const detail = redactNoticeRuleText(notice.code, notice.detail);
|
|
54
53
|
const message = redactNoticeMessage(notice.message);
|
|
@@ -58,10 +57,12 @@ export function createEngineNoticeForwarder(logger, router = defaultEngineNotice
|
|
|
58
57
|
if (typeof origin === "string" && CEILING_ORIGINS.has(origin))
|
|
59
58
|
metrics.inc("limit_ceiling_after_answer_total", { origin });
|
|
60
59
|
}
|
|
61
|
-
peerInbound.observe(notice);
|
|
62
60
|
router.route(notice);
|
|
63
61
|
};
|
|
64
62
|
}
|
|
63
|
+
export function installDefaultEngineNoticeSeat(logger, metrics) {
|
|
64
|
+
setDefaultEngineNoticeSeat(createEngineNoticeForwarder(logger, defaultEngineNoticeRouter, metrics));
|
|
65
|
+
}
|
|
65
66
|
export function createEngineNoticeSeat(logger, router = defaultEngineNoticeRouter) {
|
|
66
67
|
const warn = logger?.warn?.bind(logger);
|
|
67
68
|
return warn ? { onNotice: createEngineNoticeForwarder({ warn }, router) } : {};
|
package/dist/boot/shutdown.d.ts
CHANGED
|
@@ -98,7 +98,7 @@ export interface ShutdownCtx {
|
|
|
98
98
|
/**
|
|
99
99
|
* [ref]:被扣跨会话来信的收信方决断面(`peer-inbound-face.ts`,进程单例)的关停口 —— CC `HBe` 的宿主半场(core 不调 `PeerHeldQueue.shutdown()`)。
|
|
100
100
|
* hardShutdown 起点与审批面收尾**并行**调它、`backend.close()` 之前等它(结清要走信箱店的 CAS 与回执投递):本进程仍被扣的信按 core 的规则
|
|
101
|
-
* 带 `expired` 回执结清(reason `shutdown`)。缺席 = 本装配不接(测试装配)。
|
|
101
|
+
* 带 `expired` 回执结清(reason `shutdown`),core 等回执落箱再兑现(没落的点名 seq ⇒ 读数 `receiptFaults`)。缺席 = 本装配不接(测试装配)。
|
|
102
102
|
*/
|
|
103
103
|
peerInbound?: {
|
|
104
104
|
shutdown(): Promise<PeerInboundShutdownReading>;
|
package/dist/boot/shutdown.js
CHANGED
|
@@ -98,8 +98,10 @@ export function installShutdownHandlers(ctx) {
|
|
|
98
98
|
: (async () => {
|
|
99
99
|
const deadline = new AbortController();
|
|
100
100
|
const settled = peerInbound.shutdown().then((reading) => {
|
|
101
|
-
if (reading.
|
|
102
|
-
logger.info("peer_inbound_settled_at_shutdown", { queues: reading.queues,
|
|
101
|
+
if (reading.cards > 0)
|
|
102
|
+
logger.info("peer_inbound_settled_at_shutdown", { queues: reading.queues, cards: reading.cards });
|
|
103
|
+
for (const fault of reading.receiptFaults)
|
|
104
|
+
logger.warn("peer_inbound_shutdown_receipt_unlanded", { err: seatText("operator-log-text", fault) });
|
|
103
105
|
}, (err) => logger.warn("peer_inbound_shutdown_failed", { err: seatText("operator-log-text", String(err)) }));
|
|
104
106
|
const timedOut = delay(Math.max(0, graceDeadlineMs - Date.now()), undefined, { signal: deadline.signal }).then(() => "deadline", () => "done");
|
|
105
107
|
const winner = await Promise.race([settled.then(() => "done"), timedOut]);
|
|
@@ -15,6 +15,7 @@ import { setRedactionObserver } from "../trace/redact.js";
|
|
|
15
15
|
import { installOutboundDispatcher } from "../outbound-dispatcher.js";
|
|
16
16
|
import { provisionSemaConfigDir } from "../sema-config-dir.js";
|
|
17
17
|
import { bindOwnCredentialsConfig, declareServerWithheldEnv } from "../server-secret-env.js";
|
|
18
|
+
import { installDefaultEngineNoticeSeat } from "./runner-deps.js";
|
|
18
19
|
export function runStage01() {
|
|
19
20
|
declareServerWithheldEnv();
|
|
20
21
|
const config = loadConfig();
|
|
@@ -27,6 +28,7 @@ export function runStage01() {
|
|
|
27
28
|
const metrics = createMetrics();
|
|
28
29
|
installFailOpenRecorder({ logger, metrics, redactDetail: redactSecrets });
|
|
29
30
|
installSecretEnvScrubRecorder({ logger, metrics });
|
|
31
|
+
installDefaultEngineNoticeSeat(logger, metrics);
|
|
30
32
|
installFileHistoryFailureRecorder({ logger, metrics });
|
|
31
33
|
logConfigDiagnostics(logger);
|
|
32
34
|
for (const w of drainNumEnvWarnings())
|
|
@@ -172,7 +172,7 @@ export async function runStage07(ctx) {
|
|
|
172
172
|
.catch((e) => logger.warn("web_search_probe_failed", { provider: webSearchCfg.provider, error: seatText("operator-log-text", e instanceof Error ? e.message : String(e)) }));
|
|
173
173
|
}
|
|
174
174
|
const ensureChildSessionDurable = checkpointStore && resolveSubagentTranscriptTier(backgroundAgentStore !== undefined, sessionStore) === "full"
|
|
175
|
-
? (sessionId,
|
|
175
|
+
? (sessionId, given) => attestChildSessionDurable(sessionStore, sessionId, given)
|
|
176
176
|
: undefined;
|
|
177
177
|
const webFetchSummarize = createWebFetchSummarizeLane({ brain, config, getKeyResolver: () => webAdmin.getKeyResolver(), logger });
|
|
178
178
|
const scenarioDeps = {
|
|
@@ -13,7 +13,7 @@ import { backgroundAgentOutput, taskHandleOutput, taskHandleStop } from "../runs
|
|
|
13
13
|
import { createStoreLiveProbe } from "../store-live-probe.js";
|
|
14
14
|
import { counterStoreLabel } from "../plugins/store-backend.js";
|
|
15
15
|
import { sessionStoreLabelOf } from "../store-posture.js";
|
|
16
|
-
import { defaultPeerInboundFace
|
|
16
|
+
import { defaultPeerInboundFace } from "../peer-inbound-face.js";
|
|
17
17
|
import { startFleetClientFromConfig } from "../fleet-client.js";
|
|
18
18
|
import { createCenterWiringReader } from "../capabilities/center-wiring.js";
|
|
19
19
|
import { mintEngineSignal } from "../fleet/engine-signal.js";
|
|
@@ -344,12 +344,7 @@ export async function runStage10(ctx) {
|
|
|
344
344
|
retentionLane: retentionLaneStarted,
|
|
345
345
|
releaseRetentionLease: retentionLaneStore !== undefined ? () => retentionLaneStore.release(instanceId) : undefined,
|
|
346
346
|
servingLease,
|
|
347
|
-
peerInbound:
|
|
348
|
-
shutdown: () => defaultPeerInboundFace.shutdown(async (sessionId) => {
|
|
349
|
-
const head = await ownerAware.headOf?.(sessionId);
|
|
350
|
-
return head === undefined ? undefined : peerBoxScope(head.owner);
|
|
351
|
-
}),
|
|
352
|
-
},
|
|
347
|
+
peerInbound: defaultPeerInboundFace,
|
|
353
348
|
});
|
|
354
349
|
servingLease?.resumeStickyDenial();
|
|
355
350
|
return { ...ctx };
|
|
@@ -84,12 +84,11 @@ export interface ScenarioContext {
|
|
|
84
84
|
*/
|
|
85
85
|
sessionId?: string;
|
|
86
86
|
/**
|
|
87
|
-
* [ref]
|
|
88
|
-
*
|
|
89
|
-
* `rootSessionId` 与赎回腿写口放行的那一位住客(`parked-revive-surface.ts`,交不交座都交)
|
|
90
|
-
* 不挂任何会话级设施(那是 {@link sessionId}
|
|
91
|
-
*
|
|
92
|
-
* 挂点在停驻那一刻再读一次根行:住客不是见证的那一位(删了、同 id 被重新登记)⇒ 停驻否决 —— 不按新住客认领。
|
|
87
|
+
* [ref]:**委派树根的见证** —— 根会话 id + 调用方判断那一刻读到的根行住客(`delegated-session-owner.ts` 的 `DelegationRootWitness`)。
|
|
88
|
+
* 由取用方显式交出(一条读法,没有回落):请求腿 = 本请求经鉴权解析的会话与受理时那一读(`boot/resolve-spec.ts`);`/decide` 赎回面 = 行上的
|
|
89
|
+
* `rootSessionId` 与赎回腿写口放行的那一位住客(`parked-revive-surface.ts`,交不交座都交)。它只喂 Runner 之外的后台子代完成 relay(会话通知门的目标住客:属主与化身),
|
|
90
|
+
* 不挂任何会话级设施(那是 {@link sessionId} 的事,判据是「这只 bundle 是不是在那只会话上取用的」)。缺席 = 没有根可交(boot 期探针、run-local、行上没有根)⇒ 不接 relay。
|
|
91
|
+
* 停驻挂点不读它(core 7.62.0 起挂点只比对引擎交来的委派树属主;根见证在停驻挂点的读法随 [ref] P-5 退役)。
|
|
93
92
|
*/
|
|
94
93
|
delegationRoot?: DelegationRootWitness;
|
|
95
94
|
}
|
|
@@ -143,22 +142,19 @@ export interface ScenarioDeps {
|
|
|
143
142
|
* pre-153 行为(子代 SAFETY ask 照旧 deny+continue,零回归)。
|
|
144
143
|
* - `checkpointStore` 必须与 `RunnerDeps.checkpointStore`(主 runner 的 per-task spec 面)**同实例**
|
|
145
144
|
* ——子代 suspend 在那里铸 token,异实例 = park 全静默失效(core JSDoc 钉过症状,组装区注释同步)。
|
|
146
|
-
* - `ensureChildSessionDurable` = park 提交前的 capability 挂点(core 签名 `(sessionId) => Promise<void
|
|
147
|
-
* ——resolve = 该子代 session
|
|
148
|
-
* checkpoint 被 expire)
|
|
149
|
-
*
|
|
150
|
-
* 命中但零内容,是`register()`对任意自报 sessionId 的副作用,不是真正 durable 的会话)时 resolve
|
|
151
|
-
* (=fork 续聊的常见形),其余一律 reject——不做「主动迁移私店会话进 durable 店」这一步
|
|
152
|
-
* (ForkRoutingSessionStore 的路由表是私有状态,无迁移 API;主动迁移是更大的活,candidate 留给
|
|
153
|
-
* 后续按需再做)。
|
|
145
|
+
* - `ensureChildSessionDurable` = park 提交前的 capability 挂点(core 签名 `(sessionId, given: { owner }) => Promise<void>`,下面那个字段)
|
|
146
|
+
* ——resolve = 该子代 session 已从任意进程可读且属主是引擎交来的那一位,reject/缺席 = veto(子代按 pre-153 settle failed,
|
|
147
|
+
* checkpoint 被 expire)。实现只验不搬(`plugins/session-store.ts` 的 `attestChildSessionDurable`):宿主店里在、有真内容
|
|
148
|
+
* (`getLeafId() !== null`——排除 F-7 同族的空壳 claim-create 行)、属主相等,才 resolve。
|
|
154
149
|
*/
|
|
155
150
|
checkpointStore?: CheckpointStore;
|
|
156
151
|
/**
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
* 停驻都经它(子代沿用父的委派工具实例)。
|
|
152
|
+
* 停驻挂点 —— 签名就是 core 的(`(sessionId, given: { owner }) => Promise<void>`,core 7.62.0 [ref]):`given.owner` = 引擎判的委派树属主,挂点只比对宿主行属主与它
|
|
153
|
+
* (`plugins/session-store.ts` 的 `attestChildSessionDurable`)。场景工厂原样交给委派工具,不绑任何调用方见证(委派树根见证闭包随 [ref] P-5 退役)。
|
|
160
154
|
*/
|
|
161
|
-
ensureChildSessionDurable?: (sessionId: string,
|
|
155
|
+
ensureChildSessionDurable?: (sessionId: string, given: {
|
|
156
|
+
readonly owner: string | null;
|
|
157
|
+
}) => Promise<void>;
|
|
162
158
|
/**
|
|
163
159
|
* **Runner 之外的后台子代完成**交给哪里 —— 按委派树根见证({@link ScenarioContext.delegationRoot})造委派工具挂载期的 `background.notify`。
|
|
164
160
|
* 停驻赎回腿的复活、操作员 `handle.resume` 都不是哪一次 run 内的调用,没有调用车道;引擎只在能解出一只完成 sink(调用 ?? 挂载)时才起后台续跑,
|
|
@@ -38,7 +38,6 @@ export function buildScenarios(deps) {
|
|
|
38
38
|
const webSearch = requestWebSearchBackend(deps.requirePrincipal !== true
|
|
39
39
|
? judgeRequestWebSearch(req?.settings?.webSearch)
|
|
40
40
|
: { kind: "absent" }, deps.webSearch, deps.logger);
|
|
41
|
-
const ensureChildSessionDurable = deps.ensureChildSessionDurable;
|
|
42
41
|
const delegationRoot = ctx?.delegationRoot;
|
|
43
42
|
return {
|
|
44
43
|
tools: [
|
|
@@ -58,7 +57,7 @@ export function buildScenarios(deps) {
|
|
|
58
57
|
scope: "default",
|
|
59
58
|
...(deps.backgroundAgentStore ? { agentStore: deps.backgroundAgentStore } : {}),
|
|
60
59
|
...(deps.checkpointStore ? { checkpointStore: deps.checkpointStore } : {}),
|
|
61
|
-
...(ensureChildSessionDurable !== undefined ? { ensureChildSessionDurable:
|
|
60
|
+
...(deps.ensureChildSessionDurable !== undefined ? { ensureChildSessionDurable: deps.ensureChildSessionDurable } : {}),
|
|
62
61
|
...(deps.backgroundCompletionSinkFor !== undefined && delegationRoot !== undefined ? { notify: deps.backgroundCompletionSinkFor(delegationRoot) } : {}),
|
|
63
62
|
},
|
|
64
63
|
...createSubagentExtraTools(deps.subagentExtraTools),
|
|
@@ -28,6 +28,8 @@ export interface ConfigDirMigrationLog {
|
|
|
28
28
|
* · `noop` / `no-documents` / 目录不在 ⇒ 不出声(没有东西可迁);
|
|
29
29
|
* · `refused` ⇒ warn `config_dir_migration_refused`(一字未写;逐条点名迁移 id、坐标与契约的拒句 —— 读侧随后对这些旧形照旧响亮拒);
|
|
30
30
|
* · `unreadable` / 调用抛 ⇒ warn `config_dir_migration_failed`(一字未写或写到一半;原因 —— 坏文件的句子与读腿同一只判官)。
|
|
31
|
+
* 契约的 `unreadable` 不带成因,本仓分不出两类:① 读腿同一拍也会拒的坏文本(空 / 坏 JSON / 不是对象 / 嵌得过深〔根下 199 层〕:同一只文本判官,读腿随后另有一行 `config_candidate_rejected`,同一件事两行);
|
|
32
|
+
* ② 只有迁移读不了的(目录列不了、悬空符号链接 —— 读腿接受,这一行是唯一的信号)。静默 ① 就会连 ② 一起吞,所以两类同一行;契约交出成因后 ① 改为只留读腿那一行(待契约)。
|
|
31
33
|
* · `pending` 只在契约的 `check: true` 下出现,本仓从不传 ⇒ 撞上即是接线错,响亮抛。
|
|
32
34
|
*/
|
|
33
35
|
export declare function logConfigDirMigration(log: ConfigDirMigrationLog, m: ConfigDirMigration): void;
|
|
@@ -1,59 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 🔴 **委派子代会话的属主 ——
|
|
2
|
+
* 🔴 **委派子代会话的属主 —— 引擎判、店原样记**(core 7.62.0 [ref];[ref];server 7.144.0 起)。本模块只剩三件:存量行的一次性回填(M23)的判据与写句、
|
|
3
|
+
* 孤行哨兵属主的保留前缀、委派树根的见证形。
|
|
3
4
|
*
|
|
4
|
-
* ## 律
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* 鉴权边界拒任何报出它的主体)+ 写落下之后一行 warn `delegated_session_owner_orphaned`。宁可没人能读,也不让谁都能读。
|
|
12
|
-
* 一只记下的根 / 父是空串(`''`)⇒ 读作没有那一格(与 `session-placement.ts` 的 `placementFromRow` 同一个读法;M23 的扫描句在同一条语句里对连接键套
|
|
13
|
-
* `NULLIF(…, '')`,与判据同读)。
|
|
14
|
-
* **与引擎属主对说法不同(如实登记,收口在引擎)**:引擎 7.55.0 的属主对把「acquire 建的行」与 placed 行一并列为记 `null`(匿名);本律对**每一只** placed 行
|
|
15
|
-
* 在建行那一笔记委派树的属主(哨兵只是「来源都不在」那一形)—— 照引擎的读法记 `null`,在本服务的门上就只有 fleet 调用方读得到(发起它的主体自己读不到;`security.ts` 的 `ownerlessRowOpen`)、且留在认领句 / M23 的射程里,
|
|
16
|
-
* 照引擎「匿名 = 对每个主体放行」的字面读法则是拿到 id 的人都读得到;哨兵两条路都不走。引擎会话店试剂盒「对已有的行
|
|
17
|
-
* register 什么都不做」一格因此在 SQL 两店上登记待翻格,盒那一格的不变量(register 不认领任何已有的行)另由正向钉逐步断言(`test/store-contract-kit-db-integration.test.ts` §9b)。
|
|
18
|
-
* **写只 CAS 子行自己**:`owner IS NULL ∧ incarnation = 判断时读到的子行化身 ∧ placed`({@link CLAIM_OWNERLESS_PLACED_SQL},两方言各一句)。判断与写之间
|
|
19
|
-
* 根 / 父被删、被同 id 重建、换住客 —— 写进去的仍是**判断时那棵树**的属主,这正是律要的:不锁根、不读根、不要求「根到写时还是那个根」。之后与任何会话一样
|
|
20
|
-
* 首写不可变(`register` 的首写律)。判断那一刻的检查照旧在判断里做、不进 CAS:宿主行记下的根是不是这一棵;调用方见证之后根行换没换住客
|
|
21
|
-
* (停驻挂点,{@link assertRootStillWitnessed})。
|
|
5
|
+
* ## 律(唯一来源 = 引擎)
|
|
6
|
+
* 委派树的属主 D 由引擎判:没人派生的 run 读自己那一行(`sessions.headOf`,按化身绑一次),原样带给每一层子代(`SessionPlacement.owner: string | null`;`null` = 根匿名);
|
|
7
|
+
* 复活的子代 = 它自己那一行的属主。本仓两处只读它:
|
|
8
|
+
* · 三只持久会话店的 placing write 把 `placement.owner` 原样记进行的属主格(盒义务 7;唯一读法 `plugins/session-ownership.ts` 的 `placedSessionOwnerOf`);
|
|
9
|
+
* · 停驻挂点(`plugins/session-store.ts` 的 `attestChildSessionDurable`)只比对宿主行属主与引擎交来的 `given.owner`,不认领、不推断。
|
|
10
|
+
* 本仓此前自己的委派继承律([ref]:建行那一笔读根 / 父推断、来源都不在写孤行哨兵、停驻挂点按记录的树认领无主行、根见证换住客即拒)在这两处随之退役 ——
|
|
11
|
+
* 一个事实两份判据(引擎的 D 与本仓的推断)是双真源([ref] (a)),收成引擎那一份。
|
|
22
12
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
13
|
+
* ## 还在的:一次性迁移 M23 的回填(窗随 M23,登记表 M23 行;窗关同删本节全部)
|
|
14
|
+
* 7.132.x 及更早写下的 placed 行属主恒 NULL(当年 SQL 两店建行不记属主)。M23 每次起服按**旧律**给它们回填一次(引擎的 D 当时没有被记下来,读不回):
|
|
15
|
+
* 1. 根会话在本店有行 ⇒ 它的属主(根无主 ⇒ 子代也无主 —— 与根同读数);
|
|
16
|
+
* 2. 根行不在 ⇒ 父会话的行(同上);
|
|
17
|
+
* 3. 两者都不在本店(孤行)⇒ 一只任何已验身份都不可能等于的哨兵属主 `_orphan:<子代 id>`(前缀登记在 `security.ts` 的保留主体前缀里,鉴权边界拒任何报出它的主体)。
|
|
18
|
+
* 一只记下的根 / 父是空串(`''`)⇒ 读作没有那一格(与 `session-placement.ts` 的 `placementFromRow` 同一个读法;M23 的扫描句对连接键套 `NULLIF(…, '')`,与判据同读)。
|
|
19
|
+
* 写只 CAS 子行自己:`owner IS NULL ∧ incarnation = 扫描读到的子行化身 ∧ placed`({@link CLAIM_OWNERLESS_PLACED_SQL})。
|
|
20
|
+
* 对同一棵树,旧律的「根行的属主」与引擎的 D(根 run 读到的自己那一行的属主)是同一格的读数;两者只在「根行已不在」时分开(旧律取父 / 哨兵,引擎那时的读数没有留下)。
|
|
25
21
|
*
|
|
26
|
-
* ##
|
|
27
|
-
*
|
|
28
|
-
* 委派子代行)。会话行属主门上的无主放行臂对它**只对 fleet 调用方成立**(出示 service 凭据、不报主体;唯一判据 `security.ts` 的 `ownerlessRowOpen`,
|
|
29
|
-
* 会话面 / fork / 删除 / 审批豁免 / notify 同读;dev-open 照旧 fleet-wide),具名主体与没有 service 凭据的无身份调用方一律与别人的行同体 404 ——
|
|
30
|
-
* 所以还没定下的行只是暂时只有 fleet 面读得到,不是谁都能读。它们由一次性迁移 M23(窗内每次起服)或这一行的下一次停驻(停驻挂点的认领)
|
|
31
|
-
* 定下;两处都不承诺「一次收敛」。
|
|
32
|
-
* **只有记录委派树的行才认领**:三只持久会话店的 placed 行都记着委派树(placement 元组:SQL 两店在 `session_meta`,local 店在会话存档行头);
|
|
33
|
-
* 不记委派树的行(普通行,含升级前停驻时晋升进来的行)⇒ 没有可按的树 ⇒ 不认领(在停驻挂点照「属主不是律给的那一只 ⇒ 拒」处置)。不按更松的判据认领。
|
|
22
|
+
* ## 孤行哨兵前缀 —— **永久保留**(不随 M23 删)
|
|
23
|
+
* 已落库的行可能带着它(7.133.0–7.143.x 的建行推断与 M23 写下的);前缀从保留主体前缀里拿掉 = 让一个能自报 `_orphan:<id>` 的主体读到那些行。
|
|
34
24
|
*
|
|
35
|
-
* ##
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* 读来源 → 判 → 写(首写律)。读来源之后来源才变有主(滚动升级窗里别的副本的
|
|
39
|
-
* M23 回填了无主的父)⇒ 写下的仍是判断时的读数 —— 按律如此;那一行若因此无主,照上一节处置。
|
|
40
|
-
* · **停驻挂点**(`plugins/session-store.ts` 的 `attestChildSessionDurable`;只验、不搬):来源 = **调用方交出的委派树根,只有这一只**,连同
|
|
41
|
-
* 调用方判断那一刻读到的根行住客({@link DelegationRootWitness};见证值,判断用,不进 CAS):请求腿 = 本请求经鉴权解析的会话,住客 = 受理时那一读
|
|
42
|
-
* (`boot/resolve-spec.ts`);`/decide` 的赎回腿 = 行上的 `rootSessionId`,住客 = 赎回腿写口放行的那一位(认领之前解析器那一读;`parked-revive-surface.ts`,每一行都交)。
|
|
43
|
-
* 挂点先读一次根行:它在而住客不是见证的那一位 ⇒ 根被删、同 id 换了住客 ⇒ **拒**(不按新住客认领,{@link assertRootStillWitnessed});它不在 ⇒ 律给哨兵
|
|
44
|
-
* (根真没了)。不回落父:core 的挂点 `ensureChildSessionDurable(sessionId)` 只交子代 id,父 id 在这一写点拿不到;两条候选读法都读不到东西 ——
|
|
45
|
-
* ① 读子代行记下的父:记着委派树的行(placed 行)在下面的定夺里本就按**它记录的**根、父再判一次;不记委派树的行(升级前晋升进来的普通行)没有这一格;
|
|
46
|
-
* ② 登记簿(后台子代行 / core 注册簿):core 接口没有「按子代 sessionId 查行」的读口,SQL 表 `background_agent.session_id` 无索引,加一条只在一处用的私有读腿
|
|
47
|
-
* = 多一只读法;`scope` 列还把匿名折成 `"default"`(与真名 `default` 的租户不可分)。行上没有根(没有会话的取用形)⇒ 来源集空 ⇒ 哨兵。
|
|
48
|
-
* 宿主行的属主经 {@link settleDelegatedOwner} 定下:宿主行记着委派树而不是这一棵 ⇒ 拒(两边属主都是 null 也拒);记着这一棵 ⇒ 按**它记录的** placement(根、父)
|
|
49
|
-
* 再判一次(与 M23 对同一行同读数 —— 见证只答「是不是这一棵、根住客换没换」,来源集取行上记的),无主 ⇒ 认领(CAS 子行),认领试了没落下 ⇒ 拒;然后比对 ——
|
|
50
|
-
* 不是律给的那一只 ⇒ 停驻否决。
|
|
51
|
-
* · **一次性迁移 M23**(`plugins/one-time-migrations.ts`):存量 placed 行,来源 = 根、父(扫描时同一条语句连读两行);回填 = 认领(同一句,绑扫描读到的子行化身)。
|
|
52
|
-
* local 店没有第三处:它的 placed 行自本律起由第一处写下(属主在建行那一笔);存档在而那一格缺(建行两笔之间崩了,或同一数据根上 run-local 腿写下的 placed 存档)
|
|
53
|
-
* 读作从没被记下的属主 = 无主,照「无主的子代行」一节处置(停驻挂点的认领)。
|
|
25
|
+
* ## 委派树根的见证 —— Runner 之外的后台子代完成 relay 的地址
|
|
26
|
+
* {@link DelegationRootWitness}:根会话 id + 调用方判断那一刻读到的根行住客(属主与化身)。读者 = 会话通知门的 relay(`session-notify-door.ts`)、会话行记录座、
|
|
27
|
+
* 请求腿的腿事实;停驻挂点自 7.144.0 起不读它。
|
|
54
28
|
*/
|
|
55
29
|
import type { SessionPlacement } from "@sema-agent/core";
|
|
56
|
-
import
|
|
30
|
+
import type { SessionFace } from "./session-incarnation.js";
|
|
57
31
|
import type { SqlDialect } from "./plugins/sql-driver.js";
|
|
58
32
|
/** 孤行哨兵属主的前缀(保留主体前缀:`security.ts` 的 `RESERVED_PRINCIPAL_PREFIXES` 登记它,任何身份来源报出带它的主体都在鉴权边界被拒)。 */
|
|
59
33
|
export declare const ORPHAN_SESSION_OWNER_PREFIX = "_orphan:";
|
|
@@ -63,7 +37,7 @@ export declare function orphanSessionOwner(sessionId: string): string;
|
|
|
63
37
|
export declare function isOrphanSessionOwner(owner: string | null): boolean;
|
|
64
38
|
/** 一只来源行在本店的读数:`undefined` = 本店没有这一行;`null` = 行在、无主;串 = 属主(与 `headOf(id)?.owner` 的三态同义)。 */
|
|
65
39
|
export type SourceOwnerReading = string | null | undefined;
|
|
66
|
-
/**
|
|
40
|
+
/** 旧律的判决:继承自哪一行(属主可以是 `null` = 来源行无主),或孤行(哨兵属主)。 */
|
|
67
41
|
export type InheritedOwner = {
|
|
68
42
|
readonly kind: "inherited";
|
|
69
43
|
readonly owner: string | null;
|
|
@@ -72,122 +46,30 @@ export type InheritedOwner = {
|
|
|
72
46
|
readonly kind: "orphan";
|
|
73
47
|
readonly owner: string;
|
|
74
48
|
};
|
|
75
|
-
/** placement
|
|
49
|
+
/** placement 上旧律要读的两格(M23 扫描行上记下的那一份元组)。 */
|
|
76
50
|
export type PlacementSources = Pick<SessionPlacement, "rootSessionId" | "parentSessionId">;
|
|
77
51
|
/** placement 上的来源 id,按读法顺序(根在前、父在后;去空、去重 —— 一层子代的根就是父)。 */
|
|
78
52
|
export declare function ownerSourcesOf(placement: PlacementSources): readonly string[];
|
|
79
|
-
/**
|
|
53
|
+
/** 旧律本身(纯函数)。`sources` = 按读法顺序的 `[来源 id, 本店读数]`;第一只**在本店有行**的来源给出属主,一只都没有 ⇒ 孤行。 */
|
|
80
54
|
export declare function inheritedOwnerOf(childId: string, sources: ReadonlyArray<readonly [string, SourceOwnerReading]>): InheritedOwner;
|
|
55
|
+
/** M23 的**唯一判据**(纯函数):按记录的来源(placement 上的根、父)与它们此刻在本店的会话面(本店没有的 id 不在表里)判一次。 */
|
|
56
|
+
export declare function judgeDelegatedOwner(childId: string, placement: PlacementSources, faces: ReadonlyMap<string, SessionFace>): InheritedOwner;
|
|
81
57
|
/**
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*/
|
|
85
|
-
export interface DelegationTree {
|
|
86
|
-
readonly root: string | null;
|
|
87
|
-
readonly rootIncarnation: string | null;
|
|
88
|
-
}
|
|
89
|
-
/** 律的一次判:判决 + 读的来源 id(warn 行用)+ 判据读到的委派树(判断用)。 */
|
|
90
|
-
export interface JudgedOwner {
|
|
91
|
-
readonly inherited: InheritedOwner;
|
|
92
|
-
readonly sources: readonly string[];
|
|
93
|
-
readonly tree: DelegationTree;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* **唯一的判据**(纯函数;三处写点都调它):按记录的来源(placement 上的根、父)与它们此刻在本店的会话面(属主 + 化身;本店没有的 id 不在表里)判一次 ——
|
|
97
|
-
* 律给出属主(判断时见证到的),同时交出判据读到的委派树(判断用)。
|
|
98
|
-
*/
|
|
99
|
-
export declare function judgeDelegatedOwner(childId: string, placement: PlacementSources, faces: ReadonlyMap<string, SessionFace>): JudgedOwner;
|
|
100
|
-
/** 一次读回若干来源行的会话面(属主 + 化身;本店没有的 id 不在表里)。 */
|
|
101
|
-
export type SourceFacesReader = (ids: readonly string[]) => Promise<ReadonlyMap<string, SessionFace>>;
|
|
102
|
-
/** 按律读一次来源行、判一次(纯读,不告警 —— 孤行 warn 只在写落下之后,{@link reportWrittenOwner})。读失败照原样上抛(调用方的写随之失败,不猜一个属主)。 */
|
|
103
|
-
export declare function readDelegatedOwner(childId: string, placement: PlacementSources, read: SourceFacesReader): Promise<JudgedOwner>;
|
|
104
|
-
/** 孤行那一行 warn 的去处(logger 的子集)。 */
|
|
105
|
-
export interface OrphanOwnerLog {
|
|
106
|
-
warn(msg: string, fields?: Record<string, unknown>): void;
|
|
107
|
-
}
|
|
108
|
-
/** 写点的名字(warn 行的 `site` 字段:运维据它知道是哪一笔写下的哨兵)。`placed_create` = 按 placement 建行;`park` = 停驻挂点对宿主无主 placed 行的认领。 */
|
|
109
|
-
export type DelegatedOwnerSite = "placed_create" | "park";
|
|
110
|
-
/** 一笔写**已落下**之后调:写下的是孤行哨兵 ⇒ 一行 warn(写失败的那一笔走不到这里,不留下「已写」的话)。继承来的属主(含继承自孤行祖先的哨兵)不告。 */
|
|
111
|
-
export declare function reportWrittenOwner(log: OrphanOwnerLog, site: DelegatedOwnerSite, childId: string, judged: JudgedOwner): void;
|
|
112
|
-
/**
|
|
113
|
-
* 委派继承律的**认领句**(唯一的写法;停驻挂点对宿主无主行的认领与一次性迁移 M23 的回填**同一只**,都按 {@link claimOwnerlessParams} 绑参;窗随 M23)。
|
|
114
|
-
* **只 CAS 子行自己**:这一位住客(判断时读到的化身)、仍无主、placed 行(原文 CAS,第二次命中零行)。不读根、不锁根、不连接 —— 判断与写之间根 / 父的变化
|
|
115
|
-
* 不改变写进去的值(律:属主 = 判断时见证到的,本文件头注)。两方言只差占位符。
|
|
58
|
+
* M23 回填的**认领句**(唯一的写法;按 {@link claimOwnerlessParams} 绑参;窗随 M23)。**只 CAS 子行自己**:这一位住客(扫描读到的化身)、仍无主、placed 行
|
|
59
|
+
* (原文 CAS,第二次命中零行)。不读根、不锁根、不连接。两方言只差占位符。
|
|
116
60
|
* MySQL 协议下 mysql2 缺省带 FOUND_ROWS,但条件里有 `owner IS NULL` ⇒ 匹配行数 = 改写行数。
|
|
117
61
|
*/
|
|
118
62
|
export declare const CLAIM_OWNERLESS_PLACED_SQL: Readonly<Record<SqlDialect, string>>;
|
|
119
|
-
/** 认领句的绑参(
|
|
63
|
+
/** 认领句的绑参(两方言同序):`incarnation` = 扫描读到的子行化身。 */
|
|
120
64
|
export declare function claimOwnerlessParams(sessionId: string, incarnation: string, owner: string): readonly [string, string, string];
|
|
121
|
-
/** 店的认领口(三只持久会话店 + 缓存包装层同一个签名):这只会话的那一位住客(`incarnation`)仍是无主的 placed 行 ⇒ 写 `owner`,返回是否写成。 */
|
|
122
|
-
export type OwnerlessClaim = (sessionId: string, incarnation: string, owner: string) => Promise<boolean>;
|
|
123
|
-
/** 一只 SQL 店建 placed 行要交的 I/O(两店按各自方言交,都是店的既有读写口;读法与顺序只住在 {@link createPlacedSessionRow})。 */
|
|
124
|
-
export interface PlacedRowWriter {
|
|
125
|
-
/** 来源行的会话面一次读回。 */
|
|
126
|
-
readonly readFaces: SourceFacesReader;
|
|
127
|
-
/** 建行那一条 INSERT(属主与 placement 同一条落库;行已在 ⇒ 一个字节不动,首写律)。 */
|
|
128
|
-
insert(owner: string | null): Promise<void>;
|
|
129
|
-
/** 写后读回本行的属主(店的头读 `headOf(id)?.owner`:`undefined` = 行不在)—— 只为判「本笔的哨兵落没落下」。 */
|
|
130
|
-
readOwner(): Promise<string | null | undefined>;
|
|
131
|
-
}
|
|
132
65
|
/**
|
|
133
|
-
*
|
|
134
|
-
* 所以落库的那一行未必是本笔写的(同 id 的另一位建行者先落了)⇒ 读回属主:带着本笔判出的孤行哨兵才告一行 warn;别的 ⇒ 什么都没写下,不告。
|
|
135
|
-
* 不写后复核:判断之后来源才变有主,写下的仍是判断时的读数(律;那一行若因此无主,由无主放行臂的判据挡住非 fleet 调用方、由 M23 / 下一次停驻定下 —— 本文件头注)。
|
|
136
|
-
*/
|
|
137
|
-
export declare function createPlacedSessionRow(sessionId: string, placement: PlacementSources, io: PlacedRowWriter, log: OrphanOwnerLog): Promise<void>;
|
|
138
|
-
/**
|
|
139
|
-
* 停驻挂点的**见证**:调用方交出的委派树根 + 调用方判断那一刻读到的根行住客(属主与化身,同一读;化身 `null` = 那一读里根行不在,此时属主恒 `null`)。
|
|
66
|
+
* **委派树根的见证**:调用方交出的委派树根 + 调用方判断那一刻读到的根行住客(属主与化身,同一读;化身 `null` = 那一读里根行不在,此时属主恒 `null`)。
|
|
140
67
|
* 请求腿 = 鉴权解析的会话与受理时那一读;赎回腿 = 行上的根与写口放行的那一位住客(认领之前解析器那一读)。
|
|
141
|
-
*
|
|
68
|
+
* 读者:后台子代完成 relay(属主与化身,会话通知门的目标住客)、会话行记录座(化身)、请求腿的腿事实。
|
|
142
69
|
*/
|
|
143
70
|
export interface DelegationRootWitness {
|
|
144
71
|
readonly sessionId: string;
|
|
145
72
|
readonly owner: string | null;
|
|
146
73
|
readonly incarnation: string | null;
|
|
147
74
|
}
|
|
148
|
-
/**
|
|
149
|
-
* 「根行的化身已换 ⇒ 拒」在停驻挂点的那一道(挂点的见证是更早的一读,不是这一读):此刻根行在、而住客不是见证的那一位(见证那一读里不在,或是另一位)⇒ 抛。
|
|
150
|
-
* 根行此刻不在 ⇒ 不抛(根真没了 ⇒ 律给哨兵,不是「换了住客」)。这是**判断**的一部分;判断之后、写之前根的变化不改变写进去的值(律:属主 = 判断时见证到的)。
|
|
151
|
-
*/
|
|
152
|
-
export declare function assertRootStillWitnessed(sessionId: string, witness: Pick<DelegationRootWitness, "incarnation"> | undefined, judged: JudgedOwner): void;
|
|
153
|
-
/** 记录委派树的宿主店多交的两只口(三只持久会话店与缓存包装层;无 repo 的内存店两只都没有 ⇒ 没有可按的树 ⇒ 不认领)。 */
|
|
154
|
-
export interface DelegationTreeStore {
|
|
155
|
-
/** 本行落库的 placement 元组(`undefined` = 本行不是 placed 行 ⇒ 不记委派树)。 */
|
|
156
|
-
readonly placementOf: (sessionId: string) => Promise<PlacementSources | undefined>;
|
|
157
|
-
readonly claimOwnerless: OwnerlessClaim;
|
|
158
|
-
}
|
|
159
|
-
/** 停驻挂点要的宿主店口(按 id 读一行的唯一读口 `headOf` —— 属主与住客都是它的投影;记录委派树的店另有 {@link DelegationTreeStore})。 */
|
|
160
|
-
export interface DelegatedOwnerHost {
|
|
161
|
-
readonly headOf: HeadRead;
|
|
162
|
-
/** 缺席 = 这只店不记委派树(不实现 placement)。 */
|
|
163
|
-
readonly trees: DelegationTreeStore | undefined;
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* 「这只宿主店能不能承载委派子代的属主」—— 只判这一处(停驻挂点读它):店有按 id 的头读(`headOf`,「有没有」沿用 `headReadOf` 的同一处判法)⇒
|
|
167
|
-
* 绑好的 {@link DelegatedOwnerHost};没有 ⇒ `undefined`(调用方响亮拒:读不回属主的宿主店上,停驻出来的就是一只谁都能读的转录)。记录委派树的两只口
|
|
168
|
-
* (`placementOf` + `claimOwnerless`)**同在或同缺**:同在 ⇒ `trees`;同缺 ⇒ 不记委派树;只有一只 ⇒ `undefined`(记着树却认领不了,或认领却读不回树 —— 半只店不猜)。
|
|
169
|
-
*/
|
|
170
|
-
export declare function delegatedOwnerHostOf(store: {
|
|
171
|
-
readonly headOf?: unknown;
|
|
172
|
-
readonly placementOf?: (sessionId: string) => Promise<PlacementSources | undefined>;
|
|
173
|
-
readonly claimOwnerless?: OwnerlessClaim;
|
|
174
|
-
}): DelegatedOwnerHost | undefined;
|
|
175
|
-
/** {@link settleDelegatedOwner} 的结果:这一笔是否写下了属主(认领成了);`judged` = 宿主行记着委派树时按**它记录的树**的那一次判(缺席 = 行不记委派树,按调用方的判)。 */
|
|
176
|
-
export interface SettledOwner {
|
|
177
|
-
readonly claimed: boolean;
|
|
178
|
-
readonly judged: JudgedOwner | undefined;
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* 宿主店里这只会话的行属主**按律定下来**(停驻挂点的那一道;拒句不带任何一方的属主值 —— 它会进停驻否决的错误面)。
|
|
182
|
-
* `owner` / `tree` = 调用方按见证的根判出的属主与委派树。
|
|
183
|
-
* 1. 宿主行**记着委派树**(placed 行):记下的根 ≠ `tree.root` ⇒ 抛(不以属主是否 null 为前提:那是另一棵树的行);是这一棵 ⇒ **按它记录的 placement(根、父)
|
|
184
|
-
* 再判一次**(唯一判据 {@link judgeDelegatedOwner} —— 与 M23 / 建行复核对这一行同读数:根不在取父),调用方那一读之后根住客变了 ⇒ 抛({@link assertRootStillWitnessed},
|
|
185
|
-
* 调用方的读数当见证);行无主而律给出属主 ⇒ 认领(CAS 刚读到的那一位住客;写进去的是这一次判的读数)—— **试了没落下 ⇒ 抛**(这一行在判断之后被写过 /
|
|
186
|
-
* 删过 / 换了住客,不由事后的属主比对放行);
|
|
187
|
-
* 2. 不记委派树的行(普通行,含升级前晋升进来的行)不认领、不再判;
|
|
188
|
-
* 3. 比对 —— 此刻的属主不是律给的那一只(记着委派树 ⇒ 按记录的树判的那一只;否则 = `owner`)⇒ 抛。
|
|
189
|
-
*/
|
|
190
|
-
export declare function settleDelegatedOwner(host: DelegatedOwnerHost, sessionId: string, owner: string | null, tree: DelegationTree): Promise<SettledOwner>;
|
|
191
|
-
/** 按 id 逐只读头的店(`headOf`,活住客经 `sessionFaceOfHead` 投影 —— 空化身响亮拒)→ {@link SourceFacesReader}。 */
|
|
192
|
-
export declare function sourceFacesVia(headOf: HeadRead): SourceFacesReader;
|
|
193
75
|
//# sourceMappingURL=delegated-session-owner.d.ts.map
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { headReadOf, sessionFaceOfHead } from "./session-incarnation.js";
|
|
2
1
|
export const ORPHAN_SESSION_OWNER_PREFIX = "_orphan:";
|
|
3
2
|
export function orphanSessionOwner(sessionId) {
|
|
4
3
|
return `${ORPHAN_SESSION_OWNER_PREFIX}${sessionId}`;
|
|
@@ -25,26 +24,7 @@ export function inheritedOwnerOf(childId, sources) {
|
|
|
25
24
|
return { kind: "orphan", owner: orphanSessionOwner(childId) };
|
|
26
25
|
}
|
|
27
26
|
export function judgeDelegatedOwner(childId, placement, faces) {
|
|
28
|
-
|
|
29
|
-
const inherited = inheritedOwnerOf(childId, sources.map((id) => [id, faces.get(id)?.owner]));
|
|
30
|
-
const root = sourceIdOf(placement.rootSessionId) ?? null;
|
|
31
|
-
return { inherited, sources, tree: { root, rootIncarnation: root === null ? null : (faces.get(root)?.incarnation ?? null) } };
|
|
32
|
-
}
|
|
33
|
-
export async function readDelegatedOwner(childId, placement, read) {
|
|
34
|
-
const ids = ownerSourcesOf(placement);
|
|
35
|
-
return judgeDelegatedOwner(childId, placement, ids.length === 0 ? new Map() : await read(ids));
|
|
36
|
-
}
|
|
37
|
-
export function reportWrittenOwner(log, site, childId, judged) {
|
|
38
|
-
if (judged.inherited.kind !== "orphan")
|
|
39
|
-
return;
|
|
40
|
-
log.warn("delegated_session_owner_orphaned", {
|
|
41
|
-
sessionId: childId,
|
|
42
|
-
site,
|
|
43
|
-
sources: judged.sources,
|
|
44
|
-
owner: judged.inherited.owner,
|
|
45
|
-
note: "a delegated session's owner could not be derived: none of the sessions it was delegated from has a row in this session store (none was named, or they are gone), " +
|
|
46
|
-
"so the session was written with an owner no caller can match — only fleet-wide and operator faces reach it. If someone should read it, set its owner in the session store by hand",
|
|
47
|
-
});
|
|
27
|
+
return inheritedOwnerOf(childId, ownerSourcesOf(placement).map((id) => [id, faces.get(id)?.owner]));
|
|
48
28
|
}
|
|
49
29
|
export const CLAIM_OWNERLESS_PLACED_SQL = {
|
|
50
30
|
mysql: "UPDATE session_meta SET owner = ? WHERE session_id = ? AND incarnation = ? AND owner IS NULL AND placement_kind IS NOT NULL",
|
|
@@ -53,66 +33,4 @@ export const CLAIM_OWNERLESS_PLACED_SQL = {
|
|
|
53
33
|
export function claimOwnerlessParams(sessionId, incarnation, owner) {
|
|
54
34
|
return [owner, sessionId, incarnation];
|
|
55
35
|
}
|
|
56
|
-
export async function createPlacedSessionRow(sessionId, placement, io, log) {
|
|
57
|
-
const judged = await readDelegatedOwner(sessionId, placement, io.readFaces);
|
|
58
|
-
await io.insert(judged.inherited.owner);
|
|
59
|
-
if (judged.inherited.kind === "orphan" && (await io.readOwner()) === judged.inherited.owner)
|
|
60
|
-
reportWrittenOwner(log, "placed_create", sessionId, judged);
|
|
61
|
-
}
|
|
62
|
-
export function assertRootStillWitnessed(sessionId, witness, judged) {
|
|
63
|
-
if (witness === undefined || judged.tree.rootIncarnation === null || judged.tree.rootIncarnation === witness.incarnation)
|
|
64
|
-
return;
|
|
65
|
-
throw new Error(`session "${sessionId}" cannot be parked as this delegated session: its delegation tree's root session was deleted and its id registered again since the delegation was judged, ` +
|
|
66
|
-
"so the root's current owner is not the owner of this delegation tree");
|
|
67
|
-
}
|
|
68
|
-
export function delegatedOwnerHostOf(store) {
|
|
69
|
-
const headOf = headReadOf(store);
|
|
70
|
-
const placementOf = store.placementOf;
|
|
71
|
-
const claimOwnerless = store.claimOwnerless;
|
|
72
|
-
if (headOf === undefined)
|
|
73
|
-
return undefined;
|
|
74
|
-
if ((typeof placementOf === "function") !== (typeof claimOwnerless === "function"))
|
|
75
|
-
return undefined;
|
|
76
|
-
const trees = typeof placementOf === "function" && typeof claimOwnerless === "function"
|
|
77
|
-
? { placementOf: (id) => placementOf.call(store, id), claimOwnerless: (id, incarnation, owner) => claimOwnerless.call(store, id, incarnation, owner) }
|
|
78
|
-
: undefined;
|
|
79
|
-
return { headOf, trees };
|
|
80
|
-
}
|
|
81
|
-
export async function settleDelegatedOwner(host, sessionId, owner, tree) {
|
|
82
|
-
const refuse = () => new Error(`session "${sessionId}" cannot be parked as this delegated session: the host session store holds a session with this id that is not this delegation tree's ` +
|
|
83
|
-
"(a different owner, or a different recorded delegation root), so it does not attest this delegated transcript");
|
|
84
|
-
const face = sessionFaceOfHead(sessionId, await host.headOf(sessionId));
|
|
85
|
-
const trees = host.trees;
|
|
86
|
-
const recorded = face === undefined || trees === undefined ? undefined : await trees.placementOf(sessionId);
|
|
87
|
-
let expected = owner;
|
|
88
|
-
let judged;
|
|
89
|
-
let claimed = false;
|
|
90
|
-
if (face !== undefined && trees !== undefined && recorded !== undefined) {
|
|
91
|
-
if ((sourceIdOf(recorded.rootSessionId) ?? null) !== tree.root)
|
|
92
|
-
throw refuse();
|
|
93
|
-
judged = await readDelegatedOwner(sessionId, recorded, sourceFacesVia(host.headOf));
|
|
94
|
-
if (tree.root !== null)
|
|
95
|
-
assertRootStillWitnessed(sessionId, { incarnation: tree.rootIncarnation }, judged);
|
|
96
|
-
expected = judged.inherited.owner;
|
|
97
|
-
if (face.owner === null && expected !== null) {
|
|
98
|
-
claimed = await trees.claimOwnerless(sessionId, face.incarnation, expected);
|
|
99
|
-
if (!claimed)
|
|
100
|
-
throw refuse();
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
if ((await host.headOf(sessionId))?.owner !== expected)
|
|
104
|
-
throw refuse();
|
|
105
|
-
return { claimed, judged };
|
|
106
|
-
}
|
|
107
|
-
export function sourceFacesVia(headOf) {
|
|
108
|
-
return async (ids) => {
|
|
109
|
-
const out = new Map();
|
|
110
|
-
for (const id of ids) {
|
|
111
|
-
const face = sessionFaceOfHead(id, await headOf(id));
|
|
112
|
-
if (face !== undefined)
|
|
113
|
-
out.set(id, face);
|
|
114
|
-
}
|
|
115
|
-
return out;
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
36
|
//# sourceMappingURL=delegated-session-owner.js.map
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* `{ requested, effective, ceiling, sessionId }` —— `requested` = 被折的那个词(体的生效词:顶层 `permissionMode` 或
|
|
19
19
|
* `settings.permissions.defaultMode`)、`effective` = run 起步的词、`ceiling` = 折它的地板,三个都是 core 的模式闭集词;`sessionId` = 归属键。
|
|
20
20
|
* 🔴 **无 `runId`**:钳制发生在受理门,引擎的 run 身份要到 prepare 才铸,这一刻不存在(core 码册那一行写了 `runId`;本仓不编造,
|
|
21
|
-
* 归属看它落在哪条 run 的流 / 账本上 —— 与 `instructions.source_changed`
|
|
21
|
+
* 归属看它落在哪条 run 的流 / 账本上 —— 与 `instructions.source_changed` 同形)。
|
|
22
22
|
* 什么时候不铸:没有地板;没声明词(取地板本身不算改词,`clamped` 为假);请求的词不比地板松;会话 id 缺席(无处可投,宁缺不串)。
|
|
23
23
|
*/
|
|
24
24
|
import type { EngineNotice, EngineNoticeCode } from "@sema-agent/core";
|
|
@@ -87,11 +87,12 @@ export interface ExecutionPlacementSeat {
|
|
|
87
87
|
}
|
|
88
88
|
/**
|
|
89
89
|
* 读不出的存档的故障词(闭集;一次性迁移 M32 的 local 腿点名用 —— 随 M32 · 窗口见 `plugins/one-time-migrations.ts` 登记表,窗关后随迁移码删):
|
|
90
|
-
* · `corrupt_session` —— 存档在,会话头读不出 /
|
|
90
|
+
* · `corrupt_session` —— 存档在,会话头读不出 / 截断,或读得出而日志走不成一棵树(core 的 `SessionError` 词,原样;后一形在 core 7.61.x 及以前是 `invalid_session`,
|
|
91
|
+
* core 7.62.0 [ref] 起两形一个词,本闭集随之收窄);
|
|
91
92
|
* · `storage` —— 读的时候抛了**别的**故障(I/O 级:权限 / 磁盘 I/O / 超大 —— core 的 `readJsonlRecords` 只吞 ENOENT,其余 errno 原样抛,不是 `SessionError`),
|
|
92
93
|
* 说不出在不在也说不出内容;{@link UnusableArchive.errno} 带那只故障的 `code`。
|
|
93
94
|
*/
|
|
94
|
-
export type UnusableArchiveWord = "corrupt_session" | "
|
|
95
|
+
export type UnusableArchiveWord = "corrupt_session" | "storage";
|
|
95
96
|
/**
|
|
96
97
|
* local 的会话存档**读不出**(会话头读不出 / 日志结构走不通 / 读的时候抛了别的故障):盘上有这只会话的存档,只是读不出它 —— 与「不在」是两件事。
|
|
97
98
|
* (本型与 {@link isUnusableArchive} 随 M32 · 窗口见 `plugins/one-time-migrations.ts` 登记表。)
|
|
@@ -135,8 +136,10 @@ export declare function executionPlacementView(cell: ExecutionPlacementCell): Ex
|
|
|
135
136
|
/**
|
|
136
137
|
* 住客谓词的 JS 读法(SQL `incarnation = ? AND owner <=> ?` 的对位)= core 的行期望判官 `sessionRowMatches`(会话行格写原语锁内判的也是它,
|
|
137
138
|
* `session-row.ts` 的 `judgeSessionRowWrite`):属主与化身两样都没变才是判过的那一位 —— 同一化身的属主会变(local M10 认领、identical 重戳、
|
|
138
|
-
* SQL 导入提交点改属主)。两侧收「属主 + 化身」那一读:活住客(`SessionFace`)与店的头(`SessionHead`,门判过的那一读)
|
|
139
|
-
* (
|
|
139
|
+
* SQL 导入提交点改属主)。两侧收「属主 + 化身」那一读:活住客(`SessionFace`)与店的头(`SessionHead`,门判过的那一读)。三处调用方:
|
|
140
|
+
* · 执行格的写口判官(本模块)与执行上下文解析器步 ① 的 `judged` 复核(`http/execution-context.ts`)—— 一侧恒是执行席的读数(住客经 `sessionFaceFromRow` 解码,令牌非空);
|
|
141
|
+
* · 会话通知门投递前的住客复核(`session-notify-door.ts` 的 `recheck`)—— 两侧都是会话店的头读:不铸化身的店上**两侧都没有令牌**。
|
|
142
|
+
* 两侧都没有令牌 ⇒ 化身一项 `undefined === undefined` 恒等,判据退成**只比属主**(core 判官本身的读法,不是本处的特例);一侧有一侧没有 ⇒ 不是同一位。
|
|
140
143
|
*/
|
|
141
144
|
export declare function sameOccupant(a: Pick<SessionHead, "owner" | "incarnation">, b: Pick<SessionHead, "owner" | "incarnation">): boolean;
|
|
142
145
|
/**
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* 它是 handler 体的第二个入参 —— 触发之后 handler 不再起新事、在飞的子进程要叫停(`hooks/hook-runner.ts` 的命令钩子腿)。
|
|
12
12
|
*
|
|
13
13
|
* 站的集合 = 引擎事件表里有站的 `classic.*` 名(一条钉从 `MOD_EVENT_TABLE` 派生对拍,不手抄第二份)。
|
|
14
|
-
* 观察站(`StopFailure` / `PostCompact` / `PermissionDenied`)只许观察者:答案形是「无」,core 的迁移配方要求挂 `{ observe: true }`。
|
|
14
|
+
* 观察站(`StopFailure` / `PostCompact` / `PermissionDenied` / `SessionStart` / `SessionEnd`)只许观察者:答案形是「无」,core 的迁移配方要求挂 `{ observe: true }`。
|
|
15
15
|
*/
|
|
16
16
|
import type { ClassicEventMap, ClassicEventName, EngineDollar, On } from "@sema-agent/core";
|
|
17
17
|
/** 引擎有站的 `classic.*` 名(core 的 `ClassicEventMap` 键集)。 */
|
|
@@ -30,6 +30,8 @@ export declare const CLASSIC_STATION_ROLE: {
|
|
|
30
30
|
readonly "classic.PreCompact": "chain";
|
|
31
31
|
readonly "classic.PostCompact": "observe";
|
|
32
32
|
readonly "classic.PermissionDenied": "observe";
|
|
33
|
+
readonly "classic.SessionStart": "observe";
|
|
34
|
+
readonly "classic.SessionEnd": "observe";
|
|
33
35
|
};
|
|
34
36
|
/** 本仓 handler 体对一站作的答:观察站不作答(`void`);链站 = core 的答案形,去掉本仓从不作的 `null` / `false`(PreToolUse 的两个「不表态」词)。 */
|
|
35
37
|
type StationAnswer<K extends ClassicStation> = (typeof CLASSIC_STATION_ROLE)[K] extends "observe" ? void : Exclude<ClassicEventMap[K]["answer"], null | false>;
|
|
@@ -11,6 +11,8 @@ export const CLASSIC_STATION_ROLE = {
|
|
|
11
11
|
"classic.PreCompact": "chain",
|
|
12
12
|
"classic.PostCompact": "observe",
|
|
13
13
|
"classic.PermissionDenied": "observe",
|
|
14
|
+
"classic.SessionStart": "observe",
|
|
15
|
+
"classic.SessionEnd": "observe",
|
|
14
16
|
};
|
|
15
17
|
export function hangClassic(on, station, answer) {
|
|
16
18
|
if (CLASSIC_STATION_ROLE[station] === "observe") {
|