@sema-agent/server 7.11.0 → 7.13.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/README.md +1 -1
- package/USAGE.md +86 -2
- package/dist/adoption/plan.d.ts +38 -4
- package/dist/adoption/plan.js +72 -0
- package/dist/adoption/quiesce.d.ts +70 -0
- package/dist/adoption/quiesce.js +148 -0
- package/dist/adoption/runner.js +63 -5
- package/dist/adoption/sql.d.ts +15 -0
- package/dist/adoption/sql.js +18 -0
- package/dist/adoption/wire.d.ts +7 -1
- package/dist/adoption/wire.js +6 -0
- package/dist/approval-card.d.ts +5 -0
- package/dist/approval-card.js +22 -0
- package/dist/auth-keys.d.ts +28 -4
- package/dist/auth-keys.js +60 -15
- package/dist/boot/parked-revive-gate.d.ts +18 -2
- package/dist/boot/parked-revive-gate.js +136 -14
- package/dist/boot/permission-rules-audit.d.ts +49 -0
- package/dist/boot/permission-rules-audit.js +85 -0
- package/dist/boot/reapers.d.ts +15 -0
- package/dist/boot/reapers.js +101 -44
- package/dist/boot/resolve-spec.js +43 -12
- package/dist/boot/runner-deps.d.ts +16 -2
- package/dist/boot/runner-deps.js +5 -4
- package/dist/budget.js +22 -0
- package/dist/config-types.d.ts +31 -11
- package/dist/config.d.ts +28 -2
- package/dist/config.js +348 -79
- package/dist/governance-ask-marks.js +8 -2
- package/dist/http/active-run-conflict.d.ts +33 -8
- package/dist/http/active-run-conflict.js +37 -2
- package/dist/http/route-ctx.d.ts +6 -3
- package/dist/http/routes/adoption.js +25 -2
- package/dist/http/routes/approvals-assistant.js +35 -4
- package/dist/http/routes/capabilities.js +69 -10
- package/dist/http/routes/images.js +18 -0
- package/dist/http/routes/rules.d.ts +19 -7
- package/dist/http/routes/rules.js +180 -4
- package/dist/http/routes/runs.js +21 -5
- package/dist/http/routes/tasks.js +18 -6
- package/dist/http/server.d.ts +30 -10
- package/dist/http/server.js +183 -19
- package/dist/http/wire-types.d.ts +48 -0
- package/dist/main.js +65 -7
- package/dist/observability/fail-open.d.ts +8 -0
- package/dist/observability/fail-open.js +8 -0
- package/dist/observability/metrics.js +2 -1
- package/dist/observability/tool-trace.d.ts +5 -1
- package/dist/observability/tool-trace.js +33 -6
- package/dist/parked-decide.d.ts +13 -3
- package/dist/parked-decide.js +10 -1
- package/dist/plugins/adoption-log-sql.d.ts +40 -0
- package/dist/plugins/adoption-log-sql.js +69 -2
- package/dist/plugins/file-run-store.d.ts +85 -1
- package/dist/plugins/file-run-store.js +450 -17
- package/dist/plugins/permission-rule-store-file.d.ts +83 -0
- package/dist/plugins/permission-rule-store-file.js +371 -0
- package/dist/plugins/permission-rule-store-sql.d.ts +52 -0
- package/dist/plugins/permission-rule-store-sql.js +71 -2
- package/dist/plugins/shared-memory-store-sql.d.ts +23 -9
- package/dist/plugins/shared-memory-store-sql.js +55 -18
- package/dist/plugins/sql-driver.d.ts +19 -0
- package/dist/plugins/sql-driver.js +12 -0
- package/dist/plugins/store-backend.d.ts +12 -6
- package/dist/plugins/store-backend.js +82 -10
- package/dist/rules-consent.d.ts +98 -1
- package/dist/rules-consent.js +84 -1
- package/dist/run-local.js +126 -15
- package/dist/runtime-governance.d.ts +33 -0
- package/dist/runtime-governance.js +41 -3
- package/dist/task-settings.d.ts +44 -0
- package/dist/task-settings.js +57 -1
- package/dist/tool-approval.d.ts +38 -1
- package/dist/tool-approval.js +125 -26
- package/dist/trace/core-keyset-guard.d.ts +14 -3
- package/dist/trace/project.d.ts +19 -2
- package/dist/trace/project.js +24 -4
- package/package.json +3 -3
package/dist/run-local.js
CHANGED
|
@@ -26,6 +26,16 @@
|
|
|
26
26
|
* allow-all arm and a gated `ask` is answered by {@link createLocalApprover} — the TTY human, or a
|
|
27
27
|
* fail-closed deny — instead of parking a checkpoint nobody will ever redeem.
|
|
28
28
|
*
|
|
29
|
+
* 🔴 **design/201 的 permissionMode → shellGate 翻译表 SCOPED-OUT 本腿**(设计稿 §5,显式裁定而非欠账)。
|
|
30
|
+
* 这条腿的 operator 就是亲手起这个进程的用户,它**没有 permission mode 这个概念**(一次性 CLI 收不到
|
|
31
|
+
* 客户端表态),今昔都落 core 的缺省 `shellGate: off`。给它硬塞一个 classify 档,在没有 HITL 面的形态下
|
|
32
|
+
* 等于把每一次 shell 调用送进 auto-deny —— 那不是加固,是废掉 run-local。部署治理段(上一段)照旧全接:
|
|
33
|
+
* `MANUAL_MODE_SHELL_GATE` 想要门,operator 在自己的 `.env` 里说一句就有。同族的另一条 scoped-out 是
|
|
34
|
+
* parked-revive 赎回腿(body 全盲,core 的 `InheritedGate` 以 `max(seed, live)` 兜底,自洽)。
|
|
35
|
+
* 「三腿不共享翻译点」是设计,不是漏接:名册对账钉在
|
|
36
|
+
* `test/operator-knob-client-posture-matrix.test.ts`(`shellGateForMode` 已并入构造点名册,第四条腿哪天
|
|
37
|
+
* 接上它必须同时挂矩阵腿)。
|
|
38
|
+
*
|
|
29
39
|
* Usage: run-local "<objective>" [--root <dir>] [--json] [--scenario <name>]
|
|
30
40
|
*
|
|
31
41
|
* `main()` is a thin wrapper over the testable {@link runLocal}, which takes argv + injectable
|
|
@@ -38,7 +48,7 @@ import { realpathSync } from "node:fs";
|
|
|
38
48
|
import { createInterface } from "node:readline/promises";
|
|
39
49
|
import { fileURLToPath } from "node:url";
|
|
40
50
|
import { loadRemoteExec } from "@sema-agent/registry-core/node";
|
|
41
|
-
import { Runner, uuidv7, parseModelMention, FileStorageBackend, NodeExecutionEnv, NodeLspManager, TtlSessionStore, CenterPromptSource, FilePromptArtifactStore, FilePromptSourceStateStore, MemoryPromptArtifactStore, MemoryPromptSourceStateStore } from "@sema-agent/core";
|
|
51
|
+
import { Runner, uuidv7, parseModelMention, AdoptionError, FileStorageBackend, NodeExecutionEnv, NodeLspManager, TtlSessionStore, CenterPromptSource, FilePromptArtifactStore, FilePromptSourceStateStore, MemoryPromptArtifactStore, MemoryPromptSourceStateStore } from "@sema-agent/core";
|
|
42
52
|
import { hasOperatorGateIntent } from "./approval.js";
|
|
43
53
|
import { createBrain } from "./brain.js";
|
|
44
54
|
import { assertGuardPatternsUsable, buildOnlySensitiveBaselineWarning, createApprovalBaselinePolicy, createDeploymentGovernanceInputs, } from "./deployment-governance.js";
|
|
@@ -60,7 +70,10 @@ import { pickHandsRunner, withoutExecutionEnv } from "./capabilities/hands-lane.
|
|
|
60
70
|
import { HttpError } from "./security.js";
|
|
61
71
|
import { memoryEngineBackendFor, memorySpecForRequest } from "./memory-scope.js";
|
|
62
72
|
import { createMemorySyncRunner, createMemorySyncTransport } from "./memory-sync-client.js";
|
|
63
|
-
import { buildPricing, cappedCeiling } from "./budget.js";
|
|
73
|
+
import { buildPricing, cappedCeiling, createTracer } from "./budget.js";
|
|
74
|
+
import { createSharedRunnerDeps } from "./boot/runner-deps.js";
|
|
75
|
+
import { createOrgMemoryAdmissionWiring } from "./boot/org-memory.js";
|
|
76
|
+
import { createPermissionDeniedMeter } from "./observability/tool-trace.js";
|
|
64
77
|
import { createKeyResolver } from "./key-resolver.js";
|
|
65
78
|
import { createLogger } from "./observability/logger.js";
|
|
66
79
|
import { createMetrics } from "./observability/metrics.js";
|
|
@@ -389,11 +402,35 @@ export async function runLocal(argv, deps = {}) {
|
|
|
389
402
|
// concurrent instance throws in the constructor — surface a clear message instead of a stack.
|
|
390
403
|
let fileBackend;
|
|
391
404
|
try {
|
|
392
|
-
fileBackend = new FileStorageBackend({
|
|
405
|
+
fileBackend = new FileStorageBackend({
|
|
406
|
+
// 🔴 腐读披露座(codex 复审 R1-medium,#205 件3 连带):本批把 `sessionPolicyStore` 接进了两只
|
|
407
|
+
// Runner,而 core 对**坏掉的**策略文件是 documented fail-open —— 读不出来按「没有规则」处理。
|
|
408
|
+
// 这条 fail-open 本身是 core 的裁定(抛会让整条任务死、也会自锁修复写),但它**不许无声**
|
|
409
|
+
// (#157 安全轴纪律)。一个座覆盖 core 今天转发的三张脸,后果**各不相同**,所以文案只陈述共同事实
|
|
410
|
+
// (「一次持久读被当成缺席」)并把后果按脸分列,不下统一结论(codex R2-medium:原文案把「少了
|
|
411
|
+
// 会话规则这一层」写成「整个任务无约束」——那会误导事故定级,而且对另外两张脸根本不成立)。
|
|
412
|
+
// ENOENT 不触发(那是真缺席)。
|
|
413
|
+
root,
|
|
414
|
+
onCorruptRead: (info) => logger.warn("file_store_corrupt_read", {
|
|
415
|
+
path: info.path,
|
|
416
|
+
reason: info.reason,
|
|
417
|
+
...(info.sessionId !== undefined ? { sessionId: info.sessionId } : {}),
|
|
418
|
+
...(info.principal !== undefined ? { principal: info.principal } : {}),
|
|
419
|
+
note: "a durable read was treated as ABSENT because the bytes were unreadable (never a plain ENOENT). " +
|
|
420
|
+
"Consequence depends on which store read it: session-policy = this run lost its SUBTRACTIVE session-rule " +
|
|
421
|
+
"layer (deployment policy / hooks / shell gate still applied); session repo = a listing silently omitted " +
|
|
422
|
+
"rows; file-snapshot = a snapshot or scope read as missing. Inspect the named path before deciding.",
|
|
423
|
+
}),
|
|
424
|
+
});
|
|
393
425
|
}
|
|
394
426
|
catch (e) {
|
|
395
427
|
printErr(`cannot open local data dir ${root}: ${e instanceof Error ? e.message : String(e)}`);
|
|
396
|
-
|
|
428
|
+
// 🔴 core 5.23.0([3372] 件④):这个构造器的第二个拒绝面是 design/183 的 I6 adoption boot 门。
|
|
429
|
+
// 那一类失败**不能**再补「换一个 --root」这句 —— 换根 = 丢下一个迁移到一半的数据根(与
|
|
430
|
+
// `plugins/store-backend.ts` 的 local 臂同案同判)。core 的拒绝句已自带出路,上面那行已如实打印。
|
|
431
|
+
if (!(e instanceof AdoptionError)) {
|
|
432
|
+
printErr("(another run-local/engine instance may own it; finish it first, or use a different --root)");
|
|
433
|
+
}
|
|
397
434
|
return 2;
|
|
398
435
|
}
|
|
399
436
|
const sessionStore = fileBackend.sessionStore;
|
|
@@ -496,14 +533,80 @@ export async function runLocal(argv, deps = {}) {
|
|
|
496
533
|
...(ctx.classification !== undefined ? { classification: ctx.classification } : {}),
|
|
497
534
|
err: String(err),
|
|
498
535
|
});
|
|
499
|
-
|
|
536
|
+
// ⚠️ `createOrgMemoryAdmissionWiring` 有一条 **throw** 路径:`MEMORY_ORG_DIRECTORY_JSON` 形坏时
|
|
537
|
+
// `parseOrgDirectoryStatic` 启动期炸(config 层按 A4 分层刻意不校验它)。server 那边炸=进程拒启,
|
|
538
|
+
// 正确;但本腿的既定 UX 是「doctor 文案 + 退 2」(同 assertGuardPatternsUsable / remote-exec-file-invalid
|
|
539
|
+
// 两处),把栈抛给在终端里敲命令的人是回归。另一条 throw(多租户 org 键 + 无目录源)在本腿**结构上
|
|
540
|
+
// 不可达**——上面已把 `REQUIRE_PRINCIPAL` 强制成 false。
|
|
541
|
+
let orgMemoryAdmission;
|
|
542
|
+
try {
|
|
543
|
+
orgMemoryAdmission = createOrgMemoryAdmissionWiring({ config, logger, metrics });
|
|
544
|
+
}
|
|
545
|
+
catch (e) {
|
|
546
|
+
printErr(`MEMORY_ORG_DIRECTORY_JSON is invalid: ${e instanceof Error ? e.message : String(e)}`);
|
|
547
|
+
printErr(`(local config root: ${root} — fix it in ${join(root, ".env")} or your shell env, or unset it)`);
|
|
548
|
+
await fileBackend.dispose();
|
|
549
|
+
return 2;
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* [3397]①②③ / #205 件3 —— run-local 的两份 Runner deps 走 **server 同一只共享基座**
|
|
553
|
+
* (`createSharedRunnerDeps`,main.ts:432 的同款展开)。
|
|
554
|
+
*
|
|
555
|
+
* 为什么必须收编:这两份 deps 此前各自手写同源键,而漏配**没有任何编译期或运行期信号** —— 复扫亲验
|
|
556
|
+
* 到三处真漂移:`toolResultStore` 只在主 runner(委派子代的 `ReadToolResult(ref)` 恒空,正是 core
|
|
557
|
+
* 1.219 修过的那个病在 sub 腿复发)、`config.tiers` **全文件零接线**(而上面的 `applyEffective` 真在
|
|
558
|
+
* 填它 ⇒ tier 词 / CC 别名在这条腿上解不出来)、`hooks` / `tracer` / `sessionPolicyStore` 两处都没有。
|
|
559
|
+
* 基座是一份,新增共享键不可能只挂一半。
|
|
560
|
+
*
|
|
561
|
+
* ── 逐参裁定(local 车道**真差异**保留,不为收编硬造 stub)───────────────────────────────────
|
|
562
|
+
* · `brain` / `pricing` / `promptSource` / `executionEnvFactory` / `lspManager`:本腿真有,直供。
|
|
563
|
+
* · `config`(基座据以取 models / roles / **tiers** / usageWindows):本腿真有 —— tiers 正是本件修的漏。
|
|
564
|
+
* · `tracer`:`createTracer(metrics)` 真形。配额/舰队/prompt-manifest 四个可选跟踪器是 server 面的
|
|
565
|
+
* 设施,本腿没有 ⇒ 不传(不是 stub:那几个参数本就可选,缺席即该轴不记账)。
|
|
566
|
+
* · `deploymentHooks`:`createPermissionDeniedMeter(metrics)` —— main.ts 在**没有** toolTracer 时的
|
|
567
|
+
* 同一只值(tool-trace 落库面是 server 侧设施,一次性 CLI 无处落)。
|
|
568
|
+
* · `sessionPolicyStore`:`fileBackend.sessionPolicyStore`(core 的 File 形,其文档原话就是「本地部署
|
|
569
|
+
* 要让会话规则跨重启存活就接这里」)。E6 规则是 subtract-only ⇒ 方向只会更紧;同一个数据根上的
|
|
570
|
+
* 本地 HTTP 服务写下的规则,这条腿本就该认。
|
|
571
|
+
* · `usageWindowStore`:与 `config.usageWindows` **成对**(main.ts 同规:两键同真同假)—— 配了窗才接
|
|
572
|
+
* File 账本,没配窗时接一个空账本只会让 core 以为这条腿有治理窗。
|
|
573
|
+
* · `toolResultStore`:`fileBackend.toolResultStore`(本件的主修点:现在主/子同源)。
|
|
574
|
+
* · `orgMemoryAdmission`:`createOrgMemoryAdmissionWiring(...)` 真形。本腿 `REQUIRE_PRINCIPAL` 被强制
|
|
575
|
+
* false(见上),所以那条多租户拒启探测结构上不可达;产物在无 center / 无 `MEMORY_ORG_DIRECTORY_JSON`
|
|
576
|
+
* 时是「resolver 缺席 + deployment 自证集」——即 `MEMORY_SCOPE=org:x` 的本地用户从此走得通自证通道。
|
|
577
|
+
* · `backgroundAgentStore` / `mailboxStore` / `rosterStore`:**undefined** —— core 的 `FileStorageBackend`
|
|
578
|
+
* 根本不供这三个店(durable 后台子代 / tier-3 懒复活 / 持久名册都是 server+SQL 面的设施)。造个内存
|
|
579
|
+
* 冒牌货等于让 core 以为「具名子代能跨进程复活」,而一次性 CLI 的进程下一秒就没了。
|
|
580
|
+
* · `sharedMemoryStores`:**undefined** —— 供给面只有 SQL 形(`store-backend.ts` 明写:单机造 file 形
|
|
581
|
+
* 等于给一个人的部署做「团队共享」,能力面会因此说谎)。
|
|
582
|
+
*/
|
|
583
|
+
const sharedRunnerDeps = createSharedRunnerDeps({
|
|
584
|
+
config,
|
|
585
|
+
// 交接件⑤:commit 尾注署名座进**共享基座** ⇒ 主 runner 与 subRunner 同源。
|
|
586
|
+
// [931]① clay 拍:run-local = Sema 品牌本地形态,commit 尾注接 Sema 署名(core 1.300 缺省已翻转不署)。
|
|
587
|
+
// 修前它只写在下面主 runner 的差异键里 ⇒ **委派出去的子代提交不带署名**,而它提交进的是同一个仓、
|
|
588
|
+
// 代表的是同一个部署。那不是一次有理由的分歧(差异键注释块逐条列了理由,唯独没提它),是漏配。
|
|
589
|
+
hands: { commitCoAuthor: "Sema <noreply@vivi-ai.com>" },
|
|
500
590
|
brain,
|
|
501
|
-
models: config.models,
|
|
502
|
-
roles: config.roles,
|
|
503
591
|
pricing,
|
|
592
|
+
tracer: createTracer(metrics),
|
|
504
593
|
promptSource,
|
|
505
|
-
|
|
506
|
-
|
|
594
|
+
executionEnvFactory,
|
|
595
|
+
lspManager,
|
|
596
|
+
backgroundAgentStore: undefined,
|
|
597
|
+
mailboxStore: undefined,
|
|
598
|
+
rosterStore: undefined,
|
|
599
|
+
deploymentHooks: createPermissionDeniedMeter(metrics),
|
|
600
|
+
toolResultStore: fileBackend.toolResultStore,
|
|
601
|
+
sessionPolicyStore: fileBackend.sessionPolicyStore,
|
|
602
|
+
usageWindowStore: config.usageWindows ? fileBackend.usageWindowStore : undefined,
|
|
603
|
+
orgMemoryAdmission,
|
|
604
|
+
sharedMemoryStores: undefined,
|
|
605
|
+
});
|
|
606
|
+
const runnerDeps = {
|
|
607
|
+
...sharedRunnerDeps,
|
|
608
|
+
// ── 以下为主 runner 的差异键(不在共享基座;逐个有因)──────────────────────────────────────
|
|
609
|
+
// (`hands` 已随共享基座展开 —— 交接件⑤;这里手写同名键会 override 展开、把 subRunner 重新甩开。)
|
|
507
610
|
sessionStore,
|
|
508
611
|
...(memoryEngine ? { memoryBackend: memoryEngine.backend, memoryEngineDir: memoryEngine.root } : {}),
|
|
509
612
|
checkpointStore: fileBackend.checkpointStore,
|
|
@@ -512,11 +615,6 @@ export async function runLocal(argv, deps = {}) {
|
|
|
512
615
|
// stderr)后其余告警——compaction / prompt-cache / mcp / memory / hook 的降级——也不再无声。
|
|
513
616
|
onError: engineOnError,
|
|
514
617
|
onAsk,
|
|
515
|
-
// core 1.219 (dogfood: "ReadToolResult(ref) 恒空"): durable tool-result refs on the TOC CLI
|
|
516
|
-
// lane too — offloaded full text survives a process restart (one file per ref under the data root).
|
|
517
|
-
toolResultStore: fileBackend.toolResultStore,
|
|
518
|
-
...(executionEnvFactory ? { executionEnvFactory } : {}),
|
|
519
|
-
...(lspManager ? { lspManager } : {}),
|
|
520
618
|
// design/113 C4: run-local IS the single-user host lane (cwd = --workspace ?? process.cwd()) — the CLI path where
|
|
521
619
|
// CLAUDE.md project-awareness most belongs. Wire it unless explicitly disabled.
|
|
522
620
|
...(config.projectMemoryEnabled
|
|
@@ -530,7 +628,12 @@ export async function runLocal(argv, deps = {}) {
|
|
|
530
628
|
// [1367]① fork server half(main.ts subRunner 同款):ForkRoutingSessionStore——fork/resume 形
|
|
531
629
|
// (requireExisting)→ host 文件店优先(core 1.350 hostSessionFork 在宿主店 fork,拆店=响亮
|
|
532
630
|
// resume.session_not_found),普通子任务 → 私有 TTL 店(throwaway 姿势保留)。
|
|
533
|
-
|
|
631
|
+
// #205 件3:与主 runner **同一只**共享基座展开(上面那份 `sharedRunnerDeps`)——此前这里是一行手写
|
|
632
|
+
// 字面量,`toolResultStore`/`tiers`/`hooks`/`tracer`/`sessionPolicyStore` 全漏,子代 lane 静默降级。
|
|
633
|
+
// 差异键只剩会话店与两个座位;**checkpointStore 有意不给**(main.ts 的 sub 腿给它是为了子代 durable
|
|
634
|
+
// park,而本腿的 park 设施结构上不存在:一次性 CLI 没有 `/decide` 赎回腿,gated ask 由 TTY 真人当场答
|
|
635
|
+
// ——上面 `buildOnlySensitiveBaselineWarning` 传 `durableEnabled: false` 记的就是同一件事实)。
|
|
636
|
+
const subRunnerDeps = { ...sharedRunnerDeps, sessionStore: new ForkRoutingSessionStore(sessionStore, new TtlSessionStore({ defaultTtlDays: 1 / 24 })), onError: engineOnError, onAsk };
|
|
534
637
|
const subRunner = new Runner(subRunnerDeps);
|
|
535
638
|
// #196:无手孪生一对 —— **mirrors main.ts**(那边是 `handslessRunner` / `handslessSubRunner` 两只,同 deps
|
|
536
639
|
// 摘掉 executionEnvFactory)。run-local 是同形装配点,漏这一对 = 本地 lane 上 scan/council/team 的最终
|
|
@@ -619,6 +722,14 @@ export async function runLocal(argv, deps = {}) {
|
|
|
619
722
|
// design/129: run-local IS the pure TOC lane → session-scoped background children
|
|
620
723
|
// (CC Backgrounded semantics; parity with the server's single-user posture).
|
|
621
724
|
backgroundScope: "session",
|
|
725
|
+
// [1909]⑧(core 5.23.0 `TaskSpec.oneShot`,#205 件1 的**本腿**半场,codex R2-high 抓获):这条腿
|
|
726
|
+
// 是**定义上**的一次性提交 —— 本文件头注第一句就是「跑完一条任务、打印 TaskResult、退出」,
|
|
727
|
+
// `runLocal` 返回后 Runner 全被 dispose、进程即散。缺席这个键时,core 会照默认(交互态)给后台
|
|
728
|
+
// 委派 / run_workflow 发「结束回合,你会被通知」的回执,而这里**没有下一个回合**能接住那条通知 ——
|
|
729
|
+
// 那正是本键被造出来要消灭的丢结果病族(BGB drilldown case 2)。
|
|
730
|
+
// 与 HTTP 腿的差别在**来源**而不在语义:那边是调用方表态(缺席不写键),这边是**进程形态的事实**,
|
|
731
|
+
// 所以无条件为真,不看任何 body/表态(本腿根本收不到客户端表态,同 permissionMode 的 scoped-out 理由)。
|
|
732
|
+
oneShot: true,
|
|
622
733
|
principal: scope, // stable local memory/identity scope (single-user TOC; --user overrides)
|
|
623
734
|
// design/138 S1: enable the memory engine for this run (spec.memory is core's per-task activation half of
|
|
624
735
|
// the deps.memoryBackend switch). Scope = the --user identity (default "local", the single TOC user).
|
|
@@ -93,6 +93,39 @@ export declare function compileCommandPolicy(rules: CommandRule[] | undefined):
|
|
|
93
93
|
* - `auto` / `undefined` → `{}` (no extra tightening; still subject to the deployment baseline + commandPolicy).
|
|
94
94
|
*/
|
|
95
95
|
export declare function autonomyOverrides(autonomy: Autonomy | undefined): Partial<TaskSpec>;
|
|
96
|
+
/**
|
|
97
|
+
* #220:**部署级**判据 —— 本部署的治理层自己是否把 shellGate 抬到 `"always"`。
|
|
98
|
+
*
|
|
99
|
+
* 用途 = 持久门(park 行)的出身归因。活卡腿判「这只 ask 是治理层产的」靠 ALS 标记表(见本文件
|
|
100
|
+
* `createGovernanceAskMarkingPolicy` / `createGovernanceShellGateMarkPolicy`),而 park 腿天然跨副本、
|
|
101
|
+
* 跨重启,进程内的表在那里结构上够不着;行上唯一的取证格是 core 写的
|
|
102
|
+
* `gate.riskDescriptor.shellGateDoctrine`。
|
|
103
|
+
*
|
|
104
|
+
* 🔴 **为什么单看行上那一格不够**:有效档 `"always"` **不止治理层一个产地** —— `resolve-spec` 的 SUP 路由
|
|
105
|
+
* 姿态(`supPostureOverrides`)在 governance **之后**叠,同样产 `"always"`(本文件 `manualModeShellGate`
|
|
106
|
+
* 合成段的注释里已如实记着这条时序)。只看行 ⇒ 一条 SUP 路由出来的门会被谎报成「运维治理层强制」,
|
|
107
|
+
* 而那恰是这个信号要回答的那个问题。所以归因取**合取**:行上是 `always` **∧** 本部署的治理层本来就
|
|
108
|
+
* 要求 `always`(后者成立时,治理层就是一个真成因,SUP 是否也抬过不改变这句话的真假)。
|
|
109
|
+
*
|
|
110
|
+
* 判据逐字对着两个产地(与 `applyRuntimeGovernance` 里 `overrides.shellGate` 的取值同源):
|
|
111
|
+
* `autonomyOverrides(autonomy).shellGate`(`AUTONOMY=ask`)与 `MANUAL_MODE_SHELL_GATE` 旋钮,取「有一个
|
|
112
|
+
* 是 always」。`commandPolicy` 不入判据:它产的是 `toolPolicy` 的 ask,不抬 shellGate 档,与本格无关。
|
|
113
|
+
*
|
|
114
|
+
* ⚠️ **已知残留(codex 对抗复审 2026-08-11 逮到,如实登记而不是掩盖)**:本判据读的是**当下**的
|
|
115
|
+
* config,而 `config.autonomy` 是**热改**的(`config-center/apply-effective.ts` 就地写 `config.autonomy`)。
|
|
116
|
+
* 于是失真是**双向**的,不是我原先写的「只往缺席方向」:
|
|
117
|
+
* · 缺席向(常见):mint 之后运维把 `AUTONOMY=ask` 撤了 ⇒ 老 park 行归不出治理出身,少标一个;
|
|
118
|
+
* · **在场向**(窄):一条**纯 SUP 路由**产的门(`ROUTER_ENABLED` 开 ∧ 路由判 supervisor ∧ 当时治理层
|
|
119
|
+
* 并不要求 always),之后运维把 autonomy 热改成 `ask`,那条老行会被标成治理出身 —— 五个前提要**同时**
|
|
120
|
+
* 成立,且该行仍未决。
|
|
121
|
+
* 根治要**在 mint 那一刻把出身写进行里**(core 的 `CheckpointGate` 属主面,不是本仓能单方面做的);
|
|
122
|
+
* 在那之前这一位的定位是**分诊提示**,不是裁决输入 —— 它不参与任何门/CAS/resume 判定,消费端也只拿它
|
|
123
|
+
* 渲染徽标。登记在此,不加特征化测试(把已知残留钉成契约是另一种病)。
|
|
124
|
+
*/
|
|
125
|
+
export declare function governanceMandatesShellGateAlways(governance: {
|
|
126
|
+
autonomy?: Autonomy;
|
|
127
|
+
manualModeShellGate?: "always" | "classify";
|
|
128
|
+
}): boolean;
|
|
96
129
|
/**
|
|
97
130
|
* Apply the operator's runtime governance (autonomy + commandPolicy + manualModeShellGate) onto a base
|
|
98
131
|
* `TaskSpec`, TIGHTEN-ONLY, in a SINGLE {@link tightenTaskSpec} call: commandPolicy compiles to a `toolPolicy`
|
|
@@ -219,6 +219,38 @@ export function autonomyOverrides(autonomy) {
|
|
|
219
219
|
return {};
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* #220:**部署级**判据 —— 本部署的治理层自己是否把 shellGate 抬到 `"always"`。
|
|
224
|
+
*
|
|
225
|
+
* 用途 = 持久门(park 行)的出身归因。活卡腿判「这只 ask 是治理层产的」靠 ALS 标记表(见本文件
|
|
226
|
+
* `createGovernanceAskMarkingPolicy` / `createGovernanceShellGateMarkPolicy`),而 park 腿天然跨副本、
|
|
227
|
+
* 跨重启,进程内的表在那里结构上够不着;行上唯一的取证格是 core 写的
|
|
228
|
+
* `gate.riskDescriptor.shellGateDoctrine`。
|
|
229
|
+
*
|
|
230
|
+
* 🔴 **为什么单看行上那一格不够**:有效档 `"always"` **不止治理层一个产地** —— `resolve-spec` 的 SUP 路由
|
|
231
|
+
* 姿态(`supPostureOverrides`)在 governance **之后**叠,同样产 `"always"`(本文件 `manualModeShellGate`
|
|
232
|
+
* 合成段的注释里已如实记着这条时序)。只看行 ⇒ 一条 SUP 路由出来的门会被谎报成「运维治理层强制」,
|
|
233
|
+
* 而那恰是这个信号要回答的那个问题。所以归因取**合取**:行上是 `always` **∧** 本部署的治理层本来就
|
|
234
|
+
* 要求 `always`(后者成立时,治理层就是一个真成因,SUP 是否也抬过不改变这句话的真假)。
|
|
235
|
+
*
|
|
236
|
+
* 判据逐字对着两个产地(与 `applyRuntimeGovernance` 里 `overrides.shellGate` 的取值同源):
|
|
237
|
+
* `autonomyOverrides(autonomy).shellGate`(`AUTONOMY=ask`)与 `MANUAL_MODE_SHELL_GATE` 旋钮,取「有一个
|
|
238
|
+
* 是 always」。`commandPolicy` 不入判据:它产的是 `toolPolicy` 的 ask,不抬 shellGate 档,与本格无关。
|
|
239
|
+
*
|
|
240
|
+
* ⚠️ **已知残留(codex 对抗复审 2026-08-11 逮到,如实登记而不是掩盖)**:本判据读的是**当下**的
|
|
241
|
+
* config,而 `config.autonomy` 是**热改**的(`config-center/apply-effective.ts` 就地写 `config.autonomy`)。
|
|
242
|
+
* 于是失真是**双向**的,不是我原先写的「只往缺席方向」:
|
|
243
|
+
* · 缺席向(常见):mint 之后运维把 `AUTONOMY=ask` 撤了 ⇒ 老 park 行归不出治理出身,少标一个;
|
|
244
|
+
* · **在场向**(窄):一条**纯 SUP 路由**产的门(`ROUTER_ENABLED` 开 ∧ 路由判 supervisor ∧ 当时治理层
|
|
245
|
+
* 并不要求 always),之后运维把 autonomy 热改成 `ask`,那条老行会被标成治理出身 —— 五个前提要**同时**
|
|
246
|
+
* 成立,且该行仍未决。
|
|
247
|
+
* 根治要**在 mint 那一刻把出身写进行里**(core 的 `CheckpointGate` 属主面,不是本仓能单方面做的);
|
|
248
|
+
* 在那之前这一位的定位是**分诊提示**,不是裁决输入 —— 它不参与任何门/CAS/resume 判定,消费端也只拿它
|
|
249
|
+
* 渲染徽标。登记在此,不加特征化测试(把已知残留钉成契约是另一种病)。
|
|
250
|
+
*/
|
|
251
|
+
export function governanceMandatesShellGateAlways(governance) {
|
|
252
|
+
return autonomyOverrides(governance.autonomy).shellGate === "always" || governance.manualModeShellGate === "always";
|
|
253
|
+
}
|
|
222
254
|
/**
|
|
223
255
|
* [2942]/[2943] `governanceForced` 的**写侧** —— 把内层策略(治理层自己合成的那些)产的 `ask` 记进标记表。
|
|
224
256
|
*
|
|
@@ -319,9 +351,15 @@ export function applyRuntimeGovernance(base, governance) {
|
|
|
319
351
|
const candidate = overrides.shellGate !== undefined && SHELL_GATE_RANK[overrides.shellGate] >= SHELL_GATE_RANK[governance.manualModeShellGate]
|
|
320
352
|
? overrides.shellGate
|
|
321
353
|
: governance.manualModeShellGate;
|
|
322
|
-
// base 已更严 ⇒ 省略,让 base 原样保留(省略=行为等价,不 throw)
|
|
323
|
-
//
|
|
324
|
-
//
|
|
354
|
+
// base 已更严 ⇒ 省略,让 base 原样保留(省略=行为等价,不 throw)。**仍然装配上不可达、纯防御**
|
|
355
|
+
// ——但论证已随 design/201 换了一条:
|
|
356
|
+
// · 前提(改):resolveSpec 的 spec 字面量**现在会写 shellGate** —— 显式 permissionMode 的翻译档
|
|
357
|
+
// (`shellGateForMode`,阶段④,governance 之前),所以 base.shellGate 不再恒缺席。
|
|
358
|
+
// · 结论(不变):这一支照旧不可达。翻译表的**上限是 classify**(rank 1),而能走到这里的 candidate
|
|
359
|
+
// 下限也是 classify(旋钮词表只有 classify/always,且 candidate 已取过与 autonomy 派生值的较大者)
|
|
360
|
+
// ⇒ candidate ≥ base 恒成立。SUP 路由姿态(resolve-spec 的 supPostureOverrides)是 governance 之
|
|
361
|
+
// **后**才叠的,够不到这里的 base。
|
|
362
|
+
// 若将来有人给 base 种上更严的值(例:翻译表新增一个 ⇒ always 的模式词),这里省略而不是让
|
|
325
363
|
// tightenTaskSpec 因「override 更松」throw——candidate 已是 autonomy 派生值与旋钮的较大者,省略它不会
|
|
326
364
|
// 丢掉 autonomy 那一半(autonomy 只产 "always",即最高 rank,永远不会落进这一支)。
|
|
327
365
|
if (SHELL_GATE_RANK[candidate] >= SHELL_GATE_RANK[base.shellGate ?? "off"])
|
package/dist/task-settings.d.ts
CHANGED
|
@@ -70,6 +70,50 @@ export declare function effectiveThinking(reasoningEffort: unknown, ultracode: b
|
|
|
70
70
|
* field is the explicit per-turn intent → it WINS over a bundle defaultMode). Creates a minimal settings object when
|
|
71
71
|
* no `body.settings` bundle was sent. The result flows through {@link applyTaskSettings} (tighten-only). */
|
|
72
72
|
export declare function withPermissionMode(settings: ParsedTaskSettings | undefined, mode: SettingsPermissionMode): ParsedTaskSettings;
|
|
73
|
+
/**
|
|
74
|
+
* design/201 §6-1 —— 本请求的**生效** permission mode,单点。优先序逐字同 {@link withPermissionMode}:
|
|
75
|
+
* 顶层 `body.permissionMode`(本轮显式表态)赢过 bundle 的 `settings.permissions.defaultMode`;两处
|
|
76
|
+
* 都没有 ⇒ `undefined` = **无表态**(调用点据此不写键,而不是替调用方选一个默认值)。
|
|
77
|
+
*
|
|
78
|
+
* 🔴 为什么必须是一只函数:同一个「生效模式」此前在 resolve-spec 里被算了两次(hooks 腿的
|
|
79
|
+
* `permission_mode` 载荷、settings 折叠腿的 `withPermissionMode`),三审同点判定翻译表**不得**成为
|
|
80
|
+
* 第三个算点 —— 三处各算各的,任何一次优先序修订都会让三面分家,而分家在这条轴上的形态是
|
|
81
|
+
* 「壳发的 bundle bypass 在 A 面生效、在 B 面没生效」这种最难被外部发现的静默偏差。
|
|
82
|
+
*/
|
|
83
|
+
export declare function effectivePermissionMode(body: {
|
|
84
|
+
permissionMode?: unknown;
|
|
85
|
+
}, settings: ParsedTaskSettings | undefined): SettingsPermissionMode | undefined;
|
|
86
|
+
/**
|
|
87
|
+
* design/201 §2 —— 显式 permission mode → `TaskSpec.shellGate` 档位的**翻译表(单一真源)**。
|
|
88
|
+
*
|
|
89
|
+
* · `bypassPermissions` ⇒ `"off"` —— CC `--dangerously-skip-permissions` 的对位裁定
|
|
90
|
+
* (与 fs-write 面的 bypass 臂两面归一,见 {@link deriveSettingsPolicy})。
|
|
91
|
+
* · `auto` ⇒ `"classify"` —— **design/201 §8 开口按施工期新披露亲裁改译**(原裁定字面是 off,
|
|
92
|
+
* §8 保留「core auto 分类器覆盖面有新披露时可独立再裁,表驱动一行」——新披露见下段):
|
|
93
|
+
*
|
|
94
|
+
* ⚠️ **`auto` 这一行有一条待裁的开口(design/201 §8 明列「core auto 分类器覆盖面有新披露时可独立再裁,
|
|
95
|
+
* 表驱动一行」)。施工期对抗复审给出了那条新披露,已亲读安装包核实**:
|
|
96
|
+
* · core 只在**已经产生 `ask`** 之后才咨询分类器(`dist/core/hooks.js`:`if (input.autoMode &&
|
|
97
|
+
* decision.action === "ask" …)`);
|
|
98
|
+
* · 而 `shellGate:"off"` 下 core **不铸任何 shell 面的门**(`dist/core/runner/prepare-task.js` 的
|
|
99
|
+
* `shellGate === "off"` 臂反而发一条 `classification:"shell-gate-off"` 的 onError:「真可写 Bash 挂着
|
|
100
|
+
* 却没有 shell 安全轴折叠」)。
|
|
101
|
+
* 两条合起来:`auto` × off ⇒ Bash 既不产 ask、分类器也就永不被咨询 —— off 档下「筛选权交给分类器」
|
|
102
|
+
* 不成立。故 auto 归 classify 组:分类器坐在 classify 产的 ask 之上,语义才真是「交给分类器」
|
|
103
|
+
* (良性只读命令由分类器自动放行,其余 ask;这正是 auto 模式的本义)。发车帖向 [3378] 裁定链披露此
|
|
104
|
+
* 一行偏离;core auto 分类器若来日在 off 档下也有咨询点,可再裁回(表驱动一行)。
|
|
105
|
+
* · `default` / `acceptEdits` / `plan` ⇒ `"classify"` —— 这一档原先由壳无条件注入 `MANUAL_MODE_SHELL_GATE`
|
|
106
|
+
* 供给(车道缺省走了 operator 通道),现在归位到表态轴。`plan` 本身 handsReadOnly(core 明写此档下
|
|
107
|
+
* shellGate 无效),给它 classify 纯为一致性。
|
|
108
|
+
*
|
|
109
|
+
* 🔴 **入参非可选**:缺席(无表态)不是这张表的一行 —— 它的语义是「不写这个键」,只能在调用点判。
|
|
110
|
+
* 把它折进来会逼出一个 `undefined` 返回值,而那正是「写 off」与「不写」被混同的入口(core 的
|
|
111
|
+
* 缺席默认是 off,但**写**一个 off 会成为 governance 的 base,与缺席不是同一件事)。
|
|
112
|
+
*
|
|
113
|
+
* 🔴 **闭集 exhaustive switch,无 default 臂**:五个模式词是封闭词表,新增一个模式而不更新本表
|
|
114
|
+
* 是**编译错误**(#157 的安全轴纪律:词表的未知项不许有静默兜底臂)。
|
|
115
|
+
*/
|
|
116
|
+
export declare function shellGateForMode(effMode: SettingsPermissionMode): "off" | "classify";
|
|
73
117
|
/** The validated, service-trusted subset of `SemaSettings` we project onto the spec. `env` is parsed only to REPORT
|
|
74
118
|
* it as received-but-deferred when off the host lane (never silently dropped); a MALFORMED `hooks` likewise reports
|
|
75
119
|
* deferred (submit 路径另有 400 fail-loud),valid `hooks` 进 applied shape(hook-runner 阶段一)。 */
|
package/dist/task-settings.js
CHANGED
|
@@ -78,6 +78,60 @@ export function withPermissionMode(settings, mode) {
|
|
|
78
78
|
const base = settings ?? {};
|
|
79
79
|
return { ...base, permissions: { ...base.permissions, defaultMode: mode } };
|
|
80
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* design/201 §6-1 —— 本请求的**生效** permission mode,单点。优先序逐字同 {@link withPermissionMode}:
|
|
83
|
+
* 顶层 `body.permissionMode`(本轮显式表态)赢过 bundle 的 `settings.permissions.defaultMode`;两处
|
|
84
|
+
* 都没有 ⇒ `undefined` = **无表态**(调用点据此不写键,而不是替调用方选一个默认值)。
|
|
85
|
+
*
|
|
86
|
+
* 🔴 为什么必须是一只函数:同一个「生效模式」此前在 resolve-spec 里被算了两次(hooks 腿的
|
|
87
|
+
* `permission_mode` 载荷、settings 折叠腿的 `withPermissionMode`),三审同点判定翻译表**不得**成为
|
|
88
|
+
* 第三个算点 —— 三处各算各的,任何一次优先序修订都会让三面分家,而分家在这条轴上的形态是
|
|
89
|
+
* 「壳发的 bundle bypass 在 A 面生效、在 B 面没生效」这种最难被外部发现的静默偏差。
|
|
90
|
+
*/
|
|
91
|
+
export function effectivePermissionMode(body, settings) {
|
|
92
|
+
return coercePermissionMode(body.permissionMode) ?? settings?.permissions?.defaultMode;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* design/201 §2 —— 显式 permission mode → `TaskSpec.shellGate` 档位的**翻译表(单一真源)**。
|
|
96
|
+
*
|
|
97
|
+
* · `bypassPermissions` ⇒ `"off"` —— CC `--dangerously-skip-permissions` 的对位裁定
|
|
98
|
+
* (与 fs-write 面的 bypass 臂两面归一,见 {@link deriveSettingsPolicy})。
|
|
99
|
+
* · `auto` ⇒ `"classify"` —— **design/201 §8 开口按施工期新披露亲裁改译**(原裁定字面是 off,
|
|
100
|
+
* §8 保留「core auto 分类器覆盖面有新披露时可独立再裁,表驱动一行」——新披露见下段):
|
|
101
|
+
*
|
|
102
|
+
* ⚠️ **`auto` 这一行有一条待裁的开口(design/201 §8 明列「core auto 分类器覆盖面有新披露时可独立再裁,
|
|
103
|
+
* 表驱动一行」)。施工期对抗复审给出了那条新披露,已亲读安装包核实**:
|
|
104
|
+
* · core 只在**已经产生 `ask`** 之后才咨询分类器(`dist/core/hooks.js`:`if (input.autoMode &&
|
|
105
|
+
* decision.action === "ask" …)`);
|
|
106
|
+
* · 而 `shellGate:"off"` 下 core **不铸任何 shell 面的门**(`dist/core/runner/prepare-task.js` 的
|
|
107
|
+
* `shellGate === "off"` 臂反而发一条 `classification:"shell-gate-off"` 的 onError:「真可写 Bash 挂着
|
|
108
|
+
* 却没有 shell 安全轴折叠」)。
|
|
109
|
+
* 两条合起来:`auto` × off ⇒ Bash 既不产 ask、分类器也就永不被咨询 —— off 档下「筛选权交给分类器」
|
|
110
|
+
* 不成立。故 auto 归 classify 组:分类器坐在 classify 产的 ask 之上,语义才真是「交给分类器」
|
|
111
|
+
* (良性只读命令由分类器自动放行,其余 ask;这正是 auto 模式的本义)。发车帖向 [3378] 裁定链披露此
|
|
112
|
+
* 一行偏离;core auto 分类器若来日在 off 档下也有咨询点,可再裁回(表驱动一行)。
|
|
113
|
+
* · `default` / `acceptEdits` / `plan` ⇒ `"classify"` —— 这一档原先由壳无条件注入 `MANUAL_MODE_SHELL_GATE`
|
|
114
|
+
* 供给(车道缺省走了 operator 通道),现在归位到表态轴。`plan` 本身 handsReadOnly(core 明写此档下
|
|
115
|
+
* shellGate 无效),给它 classify 纯为一致性。
|
|
116
|
+
*
|
|
117
|
+
* 🔴 **入参非可选**:缺席(无表态)不是这张表的一行 —— 它的语义是「不写这个键」,只能在调用点判。
|
|
118
|
+
* 把它折进来会逼出一个 `undefined` 返回值,而那正是「写 off」与「不写」被混同的入口(core 的
|
|
119
|
+
* 缺席默认是 off,但**写**一个 off 会成为 governance 的 base,与缺席不是同一件事)。
|
|
120
|
+
*
|
|
121
|
+
* 🔴 **闭集 exhaustive switch,无 default 臂**:五个模式词是封闭词表,新增一个模式而不更新本表
|
|
122
|
+
* 是**编译错误**(#157 的安全轴纪律:词表的未知项不许有静默兜底臂)。
|
|
123
|
+
*/
|
|
124
|
+
export function shellGateForMode(effMode) {
|
|
125
|
+
switch (effMode) {
|
|
126
|
+
case "bypassPermissions":
|
|
127
|
+
return "off";
|
|
128
|
+
case "auto":
|
|
129
|
+
case "default":
|
|
130
|
+
case "acceptEdits":
|
|
131
|
+
case "plan":
|
|
132
|
+
return "classify";
|
|
133
|
+
}
|
|
134
|
+
}
|
|
81
135
|
/** Cap on `outputStyle` length — it lands in the system prompt, so it shares the `MAX_SYSTEM_PROMPT_CHARS` (16384)
|
|
82
136
|
* posture (an uncapped prompt body is a per-turn token-cost hole; adversarial review). Exported so the HTTP layer
|
|
83
137
|
* (prepareSpec) can 400 fail-loud on submit with the SAME bound this defensive parse enforces on every path. */
|
|
@@ -470,7 +524,9 @@ export function deriveSettingsPolicy(settings, gate, workflowGate) {
|
|
|
470
524
|
* Returns `base` untouched when the parsed settings project nothing onto the spec.
|
|
471
525
|
*/
|
|
472
526
|
// ([1557]§四 的 SHELL_GATE_RANK 副本与 shellGateSafe 守卫已随 #153 搬 runtime-governance ——
|
|
473
|
-
// settings 折叠不再触碰 TaskSpec.shellGate,base 上 governance 层施加的值原样透传。
|
|
527
|
+
// settings 折叠不再触碰 TaskSpec.shellGate,base 上 governance 层施加的值原样透传。design/201 的
|
|
528
|
+
// {@link shellGateForMode} 也不改这句话:那只纯函数产的是 governance 的 base,由 resolve-spec 阶段④
|
|
529
|
+
// 写进 spec 字面量;本折叠拿到的 `base` 里可能因此已经带着一档 shellGate,而它照旧原样透传。)
|
|
474
530
|
export function applyTaskSettings(base, settings, gate, workflowGate) {
|
|
475
531
|
const { toolPolicy, handsReadOnly, enablePlanMode } = deriveSettingsPolicy(settings, gate, workflowGate);
|
|
476
532
|
let next = base;
|
package/dist/tool-approval.d.ts
CHANGED
|
@@ -61,6 +61,38 @@ export interface ToolApprovalFrame {
|
|
|
61
61
|
* `governance-ask-marks.ts` 顶注。
|
|
62
62
|
*/
|
|
63
63
|
governanceForced?: true;
|
|
64
|
+
/**
|
|
65
|
+
* #144(core 5.25.0,[3438] 接力契约 / [3443] 主件;**ADDITIVE**,`"tool_approval"` only)——
|
|
66
|
+
* 这只 ask **命中了**调用方的一条持久 allow 规则,而那条规则**没能清掉它**。值 = 被越级的那条规则
|
|
67
|
+
* **原文**(core `AskRequest.persistedRuleShadowed`,四个 mint 点全带;core 侧已过 `inlineUntrusted`)。
|
|
68
|
+
*
|
|
69
|
+
* 🔴 **它存在的理由是一次真实的用户面回归**:core 5.25.0 收窄了消音边界(裁定逐字:「Allow rules
|
|
70
|
+
* silence the CLASSIFIER's questions, never a MANDATED one」)—— `shellGate:"always"` 的部署、工具自带
|
|
71
|
+
* egress/irreversible mark、以及既有的 governance 三类门下,用户此前被规则消掉的 ask **重新出现**。
|
|
72
|
+
* 没有这个键,人看到的是「我明明点过『不再询问』,它怎么又问」,唯一合理的结论是「我的规则坏了/没存上」。
|
|
73
|
+
* 有了它,壳能渲「你的规则仍在,只是这次调用被(运维令 / 工具本性)要求逐次确认」。
|
|
74
|
+
*
|
|
75
|
+
* 🔴 **缺席 ≠「你没有规则」**:本键只在「有规则命中 ∧ 规则清不掉这只 ask」时在场。绝大多数 ask
|
|
76
|
+
* 压根没有规则命中(缺席),而**命中且清掉了**的那些根本不会变成 ask(它们被消音了,没有卡)。
|
|
77
|
+
* 消费端禁把缺席读成「你在这条命令上没有规则」。
|
|
78
|
+
*
|
|
79
|
+
* 与 {@link ToolApprovalFrame.governanceForced} **刻意分列、不合并**:那个键回答「门是谁下的」
|
|
80
|
+
* (运维治理层),本键回答「你那条规则怎么了」。governance 只是不可消音的三个来源之一,另两个
|
|
81
|
+
* (doctrine `always` / 工具自带 mark)不打 governance 标 —— 合并会让后两类的 ask 要么谎报治理出身、
|
|
82
|
+
* 要么丢掉规则解释。出身的完整推法见 [3438]:`gate.safetyAxis` + `riskDescriptor.shellGateDoctrine` +
|
|
83
|
+
* `realApproval.origin` 组合读,server 不新铸出身键。
|
|
84
|
+
*
|
|
85
|
+
* 值是**用户内容族**(规则原文是人写的文本)⇒ 与 `message`/`sourceAgentName` 同待遇:`redactSecrets`
|
|
86
|
+
* 后上帧,UNTRUSTED-for-display。空串**不铸键**(core 只在真有命中时带它,空串是坏值不是「空规则」)。
|
|
87
|
+
*
|
|
88
|
+
* 耐久路(park 行)的对偶是 `gate.riskDescriptor.shadowedRule` —— 我方对 `riskDescriptor` 是**整体透传**
|
|
89
|
+
* (SQL 双生落整只 JSON 列、`listPending` 整只回读、两条 durable 读面整行上 wire),所以那一路**键集**零施工;
|
|
90
|
+
* 唯一的施工是**内容**:那两条读面(`GET /v1/approvals` + `/v1/approvals/stream`)在读边界对这一格补
|
|
91
|
+
* `redactSecrets`(core 只中和不脱敏,而运维队列是跨租户可见的那一条 —— 见
|
|
92
|
+
* `http/routes/approvals-assistant.ts` 的 `redactPendingDisclosures` 顶注)。
|
|
93
|
+
* 两路的钉见 `test/approval-shadowed-rule-wire.test.ts` 与 db-integration 的「#209 件4」格。
|
|
94
|
+
*/
|
|
95
|
+
persistedRuleShadowed?: string;
|
|
64
96
|
/**
|
|
65
97
|
* #154 车二(core 5.18.0 design/179):`"tool_approval"` only —— 引擎为这次 ask 铸的**规则候选**
|
|
66
98
|
* (`AskRequest.ruleSuggestions`,逐字透传:闭词表 `match` + 引擎铸的文本,server 不重铸不重排)。
|
|
@@ -146,7 +178,12 @@ export type ToolApprovalDecision = "allow" | "allow_session" | "deny";
|
|
|
146
178
|
*/
|
|
147
179
|
export type RuleRefusalReason = Extract<CardRulePersisted, {
|
|
148
180
|
ok: false;
|
|
149
|
-
}>["reason"] | "rule_lane_unavailable" | "rule_input_edited" | "rule_not_offered" | "rule_store_error"
|
|
181
|
+
}>["reason"] | "rule_lane_unavailable" | "rule_input_edited" | "rule_not_offered" | "rule_store_error"
|
|
182
|
+
/** #204 件7:这只 ask 的门来自运维治理层(`governanceForced`)⇒ 它不进规则车道。**与
|
|
183
|
+
* `rule_lane_unavailable` 刻意分词**:那个说的是「这台部署压根不供规则」(壳可以从此不渲这一格),
|
|
184
|
+
* 这个说的是「规则车道好好的,只是**这一只** ask 归 operator 管」—— 折成同一个词会让壳把一台正常
|
|
185
|
+
* 部署整条车道判死。 */
|
|
186
|
+
| "rule_governance_forced";
|
|
150
187
|
/** Validate the respond body — the closed three-choice enum (rationale in the module header). */
|
|
151
188
|
export declare function parseToolApprovalResponse(body: unknown): {
|
|
152
189
|
ok: true;
|