@sema-agent/server 7.51.0 → 7.52.1
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/README.zh-CN.md +1 -1
- package/USAGE.md +103 -24
- package/dist/adoption/plan.d.ts +4 -4
- package/dist/adoption/plan.js +11 -0
- package/dist/approval-card.d.ts +26 -2
- package/dist/approval-card.js +9 -0
- package/dist/approval-hmac.d.ts +1 -1
- package/dist/approval-reconciler.d.ts +2 -2
- package/dist/approval-reconciler.js +1 -1
- package/dist/bench/l8/artifact.d.ts +1 -1
- package/dist/bench/s1/arms.d.ts +1 -1
- package/dist/bench/s1/live-deps.d.ts +1 -1
- package/dist/bench/s1/oracle.d.ts +1 -1
- package/dist/bench/s1/row.d.ts +1 -1
- package/dist/boot/budget-tracing.d.ts +1 -1
- package/dist/boot/budget-tracing.js +2 -5
- package/dist/boot/coordinators.js +3 -1
- package/dist/boot/deferred-sandbox-path-env.d.ts +1 -1
- package/dist/boot/device-lane.js +2 -0
- package/dist/boot/engine-lease.d.ts +1 -1
- package/dist/boot/execution-env.d.ts +1 -1
- package/dist/boot/governance-seams.d.ts +2 -2
- package/dist/boot/leader.d.ts +1 -1
- package/dist/boot/memory-boundary.d.ts +2 -2
- package/dist/boot/memory-consolidation.d.ts +6 -6
- package/dist/boot/org-memory.d.ts +1 -1
- package/dist/boot/parked-revive-gate.d.ts +3 -3
- package/dist/boot/reapers.d.ts +2 -2
- package/dist/boot/reapers.js +17 -4
- package/dist/boot/resolve-spec.d.ts +12 -0
- package/dist/boot/resolve-spec.js +29 -7
- package/dist/boot/retention-lane.d.ts +2 -2
- package/dist/boot/runner-deps.d.ts +17 -7
- package/dist/boot/runner-deps.js +4 -2
- package/dist/boot/runtime-caps.d.ts +23 -0
- package/dist/boot/runtime-caps.js +24 -2
- package/dist/boot/session-faces.d.ts +1 -1
- package/dist/boot/session-faces.js +14 -3
- package/dist/boot/stores.js +31 -1
- package/dist/boot/task-list-lane.d.ts +2 -2
- package/dist/brain.js +51 -31
- package/dist/budget.d.ts +2 -2
- package/dist/capabilities/center-plugins.d.ts +1 -1
- package/dist/capabilities/collab-workflows.d.ts +1 -1
- package/dist/capabilities/hands-lane.d.ts +3 -3
- package/dist/capabilities/scenarios.d.ts +3 -3
- package/dist/capabilities/skills.d.ts +1 -1
- package/dist/config-center/hot-keys-registry.js +5 -0
- package/dist/config-center/http-client.d.ts +2 -2
- package/dist/config-center/read-face.d.ts +1 -1
- package/dist/config-center/restart-signal.d.ts +1 -1
- package/dist/config-types.d.ts +100 -25
- package/dist/config.d.ts +11 -3
- package/dist/config.js +91 -15
- package/dist/deployment-governance.d.ts +1 -1
- package/dist/device-enrollment.d.ts +2 -2
- package/dist/device-store.d.ts +3 -3
- package/dist/device-ws-hub.d.ts +4 -2
- package/dist/device-ws-hub.js +9 -3
- package/dist/device-ws-protocol.d.ts +2 -2
- package/dist/env-facts.d.ts +1 -1
- package/dist/execution-lane-caps.d.ts +1 -1
- package/dist/fleet/fleet-bus.d.ts +5 -2
- package/dist/fleet/fleet-reconciler.d.ts +1 -1
- package/dist/fleet/fleet-terminal-window.d.ts +1 -1
- package/dist/hooks/hook-runner.d.ts +8 -1
- package/dist/http/active-run-conflict.d.ts +2 -2
- package/dist/http/principal-gate.d.ts +1 -1
- package/dist/http/route-ctx.d.ts +27 -5
- package/dist/http/routes/admin-memory-optout.d.ts +48 -0
- package/dist/http/routes/admin-memory-optout.js +75 -0
- package/dist/http/routes/agents-roster.d.ts +1 -1
- package/dist/http/routes/approvals-assistant.js +21 -21
- package/dist/http/routes/capabilities.js +20 -9
- package/dist/http/routes/diagnostics.d.ts +1 -1
- package/dist/http/routes/leader.d.ts +1 -1
- package/dist/http/routes/memory-bundle.d.ts +1 -1
- package/dist/http/routes/memory-bundle.js +2 -1
- package/dist/http/routes/memory-compliance.d.ts +4 -4
- package/dist/http/routes/memory-origin.d.ts +4 -4
- package/dist/http/routes/memory-origin.js +3 -1
- package/dist/http/routes/memory-policy.js +6 -2
- package/dist/http/routes/retention-ops.d.ts +1 -1
- package/dist/http/routes/rules.d.ts +5 -2
- package/dist/http/routes/rules.js +84 -5
- package/dist/http/routes/run-memory-capture.d.ts +34 -0
- package/dist/http/routes/run-memory-capture.js +97 -0
- package/dist/http/routes/runs.d.ts +1 -0
- package/dist/http/routes/runs.js +212 -6
- package/dist/http/routes/session-sync.d.ts +4 -6
- package/dist/http/routes/session-sync.js +60 -47
- package/dist/http/routes/sessions.js +61 -188
- package/dist/http/routes/shared-memory.d.ts +1 -1
- package/dist/http/routes/tasks.js +1 -1
- package/dist/http/routes/workflows.js +19 -0
- package/dist/http/server.d.ts +35 -13
- package/dist/http/server.js +110 -14
- package/dist/http/verify-rounds.d.ts +1 -1
- package/dist/http/wire-types.d.ts +41 -8
- package/dist/leader/endpoint.d.ts +1 -1
- package/dist/leader/leader.d.ts +1 -1
- package/dist/leader/repair-oracle.d.ts +2 -2
- package/dist/leader/repair-oracle.js +1 -1
- package/dist/main.js +14 -6
- package/dist/memory-bundle-engine.d.ts +1 -1
- package/dist/memory-operator-faces.d.ts +5 -5
- package/dist/memory-origin-fence.d.ts +35 -0
- package/dist/memory-origin-fence.js +66 -0
- package/dist/memory-posture.d.ts +13 -1
- package/dist/memory-posture.js +1 -0
- package/dist/memory-scope.d.ts +6 -1
- package/dist/memory-scope.js +3 -2
- package/dist/model-select.d.ts +2 -2
- package/dist/observability/fail-open.d.ts +17 -5
- package/dist/observability/fail-open.js +17 -5
- package/dist/orchestration/hardened-vm-runner.d.ts +2 -2
- package/dist/orchestration/workflow-completion-inbox.d.ts +2 -2
- package/dist/plugins/background-shell-support.d.ts +2 -2
- package/dist/plugins/blob-backend.d.ts +7 -2
- package/dist/plugins/blob-backend.js +22 -18
- package/dist/plugins/caching-session-store.d.ts +7 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +2 -2
- package/dist/plugins/file-history-store-sql.d.ts +170 -0
- package/dist/plugins/file-history-store-sql.js +827 -0
- package/dist/plugins/file-run-store.d.ts +44 -5
- package/dist/plugins/file-run-store.js +85 -13
- package/dist/plugins/fork-routing-session-store.d.ts +1 -1
- package/dist/plugins/fork-routing-session-store.js +2 -2
- package/dist/plugins/leader-run-store-sql.d.ts +1 -1
- package/dist/plugins/local-session-store.d.ts +9 -1
- package/dist/plugins/memory-embedder-fingerprint.d.ts +1 -1
- package/dist/plugins/memory-optout-grant-store-sql.d.ts +139 -0
- package/dist/plugins/memory-optout-grant-store-sql.js +157 -0
- package/dist/plugins/memory-origin-law.d.ts +2 -2
- package/dist/plugins/memory-run-store.d.ts +4 -3
- package/dist/plugins/memory-run-store.js +2 -1
- package/dist/plugins/permission-rule-store-file.d.ts +16 -6
- package/dist/plugins/permission-rule-store-file.js +77 -13
- package/dist/plugins/permission-rule-store-sql.d.ts +156 -8
- package/dist/plugins/permission-rule-store-sql.js +123 -21
- package/dist/plugins/pg-pool.js +59 -0
- package/dist/plugins/pg-safe-json.d.ts +1 -1
- package/dist/plugins/remote-env-device.d.ts +1 -1
- package/dist/plugins/remote-env-k8s.d.ts +1 -1
- package/dist/plugins/retention-lane-store-sql.d.ts +3 -3
- package/dist/plugins/retention-store-sql.d.ts +7 -7
- package/dist/plugins/retention-store-sql.js +6 -0
- package/dist/plugins/run-store-sql.d.ts +9 -3
- package/dist/plugins/run-store-sql.js +2 -2
- package/dist/plugins/send-user-file.d.ts +1 -1
- package/dist/plugins/session-placement.d.ts +1 -1
- package/dist/plugins/sql-errors.d.ts +1 -1
- package/dist/plugins/store-backend.d.ts +59 -29
- package/dist/plugins/store-backend.js +75 -15
- package/dist/plugins/store-contracts.d.ts +5 -3
- package/dist/plugins/task-list-store-sql.d.ts +1 -1
- package/dist/plugins/tidb-pool.js +59 -0
- package/dist/plugins/web-search.d.ts +1 -1
- package/dist/prompts-domain-validate.js +1 -1
- package/dist/resource-suspend.d.ts +1 -1
- package/dist/resource-window.d.ts +3 -3
- package/dist/rules-consent.d.ts +231 -9
- package/dist/rules-consent.js +411 -13
- package/dist/run-local.js +29 -4
- package/dist/runs.d.ts +15 -1
- package/dist/runs.js +19 -0
- package/dist/runtime-caps-resolver.d.ts +36 -1
- package/dist/runtime-caps-resolver.js +25 -0
- package/dist/runtime-governance.d.ts +1 -1
- package/dist/security.d.ts +5 -5
- package/dist/security.js +1 -1
- package/dist/session-sync.d.ts +52 -37
- package/dist/session-sync.js +19 -16
- package/dist/shared-memory-scope-authorizer.d.ts +1 -1
- package/dist/sighup-idle.d.ts +1 -1
- package/dist/spec-fields.d.ts +22 -1
- package/dist/spec-fields.js +11 -0
- package/dist/store-live-probe.d.ts +2 -2
- package/dist/task-a2a.d.ts +1 -1
- package/dist/task-cwd.d.ts +1 -1
- package/dist/task-mcp.d.ts +3 -3
- package/dist/task-settings.d.ts +3 -3
- package/dist/task-workflow.d.ts +1 -1
- package/dist/text-bidi.d.ts +2 -2
- package/dist/tool-approval.d.ts +191 -9
- package/dist/tool-approval.js +248 -29
- package/dist/trace/core-keyset-guard.d.ts +3 -3
- package/dist/trace/engine-notice-wire.d.ts +6 -4
- package/dist/trace/engine-notice-wire.js +2 -0
- package/dist/trace/ledger-events.d.ts +1 -1
- package/dist/trace/ledger-sink.d.ts +2 -2
- package/dist/trace/project.d.ts +8 -2
- package/dist/trace/project.js +10 -0
- package/dist/turn-activity.d.ts +1 -1
- package/dist/wall-clock-jump-guard.d.ts +3 -3
- package/package.json +3 -3
- package/dist/http/tar.d.ts +0 -17
- package/dist/http/tar.js +0 -53
- package/dist/http/workspace-content.d.ts +0 -11
- package/dist/http/workspace-content.js +0 -20
- package/dist/plugins/file-snapshot-store-sql.d.ts +0 -170
- package/dist/plugins/file-snapshot-store-sql.js +0 -213
package/dist/device-ws-hub.js
CHANGED
|
@@ -735,9 +735,15 @@ export function createDeviceWsHub(options) {
|
|
|
735
735
|
if (draining || closed)
|
|
736
736
|
return refuseUpgrade(socket, 503);
|
|
737
737
|
const remoteKey = req.socket.remoteAddress ?? "unknown";
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
738
|
+
const perRemoteCount = preAuthByRemote.get(remoteKey) ?? 0;
|
|
739
|
+
const globalHit = preAuthCount >= maxPreAuthConnections;
|
|
740
|
+
const perRemoteHit = maxPreAuthConnectionsPerRemote !== "off" && perRemoteCount >= maxPreAuthConnectionsPerRemote;
|
|
741
|
+
if (globalHit || perRemoteHit) {
|
|
742
|
+
const arm = globalHit ? (perRemoteHit ? "both" : "global") : "per_remote";
|
|
743
|
+
metrics?.inc("device_preauth_refused_total", { arm });
|
|
744
|
+
logger?.warn("device_preauth_cap_hit", {
|
|
745
|
+
remoteKey, arm, preAuthCount, cap: maxPreAuthConnections, perRemoteCount, perRemoteCap: maxPreAuthConnectionsPerRemote,
|
|
746
|
+
});
|
|
741
747
|
return refuseUpgrade(socket, 503);
|
|
742
748
|
}
|
|
743
749
|
preAuthCount += 1;
|
|
@@ -357,11 +357,11 @@ export type DeviceFrameParse = {
|
|
|
357
357
|
detail: string;
|
|
358
358
|
};
|
|
359
359
|
/**
|
|
360
|
-
* **规范** base64(codex
|
|
360
|
+
* **规范** base64(codex 交叉轮 R1-F6,判真)。
|
|
361
361
|
*
|
|
362
362
|
* 🔴 为什么不能只判「是个字符串」再交给 `Buffer.from(v, "base64")`:Node 的解码是**宽松**的 ——
|
|
363
363
|
* `"!!!!"` 解出一个**空** Buffer 而不抛,`"aG@k="` 悄悄丢掉非法字符照样解出两个字节。于是一条被
|
|
364
|
-
* 传输层损坏(
|
|
364
|
+
* 传输层损坏(或被不可信构造)的流片会以「合法的空/短数据」进入交付路径,水位照常推进、chunkAck
|
|
365
365
|
* 照常回给设备 —— 设备释放了它手上唯一的正本,调用方拿到的是**静默损坏**的输出。
|
|
366
366
|
* 在协议边界判规范形 + 回编码自比,这条路就没了。跨仓同判据:cli 侧编码器必须产规范形。
|
|
367
367
|
*/
|
package/dist/env-facts.d.ts
CHANGED
|
@@ -76,7 +76,7 @@ export declare function ensureScratchpadDir(localDataRoot: string, sessionId: st
|
|
|
76
76
|
* fs-write gate exemptDirs + additionalDirectories 根围栏)。「没到货引擎忽略 = 无害 additive」。
|
|
77
77
|
*
|
|
78
78
|
* 信任边界:这是 exemptDirs **放宽**输入,验收 fail-closed(任何拒收形回落自算,不 fault 任务):
|
|
79
|
-
* - 多租户(requirePrincipal)一律拒——共享 worker
|
|
79
|
+
* - 多租户(requirePrincipal)一律拒——共享 worker 盘上租户自选豁免目录=超范围面;
|
|
80
80
|
* - 仅 host-semantics lane——远程沙箱 lane 的 worker 本机路径对 hands 是谎言(honest-facts 同轴);
|
|
81
81
|
* - 绝对路径 + ≤{@link MAX_PATH}(core 渲染上限同源,超限=core 只能给模型假路径)+ canonical
|
|
82
82
|
* 深度 ≥3(裸 /、/tmp、/home 一整块系统目录不能当豁免域;穿越形按 resolve 归一后判);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 执行车道**闭集**与 per-lane 能力表(design device-executor-lane-v2 §4.1 收敛件,车A-1)。
|
|
3
3
|
*
|
|
4
|
-
* ##
|
|
4
|
+
* ## 根本问题:lane 词表是开放字符串上的十几处独立判别式
|
|
5
5
|
*
|
|
6
6
|
* `config.remoteExec?.provider` 的静态型是 `string | undefined` 的判别联合,但消费面从来不是一处
|
|
7
7
|
* switch —— 而是散在十几个文件里的**独立等式/集合/否定**:`cwdHonored` 比 `=== "host"`、
|
|
@@ -102,7 +102,7 @@ export interface FleetTaskRow {
|
|
|
102
102
|
transcriptId?: string;
|
|
103
103
|
/** [1839]①/(a) 案:**终态行帧的残局键**——cli 实测裸 durable-run 完成时终态 task 帧键集只有
|
|
104
104
|
* [id,name,status,tokens,elapsedMs,description],idle 观察者(web 首屏形)从行卡拿不到终局。
|
|
105
|
-
* (b) 案(为顶层 run 造 bg_notification)
|
|
105
|
+
* (b) 案(为顶层 run 造 bg_notification)被否:那族语义=观察者眼里的**子代**,自造事件族比丰富行帧
|
|
106
106
|
* 更伤([1840]§二)。四键全部 ADDITIVE / tolerate-absent、**只在终态帧出现**:
|
|
107
107
|
* - `stoppedBy`:core open enum verbatim(与 BgNotification 同姿势);顶层 run 仅 cancel 路径有源
|
|
108
108
|
* (= "user",与 markStopSource 同源值),其余终态诚实缺席。
|
|
@@ -180,7 +180,10 @@ export interface FleetWorkflowRow {
|
|
|
180
180
|
/** MF-Fleet additive(cli [1726] 二①):**已启动**(非排队中)的 agent 数 —— CC 2.1.220 `⚠ Large workflow`
|
|
181
181
|
* 规模告警的分母。判别用 core 导出的 `deriveAgentDisplayStatus`(`startedAt` 未置 ⇒ 显示态 `queued`),
|
|
182
182
|
* **不自己重写谓词**:两端各写一遍必然漂移(本仓刚在治理面镜像上吃过同款)。
|
|
183
|
-
* ADDITIVE / tolerate-absent:缺席时消费方可退回 `doneCount + failedCount` 这个**诚实下界**(告警偏早)。
|
|
183
|
+
* ADDITIVE / tolerate-absent:缺席时消费方可退回 `doneCount + failedCount` 这个**诚实下界**(告警偏早)。
|
|
184
|
+
* 2.1.250 复核(S-11,语料=cc-decoded/cli250.js):告警函数 220↔250 逐字同形——agents 轴比的是
|
|
185
|
+
* `scheduledAgents`,`started` 是 token/agent **投影的分母**(`started>0 ? totalTokens/started : 70k`);
|
|
186
|
+
* 阈值 25/1.5e6/70k 与 `CLAUDE_CODE_WORKFLOW_SIZE_WARNING_*` 覆盖名两版一致。 */
|
|
184
187
|
startedCount?: number;
|
|
185
188
|
elapsedMs?: number;
|
|
186
189
|
tokens?: number;
|
|
@@ -64,7 +64,7 @@ export declare const FLEET_RECONCILE_INTERVAL_MS = 60000;
|
|
|
64
64
|
/** 每周期的**点读上限**。超出的行留到下一周期(顺序稳定 = bus 的插入序),不把一次扫描变成全表点读风暴。 */
|
|
65
65
|
export declare const FLEET_RECONCILE_MAX_ROWS = 200;
|
|
66
66
|
/** 一个周期的 durable 读**时间预算**。超时 = 放弃这一轮(记 F 类 fail-open),行留在面板上。
|
|
67
|
-
* 与 `FLEET_SNAPSHOT_TERMINAL_READ_BUDGET_MS`
|
|
67
|
+
* 与 `FLEET_SNAPSHOT_TERMINAL_READ_BUDGET_MS` 同值同理由:慢查询不该把整条腿卡住。 */
|
|
68
68
|
export declare const FLEET_RECONCILE_READ_BUDGET_MS = 2000;
|
|
69
69
|
/** 熔断冷却:一次读失败/超预算之后,这段时间内**不再发起任何读**。`RunStore`/`WorkflowRunStore` 契约
|
|
70
70
|
* 都没有取消面(发出去的查询撤不回),挂死的后端不该被每周期继续加压。冷却一过自动重探(自愈)。 */
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* ## 为什么本模块有进程级可变状态(路由域刻意保持零状态,故不放在 `routes/fleet.ts`)
|
|
17
17
|
* 三件都长在同一个事实上——**`WorkflowRunStore` 契约没有取消面**,`Promise.race` 只结束等待、撤不回
|
|
18
18
|
* 已发出的查询:合流(重连风暴不按连接数放大,R1-H2)、合流窗有限(不把陈旧结果发给后来者、也不让
|
|
19
|
-
* 一条永不 settle
|
|
19
|
+
* 一条永不 settle 的读把后续连接全卡住,R2-H1/R3-M2)、熔断冷却(挂死的后端不被每周期继续加压,
|
|
20
20
|
* R3-H1)。真解是店侧查询超时/取消 —— 要动契约与四个后端,已作移交项。
|
|
21
21
|
*
|
|
22
22
|
* ## scope
|
|
@@ -48,7 +48,14 @@ export declare function hookMatcherMatches(matcher: string | undefined, value: s
|
|
|
48
48
|
* 求值:名部走 permission matcher(交替/正则同 matcher 字段),括号内 arg-模式(`*` 通配)匹配工具输入的
|
|
49
49
|
* 主字符串(Bash=command,文件工具=file_path/path)。CC 行为逐条目:不匹配 → 跳过该条目(记日志);
|
|
50
50
|
* 非工具事件(payload 无 tool_name)→ "cannot be evaluated" 跳过;解析失败 → 跳过。返回:
|
|
51
|
-
* true=跑 / "not_matched" / "unevaluable"(调用方分别记账;跳过永不静默)。
|
|
51
|
+
* true=跑 / "not_matched" / "unevaluable"(调用方分别记账;跳过永不静默)。
|
|
52
|
+
*
|
|
53
|
+
* 2.1.250 复核(S-11,2026-08-30;语料=sema-internal `blueprints/cc-decoded/cli250.js`):求值器与 220
|
|
54
|
+
* 逐字同构——五种工具事件之外「cannot be evaluated for non-tool event」跳过、「Skipping hook due to if
|
|
55
|
+
* condition … not matching」跳过,两串日志逐字在场;括号内容经工具 `preparePermissionMatcher` 求值,与
|
|
56
|
+
* 本实现同向。差异登记(不改行为):①250 证据里名部=**规范化后等值比对**(`vd(toolName)!==o`),
|
|
57
|
+
* 「交替/正则」是 matcher 字段的文法——本实现名部走 hookMatcherMatches 属**超集**(alternation 也认),
|
|
58
|
+
* 如实登记不收窄;②250 的 `if` 已扩到 prompt/agent/http/mcp_tool 条目(本 runner 只翻 command,不涉)。 */
|
|
52
59
|
export declare function hookIfMatches(cond: string, payload: unknown): true | "not_matched" | "unevaluable";
|
|
53
60
|
/** createTaskHooks 的装配上下文(payload 的 CC-verbatim 字段来源)。 */
|
|
54
61
|
export interface HookRunnerContext {
|
|
@@ -73,7 +73,7 @@ export interface PendingGateMaterial {
|
|
|
73
73
|
* ③ **材料读取失败**:整只 `pendingGate` 都不在(那时本键连位置都没有)。
|
|
74
74
|
* ⇒ 读作「不知道这道门叫什么」,**不是**「这道门没有身份」,更**不是**「这一定是条老行」。
|
|
75
75
|
*
|
|
76
|
-
* 🔴 **它命名的是「本次 409 实际读到的那一行」,不是「activeTaskId 这条 run 的门」**(codex
|
|
76
|
+
* 🔴 **它命名的是「本次 409 实际读到的那一行」,不是「activeTaskId 这条 run 的门」**(codex 交叉复审
|
|
77
77
|
* R1-[high] 验真后如实登记):同 session 可以并存两条 pending checkpoint(表上**没有** per-session
|
|
78
78
|
* pending 唯一约束),而 `findPendingTokenBySession` 是**无序 `LIMIT 1`** —— 这个条件是本仓自己成文的
|
|
79
79
|
* 已知事实(见 `src/http/server.ts` mintFreshRun 腿 409 那段的逐字论证:「两条 pending 并存时旧
|
|
@@ -147,7 +147,7 @@ export interface ActiveRunConflictDoneResult {
|
|
|
147
147
|
/**
|
|
148
148
|
* #316(c):与 409 body 同键同语义(见 {@link ActiveRunConflictBody.stale})。
|
|
149
149
|
*
|
|
150
|
-
* 🔴 **跨仓欠账,如实登记**(codex
|
|
150
|
+
* 🔴 **跨仓欠账,如实登记**(codex 交叉复审 R1-[medium] 验真后的真形):sema-sdk 的
|
|
151
151
|
* `spec/openapi.yaml` 里 `ActiveRunConflictDoneResult` 是 `additionalProperties: false` 的**封闭**
|
|
152
152
|
* schema,今天只声明 `status/errorCode/errorMessage/activeTaskId/activeTaskStatus/pendingGate` 六键。
|
|
153
153
|
* ⚠️ 亲验后的关键修正:这**不是本键引入的新问题** —— `msSinceLastActivity`(#245 S1)早就在这条
|
|
@@ -13,7 +13,7 @@ export declare function systemFor(req: IncomingMessage, config: ServiceConfig):
|
|
|
13
13
|
export declare function bearerPresentedButUnverified(req: IncomingMessage, config: ServiceConfig): boolean;
|
|
14
14
|
export declare function gatedPrincipal(req: IncomingMessage, config: ServiceConfig): string | undefined;
|
|
15
15
|
/** The EXPLICIT operator gate (IMAGE-API-DESIGN.md §P2.4a MUST-FIX) — NOT the bare `isOperator` (whose empty-list
|
|
16
|
-
* "true-for-all" is
|
|
16
|
+
* "true-for-all" is an unrestricted-write exec door the instant OPERATOR_PRINCIPALS is empty). Requires a non-empty
|
|
17
17
|
* operator set AND a present principal IN it. The same form the preempt/resume endpoints already use.
|
|
18
18
|
* 🔴 SINGLE mint point (E1): a byte-identical twin `explicitOperator` used to live below (image-visibility lane)
|
|
19
19
|
* and two more hand-inlined copies sat in approvals-assistant — a future hardening of this judgment would have
|
package/dist/http/route-ctx.d.ts
CHANGED
|
@@ -51,7 +51,9 @@ export interface RouteHelpers {
|
|
|
51
51
|
usageWindowDenied(req: IncomingMessage, res: ServerResponse): Promise<boolean>;
|
|
52
52
|
safeDecode(seg: string): string | null;
|
|
53
53
|
isFleetWide(req: IncomingMessage): boolean;
|
|
54
|
-
|
|
54
|
+
/** 属主门。`notFoundForm` 选 not-yours 臂的 404 拼法:缺省 `"run"`(not_found.run 长文,#262);session
|
|
55
|
+
* 资源面传 `"session"`(not_found.session + "session not found")与其 unknown 臂同码同串(G52 [5778])。 */
|
|
56
|
+
runOwnerOk(req: IncomingMessage, res: ServerResponse, owner: string | null, notFoundForm?: "run" | "session"): boolean;
|
|
55
57
|
sessionOwnerScope(req: IncomingMessage): {
|
|
56
58
|
fleetWide: boolean;
|
|
57
59
|
gateOwner: string | null;
|
|
@@ -156,18 +158,38 @@ export interface RouteLegs {
|
|
|
156
158
|
*
|
|
157
159
|
* `onTypedFailure` = 「typed 拒绝**别写响应**,交回给我」。默认(缺席)行为不变:`HttpError` 走
|
|
158
160
|
* `sendError` 连 `extra` 一起回显。DESIGN-269 车2 的 A2A 腿必须传它 —— 那些 `extra`(尤其
|
|
159
|
-
* `scenario_unknown` 的**全部场景名**)对壳是指路材料,对一个外部 peer
|
|
160
|
-
prepareSpec(req: IncomingMessage, res: ServerResponse, body?: TaskRequestBody, onTypedFailure?: (err: import("../security.js").HttpError) => void
|
|
161
|
+
* `scenario_unknown` 的**全部场景名**)对壳是指路材料,对一个外部 peer 是内部词表外泄。 */
|
|
162
|
+
prepareSpec(req: IncomingMessage, res: ServerResponse, body?: TaskRequestBody, onTypedFailure?: (err: import("../security.js").HttpError) => void,
|
|
163
|
+
/** S-46 件C:**腿身份**(不是能力表态)。`liveStream: true` 只由 `POST /v1/tasks/stream` 的装配点按
|
|
164
|
+
* URL 传 —— 那是全树唯一挂 HITL 活流投递面的腿,逐腿 `interactionPosture` 判据的第一条合取项。
|
|
165
|
+
* 缺席 ⇒ 无人腿(bg / A2A / resume),行为字节不变。 */
|
|
166
|
+
legFacts?: {
|
|
167
|
+
liveStream?: boolean;
|
|
168
|
+
}): Promise<PreparedTaskSubmission | null>;
|
|
161
169
|
/** 同步腿的终局记账(计费/配额/指标),tasks 域用。 */
|
|
162
170
|
finalizeTaskResult(result: TaskResult, principal: string | undefined, objective: string, sessionId: string | undefined): void;
|
|
163
171
|
/** approvals 决策腿:session → pending checkpoint → markResuming CAS → 驱动续跑。
|
|
164
172
|
* `settledBy` = **这次结算的出处**(core `ApprovalSettledBy`),必填:本腿有两个调用方(HTTP
|
|
165
173
|
* `/decide` = 人;内部 D-D SLA deny-sweep = 窗到期),而函数内部分辨不出谁在叫它 —— 必填参数把
|
|
166
174
|
* 「漏报出处」变成编译错,不是一条下游把「没人答」渲染成「有人拒」的静默假出处。 */
|
|
167
|
-
resumeCheckpoint(sessionId: string, decision: "approve" | "deny", reason: string | undefined, settledBy: ApprovalSettledBy, req?: IncomingMessage, answer?: QuestionAnswer, binding?: DecideBinding,
|
|
175
|
+
resumeCheckpoint(sessionId: string, decision: "approve" | "deny", reason: string | undefined, settledBy: ApprovalSettledBy, req?: IncomingMessage, answer?: QuestionAnswer, binding?: DecideBinding,
|
|
176
|
+
/** S-02 codex R4(验真后修):remember 豁免的 grant 挂点。**工具名不再由路由自己读**(那是与本函数
|
|
177
|
+
* 载入行分歧的第三次 scope-less 读,三读分歧下会给他人工具落豁免=提权)——由本函数把**已属主校验的
|
|
178
|
+
* 载入 cp** 的工具名(`validatedToolName`,非 tool_approval / AskUserQuestion / legacy null ⇒ `null`
|
|
179
|
+
* = 无可记)一并回传,路由只对这个逐字落店。`overrideSessionId` = parked 腿的 root/host 会话 grant 锚。 */
|
|
180
|
+
onResumeCommitted?: (grant: {
|
|
181
|
+
overrideSessionId?: string;
|
|
182
|
+
validatedToolName: string | null;
|
|
183
|
+
}) => Promise<void>,
|
|
168
184
|
/** #316:透传给 {@link DriveResumeArgs.acceptEarly}(200 受理语义)。HTTP `/decide` 腿传 `true`;
|
|
169
185
|
* 内部 D-D SLA deny-sweep 不传 —— 它没有在等响应的人,提前受理对它零收益而少一条同步失败面。 */
|
|
170
|
-
acceptEarly?: boolean
|
|
186
|
+
acceptEarly?: boolean,
|
|
187
|
+
/** S-02:调用方已验身份的裁定 —— stale 臂铸 `currentPending` 指路键前在被读回的行上重跑 decide 门
|
|
188
|
+
* 同一属主判(缺席 ⇒ 恒不造键;判据属主=server.ts 铸键点注)。 */
|
|
189
|
+
decider?: {
|
|
190
|
+
principal?: string;
|
|
191
|
+
explicitOperator: boolean;
|
|
192
|
+
}): Promise<{
|
|
171
193
|
status: number;
|
|
172
194
|
body: object;
|
|
173
195
|
}>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* design/383 §3.1 / S-50 §1.3(S-49 S-2)—— per-principal **memory-capture opt-out 授权表**的 operator 四口:
|
|
3
|
+
*
|
|
4
|
+
* · `GET /v1/admin/memory-optout` → `{ deploymentDefault: Row|null, overrides: Row[] }`
|
|
5
|
+
* · `PUT /v1/admin/memory-optout` body `{allow, note?}` → 部署缺省行 upsert(哨兵 `principal=''`)
|
|
6
|
+
* · `PUT /v1/admin/memory-optout/:principal` body `{allow, note?}` → per-principal 覆盖行 upsert
|
|
7
|
+
* · `DELETE /v1/admin/memory-optout/:principal` → 撤覆盖(回落部署缺省);`{ deleted: boolean }` 诚实答
|
|
8
|
+
*
|
|
9
|
+
* Row = `{ principal, allow, updatedAtMs, updatedBy, note? }`(审计行内,S-50 §1.3;`updatedBy` = 调用方 operator
|
|
10
|
+
* principal,server 铸,body 不收)。
|
|
11
|
+
*
|
|
12
|
+
* ── 稿偏离两条(如实记)────────────────────────────────────────────────────────────────────────────
|
|
13
|
+
* ① S-50 §1.1/§1.3 写的是 `orgDefault` + `(org_id, principal)`。亲读本仓(2026-08-30 侦察):server **没有 org 身份维**
|
|
14
|
+
* (租户维 = 单 principal;`org` 只是记忆 scope 目录里的词),给表/wire 铸一根没人能填的 `org_id` 是编数据。一台
|
|
15
|
+
* worker = 一个租户面 ⇒ 「org 级缺省」落成**部署级缺省**,wire 键名随实体改叫 `deploymentDefault`。折叠律不变。
|
|
16
|
+
* ② S-50 §1.3 把部署缺省写成 `PUT …/memory-optout/default`。codex R1-[medium] 验真:`default` 是一个**合法 principal
|
|
17
|
+
* 拼写**,字面路径 `/default` 与 `%64efault`(解码后同为 `default`)在第一版里分派到**两个不同的行**(哨兵行 vs
|
|
18
|
+
* 一条名叫 default 的覆盖行)—— 命名空间碰撞,而且 DELETE 侧还不对称。修法不是「解码后再保留一个词」(那只是把
|
|
19
|
+
* 碰撞改成一条不可寻址的 principal),而是**让缺省行住在集合根**:`PUT /v1/admin/memory-optout`(集合本身的
|
|
20
|
+
* 「缺省」属性),`:principal` 面从此没有任何保留词,任何合法 principal(包括字面 `default`)都可寻址。
|
|
21
|
+
*
|
|
22
|
+
* ── 族属与门序 ────────────────────────────────────────────────────────────────────────────────────
|
|
23
|
+
* 与 `/v1/admin/drain` · `/v1/admin/config/refresh` · `/v1/admin/memory/consolidation` 逐字同族(operator lane:
|
|
24
|
+
* 同一条 `explicitOperatorOk` 门;billable=false;刻意不吃 drain / roster-pending 两道 503)。门序照 operator
|
|
25
|
+
* 面既有口径:身份(401)→ 授权(403)→ **能力(501)** → 验型(400)。授权在能力之前:一个够不着任何东西
|
|
26
|
+
* 的调用方不该从「这台有没有授权表」上读出部署形态。
|
|
27
|
+
* 🔴 四口都在 `isCredentialGatedRewrite`(server.ts)门内(codex R1-[high] 验真后补):授权的**唯一**输入是
|
|
28
|
+
* principal 头 ∧ 一次调用的爆炸半径跨租户(改缺省行 = 改整台部署每个 principal 的 verdict;读面 = 全部授权行 +
|
|
29
|
+
* operator 手写的 note)—— 无 service 凭证且未开 `ALLOW_UNAUTHED_WRITES` 的 worker 上那个头**可自造**,与
|
|
30
|
+
* `/v1/memory/erase` / `POST /v1/memory/export` 同一条论证。
|
|
31
|
+
*
|
|
32
|
+
* ── 为什么缺席形是 **501 `capability.*`** 而不是 404(与 consolidation 阀门刻意分家)──────────────────
|
|
33
|
+
* consolidation 的 404 说的是「旋钮没开」(运维表态,动作=开旋钮)。本面的缺席是「**这家店在本部署上不存在**」
|
|
34
|
+
* —— local 车道刻意不给授权表一个会遗忘的 in-memory twin(接口注),动作=换部署形态(接 SQL 后端)。那正是
|
|
35
|
+
* 本仓 501 `capability.*` 族的定义(`capability.memory_engine_required` / `capability.outcome_ledger_required`
|
|
36
|
+
* 同族)。能力位 `memoryOptOutGrant`(`GET /v1/capabilities`)读同一个事实 + operator 名单非空。
|
|
37
|
+
*
|
|
38
|
+
* ── 撤销窗(S-50 §1.4)──────────────────────────────────────────────────────────────────────────
|
|
39
|
+
* 写后**零缓存**:resolver 腿(`RuntimeCaps.allowMemoryOptOut`)每次 prepare 直读同一张表 ⇒ 下一次带声明的
|
|
40
|
+
* prepare 即读到新 verdict(撤权后新声明具名拒;已声明会话 record wins,core 判,server 零码)。
|
|
41
|
+
*/
|
|
42
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
43
|
+
import type { RouteCtx } from "../route-ctx.js";
|
|
44
|
+
export declare const ADMIN_MEMORY_OPTOUT_PATH = "/v1/admin/memory-optout";
|
|
45
|
+
/** 带参口(`:principal`)⇒ 名册门走 `ROUTE_LABEL_PATTERNS`(server.ts),不是 LITERALS。 */
|
|
46
|
+
export declare const ADMIN_MEMORY_OPTOUT_OVERRIDE_RE: RegExp;
|
|
47
|
+
export declare function handleMemoryOptOut(req: IncomingMessage, res: ServerResponse, url: string, ctx: RouteCtx): Promise<boolean>;
|
|
48
|
+
//# sourceMappingURL=admin-memory-optout.d.ts.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { sendJson, sendError } from "../send.js";
|
|
3
|
+
import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
|
|
4
|
+
import { memoryOptOutPrincipalShapeProblem } from "../../plugins/memory-optout-grant-store-sql.js";
|
|
5
|
+
export const ADMIN_MEMORY_OPTOUT_PATH = "/v1/admin/memory-optout";
|
|
6
|
+
export const ADMIN_MEMORY_OPTOUT_OVERRIDE_RE = /^\/v1\/admin\/memory-optout\/([^/]+)$/;
|
|
7
|
+
const PutBodySchema = z.object({ allow: z.boolean(), note: z.string().min(1).max(500).optional() }).strict();
|
|
8
|
+
export async function handleMemoryOptOut(req, res, url, ctx) {
|
|
9
|
+
const miss = { fell: false };
|
|
10
|
+
await handleBody(req, res, url, ctx, miss);
|
|
11
|
+
return !miss.fell;
|
|
12
|
+
}
|
|
13
|
+
function decideVerb(method, path, safeDecode) {
|
|
14
|
+
if (path === ADMIN_MEMORY_OPTOUT_PATH) {
|
|
15
|
+
if (method === "GET")
|
|
16
|
+
return { kind: "list" };
|
|
17
|
+
if (method === "PUT")
|
|
18
|
+
return { kind: "putDefault" };
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
const m = ADMIN_MEMORY_OPTOUT_OVERRIDE_RE.exec(path);
|
|
22
|
+
if (m === null)
|
|
23
|
+
return undefined;
|
|
24
|
+
if (method !== "PUT" && method !== "DELETE")
|
|
25
|
+
return undefined;
|
|
26
|
+
const principal = safeDecode(m[1]);
|
|
27
|
+
if (principal === null)
|
|
28
|
+
return "malformed";
|
|
29
|
+
return method === "PUT" ? { kind: "putOverride", principal } : { kind: "deleteOverride", principal };
|
|
30
|
+
}
|
|
31
|
+
async function handleBody(req, res, url, ctx, miss) {
|
|
32
|
+
const { deps } = ctx;
|
|
33
|
+
const path = url.split("?")[0] ?? url;
|
|
34
|
+
const verb = decideVerb(req.method, path, ctx.helpers.safeDecode);
|
|
35
|
+
if (verb === undefined) {
|
|
36
|
+
miss.fell = true;
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const principal = gatedPrincipal(req, deps.config);
|
|
40
|
+
if (deps.config.requirePrincipal && !principal) {
|
|
41
|
+
sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (!explicitOperatorOk(principal, deps.config.operatorPrincipals)) {
|
|
45
|
+
sendError(res, 403, "auth.operator_only", "memory opt-out grant administration is operator-only: the caller principal is not in OPERATOR_PRINCIPALS (an empty list means NO one is an operator — configure it to enable these endpoints)");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const operator = principal;
|
|
49
|
+
const store = deps.memoryOptOutGrant;
|
|
50
|
+
if (store === undefined) {
|
|
51
|
+
sendError(res, 501, "capability.memory_optout_grant_required", "this deployment has no memory opt-out grant table: the per-principal memory-capture opt-out authorization store lives on a SQL store backend (DB_BACKEND=mysql|pg) and the local backend deliberately keeps no forgetting twin — wire a SQL backend to administer opt-out grants");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (verb === "malformed" || ((verb.kind === "putOverride" || verb.kind === "deleteOverride") && memoryOptOutPrincipalShapeProblem(verb.principal) !== undefined)) {
|
|
55
|
+
sendError(res, 400, "request.path_malformed", "invalid memory opt-out grant principal — the path segment must percent-decode to a non-empty principal of at most 190 chars with no leading/trailing whitespace or control characters (the deployment default row is addressed by PUT on the collection root, not by a path segment)");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (verb.kind === "list") {
|
|
59
|
+
sendJson(res, 200, await store.list());
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (verb.kind === "deleteOverride") {
|
|
63
|
+
sendJson(res, 200, { principal: verb.principal, deleted: await store.deleteOverride(verb.principal) });
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const parsed = PutBodySchema.safeParse(await ctx.helpers.readJson(req));
|
|
67
|
+
if (!parsed.success) {
|
|
68
|
+
sendError(res, 400, "request.body_shape", "invalid memory opt-out grant body — expected exactly { allow: boolean, note?: string (1..500 chars) }; unknown keys are refused (a mistyped `allow` must not silently become a no-op)");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const input = { allow: parsed.data.allow, updatedBy: operator, ...(parsed.data.note !== undefined ? { note: parsed.data.note } : {}) };
|
|
72
|
+
const row = verb.kind === "putDefault" ? await store.putDefault(input) : await store.putOverride(verb.principal, input);
|
|
73
|
+
sendJson(res, 200, row);
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=admin-memory-optout.js.map
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*
|
|
17
17
|
* ## 三条边界(逐条对着失败形写)
|
|
18
18
|
* · **principal 门 + 反枚举**:身份判在店可用性判**之前**(否则「这台有没有装店」会先于 401 泄出去,
|
|
19
|
-
* 与 `GET /v1/sessions` 的同款注释);principal
|
|
19
|
+
* 与 `GET /v1/sessions` 的同款注释);principal 调用方**锁定自己的 scope**(`?scope=` 对它无效),
|
|
20
20
|
* 于是别的租户的行不是「403」也不是「空 200 但 total 多 1」——它在这个名册里**根本不存在**,
|
|
21
21
|
* 没有任何可判别面(`total` 同样是属主口径:一个多出来的计数就是存在性 oracle)。
|
|
22
22
|
* · **投影白名单**(照 `routes/fleet.ts` `stripT` 的姿势):逐键**显式拼**,并用
|
|
@@ -38,6 +38,8 @@ async function decideIdempotentReplay(cs, args) {
|
|
|
38
38
|
const boundCallId = binding.boundCallId;
|
|
39
39
|
if (boundCallId === undefined)
|
|
40
40
|
return undefined;
|
|
41
|
+
if (binding.checkpointToken !== undefined)
|
|
42
|
+
return undefined;
|
|
41
43
|
if (typeof cs.findDecidedApprovalsForBinding !== "function")
|
|
42
44
|
return undefined;
|
|
43
45
|
const rows = await cs.findDecidedApprovalsForBinding(sessionId, boundCallId);
|
|
@@ -463,30 +465,28 @@ async function handleApprovalsAssistantBody(req, res, url, ctx, miss) {
|
|
|
463
465
|
return;
|
|
464
466
|
}
|
|
465
467
|
}
|
|
466
|
-
let rememberToolName = null;
|
|
467
|
-
if (remember) {
|
|
468
|
-
const tok = await cs.findPendingTokenBySession(sessionId);
|
|
469
|
-
const cp0 = tok ? await cs.get(tok) : null;
|
|
470
|
-
const pa0 = cp0?.pendingAction;
|
|
471
|
-
const tn = pa0 && pa0.kind === "tool_approval" ? pa0.toolName : null;
|
|
472
|
-
rememberToolName = tn && tn !== "AskUserQuestion" ? tn : null;
|
|
473
|
-
}
|
|
474
468
|
let rememberApplied = false;
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
deps.logger?.info?.("approval_exemption_granted", { sessionId: grantSessionId, toolName: canonical, grantedBy: deciderPrincipal ?? null });
|
|
483
|
-
}
|
|
484
|
-
catch (e) {
|
|
485
|
-
deps.logger?.warn?.("approval_exemption_grant_failed", { sessionId: grantSessionId, toolName: rememberToolName, error: String(e).slice(0, 200) });
|
|
486
|
-
}
|
|
469
|
+
let pendingGrant;
|
|
470
|
+
const grantOnCommit = remember && deps.approvalExemptionStore
|
|
471
|
+
? async (grant) => {
|
|
472
|
+
const canonical = grant.validatedToolName;
|
|
473
|
+
if (!canonical)
|
|
474
|
+
return;
|
|
475
|
+
pendingGrant = { sessionId: grant.overrideSessionId ?? sessionId, toolName: canonical };
|
|
487
476
|
}
|
|
488
477
|
: undefined;
|
|
489
|
-
const out = await resumeCheckpoint(sessionId, decision, body.reason ?? undefined, "human", req, answer, binding, grantOnCommit, true);
|
|
478
|
+
const out = await resumeCheckpoint(sessionId, decision, body.reason ?? undefined, "human", req, answer, binding, grantOnCommit, true, { principal: deciderPrincipal, explicitOperator });
|
|
479
|
+
const outErrorCode = out.body.errorCode;
|
|
480
|
+
if (pendingGrant !== undefined && out.status === 200 && outErrorCode !== "cancelled" && deps.approvalExemptionStore) {
|
|
481
|
+
try {
|
|
482
|
+
await deps.approvalExemptionStore.grant(pendingGrant.sessionId, pendingGrant.toolName, deciderPrincipal ?? null);
|
|
483
|
+
rememberApplied = true;
|
|
484
|
+
deps.logger?.info?.("approval_exemption_granted", { sessionId: pendingGrant.sessionId, toolName: pendingGrant.toolName, grantedBy: deciderPrincipal ?? null });
|
|
485
|
+
}
|
|
486
|
+
catch (e) {
|
|
487
|
+
deps.logger?.warn?.("approval_exemption_grant_failed", { sessionId: pendingGrant.sessionId, toolName: pendingGrant.toolName, error: String(e).slice(0, 200) });
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
490
|
const replay = await decideIdempotentReplay(cs, {
|
|
491
491
|
sessionId,
|
|
492
492
|
out,
|
|
@@ -9,6 +9,7 @@ import { mcpInjectionHonored } from "../../task-mcp.js";
|
|
|
9
9
|
import { sendJson, sendError } from "../send.js";
|
|
10
10
|
import { resolveStreamApprovalGate } from "../../tool-approval.js";
|
|
11
11
|
import { projectMemoryEngineCapability } from "../../memory-posture.js";
|
|
12
|
+
import { workflowModelAllowlistFor } from "../../task-workflow.js";
|
|
12
13
|
import { centerEntitlementSourceWired } from "../../runtime-caps-resolver.js";
|
|
13
14
|
import { gatedPrincipal } from "../principal-gate.js";
|
|
14
15
|
function routePairingOf(deps, name, m) {
|
|
@@ -55,24 +56,21 @@ async function handleCapabilitiesBody(req, res, url, ctx, miss) {
|
|
|
55
56
|
fleet: deps.fleetBus
|
|
56
57
|
? { stream: true, sessionScope: true, observe: true, resume: "snapshot", bgNotifyFailClosed: true, steerPriority: "advisory", hookFailureNotice: true }
|
|
57
58
|
: false,
|
|
58
|
-
workspace:
|
|
59
|
-
const wfs = deps.fileSnapshotStore;
|
|
60
|
-
return wfs && typeof wfs.listKeys === "function" && typeof wfs.exportManifest === "function" && typeof wfs.getBlob === "function" && Boolean(deps.sessionStorage?.ownerOf)
|
|
61
|
-
? { browse: true, archive: true, maxFileBytes: deps.config.workspaceFileMaxBytes ?? 8 * 1024 * 1024 }
|
|
62
|
-
: false;
|
|
63
|
-
})(),
|
|
59
|
+
workspace: false,
|
|
64
60
|
leader: Boolean(deps.leaderEndpoint),
|
|
65
61
|
images: Boolean(deps.imageIndex),
|
|
66
62
|
memory: false,
|
|
67
63
|
memoryWrite: false,
|
|
68
64
|
memoryEngine: projectMemoryEngineCapability(deps.memoryPosture),
|
|
69
65
|
sessionBackgroundable: Boolean(deps.runStore),
|
|
66
|
+
runMemoryCaptureOptOut: Boolean(deps.runStore),
|
|
70
67
|
sharedMemory: Boolean(deps.sharedMemoryStore && deps.orgMemoryDirectory),
|
|
71
68
|
adoption: Boolean(deps.backend?.adoptionLog) && deps.config.operatorPrincipals.length > 0,
|
|
72
69
|
memoryBundle: Boolean(deps.memoryBundleExport && deps.memoryBundleImport) && deps.config.operatorPrincipals.length > 0,
|
|
73
70
|
memoryCompliance: Boolean(deps.memoryCompliance) && deps.config.operatorPrincipals.length > 0,
|
|
74
71
|
memoryOrigin: Boolean(deps.memoryOriginFace) && deps.config.operatorPrincipals.length > 0,
|
|
75
72
|
memoryConsolidationDriver: Boolean(deps.memoryConsolidation) && deps.config.operatorPrincipals.length > 0,
|
|
73
|
+
memoryOptOutGrant: Boolean(deps.memoryOptOutGrant) && deps.config.operatorPrincipals.length > 0,
|
|
76
74
|
outcomeLedger: Boolean(deps.outcomeSink?.summary) && (deps.config.requirePrincipal !== true || deps.config.operatorPrincipals.length > 0),
|
|
77
75
|
permissionRules: Boolean(deps.ruleConsent) && gatedPrincipal(req, deps.config) !== undefined,
|
|
78
76
|
permissionRulesRevoke: Boolean(deps.ruleConsent),
|
|
@@ -135,15 +133,28 @@ async function handleCapabilitiesBody(req, res, url, ctx, miss) {
|
|
|
135
133
|
resumeAt: Boolean(deps.resumeAnchorStore && deps.sessionStorage?.getLeafId),
|
|
136
134
|
promptSuggestions: true,
|
|
137
135
|
sessionPolicy: Boolean(deps.sessionPolicyStore),
|
|
138
|
-
rewindFiles: Boolean(deps.
|
|
139
|
-
|
|
136
|
+
rewindFiles: Boolean(deps.fileHistoryStore),
|
|
137
|
+
restoreFiles: Boolean(deps.fileHistoryStore),
|
|
138
|
+
rewindFilesTo: Boolean(deps.fileHistoryStore && deps.resumeAnchorStore && deps.sessionStorage?.getLeafId),
|
|
140
139
|
scheduler: Boolean(deps.config.schedulerEnabled && deps.config.requirePrincipal !== true && deps.config.remoteExec?.provider === "host"),
|
|
141
|
-
sessionSync: Boolean(deps.backend && deps.sessionStorage?.exportEntries && deps.sessionStorage?.beginImportStaging && deps.
|
|
140
|
+
sessionSync: Boolean(deps.backend && deps.sessionStorage?.exportEntries && deps.sessionStorage?.beginImportStaging && deps.fileHistoryStore),
|
|
142
141
|
sendUserFile: Boolean(deps.sendUserFile && deps.config.sendUserFile?.publicEndpoint),
|
|
143
142
|
s3PublicEndpoint: deps.config.sendUserFile?.publicEndpoint ?? null,
|
|
144
143
|
sendUserFileLedger: Boolean(deps.sendFileLedger),
|
|
145
144
|
workflows: deps.workflowsCapable ?? Boolean(deps.workflowRunStore),
|
|
146
145
|
workflowsList: Boolean(deps.workflowRunStore),
|
|
146
|
+
workflowModels: (() => {
|
|
147
|
+
const allow = deps.workflowModelAllowlist
|
|
148
|
+
?? workflowModelAllowlistFor({
|
|
149
|
+
selfOrchestrationModels: deps.config.selfOrchestrationModels ?? [],
|
|
150
|
+
requirePrincipal: deps.config.requirePrincipal,
|
|
151
|
+
models: deps.config.models ?? {},
|
|
152
|
+
})
|
|
153
|
+
?? [];
|
|
154
|
+
const bare = deps.config.models ?? {};
|
|
155
|
+
const live = expandTiers(bare, deps.config.tiers ?? {}) ?? bare;
|
|
156
|
+
return allow.filter((n) => Object.hasOwn(live, n));
|
|
157
|
+
})(),
|
|
147
158
|
manualCompact: Boolean(deps.runStore),
|
|
148
159
|
});
|
|
149
160
|
return;
|
|
@@ -91,7 +91,7 @@ export declare function assertStaticWiringConsistent(facts: StaticWiringFacts, l
|
|
|
91
91
|
* 走认证(全局门),不进本臂。
|
|
92
92
|
*
|
|
93
93
|
* 判源=TCP 对端地址(`req.socket.remoteAddress`),**非任何头**——XFF 类头在无反代的 turnkey 形上本就
|
|
94
|
-
*
|
|
94
|
+
* 是自造面;有反代的部署必然配 token(反代后 remoteAddress 恒 loopback,门自然放行,语义仍对:该形的
|
|
95
95
|
* 边界由反代+token 承担)。`remoteAddress` 缺席(socket 已断等边缘)⇒ 判拒(fail-closed,安全轴方向)。
|
|
96
96
|
*/
|
|
97
97
|
export declare function zeroCredentialLoopbackDenied(remoteAddress: string | undefined, config: {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* design/158 A9 域模块 —— v2 leader 端点(design/50)。
|
|
3
3
|
*
|
|
4
4
|
* 路由:`POST /v1/leader`(异步,形同 `/v1/runs`)· `GET /v1/leader/:id`。整域由 `deps.leaderEndpoint`
|
|
5
|
-
* 的存在与否开关;身份层与 `/v1/runs` 同款(BL-4
|
|
5
|
+
* 的存在与否开关;身份层与 `/v1/runs` 同款(BL-4:此前整条路跳过 authorize/requirePrincipal 且不记 owner,
|
|
6
6
|
* 任何持服务令牌者能提交/读任意租户的 leader run),owner 用 crypto 验证过的 `gatedPrincipal`。本域零可变状态。
|
|
7
7
|
*/
|
|
8
8
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* · 记忆引擎整个没接线(`MEMORY_ENGINE=off` / 多租 file 底座等暗态);
|
|
28
28
|
* · 引擎接了但后端没有 bundle 复合面(pg/tidb 孪生)—— 这一支在**挂载期**就判掉了
|
|
29
29
|
* (`createMemoryBundleFaces` 返回 undefined ⇒ 两口整个不挂),能力位 `memoryBundle` 因此与「路由真能用」
|
|
30
|
-
* 严格同真。🔴 首版把它当运行期事实(照常挂载、让 core 每次抛 409/422),被 codex
|
|
30
|
+
* 严格同真。🔴 首版把它当运行期事实(照常挂载、让 core 每次抛 409/422),被 codex 交叉复审驳倒并改此形:
|
|
31
31
|
* 面在不在是一次**属性检查**、boot 期判得出、且是**永久**缺席 ⇒ 照常挂着就是一个恒假的 yes,
|
|
32
32
|
* 壳会据此给用户开出一条走不通的迁移流程。
|
|
33
33
|
* ⚠️ 与**运行期**拒别混:后端**有**复合面但托管链 chain-degraded 时,core 照样抛
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { sendJson, sendError } from "../send.js";
|
|
3
|
+
import { buildOriginFencedBundle, readOriginAware } from "../../memory-origin-fence.js";
|
|
3
4
|
import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
|
|
4
5
|
export const MEMORY_BUNDLE_EXPORT_PATH = "/v1/memory/export";
|
|
5
6
|
export const MEMORY_BUNDLE_IMPORT_PATH = "/v1/memory/import";
|
|
@@ -93,7 +94,7 @@ async function handleMemoryBundleBody(req, res, url, ctx, miss) {
|
|
|
93
94
|
}
|
|
94
95
|
try {
|
|
95
96
|
const bundle = await engineExport(parsed.data.scopes);
|
|
96
|
-
sendJson(res, 200, bundle);
|
|
97
|
+
sendJson(res, 200, buildOriginFencedBundle(bundle, readOriginAware(new URL(req.url ?? "", "http://x").searchParams)));
|
|
97
98
|
}
|
|
98
99
|
catch (err) {
|
|
99
100
|
if (!sentClassifiedFailure(res, ctx, "export", err))
|
|
@@ -73,19 +73,19 @@
|
|
|
73
73
|
*
|
|
74
74
|
* ── 改写门(`isCredentialGatedRewrite`):**两口都进** ──────────────────────────────────────────
|
|
75
75
|
* 该门自 #277 起的判据是「持久改写 **或** 授权唯一输入是 principal 头、且**没有属主门兜底**的治理面」:
|
|
76
|
-
* · `POST /v1/memory/erase`
|
|
76
|
+
* · `POST /v1/memory/erase` 落在**前**一项上,而且是最深的一种:它把条目从本店**删掉**。自造一个在册
|
|
77
77
|
* operator 头即可对任一租户执行一次不可撤销的删除 —— 无凭证部署上缺的正是「上游会验这个头」这个前提。
|
|
78
78
|
* · `GET …/:entryId/provenance` 落在**后**一项上。🔴 这一条**推翻了 design/316 §3 的原始裁定**
|
|
79
|
-
* (稿子把它豁免在门外,理由是「爆炸半径=一条」);codex
|
|
79
|
+
* (稿子把它豁免在门外,理由是「爆炸半径=一条」);codex 交叉复审 [high] 指出该理由站不住,亲验后采信:
|
|
80
80
|
* ⑴ 稿子援引来类比的两条豁免读(`GET /v1/adoption/:id` / `GET /v1/rules`)在门外的**真正**原因是
|
|
81
81
|
* 它们各有**属主门**兜底,不是「读得少」;本口的授权判据就只有 `explicitOperatorOk`,过了就直接答;
|
|
82
|
-
* ⑵
|
|
82
|
+
* ⑵ 无凭证部署上那个头**可自造**,于是「一条」这个上界实际退化成「知道 id 就能读」——拿一个不可猜的
|
|
83
83
|
* id 当唯一的授权,是本仓在别处明令不接受的形;
|
|
84
84
|
* ⑶ 一条账里有:绑定的 scope+slug、贡献会话 id 与污染理由、仓内 ingest 路径与内容哈希、托管链事件。
|
|
85
85
|
* 先例逐字同源:`POST /v1/memory/export` 当初也「按原措辞本不该进」,同一条论证把它收了进来
|
|
86
86
|
* ——「把它留在门外只为守住一句措辞,是把措辞看得比它要保护的东西更重」。
|
|
87
87
|
* 🔴 判据里**刻意不写**「本口 side-effect-free」:那不是这道门的判据。该门拦的是「授权的唯一输入是
|
|
88
|
-
*
|
|
88
|
+
* 一个可自造的头」这件事 —— 用「没有副作用」当豁免理由,正是 #277 那次把 `POST /v1/memory/export`
|
|
89
89
|
* (纯读、跨租户全库)漏在门外的那句话。
|
|
90
90
|
* ⚠️ 代价与补偿如实成文:无凭证部署上两口都答 503 `auth.service_token_required`。补偿是**既有旋钮、
|
|
91
91
|
* 零新增** —— 配 `SERVICE_AUTH_TOKEN`(正路),或本地开发显式 `ALLOW_UNAUTHED_WRITES=true`
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
* · 而这份账**新长一个键**时,静默上 wire 恰恰是危险的那一侧(它是托管面,不是事实陈述面)。
|
|
44
44
|
* ⇒ 显式白名单 + **编译期差集门**(`test/wire-whitelist-exhaustiveness.test.ts` 的 `KnownExcluded`
|
|
45
45
|
* = `entryText`,逐字写理由):core 加一个键 ⇒ TS2344 ⇒ 由人处置,而不是悄悄上 wire 或悄悄漏掉。
|
|
46
|
-
* 🔴 白名单是**深**的,不是只做一层(codex
|
|
47
|
-
* —— 一层白名单 +
|
|
46
|
+
* 🔴 白名单是**深**的,不是只做一层(codex 交叉复审 [high] 抓的第一版病):嵌套的 `origin` 也逐键投影
|
|
47
|
+
* —— 一层白名单 + 一个整只透传的嵌套对象,等于给这条排除留了一个旁路,全文见 {@link buildOriginWire}。
|
|
48
48
|
* 🔴 **剥了不等于不说**:投影补一个 `custodyBytes`(托管字节数)。这是 #196 absence-reports-not-silent-green
|
|
49
49
|
* 在本口的落法 —— operator 判「这个 pending 行还揣着一条记忆吗」只需要这一位;悄悄把整个托管概念从
|
|
50
50
|
* 审计面上抹掉,会让一个**必须由人处置**的崩溃恢复席看上去不存在。
|
|
@@ -57,12 +57,12 @@
|
|
|
57
57
|
*
|
|
58
58
|
* ── 改写门(`isCredentialGatedRewrite`):**三口全进** ──────────────────────────────────────────
|
|
59
59
|
* 该门自 #277 起的判据是「持久改写 **或** 授权唯一输入是 principal 头的跨租户治理读」:
|
|
60
|
-
* · `POST …/clear` 落在**前**一项:它把一条条目的外源披露**永久摘掉**(墓碑 + 同 id 重录)
|
|
60
|
+
* · `POST …/clear` 落在**前**一项:它把一条条目的外源披露**永久摘掉**(墓碑 + 同 id 重录),自造一个
|
|
61
61
|
* 在册 operator 头即可让任意租户的一条外来记忆看上去是本店自产的 —— 而记忆是模型每次挂载都读的面;
|
|
62
62
|
* · `GET …/external` 落在**后**一项,而且是这一族里爆炸半径最大的读:一次调用取回**任意 scope**的
|
|
63
63
|
* 条目 + 每条的完整出处账(贡献会话 id、污染理由、仓内 ingest 路径与内容哈希、托管链事件)。判据与
|
|
64
64
|
* `POST /v1/memory/export`(#277 收进门的那条纯读)逐字同构:它没有属主门兜底,operator 头过了就直接答。
|
|
65
|
-
* · `GET …/clearances` —— 🔴 **本车第一版把它留在门外,codex
|
|
65
|
+
* · `GET …/clearances` —— 🔴 **本车第一版把它留在门外,codex 交叉复审 [high] 推翻,亲验后采信**。
|
|
66
66
|
* 那一版的理由是「投影后不含条目正文,爆炸半径差一个量级」,而这个理由**用错了基准**:本门的判据
|
|
67
67
|
* 从来不是「含不含正文」,而是「授权的**唯一**输入是 principal 头」∧「没有属主门兜底」。按那条判据,
|
|
68
68
|
* 一口**整部署**的清标账(每个租户的 scope+slug、entryId、rev、operator 手写的自由文本 `reason`、
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { ControlPlaneCorruptError } from "@sema-agent/core";
|
|
3
3
|
import { sendJson, sendError } from "../send.js";
|
|
4
|
+
import { buildOriginFence, readOriginAware } from "../../memory-origin-fence.js";
|
|
4
5
|
import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
|
|
5
6
|
export const MEMORY_ORIGIN_EXTERNAL_PATH = "/v1/memory/origin/external";
|
|
6
7
|
export const MEMORY_ORIGIN_CLEARANCES_PATH = "/v1/memory/origin/clearances";
|
|
@@ -141,7 +142,8 @@ async function handleMemoryOriginBody(req, res, url, ctx, miss) {
|
|
|
141
142
|
return;
|
|
142
143
|
}
|
|
143
144
|
try {
|
|
144
|
-
|
|
145
|
+
const fence = buildOriginFence(await faces.listExternal(scopes), readOriginAware(new URL(req.url ?? "", "http://x").searchParams));
|
|
146
|
+
sendJson(res, 200, { scopes, entries: fence.entries, ...(fence.withheldOriginTagged !== undefined ? { withheldOriginTagged: fence.withheldOriginTagged } : {}) });
|
|
145
147
|
}
|
|
146
148
|
catch (err) {
|
|
147
149
|
if (!sentClassifiedFailure(res, ctx, "external", err))
|