@sema-agent/server 7.103.1 → 7.104.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 +362 -3
- package/README.md +6 -1
- package/README.zh-CN.md +5 -1
- package/USAGE.md +279 -18
- package/deploy/sema-up/README.md +57 -7
- package/deploy/sema-up/cluster-up.sh +18 -4
- package/deploy/sema-up/kube-up.sh +16 -0
- package/deploy/sema-up/lib/dataroot.sh +142 -0
- package/deploy/sema-up/lib/flagshape.sh +69 -0
- package/deploy/sema-up/lib/lanes.py +14 -4
- package/deploy/sema-up/lib/shell-access.sh +41 -0
- package/deploy/sema-up/preflight.sh +14 -1
- package/deploy/sema-up/sema-up.sh +134 -37
- package/deploy/sema-up/smoke.sh +5 -0
- package/dist/auto-mode-face.js +3 -4
- package/dist/boot/budget-tracing.js +1 -1
- package/dist/boot/config-center.d.ts +6 -2
- package/dist/boot/config-center.js +5 -59
- package/dist/boot/execution-env.d.ts +11 -0
- package/dist/boot/execution-env.js +14 -0
- package/dist/boot/execution-lane.d.ts +25 -0
- package/dist/boot/execution-lane.js +95 -0
- package/dist/boot/memory-consolidation.d.ts +185 -9
- package/dist/boot/memory-consolidation.js +192 -32
- package/dist/boot/resolve-spec.d.ts +2 -0
- package/dist/boot/resolve-spec.js +28 -24
- package/dist/boot/runner-deps.d.ts +6 -2
- package/dist/boot/runner-deps.js +4 -0
- package/dist/boot/session-faces.d.ts +5 -0
- package/dist/boot/session-faces.js +18 -2
- package/dist/boot/session-incarnation-seat.d.ts +41 -0
- package/dist/boot/session-incarnation-seat.js +69 -0
- package/dist/boot/stage-01-config.js +2 -2
- package/dist/boot/stage-02-registry-pull.js +3 -1
- package/dist/boot/stage-04-stores.d.ts +2 -0
- package/dist/boot/stage-04-stores.js +3 -1
- package/dist/boot/stage-05-execution-env.d.ts +4 -0
- package/dist/boot/stage-05-execution-env.js +2 -2
- package/dist/boot/stage-06-runners.d.ts +4 -1
- package/dist/boot/stage-06-runners.js +7 -14
- package/dist/boot/stage-07-capability-layer.d.ts +4 -1
- package/dist/boot/stage-08-reapers.d.ts +4 -1
- package/dist/boot/stage-09-leader.d.ts +6 -1
- package/dist/boot/stage-09-leader.js +2 -1
- package/dist/boot/stage-10-http-server.d.ts +6 -1
- package/dist/boot/stage-10-http-server.js +4 -1
- package/dist/brain.d.ts +1 -1
- package/dist/config-catalog.d.ts +16 -1
- package/dist/config-catalog.js +9 -9
- package/dist/config-center/restart-signal.d.ts +4 -4
- package/dist/config-invariants.d.ts +7 -1
- package/dist/config-invariants.js +37 -6
- package/dist/config-provider.d.ts +42 -10
- package/dist/config-provider.js +18 -16
- package/dist/config-types.d.ts +37 -16
- package/dist/config-types.js +1 -0
- package/dist/config.d.ts +13 -28
- package/dist/config.js +53 -70
- package/dist/env-facts.d.ts +13 -3
- package/dist/env-facts.js +15 -3
- package/dist/execution-lane-caps.d.ts +15 -0
- package/dist/execution-lane-caps.js +8 -0
- package/dist/file-history-capture.d.ts +42 -35
- package/dist/file-history-capture.js +15 -10
- package/dist/host-lsp-manager.d.ts +1 -1
- package/dist/http/admission.d.ts +11 -4
- package/dist/http/admission.js +5 -1
- package/dist/http/dispatch.js +3 -3
- package/dist/http/principal-gate.d.ts +22 -0
- package/dist/http/principal-gate.js +20 -0
- package/dist/http/resume-legs.js +9 -5
- package/dist/http/routes/agents-roster.d.ts +7 -0
- package/dist/http/routes/agents-roster.js +6 -2
- package/dist/http/routes/approvals-assistant.d.ts +48 -19
- package/dist/http/routes/approvals-assistant.js +117 -50
- package/dist/http/routes/capabilities.js +3 -2
- package/dist/http/routes/config-catalog.js +1 -0
- package/dist/http/routes/memory-consolidation.d.ts +3 -2
- package/dist/http/routes/memory-policy.js +3 -3
- package/dist/http/routes/rules.js +2 -2
- package/dist/http/routes/runs.js +27 -2
- package/dist/http/routes/session-sync.js +8 -14
- package/dist/http/routes/sessions-list.js +3 -2
- package/dist/http/routes/sessions.js +44 -23
- package/dist/http/routes/shared-memory.js +2 -2
- package/dist/http/routes/tasks.js +22 -19
- package/dist/http/routes/trace-usage.js +4 -4
- package/dist/http/send.d.ts +12 -0
- package/dist/http/send.js +12 -1
- package/dist/http/server.d.ts +21 -4
- package/dist/http/server.js +7 -4
- package/dist/http/sync-submit-body.d.ts +43 -0
- package/dist/http/sync-submit-body.js +4 -0
- package/dist/http/wire-types.d.ts +23 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/leader/merge.d.ts +8 -3
- package/dist/memory-incident-sink.js +2 -1
- package/dist/model-select.d.ts +9 -0
- package/dist/model-select.js +5 -1
- package/dist/observability/corrupt-read-seat.d.ts +151 -16
- package/dist/observability/corrupt-read-seat.js +158 -15
- package/dist/observability/fail-open.d.ts +12 -0
- package/dist/observability/fail-open.js +12 -0
- package/dist/observability/run-terminal.d.ts +15 -1
- package/dist/observability/run-terminal.js +4 -0
- package/dist/parked-decide.d.ts +8 -3
- package/dist/parked-decide.js +56 -20
- package/dist/parked-revive-surface.d.ts +14 -7
- package/dist/parked-revive-surface.js +5 -1
- package/dist/plugins/approval-ask-store-file.d.ts +4 -1
- package/dist/plugins/approval-ask-store-file.js +9 -12
- package/dist/plugins/approval-ask-store-sql.d.ts +4 -0
- package/dist/plugins/approval-ask-store-sql.js +4 -1
- package/dist/plugins/approval-exemption-store.d.ts +21 -1
- package/dist/plugins/approval-exemption-store.js +36 -7
- package/dist/plugins/approval-nonce-store.d.ts +16 -2
- package/dist/plugins/approval-nonce-store.js +17 -7
- package/dist/plugins/caching-session-store.d.ts +5 -0
- package/dist/plugins/caching-session-store.js +3 -0
- package/dist/plugins/file-resume-anchor-store.d.ts +7 -2
- package/dist/plugins/file-resume-anchor-store.js +19 -5
- package/dist/plugins/file-run-store.d.ts +21 -8
- package/dist/plugins/file-run-store.js +60 -38
- package/dist/plugins/local-session-store.d.ts +22 -0
- package/dist/plugins/local-session-store.js +74 -11
- package/dist/plugins/memory-run-store.d.ts +7 -5
- package/dist/plugins/memory-run-store.js +11 -4
- package/dist/plugins/one-time-migrations.d.ts +38 -1
- package/dist/plugins/one-time-migrations.js +143 -15
- package/dist/plugins/permission-rule-store-file.d.ts +9 -2
- package/dist/plugins/permission-rule-store-file.js +37 -51
- package/dist/plugins/pg-session-storage.d.ts +3 -0
- package/dist/plugins/pg-session-storage.js +19 -10
- package/dist/plugins/run-store-sql.d.ts +12 -4
- package/dist/plugins/run-store-sql.js +15 -13
- package/dist/plugins/send-file-ledger.d.ts +5 -1
- package/dist/plugins/send-file-ledger.js +11 -6
- package/dist/plugins/session-event-payload.d.ts +14 -4
- package/dist/plugins/session-event-payload.js +5 -2
- package/dist/plugins/store-backend.d.ts +7 -3
- package/dist/plugins/store-backend.js +10 -6
- package/dist/plugins/store-contracts.d.ts +7 -2
- package/dist/plugins/tidb-pool.js +8 -2
- package/dist/plugins/tidb-session-store.d.ts +6 -1
- package/dist/plugins/tidb-session-store.js +17 -10
- package/dist/project-identity.d.ts +4 -6
- package/dist/request-key-closure.js +1 -1
- package/dist/request-value-closure.d.ts +72 -3
- package/dist/request-value-closure.js +12 -4
- package/dist/roster-bound-http-status.d.ts +6 -0
- package/dist/roster-bound-http-status.js +6 -0
- package/dist/run-local.js +22 -27
- package/dist/runs.d.ts +22 -2
- package/dist/runs.js +29 -7
- package/dist/sealed-key.d.ts +31 -4
- package/dist/sealed-key.js +9 -0
- package/dist/security.d.ts +5 -1
- package/dist/server-secret-env.d.ts +2 -2
- package/dist/session-children-retire.d.ts +92 -0
- package/dist/session-children-retire.js +85 -0
- package/dist/session-error-wire.d.ts +58 -0
- package/dist/session-error-wire.js +38 -0
- package/dist/session-incarnation.d.ts +61 -0
- package/dist/session-incarnation.js +43 -0
- package/dist/session-records-skipped-notice.d.ts +20 -0
- package/dist/session-records-skipped-notice.js +27 -0
- package/dist/tool-approval.d.ts +65 -0
- package/dist/tool-approval.js +36 -9
- package/dist/trace/core-keyset-guard.d.ts +8 -7
- package/dist/trace/engine-notice-wire.d.ts +3 -1
- package/dist/trace/engine-notice-wire.js +2 -0
- package/dist/trace/project.d.ts +1 -1
- package/dist/trace/project.js +9 -0
- package/dist/trace/wire-projection-faces.d.ts +10 -3
- package/dist/trace/wire-projection-faces.js +8 -2
- package/package.json +3 -3
|
@@ -31,12 +31,15 @@
|
|
|
31
31
|
* · **不接 `recordFailOpen`**:那条通道是给 F 类(装饰/缓存)fail-open 的计数与探针用的;这里的 fail-open
|
|
32
32
|
* 是 core 的裁定、本仓不改它,server 的职责只有一条 —— **不许无声**([ref] 安全轴纪律)。响亮 warn 就是
|
|
33
33
|
* 那条职责的兑现;要不要再进 fail-open 台账是另一件事、另一台车的设计。
|
|
34
|
+
* (⚠️ 这一条只说 core 自带 File 店的座。**本仓自有**的 JSONL 店是另一回事 —— 失败方向归本仓裁,见文件
|
|
35
|
+
* 后半的 S-582 重放属主:fail-open 的座**必须**进 F 类台账,fail-closed 的座不进台账而是拒用。)
|
|
34
36
|
*
|
|
35
37
|
* ⚠️ core 那只 guard 的纪律同样适用:sink 是**无绑定**调用的(`this` 是 undefined)。本属主返回的是箭头
|
|
36
38
|
* 闭包,天然满足;但**不要**把某个对象的裸方法引用直接塞进座 —— 读 `this` 会抛,而那个抛会被 core 的
|
|
37
39
|
* guard 吞掉,于是披露**静默丢失**(比没接座更难查)。
|
|
38
40
|
*/
|
|
39
|
-
import type
|
|
41
|
+
import { type FileStorageCorruptReadInfo } from "@sema-agent/core";
|
|
42
|
+
import { type FailOpenTag } from "./fail-open.js";
|
|
40
43
|
import type { Logger } from "./logger.js";
|
|
41
44
|
/**
|
|
42
45
|
* 铸一只腐读披露座。
|
|
@@ -46,28 +49,160 @@ import type { Logger } from "./logger.js";
|
|
|
46
49
|
* @param event warn 事件名。**留在调用点**:它是那只店的身份,接线锚(test/roster-corrupt-read.test.ts)
|
|
47
50
|
* 正是靠「构造实参里能读到这个名字」来证明座不是个空回调。
|
|
48
51
|
* @param note 这张脸的后果句(可选)。缺席 ⇒ 不写这个键。
|
|
52
|
+
* @param leg 与披露**同拍**的一条附加腿(可选;S-612 加):座拿到一次披露时先交给它、再写日志。唯一的用户是
|
|
53
|
+
* `LocalSessionStore` 那只 `FileSessionRepo` 的座 —— 会话打开作用域的计数腿(`noteReplayInSessionOpenScope`:
|
|
54
|
+
* 作用域在场时把 `replay` 计数记到那次打开上,由打开方铸 `session.records_skipped`)。腿**必须不抛**(那一只是纯内存
|
|
55
|
+
* 赋值);先腿后日志,是因为日志座可能坏(core 的 guard 会吞掉它的抛),而一次日志故障不许吃掉这一记。
|
|
56
|
+
* 座本身不变:载荷投影、事件名、后果句仍由本属主铸,腿只多拿一份同一个 `info`。
|
|
49
57
|
*/
|
|
50
|
-
export declare function createCorruptReadSeat(warn: Logger["warn"], event: string, note?: string): (info: FileStorageCorruptReadInfo) => void;
|
|
58
|
+
export declare function createCorruptReadSeat(warn: Logger["warn"], event: string, note?: string, leg?: (info: FileStorageCorruptReadInfo) => void): (info: FileStorageCorruptReadInfo) => void;
|
|
51
59
|
/**
|
|
52
60
|
* `FileStorageBackend` 那两只座共用的后果句 —— 一个座覆盖 core 转发的三张脸,所以共同事实只有一句,
|
|
53
61
|
* 差别只在第三张脸叫什么(`run-local` 腿看到的是 file-snapshot,HTTP 服务腿看到的是 file-history)。
|
|
54
62
|
* 这一句从前在两处各抄一份、只差一个词;现在词从参数来,句子只有一份。
|
|
55
63
|
*/
|
|
56
64
|
export declare function buildDurableAbsenceNote(thirdFace: string): string;
|
|
57
|
-
/** core `readJsonlRecords` 的坏行回调入参(core 不从包根导出这两级的型名 ⇒ 从那只函数的签名派生,不手抄)。 */
|
|
58
|
-
export type JsonlCorruptInfo = Parameters<NonNullable<Parameters<typeof readJsonlRecords>[1]>>[0];
|
|
59
|
-
/** 其中「一条被跳过的记录」那一级。 */
|
|
60
|
-
export type JsonlSkippedRecord = Extract<JsonlCorruptInfo, {
|
|
61
|
-
kind: "record";
|
|
62
|
-
}>;
|
|
63
65
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
* 一只店对自己记录的**形判据 + 投影**。`read` 认得 ⇒ 返回店的记录;认不得 ⇒ `undefined`。
|
|
67
|
+
*
|
|
68
|
+
* 判据写**最小必填键**:本店重放真读的那几格必须在、且形对(一格读错形会被重放读成另一件事 —— 例如豁免店的
|
|
69
|
+
* `revoked` 若是字符串 `"false"`,真值判断会把它当成撤销)。不写「是对象就行」:那一层 core 已经判过了。
|
|
70
|
+
* 🔴 `read` 必须是记录的**纯函数** —— 属主对 core 留下的每条记录会再读一次取投影(core 的判据座只回布尔)。
|
|
71
|
+
*/
|
|
72
|
+
export interface JsonlRecordShape<T> {
|
|
73
|
+
/** 被拒那一行在披露句里的名字(core `JsonlRecordJudge.what`)。 */
|
|
74
|
+
readonly what: string;
|
|
75
|
+
read(record: object): T | undefined;
|
|
76
|
+
}
|
|
77
|
+
type JsonlReplaySeatEntry = {
|
|
78
|
+
readonly direction: "fail-closed";
|
|
79
|
+
/** 腐读座的 warn 事件名。 */
|
|
80
|
+
readonly event: string;
|
|
81
|
+
/** 这只面在拒绝句里的名字。 */
|
|
82
|
+
readonly face: string;
|
|
83
|
+
/** 进每一条披露的后果句。 */
|
|
84
|
+
readonly note: string;
|
|
85
|
+
/** 拒绝句的出路段:人该做什么、移开文件会丢什么。 */
|
|
86
|
+
readonly remedy: string;
|
|
87
|
+
} | {
|
|
88
|
+
readonly direction: "fail-open";
|
|
89
|
+
readonly event: string;
|
|
90
|
+
/** 逐条计数的 F 类 tag(`fail-open.ts` 闭集词表;普查表 §1 有登记行)。 */
|
|
91
|
+
readonly tag: FailOpenTag;
|
|
92
|
+
readonly note: string;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* JSONL 座的**登记表**(闭集:未登记的座名传不进属主 ⇒ 编译红)。每一座在 `docs/FAIL-OPEN-CENSUS.md` §1 有登记行。
|
|
96
|
+
* · `fail-closed` —— 权限 / 安全轴:一行读不出 = 一次授权事实缺了一块,拿残缺的账判决会**放行**不该放行的
|
|
97
|
+
* (nonce 重放 / 已撤销的豁免复活 / 已认领的票复活 / 审批记录连同 rev 回滚)。
|
|
98
|
+
* · `fail-open` —— 可用性轴:丢一行 = 丢一段进度 / 一条列表项 / 一个事件,放行合法但**必留痕**(F 类)。
|
|
99
|
+
*/
|
|
100
|
+
export declare const JSONL_REPLAY_SEATS: {
|
|
101
|
+
readonly "approval-nonce": {
|
|
102
|
+
readonly direction: "fail-closed";
|
|
103
|
+
readonly event: "approval_nonce_corrupt_read";
|
|
104
|
+
readonly face: "approval-nonce ledger";
|
|
105
|
+
readonly note: string;
|
|
106
|
+
readonly remedy: string;
|
|
107
|
+
};
|
|
108
|
+
readonly "approval-exemption": {
|
|
109
|
+
readonly direction: "fail-closed";
|
|
110
|
+
readonly event: "approval_exemption_corrupt_read";
|
|
111
|
+
readonly face: "approval-exemption log";
|
|
112
|
+
readonly note: string;
|
|
113
|
+
readonly remedy: string;
|
|
114
|
+
};
|
|
115
|
+
readonly "permission-rule-approvals": {
|
|
116
|
+
readonly direction: "fail-closed";
|
|
117
|
+
readonly event: "permission_rule_approvals_corrupt_read";
|
|
118
|
+
readonly face: "permission-rule approval log";
|
|
119
|
+
readonly note: "S-582 fail-closed: an unreadable snapshot would roll a rule-approval record (and its rev) back to an earlier state. The face refuses every read and write until a person inspects the file.";
|
|
120
|
+
readonly remedy: "the permission-rule lane is fail-closed until a person inspects the file (moving it aside restarts the lane with an empty log)";
|
|
121
|
+
};
|
|
122
|
+
readonly "permission-rule-tickets": {
|
|
123
|
+
readonly direction: "fail-closed";
|
|
124
|
+
readonly event: "permission_rule_tickets_corrupt_read";
|
|
125
|
+
readonly face: "permission-rule import-ticket log";
|
|
126
|
+
readonly note: "S-582 fail-closed: an unreadable consume/release line would resurrect a claimed import ticket as unclaimed (one human consent redeemed twice). The face refuses every read and write until a person inspects the file.";
|
|
127
|
+
readonly remedy: "the permission-rule lane is fail-closed until a person inspects the file (moving it aside restarts the lane with an empty log)";
|
|
128
|
+
};
|
|
129
|
+
readonly "approval-ask-journal": {
|
|
130
|
+
readonly direction: "fail-open";
|
|
131
|
+
readonly event: "approval_ask_journal_corrupt_read";
|
|
132
|
+
readonly tag: "server.approval-ask-journal.corrupt-record-skipped";
|
|
133
|
+
readonly note: "F: a skipped ask-journal record loses an ask transition's PROGRESS, never a gate (every write is CAS-guarded); the original is quarantined next to the journal.";
|
|
134
|
+
};
|
|
135
|
+
readonly "run-store": {
|
|
136
|
+
readonly direction: "fail-open";
|
|
137
|
+
readonly event: "run_store_corrupt_read";
|
|
138
|
+
readonly tag: "server.run-store.corrupt-record-skipped";
|
|
139
|
+
readonly note: string;
|
|
140
|
+
};
|
|
141
|
+
readonly "resume-anchor": {
|
|
142
|
+
readonly direction: "fail-open";
|
|
143
|
+
readonly event: "resume_anchor_corrupt_read";
|
|
144
|
+
readonly tag: "server.resume-anchor.corrupt-record-skipped";
|
|
145
|
+
readonly note: "F: a skipped anchor means that eventId no longer resolves (resumeAt / rewindFilesTo answer 404 unknown_event for it; the next put re-records it).";
|
|
146
|
+
};
|
|
147
|
+
readonly "send-file-ledger": {
|
|
148
|
+
readonly direction: "fail-open";
|
|
149
|
+
readonly event: "send_file_ledger_corrupt_read";
|
|
150
|
+
readonly tag: "server.send-file-ledger.corrupt-record-skipped";
|
|
151
|
+
readonly note: "F: a skipped ledger row means that issued link is missing from its tenant's listing (the stored object itself is untouched).";
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
/** 登记名(闭集)。 */
|
|
155
|
+
export type JsonlReplaySeat = keyof typeof JSONL_REPLAY_SEATS;
|
|
156
|
+
type SeatsWhere<D extends JsonlReplaySeatEntry["direction"]> = {
|
|
157
|
+
[K in JsonlReplaySeat]: (typeof JSONL_REPLAY_SEATS)[K]["direction"] extends D ? K : never;
|
|
158
|
+
}[JsonlReplaySeat];
|
|
159
|
+
/** 登记为 fail-closed 的座(只能交给 {@link replayJsonlGuarded})。 */
|
|
160
|
+
export type FailClosedJsonlSeat = SeatsWhere<"fail-closed">;
|
|
161
|
+
/** 登记为 fail-open 的座(只能交给 {@link replayJsonlSkipping})。 */
|
|
162
|
+
export type FailOpenJsonlSeat = SeatsWhere<"fail-open">;
|
|
163
|
+
/**
|
|
164
|
+
* fail-closed 座的**带码拒绝**。`kind` 与店层拒绝契约同字(`StoreCorruptError.kind`),`seat` 说是哪一只面。
|
|
165
|
+
* 调用方各按自己的既有纪律接:豁免探针被拒 ⇒ 当未豁免(照问人);nonce 消费被拒 ⇒ 外层 500(证明不受理)。
|
|
166
|
+
*/
|
|
167
|
+
export declare class JsonlStoreCorruptError extends Error {
|
|
168
|
+
readonly kind: "corrupt";
|
|
169
|
+
readonly seat: FailClosedJsonlSeat;
|
|
170
|
+
readonly path: string;
|
|
171
|
+
readonly reason: string;
|
|
172
|
+
constructor(seat: FailClosedJsonlSeat, path: string, reason: string);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* 一个 fail-closed 文件的**损坏闸**(原 `permission-rule-store-file.ts` 的 `CorruptionGate`,S-582 收进属主)。
|
|
176
|
+
* 置位之后,店的每一个判决 / 追加方法第一行 {@link assertUsable} 即响亮拒 —— 一份不完整的授权账既不能用来
|
|
177
|
+
* 判决,也不能在它上面继续追加(追加只会让下一次重启读到同样残缺的历史)。
|
|
178
|
+
* 只是一只闩:披露由属主在跳闸那一刻经腐读座发出(逐条记录 + 计数),闸本身不再说第二遍。
|
|
179
|
+
*/
|
|
180
|
+
export declare class JsonlStoreGate {
|
|
181
|
+
private readonly seat;
|
|
182
|
+
private readonly path;
|
|
183
|
+
private reason;
|
|
184
|
+
constructor(seat: FailClosedJsonlSeat, path: string);
|
|
185
|
+
/** 记一次损坏(幂等:第一条原因就是拒绝句里报的那条)。 */
|
|
186
|
+
trip(reason: string): void;
|
|
187
|
+
get tripped(): boolean;
|
|
188
|
+
assertUsable(): void;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* **fail-closed** 座的重放:坏行 ⇒ 逐条披露 + 这只文件的闸合上(交回的 {@link JsonlStoreGate});好行照常交回
|
|
192
|
+
* (闸合上时店不再用它们判决,但删除类动作 —— 例如 E21 删会话 —— 仍可按店的规矩收掉它们)。
|
|
193
|
+
* 读错(非 ENOENT)照 core 原样抛。
|
|
194
|
+
*/
|
|
195
|
+
export declare function replayJsonlGuarded<T>(path: string, seat: FailClosedJsonlSeat, shape: JsonlRecordShape<T>, warn?: Logger["warn"]): {
|
|
196
|
+
records: T[];
|
|
197
|
+
gate: JsonlStoreGate;
|
|
198
|
+
};
|
|
199
|
+
/**
|
|
200
|
+
* **fail-open** 座的重放:坏行 ⇒ 跳过 + 逐条披露 + 逐条 `recordFailOpen(<登记的 F tag>)`;`skipped` = 跳过的条数
|
|
201
|
+
* (撕尾不计 —— core 按设计不报它)。读错(非 ENOENT)照 core 原样抛。
|
|
71
202
|
*/
|
|
72
|
-
export declare function
|
|
203
|
+
export declare function replayJsonlSkipping<T>(path: string, seat: FailOpenJsonlSeat, shape: JsonlRecordShape<T>, warn?: Logger["warn"]): {
|
|
204
|
+
records: T[];
|
|
205
|
+
skipped: number;
|
|
206
|
+
};
|
|
207
|
+
export {};
|
|
73
208
|
//# sourceMappingURL=corrupt-read-seat.d.ts.map
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import { readJsonlRecords } from "@sema-agent/core";
|
|
2
|
+
import { recordFailOpen } from "./fail-open.js";
|
|
3
|
+
export function createCorruptReadSeat(warn, event, note, leg) {
|
|
4
|
+
return (info) => {
|
|
5
|
+
leg?.(info);
|
|
6
|
+
return warn(event, {
|
|
7
|
+
kind: info.kind,
|
|
8
|
+
path: info.path,
|
|
9
|
+
reason: info.reason,
|
|
10
|
+
...corruptReadLevelFields(info),
|
|
11
|
+
...(info.sessionId !== undefined ? { sessionId: info.sessionId } : {}),
|
|
12
|
+
...(info.principal !== undefined ? { principal: info.principal } : {}),
|
|
13
|
+
...(note !== undefined ? { note } : {}),
|
|
14
|
+
});
|
|
15
|
+
};
|
|
11
16
|
}
|
|
12
17
|
function corruptReadLevelFields(info) {
|
|
13
18
|
switch (info.kind) {
|
|
@@ -29,19 +34,157 @@ export function buildDurableAbsenceNote(thirdFace) {
|
|
|
29
34
|
"layer (deployment policy / hooks / shell gate still applied); session repo = a listing silently omitted " +
|
|
30
35
|
`rows; ${thirdFace}. Inspect the named path before deciding.`);
|
|
31
36
|
}
|
|
32
|
-
|
|
37
|
+
const RULE_LANE_REMEDY = "the permission-rule lane is fail-closed until a person inspects the file (moving it aside restarts the lane with an empty log)";
|
|
38
|
+
export const JSONL_REPLAY_SEATS = {
|
|
39
|
+
"approval-nonce": {
|
|
40
|
+
direction: "fail-closed",
|
|
41
|
+
event: "approval_nonce_corrupt_read",
|
|
42
|
+
face: "approval-nonce ledger",
|
|
43
|
+
note: "S-582 fail-closed: an unreadable consumption record may be a SPENT plan_review nonce — replaying the rest would accept that proof again. " +
|
|
44
|
+
"The ledger refuses every consume/reap until a person inspects the file (direct-door plan_review answers 5xx meanwhile; /decide is unaffected).",
|
|
45
|
+
remedy: "direct-door plan_review is refused until a person inspects the file. Moving it aside forgets every consumption in it — each of those proofs " +
|
|
46
|
+
"could be replayed until it expires (PRINCIPAL_JWT_MAX_TTL_SEC plus skew after it was recorded) — so move it aside only once that window has passed, then restart",
|
|
47
|
+
},
|
|
48
|
+
"approval-exemption": {
|
|
49
|
+
direction: "fail-closed",
|
|
50
|
+
event: "approval_exemption_corrupt_read",
|
|
51
|
+
face: "approval-exemption log",
|
|
52
|
+
note: "S-582 fail-closed: an unreadable record may be a REVOKE tombstone — replaying the rest would resurrect a revoked \"don't ask again\". " +
|
|
53
|
+
"That session's exemptions are refused (every tool asks) until a person inspects the file; other sessions are unaffected.",
|
|
54
|
+
remedy: "this session's exemptions are refused (the tool asks every time) until a person inspects the file; deleting the session, or moving the file " +
|
|
55
|
+
"aside and restarting, drops this session's exemptions (they will be asked for again)",
|
|
56
|
+
},
|
|
57
|
+
"permission-rule-approvals": {
|
|
58
|
+
direction: "fail-closed",
|
|
59
|
+
event: "permission_rule_approvals_corrupt_read",
|
|
60
|
+
face: "permission-rule approval log",
|
|
61
|
+
note: "S-582 fail-closed: an unreadable snapshot would roll a rule-approval record (and its rev) back to an earlier state. The face refuses every read and write until a person inspects the file.",
|
|
62
|
+
remedy: RULE_LANE_REMEDY,
|
|
63
|
+
},
|
|
64
|
+
"permission-rule-tickets": {
|
|
65
|
+
direction: "fail-closed",
|
|
66
|
+
event: "permission_rule_tickets_corrupt_read",
|
|
67
|
+
face: "permission-rule import-ticket log",
|
|
68
|
+
note: "S-582 fail-closed: an unreadable consume/release line would resurrect a claimed import ticket as unclaimed (one human consent redeemed twice). The face refuses every read and write until a person inspects the file.",
|
|
69
|
+
remedy: RULE_LANE_REMEDY,
|
|
70
|
+
},
|
|
71
|
+
"approval-ask-journal": {
|
|
72
|
+
direction: "fail-open",
|
|
73
|
+
event: "approval_ask_journal_corrupt_read",
|
|
74
|
+
tag: "server.approval-ask-journal.corrupt-record-skipped",
|
|
75
|
+
note: "F: a skipped ask-journal record loses an ask transition's PROGRESS, never a gate (every write is CAS-guarded); the original is quarantined next to the journal.",
|
|
76
|
+
},
|
|
77
|
+
"run-store": {
|
|
78
|
+
direction: "fail-open",
|
|
79
|
+
event: "run_store_corrupt_read",
|
|
80
|
+
tag: "server.run-store.corrupt-record-skipped",
|
|
81
|
+
note: "F: a skipped run.json reads that run as ABSENT ('no such run'); a skipped events.jsonl line is one trace event missing from replay; " +
|
|
82
|
+
"a skipped claim record sends that claim down the torn-claim path (adopted by its live row, else quarantined).",
|
|
83
|
+
},
|
|
84
|
+
"resume-anchor": {
|
|
85
|
+
direction: "fail-open",
|
|
86
|
+
event: "resume_anchor_corrupt_read",
|
|
87
|
+
tag: "server.resume-anchor.corrupt-record-skipped",
|
|
88
|
+
note: "F: a skipped anchor means that eventId no longer resolves (resumeAt / rewindFilesTo answer 404 unknown_event for it; the next put re-records it).",
|
|
89
|
+
},
|
|
90
|
+
"send-file-ledger": {
|
|
91
|
+
direction: "fail-open",
|
|
92
|
+
event: "send_file_ledger_corrupt_read",
|
|
93
|
+
tag: "server.send-file-ledger.corrupt-record-skipped",
|
|
94
|
+
note: "F: a skipped ledger row means that issued link is missing from its tenant's listing (the stored object itself is untouched).",
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
function corruptRefusalSentence(seat, path, reason) {
|
|
98
|
+
const entry = JSONL_REPLAY_SEATS[seat];
|
|
99
|
+
return `refusing to use a corrupt ${entry.face} (${path}): ${reason} — ${entry.remedy}`;
|
|
100
|
+
}
|
|
101
|
+
export class JsonlStoreCorruptError extends Error {
|
|
102
|
+
kind = "corrupt";
|
|
103
|
+
seat;
|
|
104
|
+
path;
|
|
105
|
+
reason;
|
|
106
|
+
constructor(seat, path, reason) {
|
|
107
|
+
super(corruptRefusalSentence(seat, path, reason));
|
|
108
|
+
this.name = "JsonlStoreCorruptError";
|
|
109
|
+
this.seat = seat;
|
|
110
|
+
this.path = path;
|
|
111
|
+
this.reason = reason;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
export class JsonlStoreGate {
|
|
115
|
+
seat;
|
|
116
|
+
path;
|
|
117
|
+
reason;
|
|
118
|
+
constructor(seat, path) {
|
|
119
|
+
this.seat = seat;
|
|
120
|
+
this.path = path;
|
|
121
|
+
}
|
|
122
|
+
trip(reason) {
|
|
123
|
+
this.reason ??= reason;
|
|
124
|
+
}
|
|
125
|
+
get tripped() {
|
|
126
|
+
return this.reason !== undefined;
|
|
127
|
+
}
|
|
128
|
+
assertUsable() {
|
|
129
|
+
if (this.reason !== undefined)
|
|
130
|
+
throw new JsonlStoreCorruptError(this.seat, this.path, this.reason);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const warnToConsole = (event, fields) => console.warn(event, fields);
|
|
134
|
+
const isThenable = (v) => typeof v === "object" && v !== null && typeof v.then === "function";
|
|
135
|
+
function guardDisclosure(disclose, event) {
|
|
136
|
+
const reportSinkFailure = (info, err) => warnToConsole(`${event}_sink_failed`, { kind: info.kind, path: info.path, reason: info.reason, sinkError: err instanceof Error ? err.name : typeof err });
|
|
33
137
|
return (info) => {
|
|
138
|
+
let out;
|
|
139
|
+
try {
|
|
140
|
+
out = disclose(info);
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
reportSinkFailure(info, err);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (isThenable(out))
|
|
147
|
+
out.then(undefined, (err) => reportSinkFailure(info, err));
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function replayJsonl(path, seat, shape, warn, onSkipped) {
|
|
151
|
+
const entry = JSONL_REPLAY_SEATS[seat];
|
|
152
|
+
const disclose = guardDisclosure(createCorruptReadSeat(warn ?? warnToConsole, entry.event, entry.note), entry.event);
|
|
153
|
+
const judge = { what: shape.what, accepts: (record) => shape.read(record) !== undefined };
|
|
154
|
+
const kept = readJsonlRecords(path, (info) => {
|
|
34
155
|
switch (info.kind) {
|
|
35
156
|
case "record":
|
|
36
|
-
|
|
37
|
-
|
|
157
|
+
onSkipped(info);
|
|
158
|
+
break;
|
|
38
159
|
case "replay":
|
|
39
|
-
|
|
160
|
+
break;
|
|
40
161
|
default: {
|
|
41
162
|
const unreachable = info;
|
|
42
163
|
return unreachable;
|
|
43
164
|
}
|
|
44
165
|
}
|
|
45
|
-
|
|
166
|
+
disclose(info);
|
|
167
|
+
}, judge);
|
|
168
|
+
return kept.map((record) => {
|
|
169
|
+
const value = shape.read(record);
|
|
170
|
+
if (value === undefined) {
|
|
171
|
+
throw new Error(`JSONL replay seat ${seat}: the shape reader accepted a record of ${path} and then refused it — a shape reader must be a pure function of the record`);
|
|
172
|
+
}
|
|
173
|
+
return value;
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
export function replayJsonlGuarded(path, seat, shape, warn) {
|
|
177
|
+
const gate = new JsonlStoreGate(seat, path);
|
|
178
|
+
const records = replayJsonl(path, seat, shape, warn, (info) => gate.trip(info.reason));
|
|
179
|
+
return { records, gate };
|
|
180
|
+
}
|
|
181
|
+
export function replayJsonlSkipping(path, seat, shape, warn) {
|
|
182
|
+
const { tag } = JSONL_REPLAY_SEATS[seat];
|
|
183
|
+
let skipped = 0;
|
|
184
|
+
const records = replayJsonl(path, seat, shape, warn, (info) => {
|
|
185
|
+
skipped += 1;
|
|
186
|
+
recordFailOpen(tag, `path=${info.path} reason=${info.reason}`);
|
|
187
|
+
});
|
|
188
|
+
return { records, skipped };
|
|
46
189
|
}
|
|
47
190
|
//# sourceMappingURL=corrupt-read-seat.js.map
|
|
@@ -301,6 +301,18 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
301
301
|
readonly cls: "F";
|
|
302
302
|
readonly note: "S-609(`routes/approvals-assistant.ts` 的 `GET /v1/assistant/inbox`):某一行的续跑上下文(checkpoint_ctx)读出来是**坏体**(店的拒绝契约 `StoreCorruptError`;local = 档不是 JSON / 信封不合形,SQL = 列是合法 JSON 但不是一只对象)⇒ **这一行**的 `objective` 落 `null`,列表其余照常。放行的最坏后果 = inbox 上这一张卡不显示它属于哪个任务(纯展示位;这一格不参与任何决裁、续跑或门判 —— 续跑腿与 plan_review 门对同一行各自走读口、坏体一律响亮 500)。不放行的代价是整只 `summaries.map` 抛出 ⇒ 一行坏体打掉整个用户的 inbox。故 F 类;必须留痕,因为修前那句 `.catch(() => null)` 让「坏体」与「本来就没有」同形无痕(detail = 读口转述的 `where`,人拿它去修那一行)。缺席(行不在 / 店抖动)**不**记本 tag:那是读口的刻意保守,不是失败开放。";
|
|
303
303
|
};
|
|
304
|
+
readonly "server.run-store.corrupt-record-skipped": {
|
|
305
|
+
readonly cls: "F";
|
|
306
|
+
readonly note: "S-582(`plugins/file-run-store.ts`,local 车道的 File 形 run 店;三个盘面同一只座):盘上一条记录读不出形 —— 一行 `42` / 缺本店必填键的对象 / 中间的半截 JSON(撕尾不走本 tag,那是崩溃的正常落盘形)⇒ 跳过它继续。三个盘面各自的最坏后果:`run.json` 坏 ⇒ 那条 run 读成**缺席**(`GET /v1/runs/:id` 404,listRuns 少一行;它若占着 claim,EEXIST 路按「行不在」判残骸接管);`events.jsonl` 一行坏 ⇒ 回放少一个事件(其余照读、按 seq 去重不变);claim 记录坏 ⇒ 走既有的撕裂 claim 路(有非终局行就收编,否则隔离出 activeDir)。都是**可用性**损失,不开任何门 ⇒ F。修前三面全零披露(店自己的 `typeof … continue` 与 core 的静默跳过);计数让「这台机器的 run 账在腐」在遥测里显形。⚠️ `RUN_STORE_STRICT_HYDRATE=on` 的射程是「文件读不出」,不含「文件里有坏行」—— strict 档下坏行照走本 tag(未改 strict 语义,见普查表第 96 行)。";
|
|
307
|
+
};
|
|
308
|
+
readonly "server.resume-anchor.corrupt-record-skipped": {
|
|
309
|
+
readonly cls: "F";
|
|
310
|
+
readonly note: "S-582(`plugins/file-resume-anchor-store.ts`,local 车道的 E18 续跑锚日志):一条锚记录读不出形(`42` / 缺 eventId·entryId / 中间半截 JSON)⇒ 跳过它继续重放。最坏后果 = 那个 eventId 不再解析得出(`resumeAt` / `rewindFilesTo` 对它答 404 `resume_at.unknown_event` / `rewind_files_to.unknown_event` —— `boot/resolve-spec.ts` 的解析腿;下一次 put 会把它重记)。锚是**定位**事实,不是授权事实(属主守卫在行上另有 `owner` 列)⇒ F。修前零披露。";
|
|
311
|
+
};
|
|
312
|
+
readonly "server.send-file-ledger.corrupt-record-skipped": {
|
|
313
|
+
readonly cls: "F";
|
|
314
|
+
readonly note: "S-582(`plugins/send-file-ledger.ts`,local 车道的发文件账本):一条账行读不出形(`42` / 缺 id·scope·objectKey 三元组 / 中间半截 JSON)⇒ 跳过它继续。最坏后果 = 那条已签发的链接在它租户的列表里**看不见**(对象本身没动;账本本来就不存 URL)。治理面少一行是可见性损失,不是放行 ⇒ F;但它恰是治理面要看的那一类事实,所以必须留痕 —— 修前零披露。";
|
|
315
|
+
};
|
|
304
316
|
};
|
|
305
317
|
/** 词表键推导的闭集类型——未登记的 tag 传不进 {@link recordFailOpen}(编译期拒)。 */
|
|
306
318
|
export type FailOpenTag = keyof typeof FAIL_OPEN_TAGS;
|
|
@@ -274,6 +274,18 @@ export const FAIL_OPEN_TAGS = {
|
|
|
274
274
|
cls: "F",
|
|
275
275
|
note: "S-609(`routes/approvals-assistant.ts` 的 `GET /v1/assistant/inbox`):某一行的续跑上下文(checkpoint_ctx)读出来是**坏体**(店的拒绝契约 `StoreCorruptError`;local = 档不是 JSON / 信封不合形,SQL = 列是合法 JSON 但不是一只对象)⇒ **这一行**的 `objective` 落 `null`,列表其余照常。放行的最坏后果 = inbox 上这一张卡不显示它属于哪个任务(纯展示位;这一格不参与任何决裁、续跑或门判 —— 续跑腿与 plan_review 门对同一行各自走读口、坏体一律响亮 500)。不放行的代价是整只 `summaries.map` 抛出 ⇒ 一行坏体打掉整个用户的 inbox。故 F 类;必须留痕,因为修前那句 `.catch(() => null)` 让「坏体」与「本来就没有」同形无痕(detail = 读口转述的 `where`,人拿它去修那一行)。缺席(行不在 / 店抖动)**不**记本 tag:那是读口的刻意保守,不是失败开放。",
|
|
276
276
|
},
|
|
277
|
+
"server.run-store.corrupt-record-skipped": {
|
|
278
|
+
cls: "F",
|
|
279
|
+
note: "S-582(`plugins/file-run-store.ts`,local 车道的 File 形 run 店;三个盘面同一只座):盘上一条记录读不出形 —— 一行 `42` / 缺本店必填键的对象 / 中间的半截 JSON(撕尾不走本 tag,那是崩溃的正常落盘形)⇒ 跳过它继续。三个盘面各自的最坏后果:`run.json` 坏 ⇒ 那条 run 读成**缺席**(`GET /v1/runs/:id` 404,listRuns 少一行;它若占着 claim,EEXIST 路按「行不在」判残骸接管);`events.jsonl` 一行坏 ⇒ 回放少一个事件(其余照读、按 seq 去重不变);claim 记录坏 ⇒ 走既有的撕裂 claim 路(有非终局行就收编,否则隔离出 activeDir)。都是**可用性**损失,不开任何门 ⇒ F。修前三面全零披露(店自己的 `typeof … continue` 与 core 的静默跳过);计数让「这台机器的 run 账在腐」在遥测里显形。⚠️ `RUN_STORE_STRICT_HYDRATE=on` 的射程是「文件读不出」,不含「文件里有坏行」—— strict 档下坏行照走本 tag(未改 strict 语义,见普查表第 96 行)。",
|
|
280
|
+
},
|
|
281
|
+
"server.resume-anchor.corrupt-record-skipped": {
|
|
282
|
+
cls: "F",
|
|
283
|
+
note: "S-582(`plugins/file-resume-anchor-store.ts`,local 车道的 E18 续跑锚日志):一条锚记录读不出形(`42` / 缺 eventId·entryId / 中间半截 JSON)⇒ 跳过它继续重放。最坏后果 = 那个 eventId 不再解析得出(`resumeAt` / `rewindFilesTo` 对它答 404 `resume_at.unknown_event` / `rewind_files_to.unknown_event` —— `boot/resolve-spec.ts` 的解析腿;下一次 put 会把它重记)。锚是**定位**事实,不是授权事实(属主守卫在行上另有 `owner` 列)⇒ F。修前零披露。",
|
|
284
|
+
},
|
|
285
|
+
"server.send-file-ledger.corrupt-record-skipped": {
|
|
286
|
+
cls: "F",
|
|
287
|
+
note: "S-582(`plugins/send-file-ledger.ts`,local 车道的发文件账本):一条账行读不出形(`42` / 缺 id·scope·objectKey 三元组 / 中间半截 JSON)⇒ 跳过它继续。最坏后果 = 那条已签发的链接在它租户的列表里**看不见**(对象本身没动;账本本来就不存 URL)。治理面少一行是可见性损失,不是放行 ⇒ F;但它恰是治理面要看的那一类事实,所以必须留痕 —— 修前零披露。",
|
|
288
|
+
},
|
|
277
289
|
};
|
|
278
290
|
export function failOpenTagForDroppedFrame(frameType) {
|
|
279
291
|
switch (frameType) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type RunRecord } from "../plugins/store-contracts.js";
|
|
1
|
+
import { type PersistedTaskResult, type RunRecord } from "../plugins/store-contracts.js";
|
|
2
2
|
/** 一次终局写口交给观测面的原料(store 侧装配,**未脱敏** —— 脱敏在本模块内做,见 {@link notifyRunTerminal})。 */
|
|
3
3
|
export interface RunTerminalRecord {
|
|
4
4
|
/** 这条 run 的账本 id = `GET /v1/runs/:id` 的 id(本仓 run 行主键名为 taskId,同一个值)。 */
|
|
@@ -145,6 +145,20 @@ export declare function redactTerminalResult<T extends {
|
|
|
145
145
|
/** S-394:形上同样 `unknown` —— 本函数是读盘面,`stats` 的树形逐层现判(见 {@link redactStats})。 */
|
|
146
146
|
stats?: unknown;
|
|
147
147
|
} | null | undefined>(result: T): T;
|
|
148
|
+
/**
|
|
149
|
+
* 🔴 **S-173 —— run 行 `result` 列的唯一入口**:先过出口封口({@link sealResultForExit}:摘恢复凭据 + 折域外码),
|
|
150
|
+
* 再过 {@link redactTerminalResult}。三家 run 店(SQL 两方言 / file / memory)的**三个**写口 —— `setTerminal` 与
|
|
151
|
+
* park 两口 `setSuspended` / `setNeedsReview` —— 入口各调一次,列上的字节因此只有一条来路。
|
|
152
|
+
*
|
|
153
|
+
* 为什么封口也挪到店的入口(而不只靠调用方):S-173 之前这一列只收终局结果,而终局结果上凭据只可能藏在
|
|
154
|
+
* `failed.nestedPause` 一处、且每个调用点都已先封口。park 两口收的是 **`paused` 因由** —— 那一臂按 core 的
|
|
155
|
+
* 类型**必带** `token`(引擎内契约)。凭据挂在「每个调用点都记得先封口」上 = 漏一处就是一次凭据落盘、且
|
|
156
|
+
* 不会红([ref] 安全轴:fail-closed,不是约定)。两件事都**幂等**(封过口的再封是同形、脱过的再脱是同一引用),
|
|
157
|
+
* 所以调用点已封过口的腿零行为变化。
|
|
158
|
+
*
|
|
159
|
+
* `null` 原样(= 这条腿没有结果可落:引擎没给终局就收流 / 断连竞态 park / 重开 re-park)。
|
|
160
|
+
*/
|
|
161
|
+
export declare function sealRunRowResult(result: PersistedTaskResult | null): PersistedTaskResult | null;
|
|
148
162
|
/** S-107(第五轮合并重扫 wf_f00c00bc 两根 [high]):同一个 `TaskResult` 除 run 行外还被逐字追加进 run_events 的 `done` 行
|
|
149
163
|
* (bg/verify/cascade/stream/resume 五腿),`failed` 行带原文 `errorMessage`,`GET /v1/runs/:id/events` 原样回放。
|
|
150
164
|
* 三孪生 `appendEvent` 入口 + 事件读面(旧行纵深)同调本函数:只认 `done.result`(整块走 `redactTerminalResult`)与
|
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { redactSecrets, normalizeForRedaction } from "../trace/redact.js";
|
|
3
3
|
import { isModelTextLedgerEventType, isParkLedgerEventType } from "../trace/ledger-events.js";
|
|
4
4
|
import { isUnsuccessfulRunStatus } from "../plugins/store-contracts.js";
|
|
5
|
+
import { sealResultForExit } from "../terminal.js";
|
|
5
6
|
import { errorText } from "./err-text.js";
|
|
6
7
|
import { recordFailOpen } from "./fail-open.js";
|
|
7
8
|
const MESSAGE_CAP = 1024;
|
|
@@ -236,6 +237,9 @@ export function redactTerminalResult(result) {
|
|
|
236
237
|
}
|
|
237
238
|
return changed ? out : result;
|
|
238
239
|
}
|
|
240
|
+
export function sealRunRowResult(result) {
|
|
241
|
+
return result === null ? null : redactTerminalResult(sealResultForExit(result));
|
|
242
|
+
}
|
|
239
243
|
export function redactLedgerEventData(type, data) {
|
|
240
244
|
if (data === null || typeof data !== "object" || Array.isArray(data))
|
|
241
245
|
return data;
|
package/dist/parked-decide.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type { BackgroundAgentRecord, BackgroundAgentStore, CheckpointStore, Ques
|
|
|
13
13
|
import type { ParkedReviveSurfaceFactory } from "./parked-revive-surface.js";
|
|
14
14
|
import type { RebuiltInheritedGate } from "./boot/parked-revive-gate.js";
|
|
15
15
|
import { type WorkflowRunParkKey, type WorkflowAgentSessionIndex } from "./orchestration/workflow-agent-session-index.js";
|
|
16
|
+
import { type SessionFaceReader } from "./session-incarnation.js";
|
|
16
17
|
export interface ParkedAgentMatch {
|
|
17
18
|
handle: string;
|
|
18
19
|
row: BackgroundAgentRecord;
|
|
@@ -118,9 +119,13 @@ export interface ParkedDecideDeps {
|
|
|
118
119
|
* checkpointStore 同实例,[ref] 设计稿 §4),以及与它同出一张列表的继承座 `mountsCallerTool`(一层行才有;属主与全部判据
|
|
119
120
|
* 在 `parked-revive-surface.ts`)。修前是 boot 期的一只裸 Agent 工具、从不交座 ⇒ 7.32.0 起停在继承工具上的 allow 恒 422。 */
|
|
120
121
|
reviveSurface: ParkedReviveSurfaceFactory;
|
|
121
|
-
/**
|
|
122
|
-
*
|
|
123
|
-
|
|
122
|
+
/**
|
|
123
|
+
* S-790([ref] §4)—— 会话面读口(`sessionStorage.sessionFaceOf`:属主 + 化身一次读)。**必填**:本腿对根会话读两次 ——
|
|
124
|
+
* claim 之前一次、claim 成功之后一次 —— 会话不在或两次不是同一位住客 ⇒ 回滚 claim、答既有的 `409 decide.not_parked`;赎回面只在
|
|
125
|
+
* 属主相等且两次是同一位住客时交继承座。读抛 ⇒ claim 前原样上抛(没有东西要回滚),claim 后先回滚再上抛。
|
|
126
|
+
* (#1110 那只可选的 `sessionOwnerOf` 退役:可选 ⇒ 缺席时静默不复核,正是 S-790 那个窗的形。)
|
|
127
|
+
*/
|
|
128
|
+
sessionFaceOf: SessionFaceReader;
|
|
124
129
|
/** revive args 的 `subagent_type` 成员性判定集([ref] Q1 修正:非 roster 成员必须省略,否则
|
|
125
130
|
* unknown subagent_type 拒;字面比对 general-purpose 在 roster 撞名时会错省)。缺省空集=恒省略。 */
|
|
126
131
|
knownAgentTypes?: ReadonlySet<string>;
|
package/dist/parked-decide.js
CHANGED
|
@@ -2,6 +2,7 @@ import { readRunParks } 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";
|
|
5
|
+
import { sameSessionOccupant } from "./session-incarnation.js";
|
|
5
6
|
export async function findParkOrigin(sources, cp) {
|
|
6
7
|
if (sources.agentStore !== undefined) {
|
|
7
8
|
let match;
|
|
@@ -135,6 +136,16 @@ export function planWorkflowParkedDecide(origin, req) {
|
|
|
135
136
|
`agent; completed steps replay from the journal. Do not re-issue the original call.`,
|
|
136
137
|
};
|
|
137
138
|
}
|
|
139
|
+
function sessionGone(taskId) {
|
|
140
|
+
return {
|
|
141
|
+
status: 409,
|
|
142
|
+
body: {
|
|
143
|
+
error: "the session this agent belongs to was deleted (or deleted and started again under the same id) while its approval waited, so the approval no longer applies — nothing was resumed",
|
|
144
|
+
errorCode: "decide.not_parked",
|
|
145
|
+
taskId,
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
138
149
|
export async function decideParkedAgent(deps, req, match) {
|
|
139
150
|
const row = match.row;
|
|
140
151
|
const rememberRoot = req.grantRemember !== undefined ? (row.rootSessionId ?? row.parentSessionId) : undefined;
|
|
@@ -148,9 +159,14 @@ export async function decideParkedAgent(deps, req, match) {
|
|
|
148
159
|
if (row.name === undefined) {
|
|
149
160
|
return { status: 409, body: { error: "parked row carries no agent name — the revive path requires a named agent (row damaged?)", errorCode: "decide.row_unrevivable", taskId: match.handle } };
|
|
150
161
|
}
|
|
162
|
+
const root = row.rootSessionId;
|
|
163
|
+
if (root === undefined) {
|
|
164
|
+
return { status: 409, body: { error: "this agent's row carries no root session anchor — the revive path cannot tell which session this approval belongs to (row damaged?)", errorCode: "decide.row_unrevivable", taskId: match.handle } };
|
|
165
|
+
}
|
|
166
|
+
const faceBefore = await deps.sessionFaceOf(root);
|
|
167
|
+
if (faceBefore === undefined)
|
|
168
|
+
return sessionGone(match.handle);
|
|
151
169
|
const inheritedGate = deps.rebuildInheritedGate ? await deps.rebuildInheritedGate(row, { principal: decodeCheckpointScope(req.scope) }) : undefined;
|
|
152
|
-
const rootOwner = row.rootSessionId !== undefined && deps.sessionOwnerOf ? await deps.sessionOwnerOf(row.rootSessionId) : undefined;
|
|
153
|
-
const surface = deps.reviveSurface(row, { principal: decodeCheckpointScope(req.scope), ...(rootOwner !== undefined ? { rootOwner } : {}) });
|
|
154
170
|
const stores = { agentStore: deps.agentStore, checkpointStore: deps.checkpointStore };
|
|
155
171
|
const claim = await deps.registry.claimParkedAgent(stores, match.handle, { scope: row.scope, owner: row.owner });
|
|
156
172
|
if (!claim.ok) {
|
|
@@ -167,6 +183,44 @@ export async function decideParkedAgent(deps, req, match) {
|
|
|
167
183
|
}
|
|
168
184
|
}
|
|
169
185
|
const ticket = claim.ticket;
|
|
186
|
+
const quietWarn = (event, fields) => {
|
|
187
|
+
try {
|
|
188
|
+
deps.warn?.(event, fields);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
const rollback = async (why) => {
|
|
194
|
+
try {
|
|
195
|
+
const disp = await deps.registry.rollbackParkedClaim(stores, ticket);
|
|
196
|
+
quietWarn("parked_decide_rolled_back", { taskId: match.handle, why, disposition: disp });
|
|
197
|
+
return disp;
|
|
198
|
+
}
|
|
199
|
+
catch (e) {
|
|
200
|
+
quietWarn("parked_decide_rollback_failed", { taskId: match.handle, why, error: e instanceof Error ? e.message : String(e) });
|
|
201
|
+
return "retry";
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
let faceAfter;
|
|
205
|
+
try {
|
|
206
|
+
faceAfter = await deps.sessionFaceOf(root);
|
|
207
|
+
}
|
|
208
|
+
catch (e) {
|
|
209
|
+
await rollback("session_face_unreadable");
|
|
210
|
+
throw e;
|
|
211
|
+
}
|
|
212
|
+
if (!sameSessionOccupant(faceBefore, faceAfter)) {
|
|
213
|
+
await rollback("session_occupant_changed");
|
|
214
|
+
return sessionGone(match.handle);
|
|
215
|
+
}
|
|
216
|
+
let surface;
|
|
217
|
+
try {
|
|
218
|
+
surface = deps.reviveSurface(row, { principal: decodeCheckpointScope(req.scope), root: { before: faceBefore, after: faceAfter } });
|
|
219
|
+
}
|
|
220
|
+
catch (e) {
|
|
221
|
+
await rollback("revive_surface_threw");
|
|
222
|
+
throw e;
|
|
223
|
+
}
|
|
170
224
|
try {
|
|
171
225
|
await req.onDecideCommitted?.();
|
|
172
226
|
}
|
|
@@ -193,24 +247,6 @@ export async function decideParkedAgent(deps, req, match) {
|
|
|
193
247
|
},
|
|
194
248
|
},
|
|
195
249
|
};
|
|
196
|
-
const quietWarn = (event, fields) => {
|
|
197
|
-
try {
|
|
198
|
-
deps.warn?.(event, fields);
|
|
199
|
-
}
|
|
200
|
-
catch {
|
|
201
|
-
}
|
|
202
|
-
};
|
|
203
|
-
const rollback = async (why) => {
|
|
204
|
-
try {
|
|
205
|
-
const disp = await deps.registry.rollbackParkedClaim(stores, ticket);
|
|
206
|
-
quietWarn("parked_decide_rolled_back", { taskId: match.handle, why, disposition: disp });
|
|
207
|
-
return disp;
|
|
208
|
-
}
|
|
209
|
-
catch (e) {
|
|
210
|
-
quietWarn("parked_decide_rollback_failed", { taskId: match.handle, why, error: e instanceof Error ? e.message : String(e) });
|
|
211
|
-
return "retry";
|
|
212
|
-
}
|
|
213
|
-
};
|
|
214
250
|
defaultSubagentTailBus.forgetHandle(match.handle);
|
|
215
251
|
let receipt;
|
|
216
252
|
try {
|