@sema-agent/server 7.43.0 → 7.44.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/USAGE.md +18 -0
- package/dist/adoption/plan.js +31 -192
- package/dist/adoption/quiesce.js +3 -86
- package/dist/adoption/runner.js +3 -140
- package/dist/adoption/sql.js +0 -74
- package/dist/adoption/wire.js +0 -80
- package/dist/approval-ask-machine.js +0 -75
- package/dist/approval-card.js +0 -323
- package/dist/approval-deny-reasons.js +0 -42
- package/dist/approval-hmac.js +0 -35
- package/dist/approval-reconciler.js +8 -166
- package/dist/approval.js +4 -67
- package/dist/audit.js +1 -44
- package/dist/auth-bridge.js +4 -47
- package/dist/auth-keys.js +0 -23
- package/dist/bake-runner/main.js +4 -65
- package/dist/bake-runner/protocol.js +3 -58
- package/dist/bake-runner/runner.js +5 -91
- package/dist/bench/l8/artifact.js +2 -61
- package/dist/bench/l8/escape.js +0 -25
- package/dist/bench/l8/index.js +0 -14
- package/dist/bench/l8/probes.js +0 -60
- package/dist/bench/l8/run-probes.js +2 -45
- package/dist/bench/s1/arms.js +14 -189
- package/dist/bench/s1/live-deps.js +11 -253
- package/dist/bench/s1/oracle.js +1 -36
- package/dist/bench/s1/repair-oracle-adapter.js +0 -30
- package/dist/bench/s1/reviewer.js +0 -34
- package/dist/bench/s1/row.js +0 -62
- package/dist/bench/s1/run-firm.js +4 -70
- package/dist/bench/s1/runner-ctx.js +0 -40
- package/dist/bench/s1/tasks.js +0 -115
- package/dist/boot/adoption.js +0 -20
- package/dist/boot/budget-tracing.js +3 -55
- package/dist/boot/config-center.js +43 -551
- package/dist/boot/coordinators.js +1 -69
- package/dist/boot/crash-last.js +0 -18
- package/dist/boot/deferred-sandbox-path-env.js +3 -119
- package/dist/boot/execution-env.js +5 -171
- package/dist/boot/governance-seams.js +0 -92
- package/dist/boot/leader.js +0 -69
- package/dist/boot/limit-sync.js +0 -2
- package/dist/boot/memory-boundary.js +3 -91
- package/dist/boot/org-memory.js +1 -25
- package/dist/boot/parked-revive-gate.js +0 -150
- package/dist/boot/permission-rules-audit.js +1 -56
- package/dist/boot/reapers.js +15 -273
- package/dist/boot/resolve-spec.js +9 -768
- package/dist/boot/retention-lane.js +4 -87
- package/dist/boot/runner-deps.js +3 -297
- package/dist/boot/runtime-caps.js +0 -40
- package/dist/boot/session-faces.js +2 -133
- package/dist/boot/shutdown.js +9 -83
- package/dist/boot/side-query-lane.js +2 -137
- package/dist/boot/stores.js +15 -215
- package/dist/boot/task-list-lane.js +0 -18
- package/dist/boot/webfetch-summarize-lane.js +0 -47
- package/dist/boot/workflow-orchestration.js +3 -99
- package/dist/boot-reclaim.js +0 -20
- package/dist/bounded-session-map.js +0 -19
- package/dist/brain.js +2 -139
- package/dist/budget.js +9 -244
- package/dist/capabilities/builtin-tools.js +0 -3
- package/dist/capabilities/center-plugins.js +6 -51
- package/dist/capabilities/center-prompts.js +6 -71
- package/dist/capabilities/code-review-council.js +7 -40
- package/dist/capabilities/collab-workflows.js +1 -44
- package/dist/capabilities/hands-lane.js +0 -65
- package/dist/capabilities/memory-notice.js +0 -70
- package/dist/capabilities/prompt.js +0 -14
- package/dist/capabilities/prompts/code-review.js +0 -14
- package/dist/capabilities/prompts/identity.js +0 -6
- package/dist/capabilities/prompts/team.js +0 -4
- package/dist/capabilities/repo-tools.js +0 -34
- package/dist/capabilities/sandbox-file-send.js +3 -92
- package/dist/capabilities/scenarios.d.ts +0 -1
- package/dist/capabilities/scenarios.js +18 -279
- package/dist/capabilities/select-environment-tool.js +0 -32
- package/dist/capabilities/send-user-file-tool.js +2 -66
- package/dist/capabilities/skills.d.ts +1 -2
- package/dist/capabilities/skills.js +3 -21
- package/dist/capabilities/team.d.ts +3 -11
- package/dist/capabilities/team.js +3 -51
- package/dist/capabilities/tool-defer.js +0 -3
- package/dist/config-center/apply-effective.js +15 -330
- package/dist/config-center/apply-ledger.js +5 -38
- package/dist/config-center/facade.js +0 -41
- package/dist/config-center/hot-keys-registry.js +0 -20
- package/dist/config-center/http-client.js +2 -125
- package/dist/config-center/mcp-revocation.js +2 -34
- package/dist/config-center/read-face.js +0 -59
- package/dist/config-center/restart-signal.js +1 -85
- package/dist/config-center/skills-mcp.d.ts +1 -1
- package/dist/config-center/skills-mcp.js +9 -76
- package/dist/config-center/stage-limits.js +8 -35
- package/dist/config-invariants.js +0 -16
- package/dist/config-lkg.js +0 -42
- package/dist/config-provider.js +3 -186
- package/dist/config-types.js +0 -5
- package/dist/config.js +107 -1145
- package/dist/degenerate-instrument.js +3 -67
- package/dist/deployment-governance.js +0 -124
- package/dist/digest-form.js +0 -12
- package/dist/elicitation.js +3 -86
- package/dist/env-facts.js +7 -75
- package/dist/fleet/fleet-bus.js +34 -507
- package/dist/fleet/fleet-reconciler.js +9 -149
- package/dist/fleet/fleet-terminal-window.js +10 -178
- package/dist/fleet/subagent-tail-bus.js +3 -72
- package/dist/fleet-client.js +10 -70
- package/dist/fleet-lease.js +5 -79
- package/dist/git-api-kind.js +0 -3
- package/dist/governance-ask-marks.js +2 -78
- package/dist/hooks/branch-transcript.js +0 -74
- package/dist/hooks/cc-agent-hook-prompt.js +0 -29
- package/dist/hooks/cc-stop-prompt.js +1 -46
- package/dist/hooks/hook-llm.js +1 -53
- package/dist/hooks/hook-runner.js +20 -414
- package/dist/http/active-run-conflict.js +4 -129
- package/dist/http/cursor-fingerprint.d.ts +5 -0
- package/dist/http/cursor-fingerprint.js +5 -0
- package/dist/http/idempotency.js +0 -37
- package/dist/http/principal-gate.js +3 -40
- package/dist/http/route-ctx.js +0 -9
- package/dist/http/routes/a2a-serve.js +7 -319
- package/dist/http/routes/admin-config-refresh.js +0 -4
- package/dist/http/routes/admin-drain.js +0 -10
- package/dist/http/routes/adoption.js +1 -29
- package/dist/http/routes/agents-roster.js +1 -47
- package/dist/http/routes/approvals-assistant.js +32 -395
- package/dist/http/routes/attachments.js +4 -22
- package/dist/http/routes/capabilities.js +3 -420
- package/dist/http/routes/diagnostics.js +1 -81
- package/dist/http/routes/fleet.js +9 -185
- package/dist/http/routes/images.js +14 -239
- package/dist/http/routes/leader.js +0 -13
- package/dist/http/routes/memory-bundle.js +1 -61
- package/dist/http/routes/memory-policy.js +9 -99
- package/dist/http/routes/notify-wake.js +3 -37
- package/dist/http/routes/observability.js +2 -19
- package/dist/http/routes/retention-ops.js +2 -34
- package/dist/http/routes/rules.js +1 -93
- package/dist/http/routes/runs.js +64 -873
- package/dist/http/routes/session-sync.js +19 -258
- package/dist/http/routes/sessions-list.js +17 -43
- package/dist/http/routes/sessions.js +34 -226
- package/dist/http/routes/shared-memory.js +5 -36
- package/dist/http/routes/side-query.js +1 -87
- package/dist/http/routes/tasks.js +52 -722
- package/dist/http/routes/trace-usage.js +32 -195
- package/dist/http/routes/workflows.js +19 -195
- package/dist/http/run-meta.js +0 -6
- package/dist/http/send.js +0 -32
- package/dist/http/server.js +92 -1597
- package/dist/http/sse-lifecycle.js +2 -13
- package/dist/http/sse-log.js +3 -48
- package/dist/http/tar.js +5 -21
- package/dist/http/verify-rounds.js +0 -5
- package/dist/http/wire-gate.js +0 -9
- package/dist/http/workspace-content.js +0 -10
- package/dist/images/bake-validate.js +1 -70
- package/dist/images/manifest.js +1 -6
- package/dist/index.js +0 -21
- package/dist/key-resolver.js +2 -17
- package/dist/leader/diffout.js +1 -20
- package/dist/leader/diffup.js +0 -47
- package/dist/leader/endpoint.js +2 -57
- package/dist/leader/fanout.js +3 -45
- package/dist/leader/grader-env-factory.js +3 -72
- package/dist/leader/leader.js +5 -156
- package/dist/leader/merge.js +8 -115
- package/dist/leader/planner.js +3 -54
- package/dist/leader/repair-oracle.js +1 -60
- package/dist/leader/repair-wire.js +2 -79
- package/dist/leader/wire.js +8 -307
- package/dist/lsp/e2b-bridge.js +4 -64
- package/dist/lsp/e2b-manager.js +6 -94
- package/dist/lsp/lsp-frames.js +0 -12
- package/dist/lsp/manager.js +4 -96
- package/dist/lsp/ws-transport.js +5 -55
- package/dist/lsp-evict.js +1 -15
- package/dist/main.js +57 -765
- package/dist/memory-bundle-engine.js +0 -55
- package/dist/memory-export.js +0 -4
- package/dist/memory-posture.js +1 -15
- package/dist/memory-scope.js +11 -148
- package/dist/memory-sync-client.js +2 -44
- package/dist/memory-sync.js +1 -80
- package/dist/model-select.js +3 -80
- package/dist/observability/cost-quota.js +1 -17
- package/dist/observability/cost-taxonomy.js +0 -34
- package/dist/observability/fail-open.js +7 -86
- package/dist/observability/logger.js +0 -6
- package/dist/observability/metrics.js +0 -94
- package/dist/observability/otel-exporter.js +3 -13
- package/dist/observability/principal-context.js +0 -9
- package/dist/observability/prompt-manifest.js +1 -37
- package/dist/observability/rate-limit.js +0 -4
- package/dist/observability/secret-env-scrub.js +2 -56
- package/dist/observability/tool-trace.js +1 -70
- package/dist/orchestration/hardened-vm-runner.js +4 -118
- package/dist/orchestration/hardened-vm-worker-runner.js +1 -26
- package/dist/orchestration/hardened-vm-worker.js +0 -27
- package/dist/orchestration/subagent-steer.js +1 -45
- package/dist/orchestration/workflow-agent-steer.js +1 -80
- package/dist/orchestration/workflow-completion-inbox.js +32 -285
- package/dist/orchestration/workflow-notify-journal.js +16 -259
- package/dist/org-memory-admission.js +3 -47
- package/dist/parent-watch.js +2 -48
- package/dist/parked-decide.js +1 -109
- package/dist/per-task-image.js +0 -57
- package/dist/plan-cache-probe.js +3 -27
- package/dist/plugins/adoption-log-sql.js +2 -119
- package/dist/plugins/approval-ask-store-memory.js +3 -38
- package/dist/plugins/approval-ask-store-sql.js +6 -188
- package/dist/plugins/approval-exemption-store.js +2 -28
- package/dist/plugins/background-agent-store-sql.js +4 -105
- package/dist/plugins/background-shell-support.js +14 -122
- package/dist/plugins/blob-backend.js +6 -169
- package/dist/plugins/breaker-state-sql.js +8 -46
- package/dist/plugins/caching-session-store.js +4 -106
- package/dist/plugins/checkpoint-store-sql.js +18 -505
- package/dist/plugins/e2b-orphan-reclaim.js +0 -45
- package/dist/plugins/file-outcome-sink.js +0 -9
- package/dist/plugins/file-resume-anchor-store.js +4 -43
- package/dist/plugins/file-run-store.js +26 -364
- package/dist/plugins/file-snapshot-store-sql.js +10 -181
- package/dist/plugins/fork-routing-session-store.js +8 -111
- package/dist/plugins/host-platform.js +2 -91
- package/dist/plugins/image-bake-store-sql.js +7 -250
- package/dist/plugins/image-index-sql.js +4 -123
- package/dist/plugins/k8s-bg-scripts.js +4 -88
- package/dist/plugins/k8s-exec-protocol.js +0 -41
- package/dist/plugins/leader-run-store-sql.js +0 -101
- package/dist/plugins/local-checkpoint-store.js +8 -128
- package/dist/plugins/local-session-store.js +34 -296
- package/dist/plugins/local-task-attachment-store.js +2 -16
- package/dist/plugins/mailbox-store-sql.js +8 -65
- package/dist/plugins/memory-embedder-fingerprint.js +5 -166
- package/dist/plugins/memory-embedder.js +4 -70
- package/dist/plugins/memory-engine-pg.js +6 -170
- package/dist/plugins/memory-engine-tidb.js +7 -155
- package/dist/plugins/memory-engine-vector-util.js +0 -10
- package/dist/plugins/memory-key-guards.js +0 -34
- package/dist/plugins/memory-origin-law.js +0 -187
- package/dist/plugins/memory-resume-anchor-store.js +0 -17
- package/dist/plugins/memory-run-store.js +12 -89
- package/dist/plugins/memory-session-policy-store.js +0 -17
- package/dist/plugins/memory-sync-store-pg.js +4 -49
- package/dist/plugins/memory-sync-store-tidb.js +3 -35
- package/dist/plugins/outcome-ledger-sql.js +3 -97
- package/dist/plugins/permission-rule-store-file.js +5 -133
- package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
- package/dist/plugins/permission-rule-store-sql.js +50 -307
- package/dist/plugins/pg-cost-quota.js +0 -7
- package/dist/plugins/pg-pool.js +0 -92
- package/dist/plugins/pg-rate-limiter.js +2 -13
- package/dist/plugins/pg-safe-json.js +4 -40
- package/dist/plugins/pg-session-storage.js +25 -189
- package/dist/plugins/posix-shell-fs.js +1 -31
- package/dist/plugins/remote-env-adb.js +12 -101
- package/dist/plugins/remote-env-e2b.js +40 -371
- package/dist/plugins/remote-env-file-error.js +0 -37
- package/dist/plugins/remote-env-host.js +63 -483
- package/dist/plugins/remote-env-k8s.js +32 -326
- package/dist/plugins/remote-env-local-docker.js +24 -157
- package/dist/plugins/remote-env-ssh.js +20 -128
- package/dist/plugins/remote-scratchpad.js +2 -32
- package/dist/plugins/remote-shell.js +1 -32
- package/dist/plugins/resume-anchor-store-sql.js +0 -11
- package/dist/plugins/retention-lane-store-sql.js +0 -108
- package/dist/plugins/retention-store-sql.js +4 -383
- package/dist/plugins/roster-store-sql.js +0 -55
- package/dist/plugins/run-store-sql.js +14 -251
- package/dist/plugins/s3-presign.js +2 -49
- package/dist/plugins/scheduler-support.js +3 -80
- package/dist/plugins/send-file-ledger.js +4 -53
- package/dist/plugins/send-user-file.js +4 -94
- package/dist/plugins/session-placement.js +1 -89
- package/dist/plugins/session-policy-store-sql.js +3 -81
- package/dist/plugins/session-store.js +0 -57
- package/dist/plugins/shared-memory-store-sql.js +4 -167
- package/dist/plugins/sql-driver.js +0 -17
- package/dist/plugins/sql-errors.js +0 -7
- package/dist/plugins/sql-escape.js +0 -8
- package/dist/plugins/sql-row-helpers.js +0 -25
- package/dist/plugins/store-backend.js +42 -222
- package/dist/plugins/store-contracts.js +2 -46
- package/dist/plugins/task-attachment-store.js +3 -49
- package/dist/plugins/task-list-store-sql.js +0 -79
- package/dist/plugins/tidb-cost-quota.js +1 -4
- package/dist/plugins/tidb-pool.js +1 -207
- package/dist/plugins/tidb-rate-limiter.js +3 -9
- package/dist/plugins/tidb-session-storage.js +4 -70
- package/dist/plugins/tidb-session-store.js +19 -341
- package/dist/plugins/tool-result-store-sql.js +4 -179
- package/dist/plugins/usage-window-store-sql.js +0 -8
- package/dist/plugins/web-search.js +10 -132
- package/dist/plugins/workflow-journal-store-sql.js +2 -58
- package/dist/plugins/workflow-run-store-sql.js +6 -91
- package/dist/plugins/worktree-isolation.js +6 -126
- package/dist/plugins/write-behind-counter.js +16 -75
- package/dist/principal-jwt.js +5 -60
- package/dist/project-memory.js +15 -146
- package/dist/prompts-domain-validate.js +1 -51
- package/dist/question.js +2 -111
- package/dist/resource-suspend.js +0 -18
- package/dist/router/route-orchestration.js +0 -77
- package/dist/rules-consent.d.ts +44 -5
- package/dist/rules-consent.js +75 -215
- package/dist/run-local.js +14 -381
- package/dist/runs.js +29 -617
- package/dist/runtime-caps-resolver.js +6 -132
- package/dist/runtime-governance.js +1 -232
- package/dist/sandbox-pkg-source.js +0 -37
- package/dist/sealed-key.js +3 -68
- package/dist/security.js +6 -291
- package/dist/session-leaf-bus.js +0 -32
- package/dist/session-sync-content.js +1 -69
- package/dist/session-sync-kernel.js +3 -60
- package/dist/session-sync.js +3 -66
- package/dist/session-titler.js +7 -41
- package/dist/session-watch.js +9 -73
- package/dist/shared-memory-scope-authorizer.js +0 -18
- package/dist/sighup-idle.js +1 -12
- package/dist/spec-fields.js +4 -143
- package/dist/store-live-probe.js +3 -49
- package/dist/task-a2a.js +1 -126
- package/dist/task-cwd.js +1 -103
- package/dist/task-mcp.js +1 -89
- package/dist/task-settings.js +10 -300
- package/dist/task-workflow.js +8 -75
- package/dist/tool-approval.d.ts +28 -1
- package/dist/tool-approval.js +70 -1430
- package/dist/trace/artifacts.js +5 -20
- package/dist/trace/engine-notice-wire.js +6 -138
- package/dist/trace/ledger-sink.js +7 -96
- package/dist/trace/project.js +4 -448
- package/dist/trace/redact.js +9 -77
- package/dist/turn-activity.js +1 -27
- package/dist/usage-analytics.js +4 -34
- package/dist/wall-clock-jump-guard.js +1 -68
- package/package.json +3 -3
- package/dist/capabilities/scenario-alias.d.ts +0 -27
- package/dist/capabilities/scenario-alias.js +0 -61
|
@@ -1,24 +1,11 @@
|
|
|
1
1
|
import { sendJson, sendError } from "../send.js";
|
|
2
2
|
import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
|
|
3
3
|
import { RETENTION_AUDIT_DEFAULT_LIMIT, RETENTION_AUDIT_MAX_LIMIT } from "../../plugins/retention-lane-store-sql.js";
|
|
4
|
-
/** hold 路径。**`*` 不是 `+`**:空段 = 无主桶(见文件头「域的两个边界」)。 */
|
|
5
4
|
const HOLD_RE = /^\/v1\/ops\/retention\/holds\/([^/]*)$/;
|
|
6
5
|
const AUDIT_PATH = "/v1/ops/retention/audit";
|
|
7
|
-
/** `retention_hold.domain` / `retention_audit.domain` 的列宽(车1 的 DDL:`VARCHAR(190)`)。 */
|
|
8
6
|
const DOMAIN_MAX_CHARS = 190;
|
|
9
|
-
/** hold 备注的上界。`note` 列是 TEXT(不会截断),但一个无界的 operator 输入不该原样进一张**追加式**
|
|
10
|
-
* 的治理表 —— 4000 字符足够写清案号与出处,再长的属于工单系统。 */
|
|
11
7
|
const NOTE_MAX_CHARS = 4000;
|
|
12
|
-
/** operator 面的**唯一**授权文案(三条路由同受众、同理由 ⇒ 同一句;分成三句只会让 freeze 表长三倍
|
|
13
|
-
* 而消费端读到的判据一模一样)。 */
|
|
14
8
|
const OPERATOR_ONLY_MESSAGE = "managed-retention governance is an operator-only deployment action";
|
|
15
|
-
/**
|
|
16
|
-
* 审计游标 = `{id}` 的不透明 base64url。
|
|
17
|
-
*
|
|
18
|
-
* 🔴 排序键是**自增 `id` 单列**,不是 roster 那样的二元组:`id` 本身全序且唯一,二元组存在的全部理由
|
|
19
|
-
* (同毫秒兄弟行无 tiebreak ⇒ 漏页/重页)在这张表上结构性不存在。游标形仍照 roster 逐字同姿势
|
|
20
|
-
* (base64url JSON / 判别式解码 / 400 带**类名** detail)—— 消费端跨端点的游标处理代码因此同形。
|
|
21
|
-
*/
|
|
22
9
|
function encodeCursor(c) {
|
|
23
10
|
return Buffer.from(JSON.stringify(c)).toString("base64url");
|
|
24
11
|
}
|
|
@@ -28,8 +15,6 @@ function decodeCursor(raw) {
|
|
|
28
15
|
parsed = JSON.parse(Buffer.from(raw, "base64url").toString());
|
|
29
16
|
}
|
|
30
17
|
catch (err) {
|
|
31
|
-
// 只带错误的**类名**,不回显 payload 片段(与 agents-roster 的同款纪律:被误粘进查询串的凭据
|
|
32
|
-
// 不该经由错误文案再走一圈)。
|
|
33
18
|
return { ok: false, why: `not decodable base64url JSON (${err instanceof Error ? err.name : "unknown error"})` };
|
|
34
19
|
}
|
|
35
20
|
if (typeof parsed !== "object" || parsed === null)
|
|
@@ -53,8 +38,7 @@ async function handleRetentionBody(req, res, url, ctx, miss) {
|
|
|
53
38
|
miss.fell = true;
|
|
54
39
|
return;
|
|
55
40
|
}
|
|
56
|
-
|
|
57
|
-
const principal = gatedPrincipal(req, deps.config); // direct-door safe:验证过的身份,不是可伪造的头
|
|
41
|
+
const principal = gatedPrincipal(req, deps.config);
|
|
58
42
|
if (deps.config.requirePrincipal && !principal) {
|
|
59
43
|
sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
|
|
60
44
|
return;
|
|
@@ -65,8 +49,6 @@ async function handleRetentionBody(req, res, url, ctx, miss) {
|
|
|
65
49
|
}
|
|
66
50
|
const store = deps.backend?.retentionLane?.();
|
|
67
51
|
if (!store) {
|
|
68
|
-
// ⚠️ 文案里用**单引号**写 `retention:'none'`:文案冻结门的字面提取器不认带反斜杠转义的双引号串
|
|
69
|
-
// (`"([^"\n\\]*)"`),一个 `\"` 会让这条站点对那道门隐形 —— 那不是"过了门",是绕过门。
|
|
70
52
|
sendError(res, 501, "capability.retention_store_required", "managed retention requires a SQL store backend (DB_BACKEND=mysql|pg) — the local/file backend declares retention:'none' and holds nothing to govern");
|
|
71
53
|
return;
|
|
72
54
|
}
|
|
@@ -76,12 +58,9 @@ async function handleRetentionBody(req, res, url, ctx, miss) {
|
|
|
76
58
|
}
|
|
77
59
|
await handleAuditRead(req, res, store);
|
|
78
60
|
}
|
|
79
|
-
/** 放置(PUT)/ 解除(DELETE)—— 两支共用一条 upsert(见文件头的唯一纪律)。 */
|
|
80
61
|
async function handleHold(req, res, ctx, store, rawDomain, principal) {
|
|
81
62
|
const domain = ctx.helpers.safeDecode(rawDomain);
|
|
82
63
|
if (domain === null) {
|
|
83
|
-
// 复用既有的 `request.id_invalid`(sessions/attachments/adoption 同族),不为一条新路由铸第二个
|
|
84
|
-
// 同义码 —— 附录 A 判据②:只有消费端真会分支时才铸新码。
|
|
85
64
|
sendError(res, 400, "request.id_invalid", "invalid retention domain segment");
|
|
86
65
|
return;
|
|
87
66
|
}
|
|
@@ -94,7 +73,6 @@ async function handleHold(req, res, ctx, store, rawDomain, principal) {
|
|
|
94
73
|
const held = req.method === "PUT";
|
|
95
74
|
let note;
|
|
96
75
|
if (held) {
|
|
97
|
-
// 体是**可选**的(`PUT …/holds/acme` 不带体是合法的最小放置)。空体走 `{}`。
|
|
98
76
|
let raw;
|
|
99
77
|
try {
|
|
100
78
|
raw = await ctx.helpers.readJson(req);
|
|
@@ -115,7 +93,6 @@ async function handleHold(req, res, ctx, store, rawDomain, principal) {
|
|
|
115
93
|
return;
|
|
116
94
|
}
|
|
117
95
|
if (n.length > NOTE_MAX_CHARS) {
|
|
118
|
-
// 铸行之前拒:静默截断会把一条**被改写过的**审计事实写进一张追加式的表。
|
|
119
96
|
sendError(res, 400, "request.field_invalid", "the retention hold note is longer than this face accepts", {
|
|
120
97
|
detail: `note is ${n.length} characters; the limit is ${NOTE_MAX_CHARS}`,
|
|
121
98
|
});
|
|
@@ -126,12 +103,7 @@ async function handleHold(req, res, ctx, store, rawDomain, principal) {
|
|
|
126
103
|
}
|
|
127
104
|
}
|
|
128
105
|
const nowMs = Date.now();
|
|
129
|
-
const placedBy = principal ?? "operator";
|
|
130
|
-
// 🔴 状态变更与治理审计行走**同一个事务**(codex 对抗复审 R1-[high],验真后修)。两次独立提交下,
|
|
131
|
-
// 第二步失败会留下一次**无审计的状态变更** —— DELETE 那一支尤其重:冻结已解除、sweep 从下一拍起就能
|
|
132
|
-
// 删这个域的数据,而账上没有任何 `hold_released` 行说明是谁在什么时候解的。
|
|
133
|
-
// `mode`/`policyDays` 记的是**这一刻的部署事实**:一条 hold 行不带策略档,事后没人说得清"当时是在
|
|
134
|
-
// 什么留存策略下冻结的"。`fencingToken` 诚实留空 —— 它只属于 sweep 轮次,operator 动作不在任何一轮里。
|
|
106
|
+
const placedBy = principal ?? "operator";
|
|
135
107
|
await store.setHoldAudited({
|
|
136
108
|
domain,
|
|
137
109
|
held,
|
|
@@ -151,7 +123,6 @@ async function handleHold(req, res, ctx, store, rawDomain, principal) {
|
|
|
151
123
|
});
|
|
152
124
|
sendJson(res, 200, held ? { domain, held: true, placedBy, placedAtMs: nowMs } : { domain, held: false });
|
|
153
125
|
}
|
|
154
|
-
/** 审计读面(keyset 分页;窗与游标语义见文件头)。 */
|
|
155
126
|
async function handleAuditRead(req, res, store) {
|
|
156
127
|
const q = new URL(req.url ?? "", "http://x").searchParams;
|
|
157
128
|
const limitRaw = q.get("limit");
|
|
@@ -163,8 +134,6 @@ async function handleAuditRead(req, res, store) {
|
|
|
163
134
|
const beforeRaw = q.get("before");
|
|
164
135
|
const decoded = beforeRaw === null ? undefined : decodeCursor(beforeRaw);
|
|
165
136
|
if (decoded !== undefined && !decoded.ok) {
|
|
166
|
-
// 文案冻结在 wire 上(api-error-text-freeze 的一行);`detail` 是 additive 的**为什么**,
|
|
167
|
-
// 供调用方自诊(它描述的是调用方自己传进来的串,零跨租户信息)。
|
|
168
137
|
sendError(res, 400, "request.query_invalid", "before must be the opaque `nextBefore` cursor from a previous page of this endpoint", { detail: decoded.why });
|
|
169
138
|
return;
|
|
170
139
|
}
|
|
@@ -173,7 +142,6 @@ async function handleAuditRead(req, res, store) {
|
|
|
173
142
|
sendError(res, 400, "request.query_invalid", `domain must be at most ${DOMAIN_MAX_CHARS} characters`);
|
|
174
143
|
return;
|
|
175
144
|
}
|
|
176
|
-
// 多取一行用来判「还有没有下一页」——不进结果。`hasMore` 因此是**事实**而不是"limit 满了就猜有"。
|
|
177
145
|
const scanned = await store.listAudit({
|
|
178
146
|
...(domainRaw !== null ? { domain: domainRaw } : {}),
|
|
179
147
|
limit: Math.min(limit + 1, RETENTION_AUDIT_MAX_LIMIT + 1),
|
|
@@ -2,25 +2,15 @@ import { z } from "zod";
|
|
|
2
2
|
import { sendJson, sendError } from "../send.js";
|
|
3
3
|
import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
|
|
4
4
|
import { createLogger } from "../../observability/logger.js";
|
|
5
|
-
// 等待秒数的单一真源在车道:它同时被「放回认领时判这张票还撑不撑得过这段窗」用到(见那侧头注)。
|
|
6
|
-
// scope 的 wire 形也在车道(列举列 / 查询参数 / 删除体 / 游标四处同一份表示,见那侧头注)。
|
|
7
5
|
import { RULE_IMPORT_RETRY_AFTER_SEC, MAX_RULE_SCOPE_CHARS, parseRuleScope, serializeRuleScope } from "../../rules-consent.js";
|
|
8
6
|
const logger = createLogger();
|
|
9
7
|
export const RULES_CC_IMPORT_PREPARE_PATH = "/v1/rules/cc-import/prepare";
|
|
10
8
|
export const RULES_CC_IMPORT_REDEEM_PATH = "/v1/rules/cc-import/redeem";
|
|
11
|
-
/** 撤销面两口共用的字面路径(GET 列举 / DELETE 撤销)。 */
|
|
12
9
|
export const RULES_PATH = "/v1/rules";
|
|
13
|
-
/** 一页的上下限。50 = 一屏治理列表;200 = 一次响应的上界(一条规则行带 `adds[]`,不是一个标量)。
|
|
14
|
-
* 与 `GET /v1/sessions` 的 keyset 姿势同族(sessions-list.ts:137 的 `Math.min(100, …)`),数值按本面
|
|
15
|
-
* 的行宽另定 —— 抄一个数比抄一条理由更容易漂。 */
|
|
16
10
|
const RULES_PAGE_DEFAULT = 50;
|
|
17
11
|
const RULES_PAGE_MAX = 200;
|
|
18
|
-
/** 一层 settings 的上限。一份 CC settings 是人手写的 JSON,不是数据集;16 KiB 宽到装得下几百条 allow,
|
|
19
|
-
* 窄到一次请求不能拿它当上传通道。层数同理(core v1 只读三层,给 3 就够,多给等于给一个放大面)。 */
|
|
20
12
|
const MAX_LAYER_CONTENT_CHARS = 16_384;
|
|
21
13
|
const MAX_LAYERS = 3;
|
|
22
|
-
/** 🔴 边界必 schema(宪法 [2704])。`.strict()`:多给一个键 = 400,不是静默忽略 —— 一个把 `content`
|
|
23
|
-
* 拼错成 `contents` 的客户端应该当场知道,而不是拿到一份「零候选」的预览去纳闷。 */
|
|
24
14
|
const CcImportPrepareBodySchema = z
|
|
25
15
|
.object({
|
|
26
16
|
layers: z
|
|
@@ -28,12 +18,6 @@ const CcImportPrepareBodySchema = z
|
|
|
28
18
|
.object({
|
|
29
19
|
layer: z.enum(["userSettings", "projectSettings", "localSettings"]),
|
|
30
20
|
path: z.string().min(1).max(1024),
|
|
31
|
-
/** 项目层规则的作用域根。**用户自己的**目录标签(它只影响这条规则将来在哪些 cwd 下生效),
|
|
32
|
-
* 故不做**真实性**校验——server 上没有那棵树可对照,假装校验过才是谎。
|
|
33
|
-
* 🔴 但**非空**是硬的(codex 交叉复审 round5 [high] 一,验真后修):`root: ""` 会被 core 直接
|
|
34
|
-
* 变成 `{kind:"project", root:""}`,而空前缀在 `pathWithinRoot` 下**包含每一个 cwd** —— 一条
|
|
35
|
-
* 项目内规则就此悄悄升成全局规则;更糟的是本仓的读面对空 root 是整桶拒,于是一次「成功」的
|
|
36
|
-
* 导入会让这位租户**之后每一次**规则读写都失败。 */
|
|
37
21
|
root: z.string().min(1).max(1024),
|
|
38
22
|
content: z.string().max(MAX_LAYER_CONTENT_CHARS),
|
|
39
23
|
})
|
|
@@ -43,13 +27,6 @@ const CcImportPrepareBodySchema = z
|
|
|
43
27
|
})
|
|
44
28
|
.strict();
|
|
45
29
|
const CcImportRedeemBodySchema = z.object({ ticket: z.string().min(1).max(190) }).strict();
|
|
46
|
-
/** `DELETE /v1/rules` 的体。`.strict()` 同族理由:把 `scope` 拼错成 `scopes` 的客户端应该当场知道,
|
|
47
|
-
* 而不是拿到一个「删掉了 global 那条」的意外结果。`principal` = operator 越权域(缺席 = 删自己的)。
|
|
48
|
-
* 长度上限与 principal 入口上限同源(`PRINCIPAL_MAX_LENGTH = 190`,security.ts);`rule` 的 1024 覆盖
|
|
49
|
-
* core 的 `MAX_RULE_TEXT_CHARS`(512,铸侧真上界)且留足余量。
|
|
50
|
-
* 🔴 `scope` 的上限**不再是本文件自己挑的数**(A-054.24):改由铸侧的尺派生 —— 见
|
|
51
|
-
* {@link MAX_RULE_SCOPE_CHARS} 顶注(此前独立写死 1088,而铸侧能落到 4096+,于是超限 root 的规则
|
|
52
|
-
* 「列得出、删不掉」)。撤销口的收窄若严于铸侧,等于造一条**不可撤的常驻放行规则**,方向是错的一侧。 */
|
|
53
30
|
const RuleRevokeBodySchema = z
|
|
54
31
|
.object({
|
|
55
32
|
rule: z.string().min(1).max(1024),
|
|
@@ -63,13 +40,6 @@ const RulesCursorSchema = z
|
|
|
63
40
|
function encodeRulesCursor(c) {
|
|
64
41
|
return Buffer.from(JSON.stringify(c)).toString("base64url");
|
|
65
42
|
}
|
|
66
|
-
/**
|
|
67
|
-
* 读不出形 ⇒ `{ok:false, why}`,由调用方拒(400)。两条纪律:
|
|
68
|
-
* · **刻意不**「读不出就当第一页」(sessions-list 的旧姿势):那条腿的游标只影响顺序,这条腿的游标
|
|
69
|
-
* 一旦被静默丢弃,客户端会以为自己列完了整份清单。
|
|
70
|
-
* · 失败**带着原因出来**而不是塌成一个 `undefined`:调用点把 `why` 写进服务端日志。一个只回默认值的
|
|
71
|
-
* catch 是本仓在册的静默降级形(#191 门② SHAPE C)——这里不需要那种便利,原因本来就有用。
|
|
72
|
-
*/
|
|
73
43
|
function decodeRulesCursor(raw) {
|
|
74
44
|
try {
|
|
75
45
|
const parsed = RulesCursorSchema.safeParse(JSON.parse(Buffer.from(raw, "base64url").toString()));
|
|
@@ -79,10 +49,6 @@ function decodeRulesCursor(raw) {
|
|
|
79
49
|
return { ok: false, why: err instanceof Error ? err.message : String(err) };
|
|
80
50
|
}
|
|
81
51
|
}
|
|
82
|
-
/** 排序键 —— 与 `RuleConsentLane.listRules` 的 (scope, rule) 二元排序**等价**(两处若各写一份必然漂,
|
|
83
|
-
* 而漂的表现是分页静默漏行)。分隔符必须**小于**任何可能出现的字符,否则串比较与元组比较会在
|
|
84
|
-
* 「一个 scope 是另一个的前缀」处分岔(`project:/a` vs `project:/ab`)。用空格是错的 ——
|
|
85
|
-
* `Bash(echo hi)` 里就有一个;\u0000 是码点最小的字符,而且规则文本与路径里都不可能出现它。 */
|
|
86
52
|
const rowKey = (r) => `${r.scope}\u0000${r.rule}`;
|
|
87
53
|
export async function handleRules(req, res, url, ctx) {
|
|
88
54
|
const miss = { fell: false };
|
|
@@ -94,22 +60,14 @@ async function handleRulesBody(req, res, url, ctx, miss) {
|
|
|
94
60
|
const { readJson, rateLimited } = ctx.helpers;
|
|
95
61
|
const isPrepare = req.method === "POST" && url === RULES_CC_IMPORT_PREPARE_PATH;
|
|
96
62
|
const isRedeem = req.method === "POST" && url === RULES_CC_IMPORT_REDEEM_PATH;
|
|
97
|
-
// 🔴 判路写**字面量**而不是引 `RULES_PATH`(与 server.ts 的 `ROUTE_LABEL_LITERALS` 那条注同族理由):
|
|
98
|
-
// 路由名册门与文案接线门的枚举器扫的是 `url === "…"` 的**源码文本**,引常量会让这两口对两道门隐形
|
|
99
|
-
// ——那不是「过了门」,是绕过门。常量仍导出给测试/SDK 侧当单一真源用。
|
|
100
63
|
const isList = req.method === "GET" && url === "/v1/rules";
|
|
101
64
|
const isRevoke = req.method === "DELETE" && url === "/v1/rules";
|
|
102
65
|
if (!isPrepare && !isRedeem && !isList && !isRevoke) {
|
|
103
66
|
miss.fell = true;
|
|
104
67
|
return;
|
|
105
68
|
}
|
|
106
|
-
// 🔴 `prepare` 每次调用**都会**落一条 pending 审批记录 + 一张票 —— 它是本域唯一一条「一次请求 = 一行」
|
|
107
|
-
// 的写面,没有限流就是一条行洪口。与 `POST /v1/tasks/:id/asks/:askId/decision` 同姿势(限流在最前)。
|
|
108
|
-
// 撤销面两口同罩:`DELETE` 是写(每次铸一个 dot + 一条墓碑),`GET` 是整桶读 —— 两者都不该无限打。
|
|
109
69
|
if (rateLimited(req, res))
|
|
110
70
|
return;
|
|
111
|
-
// 鉴权**先于**可用性(501-before-auth 先例的反面同理):这四口没有可被探测的部署事实,但
|
|
112
|
-
// principal 是本域的全部授权基础,先立住它再谈别的。
|
|
113
71
|
const principal = gatedPrincipal(req, deps.config);
|
|
114
72
|
if (principal === undefined) {
|
|
115
73
|
sendError(res, 401, "auth.unauthorized", "unauthorized");
|
|
@@ -120,8 +78,6 @@ async function handleRulesBody(req, res, url, ctx, miss) {
|
|
|
120
78
|
sendError(res, 501, "capability.rule_store_required", "this deployment has no permission-rule store wired");
|
|
121
79
|
return;
|
|
122
80
|
}
|
|
123
|
-
// ── 撤销面两口(#203 §2)──────────────────────────────────────────────────────────────────────
|
|
124
|
-
// 放在读体之前:`GET` 没有体,`readJson` 对它是一次白等。
|
|
125
81
|
if (isList) {
|
|
126
82
|
await handleRuleList(req, res, principal, lane, deps.config.operatorPrincipals);
|
|
127
83
|
return;
|
|
@@ -146,9 +102,6 @@ async function handleRulesBody(req, res, url, ctx, miss) {
|
|
|
146
102
|
}
|
|
147
103
|
const prepared = await lane.prepareImport(principal, parsed.data.layers);
|
|
148
104
|
if (!prepared.ok) {
|
|
149
|
-
// 413(不是 400):族语义就是「体/条目超上限」,与附件/图片同码同状态(附录 A 已成文)。
|
|
150
|
-
// 帽的位置与理由(为什么单层字节帽不够、为什么帽在**铸票之前**)见 `rules-consent.ts` 的
|
|
151
|
-
// `MAX_IMPORT_CANDIDATES`。
|
|
152
105
|
sendError(res, 413, "request.payload_too_large", "too many allow entries across the submitted settings layers");
|
|
153
106
|
return;
|
|
154
107
|
}
|
|
@@ -163,31 +116,16 @@ async function handleRulesBody(req, res, url, ctx, miss) {
|
|
|
163
116
|
const redeemed = await lane.redeemImport(principal, parsed.data.ticket);
|
|
164
117
|
if (!redeemed.ok) {
|
|
165
118
|
logger.info("rule import ticket refused", { reason: redeemed.reason, detail: redeemed.detail, retryable: redeemed.retryable === true });
|
|
166
|
-
// 🔴 codex 交叉复审 round2 [medium](验真后修):**可重试**那一支不能折进 404。
|
|
167
|
-
// 为什么这不破坏零存在性 oracle:走到这一支的调用方**已经赢下了一次 principal 绑定的认领** ——
|
|
168
|
-
// 也就是说他已经证明了这张票是他自己的、没过期、没用过。对他说「稍后再试」泄不出任何**别人**的
|
|
169
|
-
// 存在性(伪造 / 别人的 / 过期 / 已用过 四类仍然同形 404,一个字都没变)。反过来把它折成 404 才是
|
|
170
|
-
// 真损失:round1 修好的「认领已放回、这张票还能用」在 wire 上被抹掉,客户端按常规 404 处置一丢了之。
|
|
171
119
|
if (redeemed.retryable === true) {
|
|
172
|
-
res.setHeader("retry-after", String(RULE_IMPORT_RETRY_AFTER_SEC));
|
|
120
|
+
res.setHeader("retry-after", String(RULE_IMPORT_RETRY_AFTER_SEC));
|
|
173
121
|
sendError(res, 503, "state.rule_import_retry", "the rule import could not be completed right now; the ticket is still usable — retry", { retryAfterSec: RULE_IMPORT_RETRY_AFTER_SEC });
|
|
174
122
|
return;
|
|
175
123
|
}
|
|
176
|
-
// 同形 404(顶注):四类成因**不上 wire**,只进服务端日志。
|
|
177
124
|
sendError(res, 404, "not_found.rule_ticket", "no such import ticket");
|
|
178
125
|
return;
|
|
179
126
|
}
|
|
180
127
|
sendJson(res, 200, { result: redeemed.result });
|
|
181
128
|
}
|
|
182
|
-
/**
|
|
183
|
-
* 解析**目标 principal**(自有域 vs operator 越权域)。
|
|
184
|
-
*
|
|
185
|
-
* 🔴 判据链是硬的:①没给 = 自己(最常见的路,零授权提问);②给了但等于自己 = 还是自己(一个把
|
|
186
|
-
* principal 显式写出来的客户端不该因此被要求当 operator);③给了别人 = **必须** `explicitOperatorOk`。
|
|
187
|
-
* 第三条用的是 `explicitOperatorOk` 而不是 `isOperator`:后者在 `OPERATOR_PRINCIPALS` 为空时「人人
|
|
188
|
-
* 都是 operator」,那在跨租户读规则这条轴上是一道世界可写的门(principal-gate.ts 的头注逐字)。
|
|
189
|
-
* **绝不**从 service token 推断:一个共享的部署凭证不是一个人,而这条口的授权对象是人。
|
|
190
|
-
*/
|
|
191
129
|
function resolveTargetPrincipal(requested, caller, operatorPrincipals) {
|
|
192
130
|
if (requested === undefined || requested === caller)
|
|
193
131
|
return { ok: true, principal: caller };
|
|
@@ -195,19 +133,14 @@ function resolveTargetPrincipal(requested, caller, operatorPrincipals) {
|
|
|
195
133
|
return { ok: false };
|
|
196
134
|
return { ok: true, principal: requested };
|
|
197
135
|
}
|
|
198
|
-
/** `GET /v1/rules` —— 列出一位 principal 名下**活着**的规则。 */
|
|
199
136
|
async function handleRuleList(req, res, caller, lane, operatorPrincipals) {
|
|
200
|
-
// `url` 在派发前已被剥掉 query,查询参数只能从 `req.url` 重新取(与 `/v1/outcomes` 同姿势)。
|
|
201
137
|
const q = new URL(req.url ?? "/", "http://localhost").searchParams;
|
|
202
138
|
const target = resolveTargetPrincipal(q.get("principal") ?? undefined, caller, operatorPrincipals);
|
|
203
139
|
if (!target.ok) {
|
|
204
140
|
sendError(res, 403, "auth.operator_only", "reading another principal's rules is an operator-only action");
|
|
205
141
|
return;
|
|
206
142
|
}
|
|
207
|
-
// limit:非数/越界一律夹取(与 sessions-list 同姿势——一个分页参数写错不该让治理面整个打不开)。
|
|
208
143
|
const limit = Math.min(RULES_PAGE_MAX, Math.max(1, Number(q.get("limit") ?? RULES_PAGE_DEFAULT) || RULES_PAGE_DEFAULT));
|
|
209
|
-
// scope 过滤:**给了就必须读得出形**。读不出不当「没过滤」——那会把一次「只看这个项目」的请求
|
|
210
|
-
// 悄悄变成一次全量列举,而客户端会照单全收当成该项目的清单。
|
|
211
144
|
const scopeRaw = q.get("scope");
|
|
212
145
|
const scopeFilter = scopeRaw === null ? undefined : parseRuleScope(scopeRaw);
|
|
213
146
|
if (scopeRaw !== null && scopeFilter === undefined) {
|
|
@@ -222,15 +155,9 @@ async function handleRuleList(req, res, caller, lane, operatorPrincipals) {
|
|
|
222
155
|
return;
|
|
223
156
|
}
|
|
224
157
|
const cursor = decoded?.ok === true ? decoded.cursor : undefined;
|
|
225
|
-
// 过滤键用**规范形**(`serializeRuleScope` 的产物),不是客户端交上来的原串:行上的 scope 是规范的,
|
|
226
|
-
// 拿一个等价但拼法不同的原串去比会静默过滤成空表。空串 = 未过滤(游标里也用这一个表示)。
|
|
227
158
|
const filterKey = scopeFilter === undefined ? "" : serializeRuleScope(scopeFilter);
|
|
228
159
|
const listing = await lane.listRules(target.principal);
|
|
229
160
|
if (cursor !== undefined && (cursor.rev !== listing.rev || cursor.principal !== target.principal || cursor.scope !== filterKey)) {
|
|
230
|
-
// 游标绑 (rev, principal, scope)(见 {@link RulesCursor} 头注)。**拒**而不是从头给:静默重置会让
|
|
231
|
-
// 客户端把第二页当成「接着上一页」,治理面上于是出现一份既漏行又重行的清单,而没有任何人知道。
|
|
232
|
-
// 三项合并成一句文案是刻意的:对客户端来说处置完全相同(丢掉游标从头列),而把「你换了 principal」
|
|
233
|
-
// 单独成句会让一个 operator 从别人桶的 rev 上读出信息。
|
|
234
161
|
sendError(res, 400, "request.query_invalid", "the rules cursor was minted at a different revision — restart the listing", { rev: listing.rev });
|
|
235
162
|
return;
|
|
236
163
|
}
|
|
@@ -242,7 +169,6 @@ async function handleRuleList(req, res, caller, lane, operatorPrincipals) {
|
|
|
242
169
|
: undefined;
|
|
243
170
|
sendJson(res, 200, { rules: page, rev: listing.rev, ...(nextCursor !== undefined ? { nextCursor } : {}) });
|
|
244
171
|
}
|
|
245
|
-
/** `DELETE /v1/rules` —— 按内容(rule, scope)撤销。 */
|
|
246
172
|
async function handleRuleRevoke(res, rawBody, caller, lane, operatorPrincipals) {
|
|
247
173
|
const parsed = RuleRevokeBodySchema.safeParse(rawBody);
|
|
248
174
|
if (!parsed.success) {
|
|
@@ -256,38 +182,20 @@ async function handleRuleRevoke(res, rawBody, caller, lane, operatorPrincipals)
|
|
|
256
182
|
}
|
|
257
183
|
const target = resolveTargetPrincipal(parsed.data.principal, caller, operatorPrincipals);
|
|
258
184
|
if (!target.ok) {
|
|
259
|
-
// 与读那一半**同码同姿势**:方向(收紧)决定了这条越权域存在,身份判据不因方向而放松。
|
|
260
185
|
sendError(res, 403, "auth.operator_only", "revoking another principal's rules is an operator-only action");
|
|
261
186
|
return;
|
|
262
187
|
}
|
|
263
188
|
const outcome = await lane.removeRule({ principal: target.principal, rule: parsed.data.rule, scope });
|
|
264
189
|
switch (outcome.status) {
|
|
265
190
|
case "removed":
|
|
266
|
-
// 🔴 `stillLive` **必须上 wire**(design/203 v2 §6 F3)。它为真的意思是「墓碑落了,但这次调用
|
|
267
|
-
// 期间又落了一次新的批准,规则按 add-wins 仍然活着」——把它折进 200 不说,客户端会把一次
|
|
268
|
-
// **没有完成**的撤销读成完成,而那正是治理面最不能出错的地方。
|
|
269
191
|
sendJson(res, 200, { status: "removed", rev: outcome.rev, stillLive: outcome.stillLive });
|
|
270
192
|
return;
|
|
271
193
|
case "no-op": {
|
|
272
|
-
// 幂等:重复删 / 本来就没有,一律 200 `no-op`。**不是 404** —— 一个「这条规则存在过吗」的
|
|
273
|
-
// 404 就是一台存在性 oracle(operator 越权域下更是跨租户的那种),而删除对「有没有过」
|
|
274
|
-
// 本来就不该作 oracle。客户端要判「我删到东西了吗」,读 `status` 即可。
|
|
275
|
-
//
|
|
276
|
-
// 🔴 `stillLive` 这一支**也要有**(codex 对抗复审 R1-F2,验真后修)。core 的原语在初始快照里
|
|
277
|
-
// 找不到匹配 dot 时**立刻**回 no-op —— 它不做 removed 那支的读回。于是「快照之后、应答之前落了
|
|
278
|
-
// 一次批准」的窗口里,规则**是活的**而端点说 no-op,客户端合理地读成「已经没有这条规则了」。
|
|
279
|
-
// 那与 F3 修好的 removed+stillLive 是**同一个**危害,只是走了另一支。
|
|
280
|
-
// 处置 = server 自己补一次读回,把**观察到的**事实如实上 wire;两支 200 的键集因此同形,消费端
|
|
281
|
-
// 一套分支读得通。读回失败不改判(no-op 本身是 core 裁定的)⇒ 保守报 `stillLive:false`?**不**:
|
|
282
|
-
// 那是编一个结论。读不出来就让它抛,由本域的全局错误臂报 500 —— 一个说不清结果的撤销不该回 200。
|
|
283
|
-
// 让 core 的原语自己报这一位是更根治的形,已作为上游请托登记(server 不改上游语义,[2862])。
|
|
284
194
|
const stillLive = (await lane.listRules(target.principal)).rows.some((r) => r.rule === parsed.data.rule && r.scope === serializeRuleScope(scope));
|
|
285
195
|
sendJson(res, 200, { status: "no-op", rev: outcome.rev, stillLive });
|
|
286
196
|
return;
|
|
287
197
|
}
|
|
288
198
|
case "failed":
|
|
289
|
-
// core 对 `failed` 的承诺是「读回确认什么都没写」或「读回本身也失败了,结局不定」——两种都是
|
|
290
|
-
// **重试可改判**的,所以 503 而不是 500。理由与 `state.rule_import_retry` 同族。
|
|
291
199
|
logger.info("rule revoke failed", { error: outcome.error });
|
|
292
200
|
sendError(res, 503, "state.rule_remove_failed", "the rule could not be revoked right now — retry, then reconcile with GET /v1/rules");
|
|
293
201
|
return;
|