@sema-agent/server 7.109.0 → 7.111.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 +25 -0
- package/README.md +8 -0
- package/README.zh-CN.md +5 -0
- package/USAGE.md +2 -2
- package/dist/approval-ask-audit-store.d.ts +38 -25
- package/dist/approval-ask-audit-store.js +26 -59
- package/dist/approval-card.d.ts +6 -5
- package/dist/approval-card.js +8 -11
- package/dist/bench/s1/run-firm.d.ts +11 -0
- package/dist/bench/s1/run-firm.js +47 -32
- package/dist/boot/coordinators.js +1 -1
- package/dist/boot/reapers.d.ts +3 -0
- package/dist/boot/reapers.js +3 -2
- package/dist/boot/resolve-spec.js +3 -4
- package/dist/boot/session-faces.d.ts +4 -0
- package/dist/boot/session-faces.js +3 -1
- package/dist/boot/stage-07-capability-layer.d.ts +2 -0
- package/dist/boot/stage-07-capability-layer.js +18 -3
- package/dist/boot/stage-08-reapers.d.ts +2 -0
- package/dist/boot/stage-08-reapers.js +2 -2
- package/dist/boot/stage-09-leader.d.ts +2 -0
- package/dist/boot/stage-09-leader.js +2 -1
- package/dist/boot/stage-10-http-server.d.ts +2 -0
- package/dist/boot/stage-10-http-server.js +2 -1
- package/dist/boot/workflow-orchestration.js +1 -1
- package/dist/config-catalog.js +1 -1
- package/dist/config-types.d.ts +3 -2
- package/dist/config.js +19 -7
- package/dist/deployment-governance.d.ts +8 -2
- package/dist/deployment-governance.js +9 -8
- package/dist/hooks/hook-runner.d.ts +2 -2
- package/dist/hooks/hook-runner.js +40 -26
- package/dist/http/durable-run-leg.js +1 -1
- package/dist/http/resume-legs.js +12 -4
- package/dist/http/routes/runs.d.ts +0 -1
- package/dist/http/routes/runs.js +36 -1
- package/dist/http/routes/sessions.js +12 -2
- package/dist/http/routes/tasks.js +13 -7
- package/dist/http/server.d.ts +5 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/instruction-selection.d.ts +34 -0
- package/dist/instruction-selection.js +23 -0
- package/dist/instruction-source-notice.d.ts +3 -3
- package/dist/memory-layer-legacy-lock.d.ts +3 -3
- package/dist/model-entry-headers.d.ts +5 -2
- package/dist/model-entry-headers.js +4 -4
- package/dist/model-unreachable-notice.d.ts +2 -2
- package/dist/observability/corrupt-read-seat.d.ts +24 -0
- package/dist/observability/corrupt-read-seat.js +24 -0
- package/dist/observability/fail-open.d.ts +25 -1
- package/dist/observability/fail-open.js +25 -1
- package/dist/observability/metrics.js +2 -1
- package/dist/observability/run-terminal.js +10 -0
- package/dist/operator-ask.d.ts +9 -8
- package/dist/operator-ask.js +9 -1
- package/dist/orchestration/workflow-agent-session-index.d.ts +12 -23
- package/dist/orchestration/workflow-agent-session-index.js +11 -50
- package/dist/orchestration/workflow-notify-journal.d.ts +11 -8
- package/dist/orchestration/workflow-notify-journal.js +20 -36
- package/dist/plugins/file-outcome-sink.d.ts +4 -1
- package/dist/plugins/file-outcome-sink.js +9 -3
- package/dist/plugins/file-run-store.js +4 -4
- package/dist/plugins/memory-run-store.d.ts +1 -1
- package/dist/plugins/memory-run-store.js +2 -1
- package/dist/plugins/pg-pool.js +15 -0
- package/dist/plugins/retention-store-sql.d.ts +1 -1
- package/dist/plugins/retention-store-sql.js +2 -0
- package/dist/plugins/run-store-sql.d.ts +1 -1
- package/dist/plugins/run-store-sql.js +3 -2
- package/dist/plugins/schema-indexes.js +2 -0
- package/dist/plugins/store-backend.d.ts +4 -0
- package/dist/plugins/store-backend.js +9 -0
- package/dist/plugins/store-contracts.d.ts +24 -0
- package/dist/plugins/store-contracts.js +10 -0
- package/dist/plugins/tidb-pool.js +17 -0
- package/dist/plugins/user-input-ledger-file.d.ts +35 -0
- package/dist/plugins/user-input-ledger-file.js +108 -0
- package/dist/plugins/user-input-ledger-sql.d.ts +54 -0
- package/dist/plugins/user-input-ledger-sql.js +129 -0
- package/dist/project-memory.d.ts +10 -2
- package/dist/project-memory.js +5 -2
- package/dist/run-local.d.ts +18 -5
- package/dist/run-local.js +24 -9
- package/dist/runs.d.ts +8 -1
- package/dist/runs.js +11 -5
- package/dist/runtime-governance.d.ts +20 -8
- package/dist/runtime-governance.js +7 -12
- package/dist/session-records-skipped-notice.d.ts +4 -3
- package/dist/task-settings.d.ts +0 -6
- package/dist/task-settings.js +0 -1
- package/dist/tool-approval.d.ts +8 -1
- package/dist/tool-approval.js +4 -1
- package/dist/trace/core-keyset-guard.d.ts +16 -1
- package/dist/trace/engine-notice-wire.d.ts +30 -35
- package/dist/trace/engine-notice-wire.js +23 -23
- package/dist/trace/ledger-sink.d.ts +4 -0
- package/dist/trace/ledger-sink.js +1 -1
- package/dist/trace/project.d.ts +11 -3
- package/dist/trace/project.js +6 -5
- package/dist/trace/self-minted-wire.d.ts +40 -0
- package/dist/trace/self-minted-wire.js +15 -0
- package/dist/trace/wire-projection-faces.d.ts +3 -3
- package/dist/trace/wire-projection-faces.js +4 -2
- package/dist/user-input-ledger.d.ts +349 -0
- package/dist/user-input-ledger.js +354 -0
- package/package.json +3 -3
package/MIGRATION.md
CHANGED
|
@@ -7,6 +7,31 @@
|
|
|
7
7
|
> ⚠️ 完整清单在仓库根 `CHANGELOG.md`——它**不随 npm tarball 出包**(本文件随包)。看完整迁移窗的
|
|
8
8
|
> 权威姿势是源码 tag diff:`git diff v<旧>..v<新>`(每版都推 `v<版本>` tag);npm 包页也镜像 CHANGELOG。
|
|
9
9
|
|
|
10
|
+
## 7.111.0(车TA,S-961 / S-972)—— `wiring_manifest` 帧 +`instructions` 段(additive,零动作)
|
|
11
|
+
|
|
12
|
+
- **wire(additive,零动作)**:`wiring_manifest` 帧(四条腿:流式 SSE / 账本行 / 非流式 `wiringManifest` 回执 / 续跑腿)多一段本服务自铸的 `instructions: { selected: string[] }` = 这条腿选中的项目指令文件(相对 cwd 的链成员);`[]` = 没有,缺席 = 无陈述(契约附录 G.21)。按封闭 schema 校验该帧的消费方加这一可选键;不要它的部署 `PROJECT_MEMORY_ENABLED=false`(连同项目记忆注入一起关)。`task.user_steer_undrained{cause:"crashed"}` 的 `message` 改句序(行动句在前、< 400 字符),`detail` 不变 —— 按文本匹配的脚本改读 `detail.cause`(S-972)。
|
|
13
|
+
- **会话删除 / 托管留存级联接上用户输入受理账本(车TC,S-965;零 DDL、零动作)**:`DELETE /v1/sessions/:id` 与 SQL 后端的托管留存清扫自本版起连带删掉该会话的受理行(SQL `user_input_acceptance` 按 `session_id` 删;local `ledger.jsonl` 追加一行会话级墓碑 `{"kind":"erased","sessionId":…,"at":…}`);`DELETE` 回执形不变。**回滚**(local 车道):≤7.110.x 不认 `erased` 行 —— 启动 warn `user_input_ledger_corrupt_read`,被删会话的行在旧进程里复现;要干净回滚,停服后删 `<localDataRoot>/user-input-ledger/ledger.jsonl`(代价 = 丢掉还没告知的崩溃丢失提醒)。SQL 车道回滚无影响。**嵌入方(类型面硬破坏)**:`UserInputLedgerStore`(包根导出型)新增必选 `deleteBySession(sessionId): Promise<number>`;自建 `StoreBackend.userInputLedger()` 的嵌入方 tsc 红即通知。
|
|
14
|
+
- **同步子代的 steer 入用户输入受理账本(车TF,S-966;零 DDL、零动作)**:`POST /v1/runs/:id/subagents/:target/steer` 自本版起与 run 自己的 steer 同一本账(先账后引擎;行记在回执上的那条 run 名下)——进程在子代收场前崩了 ⇒ 同会话下一条 run 首拍的 `task.user_steer_undrained{cause:"crashed"}` 把它并进那条 run 的条数;账本写不进 ⇒ 该路由回 **503 `steering.ledger_unavailable`**(此前 200);200 回执 additive 多一个 `messageId`(按封闭 schema 校验回执的消费方加这一可选键)。workflow agent 的 steer 不变(不入账,崩溃丢失仍零告知,另票)。**回滚**:无数据动作(行形不变;≤7.110.x 读得懂这些行,照常收敛 / 告知)。
|
|
15
|
+
- **用户输入受理账本收敛判词合一 / SQL 坏行逐行跳过(车TD,S-969;零 DDL、零动作)**:local 车道 boot 收敛自本版起读 run 行 —— 前世进程没结清、而承载它的 run 已正常收场 / 已挂起的受理补结清、不再告知(7.110.x 一律按崩溃告知);SQL 表里读不出的行逐行跳过 + 计数 `server.user-input-ledger.corrupt-record-skipped`,其余照常(7.110.x 整页读失败)。**嵌入方(类型面硬破坏)**:`UserInputLedgerStore.listOpen` 返回 `Promise<OpenScanPage>`(`{ open, next? }`,`next` = 这一页原始末行的键、缺席 = 扫到底;包根新导出 `OpenScanPage`)—— 自建店改交页对象、读不出的行自己跳过并照原始行给游标;`WriterGoneEvidence` 改为 `{ kind, runs }`(两臂都带 run 店读口)。tsc 红即通知。**回滚**:回到 7.110.x 无数据动作(账本行形不变)。
|
|
16
|
+
- **`@sema-agent/core` 7.38.0 → 7.38.2(车TG;两只 patch,core 硬破坏 0、wire 键 / 码 / 闭集零变化,零动作)**:① 内建读拒表的家锚定行(`~/.sema/settings.json` 等)另落**账户家**(core 7.38.1 #1244,S-941)—— 引擎进程的 `HOME` 被改指时,账户真实家下的同名文件由「读得到」变为 `read_path_denied`;真要读的部署用 `READ_DENY_BUILTIN_EXCLUDE` 删那一行。S-941 时期按 DEPLOY-PREREQS 加的 `READ_DENY_PATTERNS` 不锚家同名行可撤(不撤无害)。账户查询无答的主机上这些行在任何位置判(项目自己的 `.sema/settings.json` 也拒,一次 `console.warn`,下一条 run 重问)。② local 车道 File 店族的 JSONL 末段整条记录缺换行 ⇒ 认下并在下次打开补换行(core 7.38.2 #1278,S-975);末段非对象 ⇒ 截 + 多一条 `corrupt-record-skipped` 计数。**回滚**到 ≤7.110.x:回退版本的 core 会把「只缺换行」的末行截掉(只在崩溃留下的那一形上;盘上格式不变,零数据动作)。
|
|
17
|
+
|
|
18
|
+
## 7.110.0(车ST,S-955;小车 SV,S-956;车SX,S-958 / S-959;小车 SA,S-962 / S-963;主会话 S-964;车SW,S-928)—— `@sema-agent/core` 7.38.0(minor,core 硬破坏 0):通告码受众 / 铸者改读 core 表 · `terminal.failed.detail` 过境 · hook 载荷 `permission_mode` = 腿实时模式 · 类对判据走 core 屏 · run-local 无 TTY 拒记部署政策名下 · 复合体读不出词的 PreToolUse 结果交引擎屏 · 🔴 `MEMORY_SYNC_SCOPE` 缺省不再从非 v2 `MEMORY_SCOPE` 派生(拒启) · 崩溃丢掉的 200-applied steer 下一条 run 告知(用户输入受理账本)
|
|
19
|
+
|
|
20
|
+
- **🔴 记忆同步 env(S-964,配置面硬破坏)**:单用户 file 引擎上设了 `MEMORY_SYNC_URL` / `MEMORY_SYNC_TOKEN`、**没设** `MEMORY_SYNC_SCOPE`、且 `MEMORY_SCOPE` 不是 typed 键(`user:` / `org:` / `proj:` / `userproj:`;含缺省 `local`)的部署**升级后拒启**。此前这一形派生同步层 `user:<MEMORY_SCOPE>` —— 一个本部署从不写的层(写家是裸 `MEMORY_SCOPE`,core 同步按同一个名字取条目)⇒ 同步腿自 142-S2.5-W1 起每轮推 0 条。出路二选一(错误句原文点名):`MEMORY_SYNC_SCOPE=<要复制的层>`,或把 `MEMORY_SCOPE` 设成中心期待的 typed 键(如 `user:<tenant>`,缺省同步层随之同名)。显式 `MEMORY_SYNC_SCOPE` 的部署零变化。
|
|
21
|
+
|
|
22
|
+
- **hook 作者(stdin 载荷契约)**:`permission_mode` 的值源从「请求上的模式词」改为**腿的实时模式**(core 回调上下文 `permissionMode`,plan 腿续跑 / 模式释放后的真实值);PreCompact / PostCompact / StopFailure 三事件与上下文缺席时**不带**该键(此前恒带请求词)。按旧值做分支的 hook 改读实时词、对缺席作「无模式语义」处理。**接手屏**(core 7.38.0):PreToolUse hook 结果越界 `action` / 字串布尔 / `null` 成员 / 不可克隆改写 ⇒ 引擎拒(工具不跑、模型读接手句、`permissionDenied` 观察者响);无 `action` 仍是无异议 —— 本服务映射核在约内,部署自写 hook 自查。经本服务 hooks 复合体(部署基线恒挂,挂了 hooks 的 run 都经过它)的结果同判:读不出词的结果(集外 `action`、非对象 —— 含 `0` / `""` / `NaN`;无异议只认 `undefined` / `null` / `false`)原样交引擎屏 ⇒ 引擎拒(S-959;修前复合体把它读作无异议、照跑,带 `additionalContext` 的还被译成 allow)。
|
|
23
|
+
- **嵌入方(类型面,不在包导出面)**:`SERVER_NOTICE_AUDIENCE` / `ServerNoticeCode` / `isServerNoticeCode` / `HookRunnerContext.permissionMode` / `ENGINE_ABSENT_PERMISSION_MODE` 仓内硬删 —— 四枚本服务铸码的受众 / 铸者改读 core `NOTICE_AUDIENCE` / `NOTICE_MINTER`;client-core 镜像行可退役(core 包已含四码)。
|
|
24
|
+
- **结果面(additive)**:`terminal.failed.detail? { kind:"provider_rejected", status, code? }` 过境(同步 200 体 / `done.result` / `GET /v1/runs/:id` / `done` 账本行);判「可改重发」看 `status`(401 / 403 / 429 也带)。`engine_notice` D.3 白名单 +`task.tool_turn_incomplete`(user);`memory.layer_locked_legacy` 明拒(operator)。`/metrics` `brain_echo_dropped_total{field}` 可出现 `field="apiErrorCode"`。
|
|
25
|
+
- **审批面(值层,键不变)**:`askClass` / `classes` 读写两面统一走 core `screenAskClassMembers`:投规范序副本;重复员 / 非规范序首员不符形改判(只在 core 自己不写出的形上);`card_json` 存量卡零影响。
|
|
26
|
+
- **🔴 迁移窗口(core #1213)**:本版提货 core 7.38.0 **仍迁移旧模式键检查点行、不拒**;排空窗到提货 core 7.39.0 的 server 版前(7.109.0 段原句)。
|
|
27
|
+
- **run-local(S-956)**:无 TTY 被拒的 run 不再带 `haltedOnUserRejection`。退出码仍为 1;非 `--json` 停因行从 `task stopped (haltedOnUserRejection) …` 改为 `task incomplete (approval unanswered) …`;门记录 `settlement.kind` `human_refused` → `policy_refused`、`who.party` `person` → `none`;模型读 core 策略句 + 拒因。按 7.101.0 迁移建议读 `--json` 的 `haltedOnUserRejection` 判拒的脚本,改看退出码 / stderr 停因行。同一拍的兄弟调用不再被 `gate.batch_halted` 连坐,各自过门。委派子代里无 TTY 的拒也计数 ⇒ CLI 退 1(更严;TTY 下子代人答 N 仍退 0)。
|
|
28
|
+
- **嵌入方(类型面硬破坏,S-928)**:`StoreBackend`(包根导出型)新增**必选** `userInputLedger(): UserInputLedgerStore`;自建 StoreBackend 的嵌入方 tsc 红即通知(7.109.0 `listByCall` 同形);返回型 `UserInputLedgerStore` 及行型 `UserInputAcceptance` / `NewUserInputAcceptance` / `SettleHow` / `OpenScanCursor` / `WriterGoneEvidence` 自本版从包根导出。
|
|
29
|
+
- **用户输入受理账本(S-928;零动作)**:live 腿 steer 在回 200 之前先落一行受理(local:`<数据根>/user-input-ledger/ledger.jsonl`;SQL:新表 `user_input_acceptance`,
|
|
30
|
+
中央 `ensureSchema` 以 `CREATE TABLE IF NOT EXISTS` 自建,两方言 + 两条索引,无 ALTER、无回填)。账本写不进 ⇒ steer 回 **503 `steering.ledger_unavailable`**(此前 200;引擎零字节)——
|
|
31
|
+
local 车道数据根只读 / 盘满、SQL 车道库不可达时出现,其余路由不受影响。进程崩溃时丢掉的受理在该会话下一条 run 首拍告知 `task.user_steer_undrained{cause:"crashed"}`,
|
|
32
|
+
`GET /v1/sessions/:id` 多一个可选键 `userInputUndrained`(缺席 = 零条)。**回滚**:回到 ≤7.109.x 时账本文件 / 表留着无害(旧二进制不读);滚回之后的崩溃丢失恢复修前的静默。
|
|
33
|
+
- **零数据迁移**;DDL 只有上一条的新表(自建)。
|
|
34
|
+
|
|
10
35
|
## 7.109.0(车SK,S-934)—— `@sema-agent/core` 7.37.1(7.37.0 的全部变更 + #1224 工具回合律请求修复,运维通告 `context.tool_turn_repaired` 不上会话流):无人值守腿按类作答(`UNATTENDED_PERMISSION_MODE` 退役,设了即拒启)· ask 类对 `askClass` / `classes` 上 wire · 策略决定对象的普通对象律 · 迁移窗口
|
|
11
36
|
|
|
12
37
|
- **🔴 env:`UNATTENDED_PERMISSION_MODE` 设了即拒启**(任何值,含 7.106.0 的缺省 `bypassPermissions`;拒因带 `config.env_key_retired` 并点名键)。
|
package/README.md
CHANGED
|
@@ -128,6 +128,9 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
|
|
|
128
128
|
7.104.0 the execution-lane start refusals (`LEADER_ENABLED` on the host lane, a multi-tenant posture on a
|
|
129
129
|
shared lane, the device lane without its durable stores) are judged there, not in `loadConfig()`;
|
|
130
130
|
`assertExecutionLaneInvariants(config)` is the same judgment without the `config.d/remote-exec.json` override.
|
|
131
|
+
Build-time tools (e.g. a catalog editor) read two allowlists from the package root instead of hand-copying them: `RETIRED_ENVS` (the retired-env
|
|
132
|
+
tombstones) and, since 7.110.0, `CATALOG_ENTRY_HEADER_LANES` / `CATALOG_ENTRY_HEADER_NAMES` (+ type `CatalogEntryHeaderName`) — the closed allowlist of
|
|
133
|
+
model-catalog entry `headers` and the API line that reads each name. Both are frozen, the very objects the server judges with.
|
|
131
134
|
- **Bundled binaries**: the package ships two `bin` entries — `run-local` (single-machine local
|
|
132
135
|
runner) and `sema-up` (deployment bootstrap script).
|
|
133
136
|
- `run-local` boots the same engine in-process, runs one task from the CLI objective, prints the
|
|
@@ -156,6 +159,11 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
|
|
|
156
159
|
- **A run stopped by a refusal exits 1** (since 7.101.0; it used to exit 0), `--json` included: whatever the
|
|
157
160
|
model wrote before the refusal still goes to stdout, and without `--json` stderr names the stop
|
|
158
161
|
(`task stopped (haltedOnUserRejection) …`).
|
|
162
|
+
- **A refusal nobody was asked for is the deployment's policy, not yours** (since 7.110.0): with no TTY (or
|
|
163
|
+
arguments that cannot be displayed) the gate records `policy_refused` / `who.party: "none"`, the model reads
|
|
164
|
+
the engine's policy sentence instead of "the user doesn't want to proceed", and it carries on with the parts
|
|
165
|
+
that need no approval. The run still exits 1 — that call never ran — and stderr names the stop
|
|
166
|
+
(`task incomplete (approval unanswered) …`).
|
|
159
167
|
- **Full-stack, one command** (DB + object store + registry web + sandbox pool; Docker and k8s
|
|
160
168
|
paths): [`sema-agent/sema-deploy`](https://github.com/sema-agent/sema-deploy).
|
|
161
169
|
- **Sandbox package sources**: default = official upstreams (pypi/npmjs/crates.io/…). For
|
package/README.zh-CN.md
CHANGED
|
@@ -112,6 +112,8 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
|
|
|
112
112
|
`composeExecutionLane(config, ctx)` —— HTTP 服务与 `run-local` 都这么做。7.104.0 起执行车道的几条拒启(host 车道上开
|
|
113
113
|
`LEADER_ENABLED`、多租户姿态落在共享车道、device 车道缺耐久店)判在这一步,不在 `loadConfig()` 里;
|
|
114
114
|
`assertExecutionLaneInvariants(config)` 是同一份判据,不做 `config.d/remote-exec.json` 覆写。
|
|
115
|
+
构建期工具(如目录编辑页)从包根取两张表、不手抄:`RETIRED_ENVS`(退役 env 墓碑)与 7.110.0 起的 `CATALOG_ENTRY_HEADER_LANES` /
|
|
116
|
+
`CATALOG_ENTRY_HEADER_NAMES`(+ 型 `CatalogEntryHeaderName`)—— 模型目录条目 `headers` 的闭集白名单及读每个名字的线路。两者都冻结,就是服务端自己判据用的那一只。
|
|
115
117
|
- **随包二进制**:包内带两个 `bin` —— `run-local`(单机本地 runner)与
|
|
116
118
|
`sema-up`(部署引导脚本)。
|
|
117
119
|
- `run-local` 在进程内启同一套引擎,把命令行给的目标跑完一次、打印结果、退出——没有 HTTP 服务,
|
|
@@ -133,6 +135,9 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
|
|
|
133
135
|
一问 —— 主任务与子代的并发询问依次排队。
|
|
134
136
|
- **因拒答而停下的 run 退 1**(7.101.0 起,`--json` 也一样;此前退 0):模型在拒答前写的文字照打 stdout,
|
|
135
137
|
非 `--json` 形 stderr 一行点名停因(`task stopped (haltedOnUserRejection) …`)。
|
|
138
|
+
- **没有人被问的拒记在部署政策名下,不记在你名下**(7.110.0 起):无 TTY(或参数展示不出)时门记录是
|
|
139
|
+
`policy_refused` / `who.party: "none"`,模型读引擎的策略句而不是「用户不想继续」,接着做不需要审批的部分;
|
|
140
|
+
那次调用没执行 ⇒ run 照样退 1,stderr 停因行 `task incomplete (approval unanswered) …`。
|
|
136
141
|
- **一键全栈部署**(DB + 对象存储 + registry 网站 + 沙箱池,docker/k8s 双路径):
|
|
137
142
|
[`sema-agent/sema-deploy`](https://github.com/sema-agent/sema-deploy)。
|
|
138
143
|
- **沙箱装包源**:缺省 = 官方源(pypi/npmjs/crates.io/…)。中国大陆部署配
|
package/USAGE.md
CHANGED
|
@@ -690,7 +690,7 @@ FLEET_ADVERTISE_ADDRESS=http://<本机可达IP>:8090 # 可选:设了才启公
|
|
|
690
690
|
| 记忆整理阀门判决 + 驱动席(`MEMORY_CONSOLIDATION_DRIVER=on`,或未设而出厂缺省按目录派生 —— 7.104.0 起) | 重启生效 | 阀门开关与整理跑用的模型都在启动期定一次;中心改了 roles / 档位 / 目录条目后,**判决会翻**(派生开 ↔ 关,含只把主模型挪到整理座那一条上)或开着而座漂了 ⇒ `/health` 报 `restartRequired`,`restart.reasons` 含 `memory-consolidation`(7.100.0 新理由;7.104.0 起派生关的那台同样报) |
|
|
691
691
|
|
|
692
692
|
生效与否的观测口=`/health` 世代账键(`configTargetVersion`/`configAppliedVersion`+两 ordinal+`configApplyStaleMs`,见 API 表 `/health` 行)——target≠applied 持续=「改了没生效」的机读信号。
|
|
693
|
-
- **`run-local`(一次性本机 CLI)上三张审批名单同样执法**(7.101.0 起,TTY 问 / 无 TTY 拒):`APPROVAL_DENY` 当场拒;`APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` 命中 ⇒ stdin 是 TTY 就 `y/N` 问人,否则当场拒并在 stderr 点名三张名单键;`APPROVAL_AUTO_BUDGET` 照常生效(never-auto 不吃预算);TTY
|
|
693
|
+
- **`run-local`(一次性本机 CLI)上三张审批名单同样执法**(7.101.0 起,TTY 问 / 无 TTY 拒):`APPROVAL_DENY` 当场拒;`APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` 命中 ⇒ stdin 是 TTY 就 `y/N` 问人,否则当场拒并在 stderr 点名三张名单键;`APPROVAL_AUTO_BUDGET` 照常生效(never-auto 不吃预算);TTY 问句带上这次调用的参数,人答 N 而停下的 run 退 1(停因 `haltedOnUserRejection`)。**无 TTY 的拒记在部署政策名下**(7.110.0 起,S-956;此前记成人拒):该次调用 `tool_end.gate.settlement` = `{kind:"policy_refused", who:{party:"none"}}`,模型读到的是引擎的策略句(「部署政策拒了、此处没有交互审批,别重试,先做不需要审批的部分」)加一句拒因,而不是「用户不想继续,停下来等他」—— 不停批,模型接着做别的部分;那只要人批的动作没执行 ⇒ run 照样退 1,非 `--json` 形 stderr 停因行是 `task incomplete (approval unanswered): …`。参数展示不出而不问就拒的那一形(TTY 在场也一样)同此。这条腿没有 checkpoint 店也**不**拒启 —— 它有一只当场作答的席,名单的 ask 不需要 park(上表「无 checkpoint 店 ⇒ 整组拒」只说服务腿)。此前三张名单在这条腿上无声不生效。
|
|
694
694
|
- **仅 `/v1/tasks`(同步)+ `/v1/runs`(异步)**——`/v1/tasks/stream` 不支持(多次尝试非单流,400)。与 `verify` **互斥**(同时给 → 400)。
|
|
695
695
|
- 成本上界 = 任务的 `maxCostUsd`(防冷重跑税)。**三条 core 警示**:① 每档**冷重跑**重付输入成本(便宜档常过才划算);② 门收到**未脱敏**输出(自定义门转发外部 verifier 要脱敏);③ **写工具会跑 N 次**——**只用于只读/幂等任务**(每次升级整任务重跑)。开放式任务(找全 bug/文笔)没有可判定 oracle、级联会空转,那种用 `discuss`(广度交叉)而非级联(深度阶梯)。
|
|
696
696
|
|
|
@@ -873,7 +873,7 @@ UNATTENDED_APPROVAL_POLICY=park
|
|
|
873
873
|
`ordinary` 的问 —— 模式写门对普通写的问、形状层对非只读 shell 的问(写与命令照常落地,与 7.105.0 同)。其余每一类都不答:
|
|
874
874
|
写保护表目标(`write_protection`)、shell 读会话目录外(`probe`)、MCP / A2A 工具标记、transcript 写、规则店读不出、共享制品读、
|
|
875
875
|
plan 的写问(`mode`),以及运维 / hook / 规则 / 组织为人提的问(审批名单、治理层、`APPROVAL_NEVER_AUTO` …)。不答的问:
|
|
876
|
-
- **有席的腿交给席**:run-local 在终端里跑 ⇒ 问你 y/N;没有 TTY ⇒ 当场拒(stderr
|
|
876
|
+
- **有席的腿交给席**:run-local 在终端里跑 ⇒ 问你 y/N;没有 TTY ⇒ 当场拒(stderr 一句;门记录记在部署政策名下 `policy_refused` / `who.party:"none"`,7.110.0 起,S-956)。
|
|
877
877
|
- **无席的腿**(leader 各腿):配了 durable 审批块的部署 ⇒ park,等人经 `/decide` 答(与 7.105.0 同形);没配 ⇒ 当场拒(fail-closed)。
|
|
878
878
|
- 答集是常量(只 `ordinary`,core 推荐形),**不是旋钮**。7.106.0–7.108.0 的 `UNATTENDED_PERMISSION_MODE` 已退役:**设了即拒启**
|
|
879
879
|
(`config.env_key_retired`,任何值都拒);从 `.env` 里删掉它。要让机器多答某一类问,先立案,不开旋钮。
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
* `server/designs/2026-08-31-s38-local-stream-approval-v1.md`,v1 三臂 + v1.1 §6 两件增益)——
|
|
4
4
|
* local 车道(`DB_BACKEND=local`)的 **File ask 审计店**。SINGLE-FILE 店([ref] A12 单文件店纪律,
|
|
5
5
|
* 形照 `plugins/memory-optout-grant-store-sql.ts`:一个实现文件、数据形/偏离/坏值姿态全部成文在头注;
|
|
6
|
-
*
|
|
7
|
-
* 重放容忍撕尾行)。
|
|
6
|
+
* 崩溃安全机制 = append-only JSONL、fsync 逐行、重放容忍撕尾行 —— 追加与重放都走本仓 JSONL 店族的**共用口**(S-967,见下「追加 / 重放口」段))。
|
|
8
7
|
*
|
|
9
8
|
* ## 它治的病([ref] / [ref]① 车定界钉;⚠️ **病因描述是 [ref] 当时的事实,S-384 起已变**)
|
|
10
9
|
* 当时:local 车道 ask 账进程内易失(`InMemoryApprovalAskStore`),活卡窗内崩溃 = **零痕迹**:重启后
|
|
@@ -135,11 +134,24 @@
|
|
|
135
134
|
* ## `crashed_before_park` 与 FAIL-OPEN census 的关系(稿未闭环②,施工时裁定,成文于此)
|
|
136
135
|
* **不入词表**:census 词表登记的是「走了兜底而外面看不出」的**臂**;`crashed_before_park` 是一条
|
|
137
136
|
* **终态成因注**(收敛器 fail-closed 地把孤儿收成 DENIED 同码并在读面显形),它的存在恰恰是响亮化
|
|
138
|
-
*
|
|
139
|
-
* [ref] 律走 `recordFailOpen("server.approval-ask-audit.append-failed")`(census
|
|
140
|
-
*
|
|
137
|
+
* 本身,不是静默降级。本店的静默降级臂有两条:① 运行期 append 失败(审批可用性不押在审计盘上),已按
|
|
138
|
+
* [ref] 律走 `recordFailOpen("server.approval-ask-audit.append-failed")`(census 第 53 行);② boot 重放跳过中间坏行
|
|
139
|
+
* (S-967 起由重放属主逐条披露 + `server.approval-ask-audit.corrupt-record-skipped`,census 第 106 行;此前只计 `corruptLines`)。
|
|
140
|
+
* boot 期(mkdir/重放的读错)失败则**拒启**(与 File 店族同律:`FileStorageBackend` ctor 同 root 同姿态)——
|
|
141
141
|
* 一台「审计写不进却继续批」的机器 = [ref] 换个皮回来。
|
|
142
142
|
*
|
|
143
|
+
* ## 追加 / 重放口(S-967:一只追加日志实现,不留两份)
|
|
144
|
+
* 追加 = core `AppendLog`(惰性打开,首次 append 才铸文件;一行 = 单次 `writeSync` 的 `JSON.stringify(rec) + "\n"`、`fsync=true` 逐行;文件 0o600、目录 0o700;
|
|
145
|
+
* 末段无换行的字节在打开与每次追加前由它按 core 的同一只判词修 —— 解析成对象 = 丢了换行的整条记录,**补换行、字节保留**;不解析 = 崩溃半写(撕尾),
|
|
146
|
+
* **截断**回最后一个换行;解析成非对象 = 截断并披露(core 7.38.2 #1278))。重放 = `replayJsonlSkipping`(登记名 `approval-ask-audit`,fail-open + F 类 tag
|
|
147
|
+
* `server.approval-ask-audit.corrupt-record-skipped`:中间坏行逐条披露 + 计数;撕尾不计,无换行的完整末行照常进重放)。本店只剩两样自己的东西:**形判据**({@link LEDGER_SHAPE},逐 `kind` 的最小必填键
|
|
148
|
+
* + 词表外成因行「计坏行、结清照认」)与**失败方向**(运行期 append 失败 ⇒ `recordFailOpen("server.approval-ask-audit.append-failed")`,审批照常)。
|
|
149
|
+
* **一行「在账」的判据 = 它的字节解析成一只对象**(与共用口同一条,core 7.38.2 #1278:读面、打开修尾、追加门修尾三站问同一只判词;换行只是边界的拼法,
|
|
150
|
+
* 不是记录存在的条件 —— 没有一只 JSON 对象的真前缀能被解析,所以解析得出的末行就是整条记录;失败的 append 由 core 截回它开写处,不留自己的字节)。
|
|
151
|
+
* 修前本店手写了第二份追加 / 修尾(私有的脏尾标 + 写字节注入席),撕尾用**孤立换行隔离**保留残字节而不是截断;与共用口的形差(撕尾隔离 ⇄ 截断、撕尾计数、
|
|
152
|
+
* 字节全落却报错后多落的孤立换行、空白行计数、坏行披露)逐条记在 CHANGELOG 7.111.0 S-967 段,读数钉 = `test/approval-ask-audit.test.ts` 的「S-967」两组;
|
|
153
|
+
* 「完整 JSON 却缺换行的末行」一条(R2)在 core 7.38.2 下与修前同形(认下 + 补换行,C2 正向钉),不再是形差。
|
|
154
|
+
*
|
|
143
155
|
* ## 装配(为什么不在 `plugins/store-backend.ts`)
|
|
144
156
|
* 本店是协调器侧的审计 sink,不是跨后端 store 契约的一员(SQL 车道有真 ask 店,永不装本店)——
|
|
145
157
|
* 装配点在 `boot/coordinators.ts`(判据:`toolApprovalEnabled` ∧ `backend.kind === "local"` ∧
|
|
@@ -152,10 +164,14 @@
|
|
|
152
164
|
* journal 同款成文)。读面列表按收敛时刻新前旧后、上限 {@link CRASH_CONVERGED_LIST_MAX}(有界 wire)。
|
|
153
165
|
*/
|
|
154
166
|
import type { HostDecision } from "./host-decision.js";
|
|
167
|
+
import { AppendLog } from "@sema-agent/core";
|
|
168
|
+
import type { Logger } from "./observability/logger.js";
|
|
155
169
|
/** 数据子目录名(挂在 local 数据根下,与 `workflow-notify/` 同层同形)。 */
|
|
156
170
|
export declare const APPROVAL_ASK_AUDIT_DIR = "approval-ask-audit";
|
|
157
171
|
/** 崩溃收敛行的读面上限(新前旧后截断;有界 wire —— 读面不许随月份线性长)。 */
|
|
158
172
|
export declare const CRASH_CONVERGED_LIST_MAX = 200;
|
|
173
|
+
/** 追加口的窄形(core `AppendLog` 的 `append` / `close`)—— 店的测试注入席({@link FileApprovalAskAuditStore} 构造器 `internals.appendLog`)按它造替身。 */
|
|
174
|
+
export type AuditAppendPort = Pick<AppendLog, "append" | "close">;
|
|
159
175
|
/** 铸造点入参(协调器 askBroadcast 的 register-before-emit 之后挂线)。 */
|
|
160
176
|
export interface AuditAskInput {
|
|
161
177
|
/** wire 身份 = 呈卡帧上那把 `approvalId`(respond/queue 同源可达;不是本地 pending 键)。 */
|
|
@@ -302,14 +318,10 @@ export type LateSettleUnresolved = "ambiguous_anchor";
|
|
|
302
318
|
export declare class FileApprovalAskAuditStore implements ApprovalAskAuditSink, ApprovalAskAuditReadFace {
|
|
303
319
|
/** 账本绝对路径(测试/取证按文件字节对账 —— 崩溃语义的真源是文件,不是内存投影)。 */
|
|
304
320
|
readonly ledgerPath: string;
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
* 失败 ⇒ 脏标保持,本条记录走留痕臂 —— 绝不往可能的残尾上直接续字节。 */
|
|
310
|
-
private tailDirty;
|
|
311
|
-
/** 写字节口(测试注入席:半写故障的确定性构造;生产恒缺省 = `fs.writeSync`)。 */
|
|
312
|
-
private readonly writeBytes;
|
|
321
|
+
/** 追加口(core `AppendLog`,惰性打开 —— 首次 append 才铸文件;`close()` 后下一次 append 重开)。撕尾修复 / 短写补齐 / 逐行 fsync 都在它里面,本店不再自写一份。 */
|
|
322
|
+
private log;
|
|
323
|
+
/** 追加口的铸造座(测试注入席:单条写失败 / 半写的确定性构造;生产恒缺省 = `new AppendLog(path)`)。 */
|
|
324
|
+
private readonly openLog;
|
|
313
325
|
/** boot 重放快照(冻结于构造时刻 = 前世账本;本进程新写的行不进它 —— 收敛只对前世行)。 */
|
|
314
326
|
private readonly replayed;
|
|
315
327
|
/** 前世账本里的腿闭事件(taskId + 行序;decided 臂按「决议行之后」判)。 */
|
|
@@ -339,24 +351,25 @@ export declare class FileApprovalAskAuditStore implements ApprovalAskAuditSink,
|
|
|
339
351
|
/** [ref] 反查索引 ③:approvalId → 铸造坐标(命中后回填 late 行的 `taskId`)。 */
|
|
340
352
|
private readonly askTaskId;
|
|
341
353
|
/**
|
|
342
|
-
* boot 期 fail-stop
|
|
354
|
+
* boot 期 fail-stop:建目录 / 重放的**读错**(非 ENOENT)⇒ 抛 ⇒ **拒启**(File 店族同律)。root = local 数据根
|
|
343
355
|
* (`config.localDataRoot`;与 `LocalBackend` 同源,见头注装配段)。
|
|
344
|
-
* 0o700 目录 + 0o600 账本:行携租户身份(owner/sessionId),不得组/他人可读
|
|
345
|
-
* (
|
|
356
|
+
* 0o700 目录 + 0o600 账本:行携租户身份(owner/sessionId),不得组/他人可读(目录由 core `ensureDir`、账本由 `AppendLog` 铸)。
|
|
357
|
+
* 追加口惰性打开(S-967,与 `FileUserInputLedgerStore` 同形):末段的修(撕尾截断 / 完整末行补换行)随首次 append 的打开发生 —— 重放读数不依赖它
|
|
358
|
+
* (撕尾不进重放、完整末行进重放,修前修后读数同);
|
|
359
|
+
* 目录已在而盘后来满 / 变只读 ⇒ boot 照常,append 走 `recordFailOpen`。
|
|
360
|
+
* @param internals.appendLog 测试注入席(见 {@link AuditAppendPort})。
|
|
361
|
+
* @param internals.warn 腐读披露座(缺席 ⇒ 重放属主退回 `console.warn`,不退回沉默)。
|
|
346
362
|
*/
|
|
347
363
|
constructor(root: string, internals?: {
|
|
348
|
-
|
|
364
|
+
appendLog?: (path: string) => AuditAppendPort;
|
|
365
|
+
warn?: Logger["warn"];
|
|
349
366
|
});
|
|
350
|
-
/**
|
|
351
|
-
* 这里不持 logger 席,与 journal 同姿态;坏行数暴露给收敛日志)。 */
|
|
367
|
+
/** 重放的坏值计数(`convergeCrashedAsks().corruptLines`):重放属主跳过的坏行(撕尾不计)+ 成因词表外的行(行在、结清照认,坏值照样计数)。 */
|
|
352
368
|
private corruptLines;
|
|
353
|
-
/**
|
|
354
|
-
* ⚠️ 判据是**换行缺席**不是「解析失败」:崩在「JSON 全落、换行未落」的字节窗时撕尾能 parse 成功,
|
|
355
|
-
* 但不修尾照样会与下一条记录并行。 */
|
|
356
|
-
private tornTail;
|
|
369
|
+
/** 重放前世账本(共用口 `replayJsonlSkipping`):撕尾 = 未落完的写,静默跳过;坏行跳过 + 逐条披露;坏行数另暴露给收敛日志。 */
|
|
357
370
|
private replay;
|
|
358
371
|
private group;
|
|
359
|
-
/** append + fsync(逐行落盘:进程猝死不丢已记的行 ——
|
|
372
|
+
/** append + fsync(逐行落盘:进程猝死不丢已记的行 —— 本店的全部价值就在崩溃时刻;写口 = 共用的 `AppendLog`:单次 `writeSync` 一行、末段在打开与每次追加前修(撕尾截、完整末行补换行)、失败的写截回开写处)。
|
|
360
373
|
* 运行期失败**响亮不阻断**:审批可用性不押在审计盘上(盘满/只读时人还得能批),但丢行必须在遥测
|
|
361
374
|
* 显形([ref] 三件套,census 登记行同批)。
|
|
362
375
|
* 🔴 返回**这条记录是否真的落盘并 fsync 成功**([ref] codex R1-F2 采):迟到结算的 late 判据只认
|
|
@@ -432,7 +445,7 @@ export declare class FileApprovalAskAuditStore implements ApprovalAskAuditSink,
|
|
|
432
445
|
* `owner === scope` 的行(null-owner 行对 principal 不可见,fail-closed 宁少列不超范围;`"__none__"`
|
|
433
446
|
* 未鉴权哨兵自然恒空)。 */
|
|
434
447
|
listCrashConverged(scope: string | undefined): CrashConvergedRow[];
|
|
435
|
-
/**
|
|
448
|
+
/** 收追加口(优雅关闭腿;SIGKILL 下不跑也不丢 —— 每行已 fsync)。core `AppendLog.close()` 自己吞 `closeSync` 的错、不抛(全函数),本店不再包 catch。 */
|
|
436
449
|
close(): void;
|
|
437
450
|
}
|
|
438
451
|
//# sourceMappingURL=approval-ask-audit-store.d.ts.map
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, writeSync } from "node:fs";
|
|
3
2
|
import { join } from "node:path";
|
|
3
|
+
import { AppendLog, ensureDir } from "@sema-agent/core";
|
|
4
4
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
5
|
+
import { replayJsonlSkipping } from "./observability/corrupt-read-seat.js";
|
|
5
6
|
export const APPROVAL_ASK_AUDIT_DIR = "approval-ask-audit";
|
|
6
7
|
export const CRASH_CONVERGED_LIST_MAX = 200;
|
|
7
8
|
export const AUDIT_SHUTDOWN_CAUSE_WORDS = { shutdown_before_park: true };
|
|
@@ -160,11 +161,20 @@ function readLedgerRecord(u, noteOutOfSetSeat) {
|
|
|
160
161
|
return undefined;
|
|
161
162
|
}
|
|
162
163
|
}
|
|
164
|
+
const LEDGER_SHAPE = {
|
|
165
|
+
what: "approval-ask audit record of an unknown kind or with a missing / out-of-vocabulary member",
|
|
166
|
+
read(record) {
|
|
167
|
+
let outOfSetCause = false;
|
|
168
|
+
const rec = readLedgerRecord(record, () => {
|
|
169
|
+
outOfSetCause = true;
|
|
170
|
+
});
|
|
171
|
+
return rec === undefined ? undefined : { rec, outOfSetCause };
|
|
172
|
+
},
|
|
173
|
+
};
|
|
163
174
|
export class FileApprovalAskAuditStore {
|
|
164
175
|
ledgerPath;
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
writeBytes;
|
|
176
|
+
log;
|
|
177
|
+
openLog;
|
|
168
178
|
replayed = new Map();
|
|
169
179
|
replayedLegClosed = [];
|
|
170
180
|
replayedShutdownMarks = [];
|
|
@@ -177,44 +187,21 @@ export class FileApprovalAskAuditStore {
|
|
|
177
187
|
parkExpired = new Set();
|
|
178
188
|
askTaskId = new Map();
|
|
179
189
|
constructor(root, internals) {
|
|
180
|
-
this.
|
|
190
|
+
this.openLog = internals?.appendLog ?? ((p) => new AppendLog(p));
|
|
181
191
|
const dir = join(root, APPROVAL_ASK_AUDIT_DIR);
|
|
182
|
-
|
|
192
|
+
ensureDir(dir);
|
|
183
193
|
this.ledgerPath = join(dir, "ledger.jsonl");
|
|
184
|
-
this.replay();
|
|
185
|
-
if (this.tornTail) {
|
|
186
|
-
this.fd = openSync(this.ledgerPath, "a", 0o600);
|
|
187
|
-
writeSync(this.fd, "\n");
|
|
188
|
-
fsyncSync(this.fd);
|
|
189
|
-
}
|
|
194
|
+
this.replay(internals?.warn);
|
|
190
195
|
}
|
|
191
196
|
corruptLines = 0;
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
return;
|
|
196
|
-
const text = readFileSync(this.ledgerPath, "utf8");
|
|
197
|
-
this.tornTail = text.length > 0 && !text.endsWith("\n");
|
|
197
|
+
replay(warn) {
|
|
198
|
+
const { records, skipped } = replayJsonlSkipping(this.ledgerPath, "approval-ask-audit", LEDGER_SHAPE, warn);
|
|
199
|
+
this.corruptLines = skipped;
|
|
198
200
|
let line = 0;
|
|
199
|
-
for (const
|
|
200
|
-
if (raw.trim() === "")
|
|
201
|
-
continue;
|
|
201
|
+
for (const { rec, outOfSetCause } of records) {
|
|
202
202
|
line += 1;
|
|
203
|
-
|
|
204
|
-
try {
|
|
205
|
-
parsed = JSON.parse(raw);
|
|
206
|
-
}
|
|
207
|
-
catch {
|
|
208
|
-
this.corruptLines += 1;
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
const rec = readLedgerRecord(parsed, () => {
|
|
203
|
+
if (outOfSetCause)
|
|
212
204
|
this.corruptLines += 1;
|
|
213
|
-
});
|
|
214
|
-
if (rec === undefined) {
|
|
215
|
-
this.corruptLines += 1;
|
|
216
|
-
continue;
|
|
217
|
-
}
|
|
218
205
|
switch (rec.kind) {
|
|
219
206
|
case "ask": {
|
|
220
207
|
const g = this.group(rec.approvalId, line);
|
|
@@ -265,25 +252,11 @@ export class FileApprovalAskAuditStore {
|
|
|
265
252
|
}
|
|
266
253
|
append(rec) {
|
|
267
254
|
try {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if (this.tailDirty) {
|
|
271
|
-
const nl = Buffer.from("\n", "utf8");
|
|
272
|
-
let noff = 0;
|
|
273
|
-
while (noff < nl.length)
|
|
274
|
-
noff += this.writeBytes(this.fd, nl, noff, nl.length - noff);
|
|
275
|
-
fsyncSync(this.fd);
|
|
276
|
-
this.tailDirty = false;
|
|
277
|
-
}
|
|
278
|
-
const buf = Buffer.from(`${JSON.stringify(rec)}\n`, "utf8");
|
|
279
|
-
let off = 0;
|
|
280
|
-
while (off < buf.length)
|
|
281
|
-
off += this.writeBytes(this.fd, buf, off, buf.length - off);
|
|
282
|
-
fsyncSync(this.fd);
|
|
255
|
+
this.log ??= this.openLog(this.ledgerPath);
|
|
256
|
+
this.log.append(rec, true);
|
|
283
257
|
return true;
|
|
284
258
|
}
|
|
285
259
|
catch (err) {
|
|
286
|
-
this.tailDirty = true;
|
|
287
260
|
recordFailOpen("server.approval-ask-audit.append-failed", `kind=${rec.kind} err=${err instanceof Error ? err.message : String(err)}`);
|
|
288
261
|
return false;
|
|
289
262
|
}
|
|
@@ -440,14 +413,8 @@ export class FileApprovalAskAuditStore {
|
|
|
440
413
|
return rows;
|
|
441
414
|
}
|
|
442
415
|
close() {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
closeSync(this.fd);
|
|
446
|
-
}
|
|
447
|
-
catch {
|
|
448
|
-
}
|
|
449
|
-
this.fd = undefined;
|
|
450
|
-
}
|
|
416
|
+
this.log?.close();
|
|
417
|
+
this.log = undefined;
|
|
451
418
|
}
|
|
452
419
|
}
|
|
453
420
|
//# sourceMappingURL=approval-ask-audit-store.js.map
|
package/dist/approval-card.d.ts
CHANGED
|
@@ -436,11 +436,12 @@ type ClosedWordOf<S extends ClosedWordSeat> = NonNullable<ToolApprovalPark[S]>;
|
|
|
436
436
|
*
|
|
437
437
|
* 判据(display,不是裁决输入 —— 裁决在 core 的仲裁点,本仓无人值守腿的按类作答读的是 core 交来的 `AskRequest.classes` 本身,不经本函数):
|
|
438
438
|
* · **两键都缺席 ⇒ 缺席**(不计数):老引擎(core 7.37.0 之前)、老行(续跑不补,KL-1214-b)、不盖 `origin` 的站(KL-1214-a)。缺席从不编默认类;
|
|
439
|
-
* · **在场 ⇒
|
|
440
|
-
*
|
|
441
|
-
*
|
|
442
|
-
*
|
|
443
|
-
*
|
|
439
|
+
* · **在场 ⇒ 读法就是 core 的屏** `screenAskClassMembers`(S-955 ⑥,core 7.38.0 #1249 导出;引擎在写停车行前用的同一只):`askClass` 在集、`classes`
|
|
440
|
+
* 非空数组且每员在集、无重复、按 `ASK_CLASSES` 规范序取首员 === `askClass`;屏收 ⇒ 投屏给的规范序副本。屏拒(半对 / 集外词 / 首员不符 /
|
|
441
|
+
* 重复员)⇒ **两键都不投**(卡上少一行类 ≪ 卡上出现生词或半只对)+ 计一次 `server.approval-card.closed-word-out-of-set`(S-249 族,detail 前缀
|
|
442
|
+
* `askClass=` / `classes=` 判别是哪一格读不出 —— 前缀只是诊断,判据只有屏一条)。
|
|
443
|
+
* · 本仓**零自写对判**(修前这里与 {@link ApprovalCardSchema} 的 superRefine 各写一份「`classes[0] === askClass`」,按输入首员比、收重复员,与引擎的
|
|
444
|
+
* 屏是两条规则)。投出去的 `classes` 是新数组(帧不与载体共享可变引用;屏给的是冻结数组)。
|
|
444
445
|
*/
|
|
445
446
|
export declare function readAskClassPair(carrier: object): AskClassPair | undefined;
|
|
446
447
|
/** 类对的投影形(帧 / 卡 / 队列行同一形):两键同在同缺。值型取 core 寄存行上的座(core 改型 ⇒ 下面的围栏编译红)。 */
|
package/dist/approval-card.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { ASK_EVIDENCE_ABSENCE_VALUES, MAX_RULE_TEXT_CHARS, READ_ROOT_CANDIDATE_DIR_MAX, isAskClass, isAskOrigin, isPersistedRuleMandate } from "@sema-agent/core";
|
|
2
|
+
import { ASK_EVIDENCE_ABSENCE_VALUES, MAX_RULE_TEXT_CHARS, READ_ROOT_CANDIDATE_DIR_MAX, isAskClass, isAskOrigin, isPersistedRuleMandate, screenAskClassMembers } from "@sema-agent/core";
|
|
3
3
|
import { redactSecrets } from "./trace/redact.js";
|
|
4
4
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
5
5
|
import { PERSISTED_RULE_MATCHES, UNCOVERED_SEGMENT_REASONS } from "./permission-rule-vocab.js";
|
|
@@ -289,9 +289,9 @@ export function readAskClassPair(carrier) {
|
|
|
289
289
|
const classes = "classes" in carrier ? carrier.classes : undefined;
|
|
290
290
|
if (askClass === undefined && classes === undefined)
|
|
291
291
|
return undefined;
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
292
|
+
const screened = screenAskClassMembers(askClass, classes);
|
|
293
|
+
if (screened !== undefined)
|
|
294
|
+
return { askClass: screened.askClass, classes: [...screened.classes] };
|
|
295
295
|
recordFailOpen("server.approval-card.closed-word-out-of-set", isAskClass(askClass) ? `classes=${outOfSetDetail(classes)}` : `askClass=${outOfSetDetail(askClass)}`);
|
|
296
296
|
return undefined;
|
|
297
297
|
}
|
|
@@ -365,13 +365,10 @@ export const ApprovalCardSchema = z
|
|
|
365
365
|
})
|
|
366
366
|
.strict()
|
|
367
367
|
.superRefine((card, ctx) => {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
if (
|
|
371
|
-
ctx.addIssue({ code: z.ZodIssueCode.custom, path: [
|
|
372
|
-
}
|
|
373
|
-
else if (hasClass && card.classes[0] !== card.askClass) {
|
|
374
|
-
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["classes"], message: "classes[0] must equal askClass" });
|
|
368
|
+
if (card.askClass === undefined && card.classes === undefined)
|
|
369
|
+
return;
|
|
370
|
+
if (screenAskClassMembers(card.askClass, card.classes) === undefined) {
|
|
371
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["classes"], message: "askClass / classes must be a pair the engine's own screen (screenAskClassMembers) admits" });
|
|
375
372
|
}
|
|
376
373
|
});
|
|
377
374
|
export const ApprovalCardEnvelopeSchema = z
|
|
@@ -15,6 +15,17 @@
|
|
|
15
15
|
*
|
|
16
16
|
* 🔴 ENV GATING: the live path requires E2B_API_KEY + DEEPSEEK_API_KEY; absent → the driver refuses the live path
|
|
17
17
|
* with a clear message (never a silent fake run). The deterministic --dry-run never needs keys.
|
|
18
|
+
*
|
|
19
|
+
* 🔴 LEDGER I/O (S-977): the WRITE side is the repo's shared append port — core `AppendLog` (lazy open, one `writeSync` loop per
|
|
20
|
+
* row, a failed write rolled back, a torn tail repaired on open AND before every append) — so a crash that tore the last
|
|
21
|
+
* row no longer glues the resume's first row onto the broken suffix (which lost that cell's row ⇒ one cell re-run at real
|
|
22
|
+
* E2B / DeepSeek cost). No fsync (a crash loses at most the tail row ⇒ that cell stays re-runnable — the same posture as before).
|
|
23
|
+
* The READ side ({@link readLedger}) stays PRIVATE — **registered exception, "bench CLI"**: this is a single-process offline
|
|
24
|
+
* experiment driver, not a serving store; its ledger path comes from `--ledger`, its readers are this driver and its tests, and a
|
|
25
|
+
* skipped (unparseable) ledger row costs one re-run of that cell — never a gate, never a tenant fact. The serving-layer replay owner
|
|
26
|
+
* (`replayJsonlSkipping`, `observability/corrupt-read-seat.ts`) is a closed registry of census-registered seats (each one a row in
|
|
27
|
+
* `docs/FAIL-OPEN-CENSUS.md` with an F tag in the serving telemetry); a bench seat there would register a non-serving face in the
|
|
28
|
+
* serving census. Direction note: a parseable row with an unknown arm tag still THROWS ({@link armUpper}) — loud, not skipped.
|
|
18
29
|
*/
|
|
19
30
|
import { Runner } from "@sema-agent/core";
|
|
20
31
|
import { type Arm } from "./arms.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { promises as fs } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { Runner, InMemoryCheckpointStore } from "@sema-agent/core";
|
|
3
|
+
import { AppendLog, Runner, InMemoryCheckpointStore } from "@sema-agent/core";
|
|
4
4
|
import { runArm } from "./arms.js";
|
|
5
5
|
import { FIRM_TRAPS, ALL_TRAPS } from "./tasks.js";
|
|
6
6
|
import { makeBenchClock } from "./runner-ctx.js";
|
|
@@ -71,9 +71,18 @@ function armUpper(tag) {
|
|
|
71
71
|
throw new Error(`s1 run-firm: unknown arm tag ${JSON.stringify(tag)} in ledger row (expected solo|sup|team)`);
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
function createLedgerWriter(ledgerPath) {
|
|
75
|
+
let log;
|
|
76
|
+
return {
|
|
77
|
+
append(rec) {
|
|
78
|
+
log ??= new AppendLog(ledgerPath);
|
|
79
|
+
log.append(rec, false);
|
|
80
|
+
},
|
|
81
|
+
close() {
|
|
82
|
+
log?.close();
|
|
83
|
+
log = undefined;
|
|
84
|
+
},
|
|
85
|
+
};
|
|
77
86
|
}
|
|
78
87
|
function withTimeout(p, ms, label) {
|
|
79
88
|
return new Promise((resolve, reject) => {
|
|
@@ -121,35 +130,41 @@ export async function runFirm(opts) {
|
|
|
121
130
|
const { done } = await readLedger(opts.ledgerPath);
|
|
122
131
|
const ctx = { budget: opts.budget, clock: makeBenchClock(1_000_000), runId: opts.runId, gitSha: opts.gitSha };
|
|
123
132
|
let ranThisPass = 0;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
133
|
+
const ledger = createLedgerWriter(opts.ledgerPath);
|
|
134
|
+
try {
|
|
135
|
+
for (const cell of cells) {
|
|
136
|
+
const key = cellKeyStr(cell);
|
|
137
|
+
if (done.has(key)) {
|
|
138
|
+
log("cell_skip_done", { key });
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
log("cell_start", { key });
|
|
142
|
+
const cellId = `${opts.runId}-${cell.taskId}-${armTag(cell.arm)}-${String(cell.seed)}`;
|
|
143
|
+
let built;
|
|
144
|
+
let row;
|
|
145
|
+
try {
|
|
146
|
+
built = opts.mockDepsFactory
|
|
147
|
+
? opts.mockDepsFactory(cell.trap, cell.seed, cellId)
|
|
148
|
+
: buildLiveCellOrThrow(opts, cell.trap, cell.seed, cellId);
|
|
149
|
+
const supStore = cell.arm === "SUP" ? new InMemoryCheckpointStore() : undefined;
|
|
150
|
+
row = await withTimeout(runArm(cell.arm, cell.trap, cell.seed, ctx, built.deps, supStore), cellTimeoutMs, key);
|
|
151
|
+
}
|
|
152
|
+
catch (e) {
|
|
153
|
+
log("cell_infra_failed", { key, error: e instanceof Error ? e.message : String(e) });
|
|
154
|
+
row = infraFailedRow(cell, ctx);
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
await built?.dispose().catch(() => { });
|
|
158
|
+
}
|
|
159
|
+
ledger.append(row);
|
|
160
|
+
ranThisPass++;
|
|
161
|
+
if (row.runStatus !== "infra-failed")
|
|
162
|
+
done.add(key);
|
|
163
|
+
log("cell_done", { key, runStatus: row.runStatus });
|
|
147
164
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
done.add(key);
|
|
152
|
-
log("cell_done", { key, runStatus: row.runStatus });
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
ledger.close();
|
|
153
168
|
}
|
|
154
169
|
const { records } = await readLedger(opts.ledgerPath);
|
|
155
170
|
const lastByKey = new Map();
|
|
@@ -41,7 +41,7 @@ export function createLiveCoordinators(ctx) {
|
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
43
|
const approvalAskAudit = config.toolApprovalEnabled && backend?.kind === "local" && config.localDataRoot !== undefined
|
|
44
|
-
? new FileApprovalAskAuditStore(config.localDataRoot)
|
|
44
|
+
? new FileApprovalAskAuditStore(config.localDataRoot, { warn: (event, fields) => logger.warn(event, fields) })
|
|
45
45
|
: undefined;
|
|
46
46
|
if (approvalAskAudit) {
|
|
47
47
|
const converged = approvalAskAudit.convergeCrashedAsks();
|
package/dist/boot/reapers.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { Runner, type RunnerDeps, type WorkflowRunStore } from "@sema-agent/core
|
|
|
16
16
|
import type { ServiceConfig } from "../config.js";
|
|
17
17
|
import { CostQuota } from "../observability/cost-quota.js";
|
|
18
18
|
import type { RateLimiter } from "../observability/rate-limit.js";
|
|
19
|
+
import type { UserInputLedger } from "../user-input-ledger.js";
|
|
19
20
|
import type { Logger } from "../observability/logger.js";
|
|
20
21
|
import type { Metrics } from "../observability/metrics.js";
|
|
21
22
|
import type { WorkflowNotifyGate, WorkflowNotifyJournalStore } from "../orchestration/workflow-notify-journal.js";
|
|
@@ -98,6 +99,8 @@ export interface ReapersCtx {
|
|
|
98
99
|
* (`boot/permission-rules-audit.ts` 的 `DormantRuleCounter` 是同一条判据的先例)。
|
|
99
100
|
* 生产传的仍是整只束(结构上满足这个窄口)。 */
|
|
100
101
|
permissionRuleStores: PermissionRuleRetentionSweeper | undefined;
|
|
102
|
+
/** S-928 用户输入受理账本(stage-07 装配;缺席 = 无 backend)。每拍在 reapStale **之后**收敛一次(同一条腿串行,理由见 tick 里那一行)。 */
|
|
103
|
+
userInputLedger: Pick<UserInputLedger, "converge"> | undefined;
|
|
101
104
|
/** S-280 直连门 nonce 的消费记录店 —— 本 tick 只用它的**到期清扫**一手。行至多活 ~150 秒(= 证明还能被
|
|
102
105
|
* 受理的最后一刻),所以存量是「到达率 × TTL」而不是历史总量,清扫无需分批。无 backend 的 env-only
|
|
103
106
|
* 部署 ⇒ undefined ⇒ 本腿零调用(那种部署也没有 durable 审批门)。 */
|
package/dist/boot/reapers.js
CHANGED
|
@@ -56,7 +56,7 @@ export function createPlacementAwareReapSessions(deps) {
|
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
58
|
export function startReapers(ctx) {
|
|
59
|
-
const { config, logger, metrics, localRoot, backend, subRunner, runStore, checkpointStore, rateLimiter, costQuota, toolResultStore, fileHistoryErasure, taskAttachmentStore, imageBakes, worktreeReap, workflowNotifyGate, workflowRecoverOpts, workflowJournalStore, sqlWorkflowRunStore, workflowNotifyJournal, rosterStore, backgroundAgentStore, mailboxStore, toolApproval, permissionRuleStores, approvalNonceStore, getRunDenySweep, } = ctx;
|
|
59
|
+
const { config, logger, metrics, localRoot, backend, subRunner, runStore, checkpointStore, rateLimiter, costQuota, toolResultStore, fileHistoryErasure, taskAttachmentStore, imageBakes, worktreeReap, workflowNotifyGate, workflowRecoverOpts, workflowJournalStore, sqlWorkflowRunStore, workflowNotifyJournal, rosterStore, backgroundAgentStore, mailboxStore, toolApproval, permissionRuleStores, approvalNonceStore, getRunDenySweep, userInputLedger, } = ctx;
|
|
60
60
|
const leg = (p) => {
|
|
61
61
|
if (p !== undefined)
|
|
62
62
|
trackMaintenanceLeg(p);
|
|
@@ -196,7 +196,8 @@ export function startReapers(ctx) {
|
|
|
196
196
|
leg(runStore
|
|
197
197
|
?.reapStale(config.runStaleSec * 1000)
|
|
198
198
|
.then(reapCount("runs_reaped_total", { kind: "stale" }))
|
|
199
|
-
.then(() => runsReapStaleGuard.onSuccess(), runsReapStaleGuard.onError)
|
|
199
|
+
.then(() => runsReapStaleGuard.onSuccess(), runsReapStaleGuard.onError)
|
|
200
|
+
.then(() => userInputLedger?.converge().then(reapCount("user_input_converged_total", {}))));
|
|
200
201
|
if (checkpointStore) {
|
|
201
202
|
leg(checkpointStore
|
|
202
203
|
.reapExpired(Date.now())
|