@sema-agent/server 7.99.0 → 7.100.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 +13 -0
- package/USAGE.md +75 -52
- package/dist/approval-ask-machine.d.ts +10 -0
- package/dist/approval-ask-machine.js +3 -0
- package/dist/approval-card.d.ts +74 -1
- package/dist/approval-card.js +41 -8
- package/dist/approval.d.ts +44 -20
- package/dist/approval.js +14 -6
- package/dist/boot/config-center.d.ts +16 -8
- package/dist/boot/config-center.js +39 -81
- package/dist/boot/execution-env.d.ts +1 -2
- package/dist/boot/execution-env.js +2 -2
- package/dist/boot/leader.js +12 -9
- package/dist/boot/memory-consolidation.d.ts +38 -0
- package/dist/boot/memory-consolidation.js +35 -0
- package/dist/boot/resolve-spec.d.ts +0 -1
- package/dist/boot/resolve-spec.js +4 -3
- package/dist/boot/runner-deps.d.ts +2 -2
- package/dist/boot/stage-05-execution-env.d.ts +1 -1
- package/dist/boot/stage-06-runners.d.ts +2 -2
- package/dist/boot/stage-06-runners.js +3 -3
- package/dist/boot/stage-07-capability-layer.d.ts +2 -3
- package/dist/boot/stage-07-capability-layer.js +6 -6
- package/dist/boot/stage-08-reapers.d.ts +3 -4
- package/dist/boot/stage-08-reapers.js +2 -2
- package/dist/boot/stage-09-leader.d.ts +2 -3
- package/dist/boot/stage-10-http-server.d.ts +2 -3
- package/dist/boot/stage-10-http-server.js +2 -2
- package/dist/capabilities/center-plugins.d.ts +14 -2
- package/dist/capabilities/center-plugins.js +13 -5
- package/dist/capabilities/hands-lane.js +2 -1
- package/dist/capabilities/scenarios.js +18 -5
- package/dist/capabilities/tool-defer.d.ts +2 -2
- package/dist/config-catalog.d.ts +2 -2
- package/dist/config-catalog.js +12 -13
- package/dist/config-center/apply-effective.d.ts +34 -39
- package/dist/config-center/apply-effective.js +100 -64
- package/dist/config-center/apply-ledger.d.ts +35 -16
- package/dist/config-center/apply-ledger.js +25 -18
- package/dist/config-center/facade.d.ts +11 -5
- package/dist/config-center/facade.js +1 -1
- package/dist/config-center/hot-keys-registry.d.ts +11 -4
- package/dist/config-center/hot-keys-registry.js +11 -7
- package/dist/config-center/restart-signal.d.ts +21 -14
- package/dist/config-center/restart-signal.js +16 -22
- package/dist/config-center/types.d.ts +3 -3
- package/dist/config-types.d.ts +22 -7
- package/dist/cross-session-settings.js +1 -1
- package/dist/host-lsp-manager.d.ts +29 -0
- package/dist/host-lsp-manager.js +14 -0
- package/dist/http/active-run-conflict.d.ts +2 -12
- package/dist/http/active-run-conflict.js +1 -6
- package/dist/http/admission.js +8 -0
- package/dist/http/resume-legs.d.ts +1 -1
- package/dist/http/resume-legs.js +14 -10
- package/dist/http/route-ctx.d.ts +2 -2
- package/dist/http/routes/approvals-assistant.d.ts +1 -1
- package/dist/http/routes/approvals-assistant.js +2 -2
- package/dist/http/routes/memory-compliance.js +5 -0
- package/dist/http/routes/memory-origin.js +5 -0
- package/dist/http/routes/sessions.js +6 -0
- package/dist/http/routes/tasks.js +7 -6
- package/dist/http/server.d.ts +6 -5
- package/dist/http/wire-types.d.ts +6 -3
- package/dist/leader/wire.d.ts +20 -12
- package/dist/leader/wire.js +7 -6
- package/dist/memory-layer-preflight.d.ts +46 -0
- package/dist/memory-layer-preflight.js +121 -0
- package/dist/memory-operator-faces.js +17 -1
- package/dist/observability/corrupt-read-seat.d.ts +21 -4
- package/dist/observability/corrupt-read-seat.js +31 -0
- package/dist/observability/fail-open.d.ts +8 -4
- package/dist/observability/fail-open.js +8 -4
- package/dist/observability/run-terminal.js +1 -0
- package/dist/observability/secret-env-scrub.d.ts +1 -0
- package/dist/observability/secret-env-scrub.js +1 -0
- package/dist/orchestration/workflow-completion-inbox.d.ts +31 -23
- package/dist/orchestration/workflow-completion-inbox.js +8 -3
- package/dist/plugins/approval-ask-store-file.d.ts +20 -2
- package/dist/plugins/approval-ask-store-file.js +21 -3
- package/dist/plugins/approval-ask-store-memory.d.ts +2 -0
- package/dist/plugins/approval-ask-store-memory.js +39 -13
- package/dist/plugins/approval-ask-store-sql.d.ts +24 -5
- package/dist/plugins/approval-ask-store-sql.js +27 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +9 -3
- package/dist/plugins/checkpoint-store-sql.js +19 -2
- package/dist/plugins/local-checkpoint-store.js +2 -0
- package/dist/plugins/mailbox-store-sql.d.ts +7 -5
- package/dist/plugins/mailbox-store-sql.js +6 -4
- package/dist/plugins/permission-rule-store-file.js +3 -2
- package/dist/plugins/sql-json-column.d.ts +16 -3
- package/dist/plugins/sql-json-column.js +15 -4
- package/dist/plugins/web-search.d.ts +42 -22
- package/dist/plugins/web-search.js +141 -49
- package/dist/run-local.js +6 -5
- package/dist/runs.js +6 -5
- package/dist/runtime-governance.d.ts +50 -3
- package/dist/runtime-governance.js +5 -0
- package/dist/server-secret-env.d.ts +4 -2
- package/dist/task-settings.d.ts +1 -1
- package/dist/tool-approval.d.ts +107 -19
- package/dist/tool-approval.js +64 -15
- package/dist/trace/core-keyset-guard.d.ts +5 -5
- package/dist/trace/ledger-sink.js +2 -2
- package/dist/trace/project.d.ts +13 -0
- package/dist/trace/project.js +22 -5
- package/dist/trace/projection-drop.d.ts +2 -0
- package/dist/trace/projection-drop.js +6 -0
- package/dist/trace/sema-provenance.d.ts +4 -1
- package/dist/trace/sema-provenance.js +1 -1
- package/dist/trace/task-notification-facets.d.ts +65 -0
- package/dist/trace/task-notification-facets.js +31 -0
- package/dist/trace/wire-projection-faces.d.ts +3 -3
- package/dist/trace/wire-projection-faces.js +3 -3
- package/package.json +3 -3
package/dist/leader/wire.d.ts
CHANGED
|
@@ -3,6 +3,15 @@ import type { CheckpointStoreFull } from "../plugins/store-backend.js";
|
|
|
3
3
|
import { type LeaderDeps, type LeaderResult } from "./leader.js";
|
|
4
4
|
import { type DeploymentPostureSeats } from "../boot/runner-deps.js";
|
|
5
5
|
import type { LeaderRequestBody } from "./endpoint.js";
|
|
6
|
+
/** leader worker 的 durable 审批座(`LeaderWireConfig.durable` 函数席每次产出的一份)。 */
|
|
7
|
+
export interface LeaderDurableSeat {
|
|
8
|
+
readonly checkpointStore: CheckpointStoreFull;
|
|
9
|
+
readonly requireApproval: string[];
|
|
10
|
+
readonly deny?: string[];
|
|
11
|
+
readonly ttlMs?: number;
|
|
12
|
+
readonly autoBudget?: number;
|
|
13
|
+
readonly neverAuto?: string[];
|
|
14
|
+
}
|
|
6
15
|
export interface LeaderWireConfig {
|
|
7
16
|
/** Static-env compat mode (E2B only): the leader provisions/owns each env. Required when `envFactory` unset. */
|
|
8
17
|
e2bApiKey?: string;
|
|
@@ -10,18 +19,17 @@ export interface LeaderWireConfig {
|
|
|
10
19
|
* REMOTE_EXEC (e2b or k8s/Kata) — workers and the integration sandbox run on envs from THIS factory.
|
|
11
20
|
* Requires `s3` (diff-out is the worker's self-upload; there is no leader-held env to pull from). */
|
|
12
21
|
envFactory?: ExecutionEnvFactory;
|
|
13
|
-
/** Durable suspend/resume for SUB-WORKERS ([ref] C4): when
|
|
14
|
-
* question/ask policy + checkpoint store, so a gated ask/AskUserQuestion SUSPENDS the worker
|
|
15
|
-
* paused env) instead of failing — the trigger replan-lite C4 arbitrates on. Mirrors main.ts's
|
|
16
|
-
* durable wiring; ctx is seeded at provision so the surfaced token is resumable on the single-agent path.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
22
|
+
/** Durable suspend/resume for SUB-WORKERS ([ref] C4): when the seat yields a value, that worker task carries
|
|
23
|
+
* the durable question/ask policy + checkpoint store, so a gated ask/AskUserQuestion SUSPENDS the worker
|
|
24
|
+
* (checkpoint + paused env) instead of failing — the trigger replan-lite C4 arbitrates on. Mirrors main.ts's
|
|
25
|
+
* per-task durable wiring; ctx is seeded at provision so the surfaced token is resumable on the single-agent path.
|
|
26
|
+
*
|
|
27
|
+
* 🔴 **函数席,不是快照**(S-668 波 2 ①;先例 = 下方 `deploymentPosture` 的 S-174 函数席):审批名单
|
|
28
|
+
* (`governance.approvalRequire`)自本批起在进程内会被中心热换。boot 期取一次值会把这条腿钉在起服那一刻的名单上
|
|
29
|
+
* —— 起服时名单为空 ⇒ 整块缺席、之后加进来的工具在 leader worker 上**永远**不被门住(fail-OPEN)。
|
|
30
|
+
* `provisionWorker` 每只 worker 调一次(同一只 worker 的 toolPolicy / resource 停车 / ctx 保温读同一份);
|
|
31
|
+
* 返回 `undefined` = 这只 worker 不接 durable 审批(与修前「键缺席」同义)。 */
|
|
32
|
+
durable?: () => LeaderDurableSeat | undefined;
|
|
25
33
|
/** Durable tool-result offload store (TiDB). REQUIRED for `durable` to actually suspend: core's suspend
|
|
26
34
|
* pre-commit refuses with the default InMemoryToolResultStore (offloaded results would deref to null after
|
|
27
35
|
* a cross-replica resume) and silently falls back to the synchronous onAsk gate — which the leader does not
|
package/dist/leader/wire.js
CHANGED
|
@@ -241,9 +241,10 @@ export function createLeaderRunner(cfg) {
|
|
|
241
241
|
};
|
|
242
242
|
const provisionWorker = async (sub) => {
|
|
243
243
|
const sessionId = `leader-${sub.workerId}-${Date.now()}`;
|
|
244
|
+
const durable = cfg.durable?.();
|
|
244
245
|
let durableSpec = {};
|
|
245
|
-
if (
|
|
246
|
-
const d =
|
|
246
|
+
if (durable) {
|
|
247
|
+
const d = durable;
|
|
247
248
|
durableSpec = {
|
|
248
249
|
toolPolicy: combinePolicies(createDurableQuestionPolicy(), createDurableAskPolicy({ requireApproval: d.requireApproval, ...(d.deny ? { deny: d.deny } : {}), ...(d.autoBudget ? { autoBudget: d.autoBudget } : {}), ...(d.neverAuto ? { neverAuto: d.neverAuto } : {}) })),
|
|
249
250
|
checkpointStore: d.checkpointStore,
|
|
@@ -259,9 +260,9 @@ export function createLeaderRunner(cfg) {
|
|
|
259
260
|
if (repairLoopActive && resourceCfg) {
|
|
260
261
|
cfg.logger?.warn?.("leader_resource_suspend_disabled_for_repair_loop", { workerId: sub.workerId, reason: "LEADER_REPAIR_LOOP active — resource-suspend mutually exclusive with the repair loop (§10.5)" });
|
|
261
262
|
}
|
|
262
|
-
if (resourceCfg && !repairLoopActive &&
|
|
263
|
+
if (resourceCfg && !repairLoopActive && durable && cfg.toolResultStore && cfg.sessionStore) {
|
|
263
264
|
resourceSpec = {
|
|
264
|
-
checkpointStore:
|
|
265
|
+
checkpointStore: durable.checkpointStore,
|
|
265
266
|
resourceSuspend: { scope: `_leader-resource-${sub.workerId}`, totalBudgetUsd: resourceCfg.totalBudgetUsd },
|
|
266
267
|
limits: { ...workerLimits.limits, maxCostUsd: resourceCfg.sliceMaxCostUsd },
|
|
267
268
|
maxSuspends: resourceCfg.maxSuspends,
|
|
@@ -270,7 +271,7 @@ export function createLeaderRunner(cfg) {
|
|
|
270
271
|
else if (resourceCfg && !repairLoopActive) {
|
|
271
272
|
cfg.logger?.warn?.("leader_resource_suspend_inactive", {
|
|
272
273
|
workerId: sub.workerId,
|
|
273
|
-
reason: !
|
|
274
|
+
reason: !durable
|
|
274
275
|
? "no durable checkpoint store"
|
|
275
276
|
: !cfg.toolResultStore
|
|
276
277
|
? "no durable tool-result store"
|
|
@@ -278,7 +279,7 @@ export function createLeaderRunner(cfg) {
|
|
|
278
279
|
});
|
|
279
280
|
}
|
|
280
281
|
const keepCtxWarm = (runner) => {
|
|
281
|
-
const d =
|
|
282
|
+
const d = durable;
|
|
282
283
|
if (!d)
|
|
283
284
|
return runner;
|
|
284
285
|
const orig = runner.runTaskStream.bind(runner);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** 一只挡路的目录。`mode` = 权限位的四位八进制(如 `"0555"`)。 */
|
|
2
|
+
export interface UnwritableLayerDir {
|
|
3
|
+
readonly dir: string;
|
|
4
|
+
readonly mode: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* 挂载面整棵树里挡路的目录(排序后返回,空数组 = 放行)。判据见文件头「判定集合」,逐目录三步:
|
|
8
|
+
* ① 缺**搜索位**(X_OK)⇒ 挡路、不下钻 —— 经过它的任何查找都撞 EACCES(不存在的删除目标也一样,`force` 只吞
|
|
9
|
+
* ENOENT),里面空不空都一样(codex 验修轮 V2:0400 / 0600 的空目录 + 账本仍绑定 ⇒ journal 卡盘);
|
|
10
|
+
* ② 只缺**写位**(W_OK)⇒ 装着任何非目录项(文件 / 软链)或列不出内容 ⇒ 挡路;空的、只装子目录的不挡
|
|
11
|
+
* (core 锁出来的 0555 有 x,「空的锁住 local 不拒」即此臂);
|
|
12
|
+
* ③ 下钻:子目录照走;**指向挂载面内目录的软链也顺着走**(codex 验修轮 V1:删除路径可以穿过它),按目标的 `dev:ino` 去重防环;
|
|
13
|
+
* 目标在挂载面外 ⇒ 不下钻、抛 {@link MemoryLayerSymlinkEscapeError}(NP-4)。
|
|
14
|
+
*
|
|
15
|
+
* 挂载面本身不存在 ⇒ 空(店还没建出来,没有可删的东西)。可写可搜索却列不出内容的目录、判不了的软链 ⇒ 抛原错误
|
|
16
|
+
* (判不了子树 ⇒ 不放行;不借本码 —— 出路不是 `chmod u+w`)。
|
|
17
|
+
*/
|
|
18
|
+
export declare function unwritableLayerDirs(mountRoot: string): UnwritableLayerDir[];
|
|
19
|
+
/**
|
|
20
|
+
* NP-4 —— 挂载面里一条指向**树外**目录的软链:预检判不了(树外那棵子树里会不会删到、删不删得掉,本层不下钻去猜),
|
|
21
|
+
* 按 P 类 fail-closed 响亮拒 —— 与 ELOOP / EACCES 那一臂同形(原样抛 ⇒ 路由的未分类 500 + `memory_*_failed` warn,
|
|
22
|
+
* 文案只进日志)。刻意**不**是 {@link MemoryLayerUnwritableError}:那一码的出路是 `chmod u+w <dir>`,对树外目录开这句出路
|
|
23
|
+
* 等于教运维改系统目录的权限位。出路 = 把这条软链移出记忆层(日志点名软链与目标)。没有 `code` 字段:它不是 wire 码。
|
|
24
|
+
*/
|
|
25
|
+
export declare class MemoryLayerSymlinkEscapeError extends Error {
|
|
26
|
+
readonly link: string;
|
|
27
|
+
readonly target: string;
|
|
28
|
+
constructor(link: string, target: string);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 预检拒绝(带结构字段的错误值)。路由按 `instanceof` 判别(不嗅文案、不裸 as-cast);409 文案写在**路由**的
|
|
32
|
+
* 内联模板里(文案冻结门只看得见路由站点),本类只供两串排好版的插值。
|
|
33
|
+
*/
|
|
34
|
+
export declare class MemoryLayerUnwritableError extends Error {
|
|
35
|
+
readonly dirs: readonly UnwritableLayerDir[];
|
|
36
|
+
readonly code = "memory.layer_unwritable";
|
|
37
|
+
/** 409 文案的「哪些目录」插值:`"<dir>" (mode 0555), …`,超过上限只报余数。 */
|
|
38
|
+
readonly dirList: string;
|
|
39
|
+
/** 409 文案的出路插值:`chmod u+w` 的参数串 —— POSIX 单引号转义(照抄进 shell 不执行、不展开;codex R1-F3:
|
|
40
|
+
* JSON 双引号挡不住 `$()` / 反引号 / `$VAR`)。目录清单那一串用 JSON 引号只作展示。 */
|
|
41
|
+
readonly chmodTargets: string;
|
|
42
|
+
constructor(dirs: readonly UnwritableLayerDir[]);
|
|
43
|
+
}
|
|
44
|
+
/** 两个 seam 的唯一入口:挡路 ⇒ 带码拒绝;放行 ⇒ `undefined`。 */
|
|
45
|
+
export declare function memoryLayerWriteRefusal(mountRoot: string): MemoryLayerUnwritableError | undefined;
|
|
46
|
+
//# sourceMappingURL=memory-layer-preflight.d.ts.map
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { accessSync, constants as fsConstants, readdirSync, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, join, relative, sep } from "node:path";
|
|
3
|
+
const NAMED_DIRS_MAX = 8;
|
|
4
|
+
function errnoOf(err) {
|
|
5
|
+
return err.code;
|
|
6
|
+
}
|
|
7
|
+
function accessState(dir, bit) {
|
|
8
|
+
try {
|
|
9
|
+
accessSync(dir, bit);
|
|
10
|
+
return "granted";
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
return errnoOf(err) === "ENOENT" ? "gone" : "denied";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function symlinkTarget(path) {
|
|
17
|
+
let st;
|
|
18
|
+
try {
|
|
19
|
+
st = statSync(path);
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
if (errnoOf(err) === "ENOENT")
|
|
23
|
+
return "dangling";
|
|
24
|
+
throw err;
|
|
25
|
+
}
|
|
26
|
+
return st.isDirectory() ? "directory" : "other";
|
|
27
|
+
}
|
|
28
|
+
export function unwritableLayerDirs(mountRoot) {
|
|
29
|
+
const out = [];
|
|
30
|
+
const seen = new Set();
|
|
31
|
+
const stack = [mountRoot];
|
|
32
|
+
let mountReal;
|
|
33
|
+
const mountRealOnce = () => (mountReal ??= realpathSync(mountRoot));
|
|
34
|
+
for (let dir = stack.pop(); dir !== undefined; dir = stack.pop()) {
|
|
35
|
+
const search = accessState(dir, fsConstants.X_OK);
|
|
36
|
+
if (search === "gone")
|
|
37
|
+
continue;
|
|
38
|
+
if (search === "denied") {
|
|
39
|
+
out.push(...blocker(dir));
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const id = statSync(dir, { throwIfNoEntry: false });
|
|
43
|
+
if (id === undefined)
|
|
44
|
+
continue;
|
|
45
|
+
const key = `${id.dev}:${id.ino}`;
|
|
46
|
+
if (seen.has(key))
|
|
47
|
+
continue;
|
|
48
|
+
seen.add(key);
|
|
49
|
+
const write = accessState(dir, fsConstants.W_OK);
|
|
50
|
+
if (write === "gone")
|
|
51
|
+
continue;
|
|
52
|
+
let children;
|
|
53
|
+
try {
|
|
54
|
+
children = readdirSync(dir, { withFileTypes: true });
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
if (errnoOf(err) === "ENOENT")
|
|
58
|
+
continue;
|
|
59
|
+
if (write === "granted")
|
|
60
|
+
throw err;
|
|
61
|
+
out.push(...blocker(dir));
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (write === "denied" && children.some((c) => !c.isDirectory()))
|
|
65
|
+
out.push(...blocker(dir));
|
|
66
|
+
for (const c of children) {
|
|
67
|
+
const child = join(dir, c.name);
|
|
68
|
+
if (c.isDirectory())
|
|
69
|
+
stack.push(child);
|
|
70
|
+
else if (c.isSymbolicLink() && symlinkTarget(child) === "directory") {
|
|
71
|
+
assertInsideMount(child, mountRealOnce());
|
|
72
|
+
stack.push(child);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return out.sort((a, b) => (a.dir < b.dir ? -1 : a.dir > b.dir ? 1 : 0));
|
|
77
|
+
}
|
|
78
|
+
function assertInsideMount(link, mountReal) {
|
|
79
|
+
const target = realpathSync(link);
|
|
80
|
+
const rel = relative(mountReal, target);
|
|
81
|
+
if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel))
|
|
82
|
+
throw new MemoryLayerSymlinkEscapeError(link, target);
|
|
83
|
+
}
|
|
84
|
+
export class MemoryLayerSymlinkEscapeError extends Error {
|
|
85
|
+
link;
|
|
86
|
+
target;
|
|
87
|
+
constructor(link, target) {
|
|
88
|
+
super(`memory layer preflight cannot judge the mount: ${JSON.stringify(link)} is a symlink to ${JSON.stringify(target)}, outside the memory mount — not followed; move the symlink out of the memory layer and retry`);
|
|
89
|
+
this.link = link;
|
|
90
|
+
this.target = target;
|
|
91
|
+
this.name = "MemoryLayerSymlinkEscapeError";
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function blocker(dir) {
|
|
95
|
+
const st = statSync(dir, { throwIfNoEntry: false });
|
|
96
|
+
return st === undefined ? [] : [{ dir, mode: (st.mode & 0o7777).toString(8).padStart(4, "0") }];
|
|
97
|
+
}
|
|
98
|
+
function shellQuote(s) {
|
|
99
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
100
|
+
}
|
|
101
|
+
export class MemoryLayerUnwritableError extends Error {
|
|
102
|
+
dirs;
|
|
103
|
+
code = "memory.layer_unwritable";
|
|
104
|
+
dirList;
|
|
105
|
+
chmodTargets;
|
|
106
|
+
constructor(dirs) {
|
|
107
|
+
const named = dirs.slice(0, NAMED_DIRS_MAX);
|
|
108
|
+
const rest = dirs.length - named.length;
|
|
109
|
+
const dirList = named.map((d) => `${JSON.stringify(d.dir)} (mode ${d.mode})`).join(", ") + (rest > 0 ? `, and ${rest} more` : "");
|
|
110
|
+
super(`memory layer directories are not writable on disk: ${dirList}`);
|
|
111
|
+
this.dirs = dirs;
|
|
112
|
+
this.name = "MemoryLayerUnwritableError";
|
|
113
|
+
this.dirList = dirList;
|
|
114
|
+
this.chmodTargets = named.map((d) => shellQuote(d.dir)).join(" ");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
export function memoryLayerWriteRefusal(mountRoot) {
|
|
118
|
+
const dirs = unwritableLayerDirs(mountRoot);
|
|
119
|
+
return dirs.length > 0 ? new MemoryLayerUnwritableError(dirs) : undefined;
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=memory-layer-preflight.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { MemoryEngine, } from "@sema-agent/core";
|
|
2
2
|
import { memoryMountRootOf } from "./memory-scope.js";
|
|
3
|
+
import { memoryLayerWriteRefusal } from "./memory-layer-preflight.js";
|
|
3
4
|
export function backendOwnsControlPlane(backend) {
|
|
4
5
|
const b = backend;
|
|
5
6
|
return typeof b?.controlPlaneRoot === "string" && b.controlPlaneRoot !== "";
|
|
@@ -40,6 +41,7 @@ function erasureEnvelopeRefusal(input, allowedScopes) {
|
|
|
40
41
|
export function createMemoryComplianceFaces(store, opts) {
|
|
41
42
|
if (!backendOwnsControlPlane(store.backend))
|
|
42
43
|
return undefined;
|
|
44
|
+
const mountRoot = memoryMountRootOf(store.backend, store.root);
|
|
43
45
|
const engine = new MemoryEngine({
|
|
44
46
|
backend: store.backend,
|
|
45
47
|
memoryDir: memoryMountRootOf(store.backend, store.root),
|
|
@@ -57,6 +59,14 @@ export function createMemoryComplianceFaces(store, opts) {
|
|
|
57
59
|
const refusal = erasureEnvelopeRefusal(input, opts?.allowedScopes);
|
|
58
60
|
if (refusal !== undefined)
|
|
59
61
|
return Promise.reject(refusal);
|
|
62
|
+
try {
|
|
63
|
+
const locked = memoryLayerWriteRefusal(mountRoot);
|
|
64
|
+
if (locked !== undefined)
|
|
65
|
+
return Promise.reject(locked);
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
return Promise.reject(err);
|
|
69
|
+
}
|
|
60
70
|
return engine.eraseMemoryEntries(input);
|
|
61
71
|
},
|
|
62
72
|
};
|
|
@@ -64,6 +74,7 @@ export function createMemoryComplianceFaces(store, opts) {
|
|
|
64
74
|
export function createMemoryOriginFaces(store, opts) {
|
|
65
75
|
if (!backendOwnsControlPlane(store.backend))
|
|
66
76
|
return undefined;
|
|
77
|
+
const mountRoot = memoryMountRootOf(store.backend, store.root);
|
|
67
78
|
const engine = new MemoryEngine({
|
|
68
79
|
backend: store.backend,
|
|
69
80
|
memoryDir: memoryMountRootOf(store.backend, store.root),
|
|
@@ -73,7 +84,12 @@ export function createMemoryOriginFaces(store, opts) {
|
|
|
73
84
|
return {
|
|
74
85
|
listExternal: (scopes) => engine.listExternalOriginEntries(scopes),
|
|
75
86
|
listClearances: () => engine.listOriginClearances(),
|
|
76
|
-
clear: (entryId, input) =>
|
|
87
|
+
clear: async (entryId, input) => {
|
|
88
|
+
const locked = memoryLayerWriteRefusal(mountRoot);
|
|
89
|
+
if (locked !== undefined)
|
|
90
|
+
throw locked;
|
|
91
|
+
return engine.clearEntryOrigin(entryId, input);
|
|
92
|
+
},
|
|
77
93
|
};
|
|
78
94
|
}
|
|
79
95
|
export function createSessionMemoryStatusFace(store, opts) {
|
|
@@ -18,9 +18,10 @@
|
|
|
18
18
|
*
|
|
19
19
|
* 载荷形不是本仓铸的:`FileStorageCorruptReadInfo` 是 **core 的导出**,并且是 core 亲口说的**最宽**那一只
|
|
20
20
|
* ——"the widest shape any store this backend constructs can deliver, so ONE sink reaches all of them…
|
|
21
|
-
* the two-field store seats are satisfied by it"
|
|
22
|
-
*
|
|
23
|
-
* (`{path,reason}`
|
|
21
|
+
* the two-field store seats are satisfied by it"。于是本属主就按那只最宽形收参 —— 窄座的载荷都可赋给它
|
|
22
|
+
* ⇒ 吃最宽形的函数可赋给窄座。**本仓因此零镜像类型**。
|
|
23
|
+
* ⚠️ core 7.30.0(#1041 三态,S-616 提货)起 `CorruptReadInfo` 不再是 `{path, reason}` 两键形,而是按 `kind`
|
|
24
|
+
* 分三级的 union(`record` / `replay` / `file`,`path` + `reason` 每级都在);逐级读法见 {@link corruptReadLevelFields}。
|
|
24
25
|
*
|
|
25
26
|
* ## 刻意不做的两件事
|
|
26
27
|
* · **不在这里下统一结论**:后果按脸各不相同(少了会话规则这一层 / 列表静默少行 / rewind 边界读成缺席 /
|
|
@@ -35,7 +36,7 @@
|
|
|
35
36
|
* 闭包,天然满足;但**不要**把某个对象的裸方法引用直接塞进座 —— 读 `this` 会抛,而那个抛会被 core 的
|
|
36
37
|
* guard 吞掉,于是披露**静默丢失**(比没接座更难查)。
|
|
37
38
|
*/
|
|
38
|
-
import type { FileStorageCorruptReadInfo } from "@sema-agent/core";
|
|
39
|
+
import type { FileStorageCorruptReadInfo, readJsonlRecords } from "@sema-agent/core";
|
|
39
40
|
import type { Logger } from "./logger.js";
|
|
40
41
|
/**
|
|
41
42
|
* 铸一只腐读披露座。
|
|
@@ -53,4 +54,20 @@ export declare function createCorruptReadSeat(warn: Logger["warn"], event: strin
|
|
|
53
54
|
* 这一句从前在两处各抄一份、只差一个词;现在词从参数来,句子只有一份。
|
|
54
55
|
*/
|
|
55
56
|
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
|
+
/**
|
|
64
|
+
* S-616(core 7.30.0 #1041 提货)—— 本仓直接调 `readJsonlRecords` 的店(approval-ask 日志、permission-rule 两本日志)
|
|
65
|
+
* 的坏行回调**唯一**形。core 自这一版起对一次坏读报两级:每条被跳过的记录一发 `record`,这次重放的计数一发
|
|
66
|
+
* `replay`(`dropped` = 同一批 `record` 的条数)。这些店按**记录**计数、按记录告警 / 跳闸 —— 手写回调不分级就把同一条
|
|
67
|
+
* 坏行记两次(k3s8 实测:approval-ask 的「1 条解析不出」读成 2 条,permission-rule 的 onError 一行变两行)。
|
|
68
|
+
* 规则一句:**只按记录计,计数级是同一事实的汇总,不再计一次**;穷举 switch,core 加第三级 ⇒ 编译红。
|
|
69
|
+
* 与 {@link createCorruptReadSeat} 的分工:那只是 core 自带 File 店的**披露座**(两级都转发,日志按 `kind` 分桶);
|
|
70
|
+
* 本函数是本仓自有店**消费**坏行事实的口子(计数 / 跳闸只要记录级)。
|
|
71
|
+
*/
|
|
72
|
+
export declare function createSkippedRecordSink(onRecord: (info: JsonlSkippedRecord) => void): (info: JsonlCorruptInfo) => void;
|
|
56
73
|
//# sourceMappingURL=corrupt-read-seat.d.ts.map
|
|
@@ -1,16 +1,47 @@
|
|
|
1
1
|
export function createCorruptReadSeat(warn, event, note) {
|
|
2
2
|
return (info) => warn(event, {
|
|
3
|
+
kind: info.kind,
|
|
3
4
|
path: info.path,
|
|
4
5
|
reason: info.reason,
|
|
6
|
+
...corruptReadLevelFields(info),
|
|
5
7
|
...(info.sessionId !== undefined ? { sessionId: info.sessionId } : {}),
|
|
6
8
|
...(info.principal !== undefined ? { principal: info.principal } : {}),
|
|
7
9
|
...(note !== undefined ? { note } : {}),
|
|
8
10
|
});
|
|
9
11
|
}
|
|
12
|
+
function corruptReadLevelFields(info) {
|
|
13
|
+
switch (info.kind) {
|
|
14
|
+
case "record":
|
|
15
|
+
return { line: info.line, form: info.form };
|
|
16
|
+
case "replay":
|
|
17
|
+
return { kept: info.kept, dropped: info.dropped, total: info.total };
|
|
18
|
+
case "file":
|
|
19
|
+
return {};
|
|
20
|
+
default: {
|
|
21
|
+
const unreachable = info;
|
|
22
|
+
return unreachable;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
10
26
|
export function buildDurableAbsenceNote(thirdFace) {
|
|
11
27
|
return ("a durable read was treated as ABSENT because the bytes were unreadable (never a plain ENOENT). " +
|
|
12
28
|
"Consequence depends on which store read it: session-policy = that run lost its SUBTRACTIVE session-rule " +
|
|
13
29
|
"layer (deployment policy / hooks / shell gate still applied); session repo = a listing silently omitted " +
|
|
14
30
|
`rows; ${thirdFace}. Inspect the named path before deciding.`);
|
|
15
31
|
}
|
|
32
|
+
export function createSkippedRecordSink(onRecord) {
|
|
33
|
+
return (info) => {
|
|
34
|
+
switch (info.kind) {
|
|
35
|
+
case "record":
|
|
36
|
+
onRecord(info);
|
|
37
|
+
return;
|
|
38
|
+
case "replay":
|
|
39
|
+
return;
|
|
40
|
+
default: {
|
|
41
|
+
const unreachable = info;
|
|
42
|
+
return unreachable;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
16
47
|
//# sourceMappingURL=corrupt-read-seat.js.map
|
|
@@ -39,7 +39,7 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
39
39
|
};
|
|
40
40
|
readonly "server.trace.projection-input-malformed": {
|
|
41
41
|
readonly cls: "F";
|
|
42
|
-
readonly note: "7.78.0(core 7.18.0 #741/#786/#789/#790,`trace/project.ts`
|
|
42
|
+
readonly note: "7.78.0(core 7.18.0 #741/#786/#789/#790,`trace/project.ts` 的五张新投影脸共用一条规则;之后逐版入族 —— NP-5 / 7.100.0 起含 `trace/task-notification-facets.ts` 的 S-616 委派终态 facet / 重供两键与 `compacted.summarizedCrossSessionMessages`,助手住叶模块 `trace/projection-drop.ts`,全表见普查表第 73 行):`tool_progress` / `tool_disclosure` 两帧的必需位读不出 ⇒ **整帧不发**;`wiring_manifest.hooks[]` / `wiring_manifest.lsp` / `context_usage.sections[]` 三段的容器形或成员形读不出 ⇒ 那一行/那一段被丢、全丢光即键缺席。规则一句话:**在场但读不出 ⇒ 计一次并丢弃;缺席从不计数**(缺席是这几张脸上的一等状态,契约把它定义成一个正面事实 —— `hooks` 缺席 = 「本条腿没有自己的钩子记录」、`sections` 缺席 = 「这条腿没装配出段 IR」、`lsp` 缺席 = 「本部署没接 LSP」)。放行的最坏后果=这条腿的消费端少一张观测/渲染面(壳退回上一份已知状态或通用卡片),**不影响任何裁决**——门在引擎里早已判完。反方向(半张脸照发)严格更坏:消费端会把读不出的行当成那个正面事实。计数是**唯一**能把「坏世代 / 第三方 producer 一直在发坏载荷」与那个正面事实分开的位。";
|
|
43
43
|
};
|
|
44
44
|
readonly "server.task-mcp.source-label-dropped": {
|
|
45
45
|
readonly cls: "F";
|
|
@@ -121,6 +121,10 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
121
121
|
readonly cls: "F";
|
|
122
122
|
readonly note: "运维待批队列(`GET /v1/approvals` / `/stream`)的某一行,`checkpoint.risk_descriptor` 这一格**不是合法 JSON 文本**(手改过的行 / 回滚残留 / 列语义漂;与同一 `rows.map` 里的 `rule_suggestions` 是同族坏法,两方言都是 TEXT 列)。放行的最坏后果 = **这一行**的分诊 descriptor 落 null:severity 折 0 排到有 descriptor 的行之后、`shadowedRule` 无可脱敏、读面派生的 `governanceForced` 徽章不亮 —— 三者都是**展示/分诊**,不是执法判据(门早已 park,真按 `shellGateDoctrine` 判的 `active-run-conflict.ts` 读的是 checkpoint blob 的 `get()`,不经本读面),且 `governanceForced` 的成文语义本就是「缺席 = 没有治理来源的**证据**」。不放行的代价是整只 `rows.map` 抛出 ⇒ 一行坏 cell 打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远空),连同其余所有行一起消失。故 F 类;tag 与候选那格**分开计**,并计会让「哪一格在坏」在遥测里读不出来。";
|
|
123
123
|
};
|
|
124
|
+
readonly "server.approvals.park-row-blob-unreadable": {
|
|
125
|
+
readonly cls: "F";
|
|
126
|
+
readonly note: "S-700(`plugins/checkpoint-store-sql.ts` 的 `readParkRowFactsCell`):运维待批队列(`GET /v1/approvals` / `/stream`)的某一行,`checkpoint` 列里寄存行的 **`pendingAction` 子树**(NP-3 / 7.100.0 起引擎内只取这一棵、整只快照不出库;只为 park 行事实 `requiresRealApproval` / `ruleOffersAbsence` 而读)**在场却不是一只对象**(JSON `null` 字面 / 串 / 数 —— 手改 / 迁移脚本改过)。子树**缺席**(SQL NULL:成员不在,或整只文档不是对象 —— 后者 `get()` 照旧拒;MariaDB 文本承载上的非 JSON 字节同样答 SQL NULL)⇒ 事实缺席、不计数。放行的最坏后果 = **这一行**的两位 park 行事实缺席(展示/分诊材料;core 定性 display metadata,resume 的带子读 gate 自己的位),队列其余部分照常 —— 而这一行本来就 resume 不了(`get()` 对同一格拒)。不放行的代价是整只 `rows.map` 抛出 ⇒ 一行坏 blob 打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远空),与同一 `rows.map` 里 `rule_suggestions` / `risk_descriptor` 两格同族同判。故 F 类;tag 与那两格**分开计**(哪一格在坏要读得出来)。计数非零 = 这一行的 decide 同样读不动(`get()` 对同一格拒),去按 session 查那一行。";
|
|
127
|
+
};
|
|
124
128
|
readonly "server.sql-json.checkpoint.pending-steer-queue-lossy": {
|
|
125
129
|
readonly cls: "F";
|
|
126
130
|
readonly note: "S-550(`plugins/checkpoint-store-sql.ts` 的 `get()`,core 5.14.0 #147 的耐久转向队列):一条挂起 run 的 `checkpoint.pending_steer_queue` 这一格**没能完整读出来**(不是合法 JSON 文本 / 不是数组 / 某一条目的四个必备键不合形)⇒ 读不出的那部分**丢掉**,run 照常 resume。放行的最坏后果 = 一条已经 park 的操作员转向指令**没有**在这次 resume 上注入(人以为说过的话没送到);不放行的代价是这条挂起的 run **永远不可 resume** —— resume 是它唯一的出路,为一格读不出的转向把整条 run 变成砖是错误的失败方向(写侧刻意相反:`setPendingSteer` 往一份读不全的队列上追加会把读不出的条目永久抹掉,那里 fail-loud)。故 F 类。留痕从一条 `logger.info` 升成登记 tag 的理由:日志行在**遥测轴**上是零信号,「这台机器的转向队列一直读不出来」与「这台机器本来就没人 steer」在指标上完全同形。计数非零 = 去按日志里的 token 指纹查那一行。";
|
|
@@ -227,7 +231,7 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
227
231
|
};
|
|
228
232
|
readonly "server.approval.ambiguous-cas.expire": {
|
|
229
233
|
readonly cls: "P-DEBT";
|
|
230
|
-
readonly note: "S-25(设计件 2026-08-31 §3 步骤 3;DEBTS S-25 / #290)→ S-69 收窄(S-25-R1 终局,core design/384 §4.3 七律 / §4.4 键形对表,[5934]):流内 ask 的**窗到期臂**打原子终局 claim(`claimTerminal(expire)`,S-69 前 `expireAsk` CAS)被 reject(抛错 / 超墙钟,什么也证明不了 —— 律 3 K2)之后,有界退避**重发同一 claim**(6 格 ⇒ 至多 7 把,在飞帽 2 按持久 askId 记、撞帽拍不计 attempt、让路探针读同帽同界、帽满或同 askId 尚有在飞操作时探针不计 rung、真相广播同 askId 全部登记(本登记已 done 亦广播)—— codex S-69 R2-1/R3-1/R3-2/R4-1/R5-1/R6-1/R6-2/R7-1;末格睡后仍再发 —— codex R1-F1 同律;总墙钟硬上界 TERMINAL_LADDER_WALL_MS ≈10.15s —— R2-2 修正算术 + R3-3 硬封顶)**全部 reject** ⇒ 按本臂意图投影结算 = park 路由(`\"unavailable\"`;`deny` 政策下 `{allow:false,settledBy:\"timeout\"}`)并留 park 墓碑。方向 fail-closed(无 DECIDED=approve 行绝不执行工具),但行**可能**已被另一副本判成 DECIDED(approve) 而本腿没兑现 —— 人批了、工具不跑、run 再挂起要人再批一次 = 丢一次人类决议,故记债不当合法兜底。补偿:core 侧仍 pending 的 checkpoint(人再批一次就走)+ 跨副本收敛器兜底(A-075.116②:其兜底覆盖本形经确认后本 tag 降 F)。**不计**的形(S-69 起命中条件收窄到「店对整条阶梯持续不可用」):claim 输 ⇒ 败方答案**携真相**(DECIDED ⇒ 按人的决议结算,零补读;PARKING/PARKED ⇒ park 路由;absent = 行不在/错 scope ⇒ K1 零退避投影),迟到回来的 claim 同样自足(迟到赢 ⇒ 副作用补做,迟到输 ⇒ 按真决议结算 —— 此前迟到回调只认 won 的那条残余窗即 S-25-R1,本批闭合);编辑在飞标记在场 ⇒ 有界让路(不重打 claim)、用尽走 park 路由(标记的事不是店的债,codex R1-F2/R2-F2;负终态与带载荷 approve 不受标记压制,R2-F1/R3-F1);阶梯里曾拿到真相而标记已放开 ⇒ 按那份真相结算。detail 带 askId 与 claim 把数。";
|
|
234
|
+
readonly note: "S-25(设计件 2026-08-31 §3 步骤 3;DEBTS S-25 / #290)→ S-69 收窄(S-25-R1 终局,core design/384 §4.3 七律 / §4.4 键形对表,[5934]):流内 ask 的**窗到期臂**打原子终局 claim(`claimTerminal(expire)`,S-69 前 `expireAsk` CAS;NP-2 / 7.100.0 起 `deny` 政策下意图是 `refuse` = 当场落 DECIDED(deny)+`settled_by=policy`,同一架阶梯、同一个 tag)被 reject(抛错 / 超墙钟,什么也证明不了 —— 律 3 K2)之后,有界退避**重发同一 claim**(6 格 ⇒ 至多 7 把,在飞帽 2 按持久 askId 记、撞帽拍不计 attempt、让路探针读同帽同界、帽满或同 askId 尚有在飞操作时探针不计 rung、真相广播同 askId 全部登记(本登记已 done 亦广播)—— codex S-69 R2-1/R3-1/R3-2/R4-1/R5-1/R6-1/R6-2/R7-1;末格睡后仍再发 —— codex R1-F1 同律;总墙钟硬上界 TERMINAL_LADDER_WALL_MS ≈10.15s —— R2-2 修正算术 + R3-3 硬封顶)**全部 reject** ⇒ 按本臂意图投影结算 = park 路由(`\"unavailable\"`;`deny` 政策下 `{allow:false,settledBy:\"timeout\"}`)并留 park 墓碑。方向 fail-closed(无 DECIDED=approve 行绝不执行工具),但行**可能**已被另一副本判成 DECIDED(approve) 而本腿没兑现 —— 人批了、工具不跑、run 再挂起要人再批一次 = 丢一次人类决议,故记债不当合法兜底。补偿:core 侧仍 pending 的 checkpoint(人再批一次就走)+ 跨副本收敛器兜底(A-075.116②:其兜底覆盖本形经确认后本 tag 降 F)。**不计**的形(S-69 起命中条件收窄到「店对整条阶梯持续不可用」):claim 输 ⇒ 败方答案**携真相**(DECIDED ⇒ 按人的决议结算,零补读;PARKING/PARKED ⇒ park 路由;absent = 行不在/错 scope ⇒ K1 零退避投影),迟到回来的 claim 同样自足(迟到赢 ⇒ 副作用补做,迟到输 ⇒ 按真决议结算 —— 此前迟到回调只认 won 的那条残余窗即 S-25-R1,本批闭合);编辑在飞标记在场 ⇒ 有界让路(不重打 claim)、用尽走 park 路由(标记的事不是店的债,codex R1-F2/R2-F2;负终态与带载荷 approve 不受标记压制,R2-F1/R3-F1);阶梯里曾拿到真相而标记已放开 ⇒ 按那份真相结算。detail 带 askId 与 claim 把数。";
|
|
231
235
|
};
|
|
232
236
|
readonly "server.approval.ambiguous-cas.cancel": {
|
|
233
237
|
readonly cls: "P-DEBT";
|
|
@@ -235,7 +239,7 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
235
239
|
};
|
|
236
240
|
readonly "server.approval.ambiguous-cas.unreachable": {
|
|
237
241
|
readonly cls: "P-DEBT";
|
|
238
|
-
readonly note: "S-25 同族第三员 → S-69 收窄:**emit 全灭臂**(开卡帧两族都没送到任何连接)打 `claimTerminal(expire)`(S-69 前 `expireAsk` CAS)被 reject 之后,有界重发至多 7 把 claim(在飞帽 2,墙钟上界同 expire 员)全部 reject ⇒ 按本臂意图投影结算 = `emitFailed` ⇒ `\"unavailable\"`(park 路由;`deny` 政策下 deny),不留墓碑(卡没到任何人手上,无人持有这把 approvalId)。S-25 之前这条 catch 不读行直接强改 —— 正是 round2 finding A「durable 行已批准、活终局被 emit 全灭强改成 unavailable」的 catch 同形缺口;S-25 起行读出 DECIDED ⇒ 按人的决议结算;S-69 起真相随败方答案回来、迟到答案自足,只有店对整条阶梯持续 reject 才计数。";
|
|
242
|
+
readonly note: "S-25 同族第三员 → S-69 收窄:**emit 全灭臂**(开卡帧两族都没送到任何连接)打 `claimTerminal(expire)`(S-69 前 `expireAsk` CAS;NP-2 起 `deny` 政策下是 `refuse`,同窗到期员)被 reject 之后,有界重发至多 7 把 claim(在飞帽 2,墙钟上界同 expire 员)全部 reject ⇒ 按本臂意图投影结算 = `emitFailed` ⇒ `\"unavailable\"`(park 路由;`deny` 政策下 deny),不留墓碑(卡没到任何人手上,无人持有这把 approvalId)。S-25 之前这条 catch 不读行直接强改 —— 正是 round2 finding A「durable 行已批准、活终局被 emit 全灭强改成 unavailable」的 catch 同形缺口;S-25 起行读出 DECIDED ⇒ 按人的决议结算;S-69 起真相随败方答案回来、迟到答案自足,只有店对整条阶梯持续 reject 才计数。";
|
|
239
243
|
};
|
|
240
244
|
readonly "server.approval-ask-audit.append-failed": {
|
|
241
245
|
readonly cls: "F";
|
|
@@ -271,7 +275,7 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
271
275
|
};
|
|
272
276
|
readonly "server.approval-card.closed-word-out-of-set": {
|
|
273
277
|
readonly cls: "F";
|
|
274
|
-
readonly note: "S-249(`approval-card.ts` 的边界窄读族;今天的唯一成员是 `readRuleOffersAbsence`):活卡帧 / `card_json` 的一个**闭集词**座上,键在场而词不在本仓认得的那张表里(世代差,或第三方 producer)⇒ 该座**按缺席**处置。放行的最坏后果=卡上少一行「为什么这次没有规则候选」的解释(壳退回不显示该行),它**不改变任何裁决**——门在引擎里早已判完,本座是展示/观测面;反方向(把一个消费端词表外的值原样上卡)更坏:下游按闭集写的分支会落进 default 臂或渲染出生词。本臂存在的全部理由是**判别**:修前「上游发了个生词」与「上游根本没发这个键」在遥测上同形,而前者意味着本仓的词表该跟着 core 加词了(型面另有两向编译围栏,但那只在**重新编译**时说话,跑在线上的那一版不会自己红)。detail 带座名与那个词(经 redactSecrets 截 40)。";
|
|
278
|
+
readonly note: "S-249(`approval-card.ts` 的边界窄读族;今天的唯一成员是 `readRuleOffersAbsence`):活卡帧 / `card_json` / (S-700 起)运维队列行(`parkRowFacts` 读寄存行上的 park 孪生座;队列是热读面 ⇒ 同一行每读一次计一次)的一个**闭集词**座上,键在场而词不在本仓认得的那张表里(世代差,或第三方 producer)⇒ 该座**按缺席**处置。放行的最坏后果=卡上少一行「为什么这次没有规则候选」的解释(壳退回不显示该行),它**不改变任何裁决**——门在引擎里早已判完,本座是展示/观测面;反方向(把一个消费端词表外的值原样上卡)更坏:下游按闭集写的分支会落进 default 臂或渲染出生词。本臂存在的全部理由是**判别**:修前「上游发了个生词」与「上游根本没发这个键」在遥测上同形,而前者意味着本仓的词表该跟着 core 加词了(型面另有两向编译围栏,但那只在**重新编译**时说话,跑在线上的那一版不会自己红)。detail 带座名与那个词(经 redactSecrets 截 40);数 / 布尔等原始值原样,对象 / 数组 / 函数 / null 只报形名(`<object>` / `<array>` / `<function>` / `<null>`,S-700 codex r1:格式化从不调用值自己的转换方法 —— 持久层里一格 `{\"toString\":null}` 曾让诊断本身抛出、打掉整只队列)。";
|
|
275
279
|
};
|
|
276
280
|
readonly "server.pg-pool.idle-client-error": {
|
|
277
281
|
readonly cls: "F";
|
|
@@ -30,7 +30,7 @@ export const FAIL_OPEN_TAGS = {
|
|
|
30
30
|
},
|
|
31
31
|
"server.trace.projection-input-malformed": {
|
|
32
32
|
cls: "F",
|
|
33
|
-
note: "7.78.0(core 7.18.0 #741/#786/#789/#790,`trace/project.ts`
|
|
33
|
+
note: "7.78.0(core 7.18.0 #741/#786/#789/#790,`trace/project.ts` 的五张新投影脸共用一条规则;之后逐版入族 —— NP-5 / 7.100.0 起含 `trace/task-notification-facets.ts` 的 S-616 委派终态 facet / 重供两键与 `compacted.summarizedCrossSessionMessages`,助手住叶模块 `trace/projection-drop.ts`,全表见普查表第 73 行):`tool_progress` / `tool_disclosure` 两帧的必需位读不出 ⇒ **整帧不发**;`wiring_manifest.hooks[]` / `wiring_manifest.lsp` / `context_usage.sections[]` 三段的容器形或成员形读不出 ⇒ 那一行/那一段被丢、全丢光即键缺席。规则一句话:**在场但读不出 ⇒ 计一次并丢弃;缺席从不计数**(缺席是这几张脸上的一等状态,契约把它定义成一个正面事实 —— `hooks` 缺席 = 「本条腿没有自己的钩子记录」、`sections` 缺席 = 「这条腿没装配出段 IR」、`lsp` 缺席 = 「本部署没接 LSP」)。放行的最坏后果=这条腿的消费端少一张观测/渲染面(壳退回上一份已知状态或通用卡片),**不影响任何裁决**——门在引擎里早已判完。反方向(半张脸照发)严格更坏:消费端会把读不出的行当成那个正面事实。计数是**唯一**能把「坏世代 / 第三方 producer 一直在发坏载荷」与那个正面事实分开的位。",
|
|
34
34
|
},
|
|
35
35
|
"server.task-mcp.source-label-dropped": {
|
|
36
36
|
cls: "F",
|
|
@@ -112,6 +112,10 @@ export const FAIL_OPEN_TAGS = {
|
|
|
112
112
|
cls: "F",
|
|
113
113
|
note: "运维待批队列(`GET /v1/approvals` / `/stream`)的某一行,`checkpoint.risk_descriptor` 这一格**不是合法 JSON 文本**(手改过的行 / 回滚残留 / 列语义漂;与同一 `rows.map` 里的 `rule_suggestions` 是同族坏法,两方言都是 TEXT 列)。放行的最坏后果 = **这一行**的分诊 descriptor 落 null:severity 折 0 排到有 descriptor 的行之后、`shadowedRule` 无可脱敏、读面派生的 `governanceForced` 徽章不亮 —— 三者都是**展示/分诊**,不是执法判据(门早已 park,真按 `shellGateDoctrine` 判的 `active-run-conflict.ts` 读的是 checkpoint blob 的 `get()`,不经本读面),且 `governanceForced` 的成文语义本就是「缺席 = 没有治理来源的**证据**」。不放行的代价是整只 `rows.map` 抛出 ⇒ 一行坏 cell 打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远空),连同其余所有行一起消失。故 F 类;tag 与候选那格**分开计**,并计会让「哪一格在坏」在遥测里读不出来。",
|
|
114
114
|
},
|
|
115
|
+
"server.approvals.park-row-blob-unreadable": {
|
|
116
|
+
cls: "F",
|
|
117
|
+
note: "S-700(`plugins/checkpoint-store-sql.ts` 的 `readParkRowFactsCell`):运维待批队列(`GET /v1/approvals` / `/stream`)的某一行,`checkpoint` 列里寄存行的 **`pendingAction` 子树**(NP-3 / 7.100.0 起引擎内只取这一棵、整只快照不出库;只为 park 行事实 `requiresRealApproval` / `ruleOffersAbsence` 而读)**在场却不是一只对象**(JSON `null` 字面 / 串 / 数 —— 手改 / 迁移脚本改过)。子树**缺席**(SQL NULL:成员不在,或整只文档不是对象 —— 后者 `get()` 照旧拒;MariaDB 文本承载上的非 JSON 字节同样答 SQL NULL)⇒ 事实缺席、不计数。放行的最坏后果 = **这一行**的两位 park 行事实缺席(展示/分诊材料;core 定性 display metadata,resume 的带子读 gate 自己的位),队列其余部分照常 —— 而这一行本来就 resume 不了(`get()` 对同一格拒)。不放行的代价是整只 `rows.map` 抛出 ⇒ 一行坏 blob 打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远空),与同一 `rows.map` 里 `rule_suggestions` / `risk_descriptor` 两格同族同判。故 F 类;tag 与那两格**分开计**(哪一格在坏要读得出来)。计数非零 = 这一行的 decide 同样读不动(`get()` 对同一格拒),去按 session 查那一行。",
|
|
118
|
+
},
|
|
115
119
|
"server.sql-json.checkpoint.pending-steer-queue-lossy": {
|
|
116
120
|
cls: "F",
|
|
117
121
|
note: "S-550(`plugins/checkpoint-store-sql.ts` 的 `get()`,core 5.14.0 #147 的耐久转向队列):一条挂起 run 的 `checkpoint.pending_steer_queue` 这一格**没能完整读出来**(不是合法 JSON 文本 / 不是数组 / 某一条目的四个必备键不合形)⇒ 读不出的那部分**丢掉**,run 照常 resume。放行的最坏后果 = 一条已经 park 的操作员转向指令**没有**在这次 resume 上注入(人以为说过的话没送到);不放行的代价是这条挂起的 run **永远不可 resume** —— resume 是它唯一的出路,为一格读不出的转向把整条 run 变成砖是错误的失败方向(写侧刻意相反:`setPendingSteer` 往一份读不全的队列上追加会把读不出的条目永久抹掉,那里 fail-loud)。故 F 类。留痕从一条 `logger.info` 升成登记 tag 的理由:日志行在**遥测轴**上是零信号,「这台机器的转向队列一直读不出来」与「这台机器本来就没人 steer」在指标上完全同形。计数非零 = 去按日志里的 token 指纹查那一行。",
|
|
@@ -218,7 +222,7 @@ export const FAIL_OPEN_TAGS = {
|
|
|
218
222
|
},
|
|
219
223
|
"server.approval.ambiguous-cas.expire": {
|
|
220
224
|
cls: "P-DEBT",
|
|
221
|
-
note: "S-25(设计件 2026-08-31 §3 步骤 3;DEBTS S-25 / #290)→ S-69 收窄(S-25-R1 终局,core design/384 §4.3 七律 / §4.4 键形对表,[5934]):流内 ask 的**窗到期臂**打原子终局 claim(`claimTerminal(expire)`,S-69 前 `expireAsk` CAS)被 reject(抛错 / 超墙钟,什么也证明不了 —— 律 3 K2)之后,有界退避**重发同一 claim**(6 格 ⇒ 至多 7 把,在飞帽 2 按持久 askId 记、撞帽拍不计 attempt、让路探针读同帽同界、帽满或同 askId 尚有在飞操作时探针不计 rung、真相广播同 askId 全部登记(本登记已 done 亦广播)—— codex S-69 R2-1/R3-1/R3-2/R4-1/R5-1/R6-1/R6-2/R7-1;末格睡后仍再发 —— codex R1-F1 同律;总墙钟硬上界 TERMINAL_LADDER_WALL_MS ≈10.15s —— R2-2 修正算术 + R3-3 硬封顶)**全部 reject** ⇒ 按本臂意图投影结算 = park 路由(`\"unavailable\"`;`deny` 政策下 `{allow:false,settledBy:\"timeout\"}`)并留 park 墓碑。方向 fail-closed(无 DECIDED=approve 行绝不执行工具),但行**可能**已被另一副本判成 DECIDED(approve) 而本腿没兑现 —— 人批了、工具不跑、run 再挂起要人再批一次 = 丢一次人类决议,故记债不当合法兜底。补偿:core 侧仍 pending 的 checkpoint(人再批一次就走)+ 跨副本收敛器兜底(A-075.116②:其兜底覆盖本形经确认后本 tag 降 F)。**不计**的形(S-69 起命中条件收窄到「店对整条阶梯持续不可用」):claim 输 ⇒ 败方答案**携真相**(DECIDED ⇒ 按人的决议结算,零补读;PARKING/PARKED ⇒ park 路由;absent = 行不在/错 scope ⇒ K1 零退避投影),迟到回来的 claim 同样自足(迟到赢 ⇒ 副作用补做,迟到输 ⇒ 按真决议结算 —— 此前迟到回调只认 won 的那条残余窗即 S-25-R1,本批闭合);编辑在飞标记在场 ⇒ 有界让路(不重打 claim)、用尽走 park 路由(标记的事不是店的债,codex R1-F2/R2-F2;负终态与带载荷 approve 不受标记压制,R2-F1/R3-F1);阶梯里曾拿到真相而标记已放开 ⇒ 按那份真相结算。detail 带 askId 与 claim 把数。",
|
|
225
|
+
note: "S-25(设计件 2026-08-31 §3 步骤 3;DEBTS S-25 / #290)→ S-69 收窄(S-25-R1 终局,core design/384 §4.3 七律 / §4.4 键形对表,[5934]):流内 ask 的**窗到期臂**打原子终局 claim(`claimTerminal(expire)`,S-69 前 `expireAsk` CAS;NP-2 / 7.100.0 起 `deny` 政策下意图是 `refuse` = 当场落 DECIDED(deny)+`settled_by=policy`,同一架阶梯、同一个 tag)被 reject(抛错 / 超墙钟,什么也证明不了 —— 律 3 K2)之后,有界退避**重发同一 claim**(6 格 ⇒ 至多 7 把,在飞帽 2 按持久 askId 记、撞帽拍不计 attempt、让路探针读同帽同界、帽满或同 askId 尚有在飞操作时探针不计 rung、真相广播同 askId 全部登记(本登记已 done 亦广播)—— codex S-69 R2-1/R3-1/R3-2/R4-1/R5-1/R6-1/R6-2/R7-1;末格睡后仍再发 —— codex R1-F1 同律;总墙钟硬上界 TERMINAL_LADDER_WALL_MS ≈10.15s —— R2-2 修正算术 + R3-3 硬封顶)**全部 reject** ⇒ 按本臂意图投影结算 = park 路由(`\"unavailable\"`;`deny` 政策下 `{allow:false,settledBy:\"timeout\"}`)并留 park 墓碑。方向 fail-closed(无 DECIDED=approve 行绝不执行工具),但行**可能**已被另一副本判成 DECIDED(approve) 而本腿没兑现 —— 人批了、工具不跑、run 再挂起要人再批一次 = 丢一次人类决议,故记债不当合法兜底。补偿:core 侧仍 pending 的 checkpoint(人再批一次就走)+ 跨副本收敛器兜底(A-075.116②:其兜底覆盖本形经确认后本 tag 降 F)。**不计**的形(S-69 起命中条件收窄到「店对整条阶梯持续不可用」):claim 输 ⇒ 败方答案**携真相**(DECIDED ⇒ 按人的决议结算,零补读;PARKING/PARKED ⇒ park 路由;absent = 行不在/错 scope ⇒ K1 零退避投影),迟到回来的 claim 同样自足(迟到赢 ⇒ 副作用补做,迟到输 ⇒ 按真决议结算 —— 此前迟到回调只认 won 的那条残余窗即 S-25-R1,本批闭合);编辑在飞标记在场 ⇒ 有界让路(不重打 claim)、用尽走 park 路由(标记的事不是店的债,codex R1-F2/R2-F2;负终态与带载荷 approve 不受标记压制,R2-F1/R3-F1);阶梯里曾拿到真相而标记已放开 ⇒ 按那份真相结算。detail 带 askId 与 claim 把数。",
|
|
222
226
|
},
|
|
223
227
|
"server.approval.ambiguous-cas.cancel": {
|
|
224
228
|
cls: "P-DEBT",
|
|
@@ -226,7 +230,7 @@ export const FAIL_OPEN_TAGS = {
|
|
|
226
230
|
},
|
|
227
231
|
"server.approval.ambiguous-cas.unreachable": {
|
|
228
232
|
cls: "P-DEBT",
|
|
229
|
-
note: "S-25 同族第三员 → S-69 收窄:**emit 全灭臂**(开卡帧两族都没送到任何连接)打 `claimTerminal(expire)`(S-69 前 `expireAsk` CAS)被 reject 之后,有界重发至多 7 把 claim(在飞帽 2,墙钟上界同 expire 员)全部 reject ⇒ 按本臂意图投影结算 = `emitFailed` ⇒ `\"unavailable\"`(park 路由;`deny` 政策下 deny),不留墓碑(卡没到任何人手上,无人持有这把 approvalId)。S-25 之前这条 catch 不读行直接强改 —— 正是 round2 finding A「durable 行已批准、活终局被 emit 全灭强改成 unavailable」的 catch 同形缺口;S-25 起行读出 DECIDED ⇒ 按人的决议结算;S-69 起真相随败方答案回来、迟到答案自足,只有店对整条阶梯持续 reject 才计数。",
|
|
233
|
+
note: "S-25 同族第三员 → S-69 收窄:**emit 全灭臂**(开卡帧两族都没送到任何连接)打 `claimTerminal(expire)`(S-69 前 `expireAsk` CAS;NP-2 起 `deny` 政策下是 `refuse`,同窗到期员)被 reject 之后,有界重发至多 7 把 claim(在飞帽 2,墙钟上界同 expire 员)全部 reject ⇒ 按本臂意图投影结算 = `emitFailed` ⇒ `\"unavailable\"`(park 路由;`deny` 政策下 deny),不留墓碑(卡没到任何人手上,无人持有这把 approvalId)。S-25 之前这条 catch 不读行直接强改 —— 正是 round2 finding A「durable 行已批准、活终局被 emit 全灭强改成 unavailable」的 catch 同形缺口;S-25 起行读出 DECIDED ⇒ 按人的决议结算;S-69 起真相随败方答案回来、迟到答案自足,只有店对整条阶梯持续 reject 才计数。",
|
|
230
234
|
},
|
|
231
235
|
"server.approval-ask-audit.append-failed": {
|
|
232
236
|
cls: "F",
|
|
@@ -244,7 +248,7 @@ export const FAIL_OPEN_TAGS = {
|
|
|
244
248
|
"server.redact.ppk-eat-to-end": { cls: "F", note: "S-107 第三十二轮重扫:PuTTY .ppk 容器扫描器(redact.ts redactPpkContainers)在「无合格终止 / 收口后同容器范围再见私钥字段 / 收口后到下一版本头之间夹非空白」三形下从头**吃到输入末尾**(fail-closed 取证丢弃)——终态 errorMessage 座上=整条错误文案变成 «redacted:private-key»,与 redactions_applied_total{private-key} 的诚实替换同形不可分,故要有自己的计数/探针;影子扫不计;计数=多少次整段吃尾。" },
|
|
245
249
|
"server.approval-card.closed-word-out-of-set": {
|
|
246
250
|
cls: "F",
|
|
247
|
-
note: "S-249(`approval-card.ts` 的边界窄读族;今天的唯一成员是 `readRuleOffersAbsence`):活卡帧 / `card_json` 的一个**闭集词**座上,键在场而词不在本仓认得的那张表里(世代差,或第三方 producer)⇒ 该座**按缺席**处置。放行的最坏后果=卡上少一行「为什么这次没有规则候选」的解释(壳退回不显示该行),它**不改变任何裁决**——门在引擎里早已判完,本座是展示/观测面;反方向(把一个消费端词表外的值原样上卡)更坏:下游按闭集写的分支会落进 default 臂或渲染出生词。本臂存在的全部理由是**判别**:修前「上游发了个生词」与「上游根本没发这个键」在遥测上同形,而前者意味着本仓的词表该跟着 core 加词了(型面另有两向编译围栏,但那只在**重新编译**时说话,跑在线上的那一版不会自己红)。detail 带座名与那个词(经 redactSecrets 截 40)。",
|
|
251
|
+
note: "S-249(`approval-card.ts` 的边界窄读族;今天的唯一成员是 `readRuleOffersAbsence`):活卡帧 / `card_json` / (S-700 起)运维队列行(`parkRowFacts` 读寄存行上的 park 孪生座;队列是热读面 ⇒ 同一行每读一次计一次)的一个**闭集词**座上,键在场而词不在本仓认得的那张表里(世代差,或第三方 producer)⇒ 该座**按缺席**处置。放行的最坏后果=卡上少一行「为什么这次没有规则候选」的解释(壳退回不显示该行),它**不改变任何裁决**——门在引擎里早已判完,本座是展示/观测面;反方向(把一个消费端词表外的值原样上卡)更坏:下游按闭集写的分支会落进 default 臂或渲染出生词。本臂存在的全部理由是**判别**:修前「上游发了个生词」与「上游根本没发这个键」在遥测上同形,而前者意味着本仓的词表该跟着 core 加词了(型面另有两向编译围栏,但那只在**重新编译**时说话,跑在线上的那一版不会自己红)。detail 带座名与那个词(经 redactSecrets 截 40);数 / 布尔等原始值原样,对象 / 数组 / 函数 / null 只报形名(`<object>` / `<array>` / `<function>` / `<null>`,S-700 codex r1:格式化从不调用值自己的转换方法 —— 持久层里一格 `{\"toString\":null}` 曾让诊断本身抛出、打掉整只队列)。",
|
|
248
252
|
},
|
|
249
253
|
"server.pg-pool.idle-client-error": {
|
|
250
254
|
cls: "F",
|
|
@@ -5,6 +5,7 @@ import type { Metrics } from "./metrics.js";
|
|
|
5
5
|
export declare const SECRET_ENV_SCRUB_SITES: {
|
|
6
6
|
readonly "server.remote-env-host.inherit-scrub": "模型可驱动的 host shell 继承 env(`inheritEnv:\"scrub\"` 默认腿)——这条腿上的一次泄漏等于把编排器的脑钥/仓库令牌交给模型选的命令";
|
|
7
7
|
readonly "server.project-memory.git-subprocess": "project-memory 读仓叙事时的 git 子进程 env —— 仓内的钩子/凭证助手会以本进程身份运行";
|
|
8
|
+
readonly "server.lsp.spawn-env": "宿主车道本地语言服务器子进程 env(core 7.30.1 #1073 的 `NodeLspManagerOptions.env`)—— 语言服务器会执行仓里的代码(构建脚本/插件/工作区配置),一次泄漏等于把本进程的凭据交给仓";
|
|
8
9
|
};
|
|
9
10
|
/** 词表键推导的闭集类型。 */
|
|
10
11
|
export type SecretEnvScrubSite = keyof typeof SECRET_ENV_SCRUB_SITES;
|
|
@@ -2,6 +2,7 @@ import { summarizeRedactions } from "@sema-agent/core";
|
|
|
2
2
|
export const SECRET_ENV_SCRUB_SITES = {
|
|
3
3
|
"server.remote-env-host.inherit-scrub": "模型可驱动的 host shell 继承 env(`inheritEnv:\"scrub\"` 默认腿)——这条腿上的一次泄漏等于把编排器的脑钥/仓库令牌交给模型选的命令",
|
|
4
4
|
"server.project-memory.git-subprocess": "project-memory 读仓叙事时的 git 子进程 env —— 仓内的钩子/凭证助手会以本进程身份运行",
|
|
5
|
+
"server.lsp.spawn-env": "宿主车道本地语言服务器子进程 env(core 7.30.1 #1073 的 `NodeLspManagerOptions.env`)—— 语言服务器会执行仓里的代码(构建脚本/插件/工作区配置),一次泄漏等于把本进程的凭据交给仓",
|
|
5
6
|
};
|
|
6
7
|
const SCRUB_COUNTER = "secret_env_scrubbed_total";
|
|
7
8
|
const DISCLOSED_CAP = 512;
|