@sema-agent/server 7.11.0 → 7.12.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 +56 -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 +57 -0
- package/dist/boot/resolve-spec.js +43 -12
- package/dist/budget.js +22 -0
- package/dist/config-types.d.ts +17 -9
- package/dist/config.d.ts +28 -2
- package/dist/config.js +348 -79
- package/dist/governance-ask-marks.js +8 -2
- package/dist/http/route-ctx.d.ts +6 -3
- package/dist/http/routes/approvals-assistant.js +2 -1
- package/dist/http/routes/capabilities.js +44 -9
- package/dist/http/routes/rules.d.ts +19 -7
- package/dist/http/routes/rules.js +180 -4
- package/dist/http/server.d.ts +4 -2
- package/dist/http/server.js +81 -3
- package/dist/http/wire-types.d.ts +48 -0
- package/dist/main.js +19 -1
- package/dist/observability/fail-open.d.ts +4 -0
- package/dist/observability/fail-open.js +4 -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/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 +7 -0
- package/dist/plugins/permission-rule-store-sql.js +11 -0
- package/dist/plugins/store-backend.d.ts +12 -6
- package/dist/plugins/store-backend.js +58 -9
- package/dist/rules-consent.d.ts +69 -1
- package/dist/rules-consent.js +43 -1
- package/dist/run-local.js +120 -13
- package/dist/runtime-governance.js +9 -3
- package/dist/task-settings.d.ts +44 -0
- package/dist/task-settings.js +57 -1
- package/dist/tool-approval.d.ts +6 -1
- package/dist/tool-approval.js +106 -27
- package/dist/trace/core-keyset-guard.d.ts +13 -2
- package/dist/trace/project.d.ts +19 -2
- package/dist/trace/project.js +24 -4
- package/package.json +2 -2
|
@@ -46,7 +46,7 @@ import { mcpForScenario } from "../config-center/facade.js";
|
|
|
46
46
|
import { normalizeAttachments, normalizeResilience, normalizeResumeAtMode, normalizeSuggestNextPrompts, promptProfileFromBody, resolveTaskLimits, retainBackgroundProcessesFromBody, taskAgentsSpecFragment, toolMaterializeStrategyFromBody, toolNameListFromBody } from "../spec-fields.js";
|
|
47
47
|
import { cwdHonored, effectiveHostWorkspace, inProcessSingleUserLane, isValidCwd, parseAdditionalDirectories, satisfiedByProcessCwd, shellEnvMismatchCount } from "../task-cwd.js";
|
|
48
48
|
import { resolveRequestMcp } from "../task-mcp.js";
|
|
49
|
-
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, acceptAppendSystemPrompt, applyTaskSettings,
|
|
49
|
+
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, acceptAppendSystemPrompt, applyTaskSettings, effectivePermissionMode, effectiveThinking, hasConstitutionAnchors, parseTaskSettings, providerDropsAppend, shellGateForMode, withPermissionMode } from "../task-settings.js";
|
|
50
50
|
import { enableForkFromBody, normalizeRetainSubagentSessions, selfOrchestrationFromBody } from "../task-workflow.js";
|
|
51
51
|
import { redactSecrets } from "../trace/redact.js";
|
|
52
52
|
import { DeferredSandboxPathEnv, isSandboxPathAdjudicationLane, sandboxPathEnvSlots } from "./deferred-sandbox-path-env.js";
|
|
@@ -77,10 +77,15 @@ export function createResolveSpec(ctx) {
|
|
|
77
77
|
// A10 留档件#2:六阶段流水线。顺序=原文顺序(每一阶段的体逐字搬运),阶段之间只传显式产出;
|
|
78
78
|
// 六个阶段函数定义在本 `return` 之后(自上而下先读骨架、再读各阶段)。
|
|
79
79
|
const gated = await gateScenarioAndAppend(body, auth, opts);
|
|
80
|
-
|
|
80
|
+
// design/201 §6-1:本请求的**生效** permission mode —— 一次算,三处消费(hooks 载荷的
|
|
81
|
+
// `permission_mode`、阶段④的 shellGate 翻译写点、阶段⑤的 settings 折叠)。此前前两者各算一次,
|
|
82
|
+
// 翻译表要是再开第三个算点,「顶层键 vs bundle defaultMode 谁赢」这条优先序就有三份可以各自漂移的
|
|
83
|
+
// 副本,而漂移在这条轴上的形态是「壳发的 bundle bypass 在这一面生效、在那一面没生效」——三审同点。
|
|
84
|
+
const effMode = effectivePermissionMode(body, gated.parsedSettings.settings);
|
|
85
|
+
const lane = bindSettingsCwdEnvAndModel(body, auth, gated, effMode);
|
|
81
86
|
const anchors = await resolveHistoryAnchors(body, auth);
|
|
82
|
-
const spec = assembleSpecLiteral(body, auth, opts, { ...gated, ...lane, ...anchors });
|
|
83
|
-
const folded = await foldGovernanceAndSettings(body, auth, spec, gated);
|
|
87
|
+
const spec = assembleSpecLiteral(body, auth, opts, { ...gated, ...lane, ...anchors }, effMode);
|
|
88
|
+
const folded = await foldGovernanceAndSettings(body, auth, spec, gated, effMode);
|
|
84
89
|
const final = await applyImageFactsAndRouting(body, auth, folded.governed, {
|
|
85
90
|
scenarioName: gated.scenarioName,
|
|
86
91
|
scratchpadDir: folded.scratchpadDir,
|
|
@@ -204,7 +209,7 @@ export function createResolveSpec(ctx) {
|
|
|
204
209
|
/** 阶段②(settings·cwd·env):吃 请求体 + auth + 阶段①的 objective/parsedSettings,吐 taskHooks、
|
|
205
210
|
* additionalDirectories、档位展开后的 wireCatalog 与选中的 picked;副作用=per-session cwd/shellEnv 注册与
|
|
206
211
|
* honored/ignored 日志、未知模型回落与无视觉模型降级的计数/告警。 */
|
|
207
|
-
const bindSettingsCwdEnvAndModel = (body, auth, gated) => {
|
|
212
|
+
const bindSettingsCwdEnvAndModel = (body, auth, gated, effMode) => {
|
|
208
213
|
const { objective, parsedSettings } = gated;
|
|
209
214
|
// [#40 / TOC cwd seam] register the caller's launch dir so the HOST factory runs the agent there (read by
|
|
210
215
|
// ctx.sessionId). 🔒 GATED: only the single-user host lane (cwdHonored) honors it; on any other lane / multi-tenant
|
|
@@ -259,8 +264,8 @@ export function createResolveSpec(ctx) {
|
|
|
259
264
|
}
|
|
260
265
|
// payload 的 permission_mode 用【生效】模式——body.permissionMode(显式请求)优先于
|
|
261
266
|
// settings.permissions.defaultMode(与 spec 的 withPermissionMode 同一优先序),否则钩子读到的模式与
|
|
262
|
-
// 实际裁决模式不一致(如 body=plan 时钩子仍看到 default)。
|
|
263
|
-
|
|
267
|
+
// 实际裁决模式不一致(如 body=plan 时钩子仍看到 default)。design/201 起该优先序由
|
|
268
|
+
// `effectivePermissionMode` 单点裁定,本处只消费(形参 `effMode`),不再自算一份。
|
|
264
269
|
return createTaskHooks(hc, {
|
|
265
270
|
logger,
|
|
266
271
|
sessionId: auth?.sessionId ?? "",
|
|
@@ -302,6 +307,8 @@ export function createResolveSpec(ctx) {
|
|
|
302
307
|
// containment onto operator/other-tenant host paths). Unlike cwd/shellEnv (registered per-session for the host
|
|
303
308
|
// FACTORY to read at env construction), additionalDirectories is a plain TaskSpec field core reads directly, so
|
|
304
309
|
// it rides onto the spec below. Ignored (logged, never silent) off the host lane. Re-applies on resume (from body).
|
|
310
|
+
// 键在 `TaskRequestBody` 上显式声明(#205 件1)⇒ 直读,不再裸铸([2704] 边界必 schema)。
|
|
311
|
+
// 运行期校验保留:resume 腿重放的是**持久化 body**,不过 HTTP 400 门,类型说得再准也管不住盘上的字节。
|
|
305
312
|
const rawAddDirs = body.additionalDirectories;
|
|
306
313
|
const additionalDirectories = cwdHonored(config) ? parseAdditionalDirectories(rawAddDirs) : undefined;
|
|
307
314
|
if (rawAddDirs !== undefined && !cwdHonored(config)) {
|
|
@@ -311,7 +318,7 @@ export function createResolveSpec(ctx) {
|
|
|
311
318
|
// 与 additionalDirectories 同门(host 单用户车道)、同形校验(parseAdditionalDirectories:绝对路径
|
|
312
319
|
// 数组 fail-loud)、同「忽略必留声」纪律;差异只在语义:只进 READ 面(classify 读边界 auto-allow +
|
|
313
320
|
// read_file/grep 等 containment),写面照旧拒——读授权永不静默变写授权(core 侧保证,我们只透传)。
|
|
314
|
-
const rawAddReadDirs = body.additionalReadDirectories;
|
|
321
|
+
const rawAddReadDirs = body.additionalReadDirectories; // 同上:直读(#205 件1)
|
|
315
322
|
const additionalReadDirectories = cwdHonored(config) ? parseAdditionalDirectories(rawAddReadDirs) : undefined;
|
|
316
323
|
if (rawAddReadDirs !== undefined && !cwdHonored(config)) {
|
|
317
324
|
logger.warn("task_additional_read_directories_ignored", { honored: false, sessionId: auth?.sessionId ?? null });
|
|
@@ -409,7 +416,7 @@ export function createResolveSpec(ctx) {
|
|
|
409
416
|
};
|
|
410
417
|
/** 阶段④(spec 字面量):吃 请求体 + auth + `opts.leg` + 前三阶段的全部产出,吐**尚未过治理层**的 TaskSpec
|
|
411
418
|
* 字面量 —— 部署方拥有的 body→TaskSpec 映射本体(compactionModel 的 fresh 腿 400 也在这一段)。 */
|
|
412
|
-
const assembleSpecLiteral = (body, auth, opts, parts) => {
|
|
419
|
+
const assembleSpecLiteral = (body, auth, opts, parts, effMode) => {
|
|
413
420
|
const { scenarioName, cap, hands, centerDecls, acceptedAppend, parsedSettings, attachmentNotice, taskHooks, additionalDirectories, additionalReadDirectories, wireCatalog, picked, resumeAtEntryId, rewindFilesToEntryId, s4DefaultScopes, } = parts;
|
|
414
421
|
const spec = {
|
|
415
422
|
// D-1:附件告知随 objective 进 durable 流(只有名字/mime/尺寸——**内容永不进流**,这正是
|
|
@@ -451,6 +458,8 @@ export function createResolveSpec(ctx) {
|
|
|
451
458
|
// 历史复审轴B #5(1.254):compactionModel 已接([1479]①)而 design/145 配套旋钮 clampTolerance
|
|
452
459
|
// 够不着=不对称。窄收单键(0..1 数值;其余 compaction 键仍是操作方轴,有意不开)。
|
|
453
460
|
...(() => {
|
|
461
|
+
// 键已在 `TaskRequestBody` 上声明为 `{ clampTolerance?: number }`(#205 件1)⇒ 直读;
|
|
462
|
+
// 下面的 typeof/区间校验照旧(resume 重放持久化 body 不过 HTTP 门)。
|
|
454
463
|
const ct = body.compaction?.clampTolerance;
|
|
455
464
|
return typeof ct === "number" && Number.isFinite(ct) && ct >= 0 && ct <= 1 ? { compaction: { clampTolerance: ct } } : {};
|
|
456
465
|
})(),
|
|
@@ -508,6 +517,18 @@ export function createResolveSpec(ctx) {
|
|
|
508
517
|
// out). USER-driven plan (permissionMode=plan) ADDS handsReadOnly on top via applyTaskSettings (which also sets
|
|
509
518
|
// enablePlanMode, idempotent). Additive — widens nothing; the model self-selects when to use it.
|
|
510
519
|
enablePlanMode: config.planModeEnabled ? true : undefined,
|
|
520
|
+
// design/201 §2:**显式** permission mode → shell 门档位的翻译(bypassPermissions ⇒ "off",
|
|
521
|
+
// auto/default/acceptEdits/plan ⇒ "classify";表逐字在 `shellGateForMode`)。这一档是 governance 的
|
|
522
|
+
// **base** —— 部署旋钮 `MANUAL_MODE_SHELL_GATE` 与 `AUTONOMY=ask` 在其后的 governance 拍只升不降地
|
|
523
|
+
// 覆盖它(#153 那条无条件施加的腿一行未动),所以客户端表态**减不掉**任何 operator 面的收紧。
|
|
524
|
+
//
|
|
525
|
+
// 🔴 **表态缺席 ⇒ 不写这个键**(不是写 "off",也不是写 "classify"):
|
|
526
|
+
// · 写 "classify" 会让今天 env 真缺席的**无壳直连**部署(多租户云 / OA / scan 车道 / SDK 直连)
|
|
527
|
+
// 在一个 minor 版本里静默收紧,而它们多半没有审批面 ⇒ 失败形是 headless auto-deny;
|
|
528
|
+
// · 写 "off" 与不写在 core 的**默认**上等价,但在本进程里不等价 —— 写下去它就成了 governance 的
|
|
529
|
+
// base,把 runtime-governance 那条「base 更严则省略」的防御臂喂上一个人造输入。
|
|
530
|
+
// 缺席不写 = 今日行为逐字节不变,车道缺省由壳恒 stamp mode 保证(设计 §3 前提③)。
|
|
531
|
+
...(effMode ? { shellGate: shellGateForMode(effMode) } : {}),
|
|
511
532
|
// Workflow super-set unlock (same shape as the rank-1 roster gap): per-task activation of the
|
|
512
533
|
// LLM-authored workflow engine (core `run_workflow` + the workflow.ts agent/parallel/pipeline orchestration).
|
|
513
534
|
// The engine is BUILT but the HTTP API never mapped a body field to `spec.selfOrchestration`, so run_workflow
|
|
@@ -572,6 +593,14 @@ export function createResolveSpec(ctx) {
|
|
|
572
593
|
...(typeof body.interactiveTools === "boolean"
|
|
573
594
|
? { interactiveTools: body.interactiveTools }
|
|
574
595
|
: {}),
|
|
596
|
+
// [1909]⑧(core 5.23.0 `TaskSpec.oneShot`):这次**提交**是不是一次性的 —— 没有后续回合能接住异步
|
|
597
|
+
// 后台通知(headless `sema -p` 的进程随本回合退出)。core 只拿它改 run_workflow / 委派回执的**指引
|
|
598
|
+
// 文本**(告诉模型主动 block-wait,而不是「结束回合等通知」——后者在一次性进程里必丢结果),不授予
|
|
599
|
+
// 任何能力 ⇒ 无租户门,与 interactiveTools 同姿势直通。
|
|
600
|
+
// 🔴 **表态在场才写、缺席不写键**(与 shellGate 同形):不写 = core 默认(交互态)逐字节不变;写一个
|
|
601
|
+
// 「默认值」上去等于替没表态的调用方做了表态。非 boolean(resume 重放持久化 body / 提交面已 400)
|
|
602
|
+
// 同样不写键。四条 rebuild 腿读的是同一份持久化 body ⇒ 首跑与续跑天然同值。
|
|
603
|
+
...(typeof body.oneShot === "boolean" ? { oneShot: body.oneShot } : {}),
|
|
575
604
|
// design/131 (core 1.246): per-task resilience INTENT flags. allowDegrade/allowFailover are
|
|
576
605
|
// caller-facing (a bench/eval run wants true failure shapes); bypassBreaker is operator-only (normalizer
|
|
577
606
|
// drops it for non-operators — it punches through a SHARED breaker). All-absent = byte-compat.
|
|
@@ -816,7 +845,7 @@ export function createResolveSpec(ctx) {
|
|
|
816
845
|
/** 阶段⑤(governance 折叠):吃 阶段④的 spec + 请求体 + auth + 阶段①的 append/settings 三量,吐 折完运营方
|
|
817
846
|
* governance 与客户端 settings(tighten-only)后的 governed,外加两个下游还要用的量:本请求的 scratchpadDir
|
|
818
847
|
* 与 hostSemanticsLane 判别。 */
|
|
819
|
-
const foldGovernanceAndSettings = async (body, auth, spec, gated) => {
|
|
848
|
+
const foldGovernanceAndSettings = async (body, auth, spec, gated, effMode) => {
|
|
820
849
|
const { appendLessPack, acceptedAppend, parsedSettings } = gated;
|
|
821
850
|
// Runtime governance "second baton" (center §10): compile the operator's autonomy + commandPolicy onto the
|
|
822
851
|
// spec TIGHTEN-ONLY. commandPolicy layers via combinePolicies onto the approval/durable baseline above (NOT a
|
|
@@ -871,8 +900,10 @@ export function createResolveSpec(ctx) {
|
|
|
871
900
|
// five CC modes are honored as gate-SHAPE choices (see coercePermissionMode/deriveSettingsPolicy): `plan` ⇒
|
|
872
901
|
// read-only hands + present_plan; `default`/`auto` ⇒ the fs-write ask gate; `acceptEdits` ⇒ the cwd-domain
|
|
873
902
|
// variant; `bypassPermissions` ⇒ no mode gate (the deployment baseline is composed above and untouchable).
|
|
874
|
-
|
|
875
|
-
|
|
903
|
+
// design/201 §6-1:折叠也读**同一只** effMode(顶层键赢过 bundle defaultMode 的裁定在
|
|
904
|
+
// `effectivePermissionMode` 里说一次)。effMode 来自 bundle 时这里把同值原样写回,是恒等折叠;
|
|
905
|
+
// 缺席时整条 settings 原样透传(无表态请求的 pre-[816] 行为不变)。
|
|
906
|
+
let effectiveSettings = effMode ? withPermissionMode(parsedSettings.settings, effMode) : parsedSettings.settings;
|
|
876
907
|
// codex R3 second bypass: settings.outputStyle ALSO lands in spec.appendSystemPrompt (applyTaskSettings
|
|
877
908
|
// fold) — on an append-less pack core would discard it silently, exactly like the top-level rider. Same
|
|
878
909
|
// disposition: fresh submits 400 at the HTTP gate; here (the resume mirror + any leg the gate didn't see)
|
package/dist/budget.js
CHANGED
|
@@ -305,6 +305,28 @@ export function createTracer(metrics, costQuota, modelUsage, fleetUsage, fleetLe
|
|
|
305
305
|
// not counted here: `compacted` is a TaskEvent riding the run stream, not a trace frame.
|
|
306
306
|
metrics.inc("compaction_events_total", { outcome: e.kind.slice("compaction.".length), trigger: e.trigger ?? "none" });
|
|
307
307
|
}
|
|
308
|
+
else if (e.kind === "permission.persisted_rule_allowed" || e.kind === "permission.rule_store_unreadable") {
|
|
309
|
+
// 🔴 [3372] core 5.23.0 提货批,codex 复审 round2 [medium](验真后修)。首版把这两帧登记成
|
|
310
|
+
// 「丢弃,已有等价信号」——**那条理由是假的**(亲验:`plugins/permission-rule-store-sql.ts` 里既没有
|
|
311
|
+
// `noteStoreError` 也没有 `onCorruptRead`),而它们又是**真可达**的(#154 车二把规则店接上了)。
|
|
312
|
+
// 一条假理由比没有理由更坏:台账的全部价值就在于它写的话是真的。
|
|
313
|
+
//
|
|
314
|
+
// 两帧各自答一个**别处答不出来**的问题,而且都在安全轴上:
|
|
315
|
+
// · `persisted_rule_allowed` —— design/179 明写它是 allow 侧的**归因通道**:「这次为什么没问我?」
|
|
316
|
+
// `permission_denied_total` 的补集答不了(它连「有没有走规则」都不知道),`tool_trace` 是默认关
|
|
317
|
+
// 的诊断旋钮、也不带规则归因。持久规则店是本装配里**唯一的放宽缝**,它每放行一次都该有账。
|
|
318
|
+
// · `rule_store_unreadable` —— 规则店读不出 ⇒ 该次调用按**零规则**裁决(诚实地问,而不是假装空店)。
|
|
319
|
+
// 后果是一场**审批风暴**:运维看到的是「卡突然变多」,而病因在店上。没有这个计数就只能靠猜。
|
|
320
|
+
// 标签有界:`event` 二取一(闭集,由上面的 `===` 判别式本身封闭)。规则原文/店的错误句**不进标签**
|
|
321
|
+
// (前者是用户数据、后者无界)——它们该去结构化日志,不是 Prometheus 基数。
|
|
322
|
+
metrics.inc("permission_rule_events_total", { event: e.kind.slice("permission.".length) });
|
|
323
|
+
}
|
|
324
|
+
// ── 未命中 = 该帧不进 /metrics ────────────────────────────────────────────────────────────────
|
|
325
|
+
// 这条 if/else 链没有 else:没命中的 `TraceEvent` 就地丢弃。对多数 kind 这是对的(计量面不该给每个
|
|
326
|
+
// 诊断帧开计数器),但「丢」必须是**裁定**而不是漏接。[3372](core 5.23.0 一次给安全轴加了三个
|
|
327
|
+
// `permission.*` 帧)之后,该族的逐条处置记在 `trace/core-keyset-guard.ts` 的 ⑦ 面并由 tsc 守着:
|
|
328
|
+
// core 再加一个 `permission.*` 帧,那里编译期先红,逼人裁「接还是不接」。其余族(compaction/brain/
|
|
329
|
+
// task/…)仍按本链的显式分支处置,无门。
|
|
308
330
|
};
|
|
309
331
|
}
|
|
310
332
|
/**
|
package/dist/config-types.d.ts
CHANGED
|
@@ -869,19 +869,27 @@ export interface ServiceConfigFlat {
|
|
|
869
869
|
toolApprovalEnabled: boolean;
|
|
870
870
|
/**
|
|
871
871
|
* #154 车二(core design/179 持久化权限规则「不再询问」车道)**总开关**。`PERMISSION_RULES_ENABLED`,
|
|
872
|
-
*
|
|
872
|
+
* **#203 起默认 ON**(clay [3396] 裁)。
|
|
873
873
|
*
|
|
874
|
-
* 🔴
|
|
875
|
-
*
|
|
876
|
-
* `removePersistedRule`
|
|
877
|
-
*
|
|
878
|
-
*
|
|
874
|
+
* 🔴 它曾经默认 OFF,而那个默认有**唯一**一条理由(codex 车二 round7 [high] 一):这条车道**只会加
|
|
875
|
+
* 放行、不会减**,而车二**没有撤销面**——列出 / 删除自己已存规则的口子没建,core 的
|
|
876
|
+
* `removePersistedRule` 在包里但本仓零调用点。一次误导入的全局规则因此会一直生效,租户自己拿它没
|
|
877
|
+
* 办法,只能找运维改库。#203 交付的正是那条撤销面本身(`GET /v1/rules` + `DELETE /v1/rules`,后者
|
|
878
|
+
* 恒经 `removePersistedRule` 产墓碑)加上 operator 的越权回收腿 ⇒ **前置条件兑现,默认翻转**。
|
|
879
|
+
* 记这段史不是怀旧:哪天有人想把某条「只加放行」的车道默认打开,判据就是这一条 —— 收得回来才敢开。
|
|
880
|
+
*
|
|
881
|
+
* 显式 `PERMISSION_RULES_ENABLED=false` 仍然关得掉(默认翻转不吃部署侧的表态);升级到本版的既有
|
|
882
|
+
* 部署若库里已有规则桶,boot 期会打一条 `permission_rules_activated_by_default` 诊断行,理由与三条
|
|
883
|
+
* 判据见 `boot/permission-rules-audit.ts`。
|
|
879
884
|
*
|
|
880
885
|
* 关 ⇒ 规则店根本不装配:`RunnerDeps.permissionRuleStore` 缺席(引擎的 `permissionRules.storeWired`
|
|
881
|
-
* 如实报 false)、ask 帧不带 `ruleSuggestions`、回决的 `persistRule`
|
|
882
|
-
*
|
|
886
|
+
* 如实报 false)、ask 帧不带 `ruleSuggestions`、回决的 `persistRule` 拒、`/v1/rules/*` 全族 501。
|
|
887
|
+
* 三张表仍随中央 `ensureSchema` 建(纯 CREATE、零行),这不是行为面。
|
|
883
888
|
*/
|
|
884
889
|
permissionRulesEnabled: boolean;
|
|
890
|
+
/** 运维是否**显式**设过 `PERMISSION_RULES_ENABLED`(取值之外的一位,同 {@link dbBackendExplicit} 的
|
|
891
|
+
* 姿势)。唯一消费者 = boot 期休眠行审计:对显式表过态的部署说「N 只桶随默认 ON 激活」是假话。 */
|
|
892
|
+
permissionRulesEnabledExplicit: boolean;
|
|
885
893
|
/** #151 车2(design/172 §3.3 D3,窗长三元的安全余量):`ToolApprovalCoordinator` 的可选 `windowMarginMs`
|
|
886
894
|
* 构造项——有效窗 = `min(ttlMs, legRemainingMs − 本值)`,余量不足 ⇒ 不开窗直接走窗到期同路(park)。
|
|
887
895
|
* 仅在装配点把 `ToolApprovalRunContext.legDeadlineMonotonic` 传给协调器(车3 的活)时才实际生效——本
|
|
@@ -1024,7 +1032,7 @@ export type ServiceStoreConfig = Pick<ServiceConfigFlat, "sessionBackend" | "ses
|
|
|
1024
1032
|
export type ServiceModelPlaneConfig = Pick<ServiceConfigFlat, "gatewayBaseUrl" | "gatewayApiKey" | "gatewayFallbackUrls" | "gatewayMaxRetries" | "anthropic" | "resilience" | "model" | "models" | "modelApiKeyEnv" | "modelApiKeys" | "modelQuotaWeights" | "tiers" | "projects" | "roles" | "cascadeLadder" | "degrade">;
|
|
1025
1033
|
/** 组:approval(审批 / HITL 门)。`directDoorActive` 无 env 解析腿(装配层三域合取的产物),但语义上
|
|
1026
1034
|
* 就是本组的门状态,故进组;`parseApprovalDomain` 的返回类型相应是 `Omit<…, "directDoorActive">`。 */
|
|
1027
|
-
export type ServiceApprovalConfig = Pick<ServiceConfigFlat, "approvalRequire" | "approvalDeny" | "approvalTimeoutSec" | "approvalAutoBudget" | "approvalNeverAuto" | "approvalHmacKeys" | "durableApproval" | "directApprovalDoor" | "directDoorActive" | "resourceSuspend" | "resourceSuspendTtlSec" | "askQuestionEnabled" | "questionThrottle" | "toolApprovalEnabled" | "permissionRulesEnabled" | "streamAskWindowMarginMs" | "streamApproval" | "mcpElicitation" | "sensitiveWritePatterns" | "manualModeShellGate">;
|
|
1035
|
+
export type ServiceApprovalConfig = Pick<ServiceConfigFlat, "approvalRequire" | "approvalDeny" | "approvalTimeoutSec" | "approvalAutoBudget" | "approvalNeverAuto" | "approvalHmacKeys" | "durableApproval" | "directApprovalDoor" | "directDoorActive" | "resourceSuspend" | "resourceSuspendTtlSec" | "askQuestionEnabled" | "questionThrottle" | "toolApprovalEnabled" | "permissionRulesEnabled" | "permissionRulesEnabledExplicit" | "streamAskWindowMarginMs" | "streamApproval" | "mcpElicitation" | "sensitiveWritePatterns" | "manualModeShellGate">;
|
|
1028
1036
|
/** 组:memory(记忆面 + TOC 同步腿)。 */
|
|
1029
1037
|
export type ServiceMemoryConfig = Pick<ServiceConfigFlat, "memoryEngineEnabled" | "memoryEngineDir" | "memoryEngineRemoteLaneAllowed" | "memoryEngineBackend" | "memoryScope" | "memorySync" | "memoryOrgAdmissionMode" | "memoryOrgDirectoryJson" | "memoryOrgGrantTtlMs" | "memoryOrgUnavailableBackoffMs" | "projectMemoryEnabled" | "syncImportLeaseStaleSec">;
|
|
1030
1038
|
/** 组:auth(鉴权 / 身份 / 治理棒)。`commandPolicy` 只有 sema-registry 腿(无 env 标量形),故 env 解析
|
package/dist/config.d.ts
CHANGED
|
@@ -46,10 +46,36 @@ export declare function parseNumOrFail(name: string, raw: string | undefined): n
|
|
|
46
46
|
* existing `|| fallback` chains built on `parseNumOrFail` are untouched by this stricter sibling.
|
|
47
47
|
*/
|
|
48
48
|
export declare function parseNumOrFailNonNegative(name: string, raw: string | undefined): number;
|
|
49
|
-
|
|
49
|
+
/** Optional FAIL-SAFE numeric env for an OPTIONAL feature knob: a non-numeric / non-positive value yields `undefined`
|
|
50
|
+
* (the caller falls back to its own default) rather than NaN OR a hard startup throw. Used for SESSION_SNAPSHOT_TTL_SEC
|
|
51
|
+
* (finding 20): a typo like "600s" must NOT sign every presigned URL with X-Amz-Expires=NaN (total snapshot outage),
|
|
52
|
+
* but ALSO must not crash boot of an unrelated subsystem — degrade to the MinioBlobBackend default. */
|
|
53
|
+
/** S20 (SILENT-FALLBACK P1): soft-knob parse rejects, collected here because config parsing runs BEFORE the
|
|
54
|
+
* logger exists. main.ts drains this once after createLogger and warns per entry ("configured X=raw is
|
|
55
|
+
* invalid, using the default"). Module-level on purpose; drain clears it (repeat loadConfig in tests is fine). */
|
|
56
|
+
/**
|
|
57
|
+
* #210(黑板 [3425]③):一条配置诊断的**种类**。
|
|
58
|
+
*
|
|
59
|
+
* ⚠️ 为什么是显式 `kind` 而不是「`used` 在不在场」(codex 自审 R2-F1 真修):第一版拿可选字段当判别式,
|
|
60
|
+
* 于是三件语义完全不同的事挤在两个出口上 —— 空串**保留原值**被报成「越界夹取」、坏值**回默认**因为带了
|
|
61
|
+
* 采用值也被报成夹取、而钥集条目**被丢弃**(没有任何采用值、也不该印 raw)却被报成「坏值回默认」。
|
|
62
|
+
* 告警规则是按事件名分流的,报错的种类就是报错的事实本身;可选字段的在场性是实现细节,不该被当契约用。
|
|
63
|
+
*
|
|
64
|
+
* `invalid-default` 是**缺省种类**(不写 `kind` 即是它)—— 历史形 `{env,raw}` 的三个存量生产者
|
|
65
|
+
* (`optFinitePositiveEnv` / `boolEnv` / `boolEnvWithLegacyNegated`)因此逐字不动,它们的断言也不破。
|
|
66
|
+
*/
|
|
67
|
+
type ConfigWarningKind = "invalid-default" | "clamped" | "empty-preserved" | "entry-rejected" | "set-rejected";
|
|
68
|
+
interface ConfigWarning {
|
|
50
69
|
env: string;
|
|
70
|
+
/** 环境原文。`entry-rejected` 上恒为脱敏占位 —— 那两条 env 装的是凭据,原文永不进日志。 */
|
|
51
71
|
raw: string;
|
|
52
|
-
|
|
72
|
+
/** 真正生效的值(`clamped` 必带;`invalid-default` / `empty-preserved` 可带)。 */
|
|
73
|
+
used?: string;
|
|
74
|
+
kind?: ConfigWarningKind;
|
|
75
|
+
/** `entry-rejected` / `set-rejected` 专用:条目序号 + 缺陷字段名,或整份坏的形状原因(都不带值)。 */
|
|
76
|
+
detail?: string;
|
|
77
|
+
}
|
|
78
|
+
export declare function drainConfigWarnings(): ConfigWarning[];
|
|
53
79
|
export declare function drainConfigNotices(): Array<{
|
|
54
80
|
event: string;
|
|
55
81
|
fields: Record<string, string>;
|