@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
|
@@ -5,23 +5,13 @@ export async function handleLeader(req, res, url, ctx) {
|
|
|
5
5
|
await handleLeaderBody(req, res, url, ctx, miss);
|
|
6
6
|
return !miss.fell;
|
|
7
7
|
}
|
|
8
|
-
/** 路由体 = 从 `server.ts` 的 `handle()` 里**整段剪切**过来的原文(唯一改动:统一去缩进两格)。
|
|
9
|
-
* 注意本域的落空语义:`deps.leaderEndpoint` 在但 URL 不匹配、或 endpoint 返回 undefined,都要**继续**
|
|
10
|
-
* 往下走(原文的 if 块不 return),故只有真的应答过才算已处理——由末尾的 `miss.fell` 如实表达。 */
|
|
11
8
|
async function handleLeaderBody(req, res, url, ctx, miss) {
|
|
12
9
|
const { deps } = ctx;
|
|
13
10
|
const { readJson, rateLimited, quotaExceeded, leaseDenied } = ctx.helpers;
|
|
14
|
-
// v2 leader endpoint (design/50): POST /v1/leader (async, like /v1/runs) + GET /v1/leader/:id.
|
|
15
11
|
if (deps.leaderEndpoint) {
|
|
16
12
|
const isLeaderPost = req.method === "POST" && url === "/v1/leader";
|
|
17
13
|
const isLeaderGet = req.method === "GET" && /^\/v1\/leader\/[^/]+$/.test(url);
|
|
18
14
|
if (isLeaderPost || isLeaderGet) {
|
|
19
|
-
// BL-4: gate the leader route through the SAME identity layer as /v1/runs — previously it skipped
|
|
20
|
-
// authorize/requirePrincipal entirely and recorded no owner, so any service-token holder could submit
|
|
21
|
-
// and read ANY tenant's leader run. Enforce requirePrincipal (401 before any work), then pass the
|
|
22
|
-
// resolved principal so the endpoint records it as the run owner (POST) and 404s non-owners (GET).
|
|
23
|
-
// 🔴 F-fix: VERIFIED identity (gatedPrincipal) for the leader-run owner — a spoofable header would let a
|
|
24
|
-
// direct-door caller submit/read another tenant's leader run (the recorded owner gates the GET 404).
|
|
25
15
|
if (deps.config.requirePrincipal && !gatedPrincipal(req, deps.config)) {
|
|
26
16
|
sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
|
|
27
17
|
return;
|
|
@@ -38,9 +28,6 @@ async function handleLeaderBody(req, res, url, ctx, miss) {
|
|
|
38
28
|
sendError(res, 400, "request.invalid_json", "invalid JSON body");
|
|
39
29
|
return;
|
|
40
30
|
}
|
|
41
|
-
// #193 车4 件1:`Idempotency-Key` 头接到 leader 的 durable 提交幂等上(与 POST /v1/runs、
|
|
42
|
-
// POST /v1/images/bakes 同一条头名)。原始头值原样递给 endpoint —— 按提交者 scope + 定宽
|
|
43
|
-
// hash 都发生在那一层(`leaderIdemKey`),路由层不做语义加工。
|
|
44
31
|
const r = await deps.leaderEndpoint.handle("POST", url, body, requester, headerStr(req.headers["idempotency-key"]) ?? null);
|
|
45
32
|
if (r) {
|
|
46
33
|
sendJson(res, r.status, r.body);
|
|
@@ -3,29 +3,8 @@ import { sendJson, sendError } from "../send.js";
|
|
|
3
3
|
import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
|
|
4
4
|
export const MEMORY_BUNDLE_EXPORT_PATH = "/v1/memory/export";
|
|
5
5
|
export const MEMORY_BUNDLE_IMPORT_PATH = "/v1/memory/import";
|
|
6
|
-
/** export 请求体 —— **形**验(义归 core;见文件头「验型的分权」)。 */
|
|
7
6
|
const ExportRequestSchema = z.object({ scopes: z.array(z.string().min(1)).min(1) });
|
|
8
|
-
/** import 请求体 —— 只断言 `bundle` 是个**对象**;整包判决是 core 的单一属主面。
|
|
9
|
-
* `z.record(z.string(), z.unknown())` 而不是 `z.object({...})`:在本层写出 bundle 的字段表就是复制
|
|
10
|
-
* core 的 schema(第二真源),而 `z.unknown()` 的 record 恰好表达「结构由下游判」。 */
|
|
11
7
|
const ImportRequestSchema = z.object({ bundle: z.record(z.string(), z.unknown()) });
|
|
12
|
-
/**
|
|
13
|
-
* core 抛出的**已分类**错误 → HTTP 状态。闭集穷举 `switch`:core 新增一个码 ⇒ 这里落 `undefined`
|
|
14
|
-
* ⇒ 走 500 fail-loud(而不是被某个默认臂静默塞进 4xx)。安全轴上的静默默认值正是本仓禁止的东西。
|
|
15
|
-
*
|
|
16
|
-
* · `config.memory_{export,import}_request` = **调用方**给的请求参数不合法(非空/去重/可序列化)⇒ 400;
|
|
17
|
-
* · `memory.export_incomplete` = 店**声明自己说不清**(chain-degraded / 无导出复合面)⇒ 409:不是请求
|
|
18
|
-
* 的错、也不是服务器故障,是**当前状态**下这个包铸不出来(去修 chain 或换后端后重试即改判);
|
|
19
|
-
* · `memory.import_rejected` = 请求**形**合法但**内容**不可接受(整包拒批,零落地)⇒ 422 —— 与 400
|
|
20
|
-
* 分家是承重的:400 说"你的 HTTP 请求写错了",422 说"包本身被判不合格",两句话的运维动作不同。
|
|
21
|
-
*
|
|
22
|
-
* 🔴 每一支都把码**再写一遍字面量**(而不是 `sendError(res, status, code, …)` 把变量透下去):错误码
|
|
23
|
-
* 目录门(test/error-code-catalog-live.test.ts)是**扫源码字面量**的,变量态的码整条对它隐形 ⇒ 这四个
|
|
24
|
-
* 码会「wire 上发得出来、附录 A 里查不到」。同门另有一张中继登记表可走,但那张表是**有上限的棘轮**
|
|
25
|
-
* (「码不写字面量」不许变成默认写法),而这里本来就该是一张闭集词表 —— 写字面量既过门又把闭集写实。
|
|
26
|
-
*/
|
|
27
|
-
// ⚠️ 形参叫 `coreCode` 而不是 `code`:错误体自 core 3.0.0 起**只有** `errorCode`,而 test/error-code-key-gate
|
|
28
|
-
// 扫的是「还在发 legacy `code` 键」的站点(文本判据)——一个叫 `code` 的形参会被它当成那种站点收走。
|
|
29
8
|
function sentCoreCode(res, coreCode, message) {
|
|
30
9
|
switch (coreCode) {
|
|
31
10
|
case "config.memory_export_request":
|
|
@@ -44,36 +23,19 @@ function sentCoreCode(res, coreCode, message) {
|
|
|
44
23
|
return false;
|
|
45
24
|
}
|
|
46
25
|
}
|
|
47
|
-
/** core 的抛出形 = `Error` + `.code`(engine.js 逐字)。裸 as-cast 禁令下的判别式读法。 */
|
|
48
26
|
function codeOf(err) {
|
|
49
27
|
if (!(err instanceof Error))
|
|
50
28
|
return undefined;
|
|
51
29
|
const { code } = err;
|
|
52
30
|
return typeof code === "string" ? code : undefined;
|
|
53
31
|
}
|
|
54
|
-
/**
|
|
55
|
-
* 引擎调用的统一收尾:已分类的码**原样**上 wire(码与 core 的诊断文案都不改写 —— 那句文案是运维排障
|
|
56
|
-
* 的第一手材料,判据帖 G3 就断言它含 "chain-degraded");未分类的一律 500 且**不回显**内部文本。
|
|
57
|
-
* 返回 true = 已应答。
|
|
58
|
-
*/
|
|
59
32
|
function sentClassifiedFailure(res, ctx, face, err) {
|
|
60
33
|
const code = codeOf(err);
|
|
61
34
|
if (code !== undefined && sentCoreCode(res, code, err instanceof Error ? err.message : String(err)))
|
|
62
35
|
return true;
|
|
63
36
|
ctx.deps.logger?.warn?.(`memory_bundle_${face}_failed`, { err: String(err) });
|
|
64
|
-
return false;
|
|
37
|
+
return false;
|
|
65
38
|
}
|
|
66
|
-
/**
|
|
67
|
-
* `MemoryImportReport` → wire 的**显式白名单**投影。
|
|
68
|
-
*
|
|
69
|
-
* 🔴 为什么是显式白名单而不是整行透传:报告是**治理处置**的清单,core 日后加一个新的处置座(例如又一
|
|
70
|
-
* 类"被扣下的行"),整行透传会让它**静默**上 wire 而没人给它写过契约;白名单让它变成**编译错误**
|
|
71
|
-
* (`test/memory-bundle-http.test.ts` 的差集断言 = `wire-whitelist-exhaustiveness` 同款门形)。
|
|
72
|
-
* 今天**每一个**键都在场:每一条都是 operator 判「这次导入到底发生了什么」必须看到的东西,一个都
|
|
73
|
-
* 不该省 —— 「每个否定处置都被报告,绝不静默」是 core 对这份报告的原话。
|
|
74
|
-
*/
|
|
75
|
-
// 🔴 **不要**给这个函数标 `: Record<string, unknown>` 返回型:那会让 `keyof ReturnType<…>` 塌成 `string`,
|
|
76
|
-
// 上面那道编译期差集门当场变成**恒真**(vacuous)—— 写这条钉时正踩到过。返回型交给推断,键集才是字面的。
|
|
77
39
|
export function buildImportReportWire(r) {
|
|
78
40
|
return {
|
|
79
41
|
v: r.v,
|
|
@@ -107,35 +69,21 @@ async function handleMemoryBundleBody(req, res, url, ctx, miss) {
|
|
|
107
69
|
miss.fell = true;
|
|
108
70
|
return;
|
|
109
71
|
}
|
|
110
|
-
// ── 身份(401)── direct-door safe:验证过的身份,不是可伪造的头。
|
|
111
72
|
const principal = gatedPrincipal(req, deps.config);
|
|
112
73
|
if (deps.config.requirePrincipal && !principal) {
|
|
113
74
|
sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
|
|
114
75
|
return;
|
|
115
76
|
}
|
|
116
|
-
// ── 授权(403)── 治理面 fail-closed,与 adoption / retention-ops 同一条门。
|
|
117
|
-
// ⚠️ 文案写**内联字面量**而不是抽个模块常量:文案冻结门(test/api-error-text-freeze.test.ts)是**扫源码
|
|
118
|
-
// 文本**的,把 message 换成一个常量名的写法它结构性够不到 ⇒ 这句 wire 文案就没有被冻住。
|
|
119
|
-
// ⚠️ 本段注释里也不许照抄一个带状态码与引号码名的调用样例 —— 那道门的提取器会把**注释里**的样例
|
|
120
|
-
// 当成一条真站点收走(写这条注时真的踩到过:凭空多出一个 `403:code` 对)。
|
|
121
|
-
// 两口同受众同理由,所以两处逐字同一句(族断言按去重集合比,同串出现两次不改判)。
|
|
122
77
|
if (!explicitOperatorOk(principal, deps.config.operatorPrincipals)) {
|
|
123
78
|
sendError(res, 403, "auth.operator_only", "memory governance bundles are an operator-only deployment action");
|
|
124
79
|
return;
|
|
125
80
|
}
|
|
126
|
-
// ── 能力(501)── 在授权之后:部署形态不对越权者泄漏。
|
|
127
|
-
// ⚠️ 与 `capability.memory_store_required`(GET 导出面要 DB 平面)**刻意不合并**:两码的运维动作相反
|
|
128
|
-
// (那条要 pg|tidb,本条要引擎在场且后端带 bundle 复合面),消费端真会分支——附录 A 判据②。
|
|
129
|
-
// 🔴 缺席有**两个**成因,文案两个都点名(codex [medium] 后修:此前只说「引擎没接线」,而 pg/tidb 部署
|
|
130
|
-
// 是引擎接了、后端没那两个可选面 —— 只说一半会让运维去查一个根本没问题的旋钮)。两因同码:消费端
|
|
131
|
-
// 的分支一样(本部署做不了 bundle,换部署形态),分成两码只会让码表长一倍而判据不变(判据②)。
|
|
132
81
|
const engineExport = deps.memoryBundleExport;
|
|
133
82
|
const engineImport = deps.memoryBundleImport;
|
|
134
83
|
if (engineExport === undefined || engineImport === undefined) {
|
|
135
84
|
sendError(res, 501, "capability.memory_engine_required", "memory governance bundles are not available on this deployment — they require a wired memory engine (MEMORY_ENGINE=on) whose backend implements the v2-c bundle composite (exportSnapshotOf / importBundleCommit); the file memory backend does, the pg/tidb memory backends do not");
|
|
136
85
|
return;
|
|
137
86
|
}
|
|
138
|
-
// ── 验型(400)── 体读的 413/400(超长 / 坏 JSON)由顶层 typed catch 统一处理(鉴权已完成)。
|
|
139
87
|
const raw = await ctx.helpers.readJson(req);
|
|
140
88
|
if (isExport) {
|
|
141
89
|
const parsed = ExportRequestSchema.safeParse(raw);
|
|
@@ -144,14 +92,10 @@ async function handleMemoryBundleBody(req, res, url, ctx, miss) {
|
|
|
144
92
|
return;
|
|
145
93
|
}
|
|
146
94
|
try {
|
|
147
|
-
// 🔴 bundle **原样**下发(含 `doc` 披露段)—— 投影它就是替铸包方复述披露,见文件头。
|
|
148
95
|
const bundle = await engineExport(parsed.data.scopes);
|
|
149
96
|
sendJson(res, 200, bundle);
|
|
150
97
|
}
|
|
151
98
|
catch (err) {
|
|
152
|
-
// 未分类 ⇒ 500,文案是**内联整串**(不是 `memory bundle ${face} failed` 模板):冻结门的模板臂只
|
|
153
|
-
// 钉到第一个 `${` 为止,"memory bundle " 这个前缀会把「哪一半炸了」留在门外,而那正是消费端能从
|
|
154
|
-
// 500 上读到的全部信息。同族先例:"memory export failed" / "memory sync failed"(memory-policy.ts)。
|
|
155
99
|
if (!sentClassifiedFailure(res, ctx, "export", err))
|
|
156
100
|
sendError(res, 500, "internal.error", "memory bundle export failed");
|
|
157
101
|
}
|
|
@@ -163,10 +107,6 @@ async function handleMemoryBundleBody(req, res, url, ctx, miss) {
|
|
|
163
107
|
return;
|
|
164
108
|
}
|
|
165
109
|
try {
|
|
166
|
-
// 整包判决(结构/完整性/授权信封)全在 core:本层递交的就是调用方给的那个对象。
|
|
167
|
-
// 递交的是**未校验**的对象:整包判决(结构/完整性/授权信封)是 core 的单一属主面,所以 seam 的
|
|
168
|
-
// 入参类型就写 `unknown` —— 在本层把它断言成 MemoryExportBundle 是**说谎**(我们并不知道它是),
|
|
169
|
-
// 而说谎的那一下正好把类型检查关灯(type-hygiene 门抓的就是这个)。
|
|
170
110
|
const report = await engineImport(parsed.data.bundle);
|
|
171
111
|
sendJson(res, 200, buildImportReportWire(report));
|
|
172
112
|
}
|
|
@@ -3,22 +3,7 @@ import { parseMemorySyncRequest } from "../../memory-sync.js";
|
|
|
3
3
|
import { sendJson, sendError } from "../send.js";
|
|
4
4
|
import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
|
|
5
5
|
const ORG_PREFIX = "org:";
|
|
6
|
-
/**
|
|
7
|
-
* #271 件3(#275 A2 车立的欠账,[4163] 我方认领)—— `GET /v1/memory/export` 的 keyset 分页上限。
|
|
8
|
-
*
|
|
9
|
-
* 在此之前本面是**整盘导出**:一个几千条目的盘就是一个几 MB 的响应体,壳侧(sema-web 只读页)
|
|
10
|
-
* 无从分次要 —— 那是它真实撞到的天花板。分页是 **additive** 的:`?limit=` 缺席 ⇒ 旧契约逐字不变
|
|
11
|
-
* (全量数组、响应键一个不多),所以现行消费端零破坏;要分页的调用方自己带 `limit` 进来。
|
|
12
|
-
*
|
|
13
|
-
* ⚠️ 诚实记档:本面的分页封的是**回体**(wire 天花板),不是 DB 读 —— reader
|
|
14
|
-
* (`exportMemoryScope`)仍是整 scope 一次读出。真正的下推需要 core 的 `MemoryBackend.listHeaders`
|
|
15
|
-
* 长出分页参数(上游接缝,源头修复纪律:不在本层用别的口硬凑)。壳侧的痛点(响应体大小/首屏)由
|
|
16
|
-
* 本形解决,库侧的读放大另案。
|
|
17
|
-
*/
|
|
18
6
|
export const MEMORY_EXPORT_MAX_LIMIT = 500;
|
|
19
|
-
/** 游标 = 条目 `id` 的不透明 base64url(声明序=id 升序;id 是 entry 平面的 PK ⇒ 唯一,单键即全序,
|
|
20
|
-
* 不像 roster 的 `(spawnedAt, handle)` 需要 tiebreak)。`before` / `nextBefore` 是本仓分页的
|
|
21
|
-
* **同一套词**(roster 先例):调用方只把上一页的 `nextBefore` 原样回传,不解读内容。 */
|
|
22
7
|
function encodeExportCursor(c) {
|
|
23
8
|
return Buffer.from(JSON.stringify(c)).toString("base64url");
|
|
24
9
|
}
|
|
@@ -37,38 +22,14 @@ function decodeExportCursor(raw) {
|
|
|
37
22
|
return { ok: false, why: "cursor payload lacks a non-empty `id`" };
|
|
38
23
|
return { ok: true, cursor: { id } };
|
|
39
24
|
}
|
|
40
|
-
/** 本面是否让目录参与判决。闭集穷举 switch:新增第三个模式是**编译**错误,不会静默落进某个默认臂
|
|
41
|
-
* ——这条门在授权轴上,静默默认值正是它不许有的东西。 */
|
|
42
25
|
function directoryDecidesThisFace(mode) {
|
|
43
26
|
switch (mode) {
|
|
44
27
|
case "enforce":
|
|
45
28
|
return true;
|
|
46
29
|
case "audit":
|
|
47
|
-
return false;
|
|
30
|
+
return false;
|
|
48
31
|
}
|
|
49
32
|
}
|
|
50
|
-
/**
|
|
51
|
-
* memory-policy 两面(export 读 / sync 写)共用的属主门 —— design/170 件A §7「第二消费者收编」。
|
|
52
|
-
*
|
|
53
|
-
* 收编前本门把 `org:*` 写死 operator-only(注逐字「membership waits for the S3 登记簿」),于是同一进程里
|
|
54
|
-
* 「谁属于 org:acme」有两个答案:core 准入门问授权目录、本面问 operatorPrincipals。收编后 `org:` 成员性
|
|
55
|
-
* 只由 `deps.orgMemoryDirectory` 回答(与准入 seam 同一个实例、同一份缓存)。
|
|
56
|
-
*
|
|
57
|
-
* 判决序(前两级刻意在目录之前 —— 它们不需要目录也成立,顺带让常路零额外 I/O):
|
|
58
|
-
* ① 本人 user 盘逐字匹配 ⇒ 通过;② 显式 operator ⇒ 通过(ops/迁移面不依赖目录在场);
|
|
59
|
-
* ③ `org:` 键 + 目录在场 + **enforce 模式** ⇒ 查目录:瞬时不可用照原样上抛;取数成功但无该键 ⇒
|
|
60
|
-
* `not_found`;`need==="write"` 另要条目 `write === true`(读授予不隐含写授予,与准入面 writeScope
|
|
61
|
-
* 收窄同源);④ 其余(`userproj:` 等目录没有答案的键、无目录源的部署、audit 模式)⇒ `not_found`。
|
|
62
|
-
*
|
|
63
|
-
* **audit 模式为什么在本面等于「不接目录」**(codex 对抗审 R1,2026-08-07):`audit` 是 operator 表态
|
|
64
|
-
* 「这份目录还没被验证过,先别拿它做判决」。拿未验证的目录去开真数据面(读)甚至写面,是这条表态能
|
|
65
|
-
* 造成的最严重后果——方向恰好反了;`audit=零行为变化` 的公开契约在本面因此读作「逐字保持收编前的
|
|
66
|
-
* operator-only」,连额外那一跳目录 I/O 和 enforce 才有的 503 响应形都不引入。org 面的诊断信号属于
|
|
67
|
-
* 准入侧的 `memory_admission_total{outcome,mode}`,不在本面另开一路。
|
|
68
|
-
*
|
|
69
|
-
* 拒绝一律 `not_found` 而非 403:本面的零存在性 oracle 纪律不变(与 run/trace 属主门同口径),
|
|
70
|
-
* 「不是成员」和「没这个 scope」对调用方必须不可区分。
|
|
71
|
-
*/
|
|
72
33
|
async function gateMemoryScope(deps, principal, scope, need) {
|
|
73
34
|
if (scope === formatUserScope(principal))
|
|
74
35
|
return { kind: "allow" };
|
|
@@ -86,18 +47,16 @@ async function gateMemoryScope(deps, principal, scope, need) {
|
|
|
86
47
|
return { kind: "not_found" };
|
|
87
48
|
return need === "write" && grant.write !== true ? { kind: "not_found" } : { kind: "allow" };
|
|
88
49
|
}
|
|
89
|
-
/** 门的拒绝臂下发:瞬时 ⇒ 503 `memory.admission_required` + `retryAfterSec`(秒上取整,永不 0 —— C6;
|
|
90
|
-
* 族形与 sync 提交腿的同码响应逐字一致);终局 ⇒ 404 零 oracle。返回 true = 已应答,调用方须 return。 */
|
|
91
50
|
function sendScopeGateRefusal(res, gate) {
|
|
92
51
|
if (gate.kind === "allow")
|
|
93
52
|
return false;
|
|
94
53
|
if (gate.kind === "unavailable") {
|
|
95
54
|
const retryAfterSec = Math.max(1, Math.ceil(gate.retryAfterMs / 1000));
|
|
96
|
-
res.setHeader("retry-after", String(retryAfterSec));
|
|
55
|
+
res.setHeader("retry-after", String(retryAfterSec));
|
|
97
56
|
sendError(res, 503, "memory.admission_required", "org memory directory unavailable — authorization could not be established", { retryAfterSec });
|
|
98
57
|
return true;
|
|
99
58
|
}
|
|
100
|
-
sendError(res, 404, "not_found.memory_scope", "not found");
|
|
59
|
+
sendError(res, 404, "not_found.memory_scope", "not found");
|
|
101
60
|
return true;
|
|
102
61
|
}
|
|
103
62
|
export async function handleMemoryPolicy(req, res, url, ctx) {
|
|
@@ -105,27 +64,11 @@ export async function handleMemoryPolicy(req, res, url, ctx) {
|
|
|
105
64
|
await handleMemoryPolicyBody(req, res, url, ctx, miss);
|
|
106
65
|
return !miss.fell;
|
|
107
66
|
}
|
|
108
|
-
/** 路由体 = 从 `server.ts` 的 `handle()` 里**整段剪切**过来的原文(唯一改动:统一去缩进两格)。裸 `return;`
|
|
109
|
-
* 的语义原样 =「本域已应答」;走到函数尾才是「没匹配上」,置 `miss.fell` 交回装配器继续下一个域。
|
|
110
|
-
* 跨域的东西(deps / 助手 / 单域状态)一律在首行解构出来,于是下面每一行都逐字不变。 */
|
|
111
67
|
async function handleMemoryPolicyBody(req, res, url, ctx, miss) {
|
|
112
68
|
const { deps } = ctx;
|
|
113
69
|
const { readJson } = ctx.helpers;
|
|
114
|
-
// 142-S5 §1.4 — GET /v1/memory/export?scope=<key>: per-scope memory export (传输中立包=条目全量数组;
|
|
115
|
-
// C-additive 落点). After the global service-credential gate (a credential/SSO-JWT is already
|
|
116
|
-
// verified); the OWNER gate here is the tenant boundary: the verified principal may export exactly its OWN
|
|
117
|
-
// user disk (`scope === formatUserScope(principal)` — core's mint, byte-identical to memoryScopeFor's) or be
|
|
118
|
-
// an explicit operator (explicitOperatorOk, ops/migration face), or — design/170 件A §7 收编 — a
|
|
119
|
-
// DIRECTORY MEMBER of an `org:` key (the same org-memory directory instance core's admission seam reads,
|
|
120
|
-
// so one process holds ONE answer to "who belongs to org:acme"). Anything else = 404, NEVER 403 (zero
|
|
121
|
-
// existence oracle, run/trace owner-gate parity); a directory that cannot answer = 503, never 404.
|
|
122
|
-
// `userproj:*` stays operator-only by construction (the directory has no answer for that key shape).
|
|
123
|
-
// proj:* keys of repo-backed projects never live in this DB (铁律: git is their authority) — an owner
|
|
124
|
-
// asking for one just gets an empty set, honestly.
|
|
125
70
|
if (url === "/v1/memory/export" && req.method === "GET") {
|
|
126
|
-
|
|
127
|
-
// to a caller who couldn't read anything anyway.
|
|
128
|
-
const principal = gatedPrincipal(req, deps.config); // direct-door safe: verified identity, never the spoofable header
|
|
71
|
+
const principal = gatedPrincipal(req, deps.config);
|
|
129
72
|
if (!principal) {
|
|
130
73
|
sendError(res, 401, "auth.unauthorized", "unauthorized");
|
|
131
74
|
return;
|
|
@@ -134,7 +77,7 @@ async function handleMemoryPolicyBody(req, res, url, ctx, miss) {
|
|
|
134
77
|
sendError(res, 501, "capability.memory_store_required", "memory export requires a DB memory backend (MEMORY_ENGINE_BACKEND=pg|tidb)");
|
|
135
78
|
return;
|
|
136
79
|
}
|
|
137
|
-
const q = new URL(req.url ?? "", "http://x").searchParams;
|
|
80
|
+
const q = new URL(req.url ?? "", "http://x").searchParams;
|
|
138
81
|
const scope = q.get("scope");
|
|
139
82
|
if (!scope) {
|
|
140
83
|
sendError(res, 400, "request.query_invalid", "missing ?scope=<key>");
|
|
@@ -142,18 +85,12 @@ async function handleMemoryPolicyBody(req, res, url, ctx, miss) {
|
|
|
142
85
|
}
|
|
143
86
|
if (sendScopeGateRefusal(res, await gateMemoryScope(deps, principal, scope, "read")))
|
|
144
87
|
return;
|
|
145
|
-
// 分页参数的验型排在**属主门之后**(与 roster 的「先门后参」同序):越界者恒 404,再坏的查询串
|
|
146
|
-
// 也不许把它换成 400 —— 那就是一格「这个 scope 存在」的信号。
|
|
147
88
|
const limitRaw = q.get("limit");
|
|
148
89
|
if (limitRaw !== null && !(/^[0-9]+$/.test(limitRaw) && Number(limitRaw) >= 1 && Number(limitRaw) <= MEMORY_EXPORT_MAX_LIMIT)) {
|
|
149
90
|
sendError(res, 400, "request.query_invalid", `limit must be a decimal integer in 1..${MEMORY_EXPORT_MAX_LIMIT}`);
|
|
150
91
|
return;
|
|
151
92
|
}
|
|
152
93
|
const beforeRaw = q.get("before");
|
|
153
|
-
// 🔴 `before` 必须与 `limit` 同行(codex 对抗复审 H2):否则「缺席 limit = 旧契约逐字不变」有两个
|
|
154
|
-
// 破口 —— 合法游标会让调用方拿到**过滤过的**子集却按全量读,坏游标会把旧行为的 200 全量变 400。
|
|
155
|
-
// 取**响亮拒**而不是「静默忽略 before」:后者让「分页时忘了带 limit」的调用方悄悄收到整盘,
|
|
156
|
-
// 正是本件要消灭的那个天花板。两个参数都不带的旧调用**一个字节都没碰到这条腿**。
|
|
157
94
|
if (beforeRaw !== null && limitRaw === null) {
|
|
158
95
|
sendError(res, 400, "request.query_invalid", "before requires an explicit limit — without one this endpoint returns the whole scope by contract");
|
|
159
96
|
return;
|
|
@@ -165,26 +102,19 @@ async function handleMemoryPolicyBody(req, res, url, ctx, miss) {
|
|
|
165
102
|
}
|
|
166
103
|
try {
|
|
167
104
|
const entries = await deps.memoryExport(scope);
|
|
168
|
-
|
|
169
|
-
// 消费零破坏)。分页面才排序/切片 —— 两条腿在真部署上给出同一个序(reader 是
|
|
170
|
-
// exportMemoryScope,两方言读面都 ORDER BY id),但冻结面不靠"碰巧相同",它就不动。
|
|
171
|
-
if (limitRaw === null) { // `before` 无 limit 已在上面 400 掉 ⇒ 这里等价于「两个分页参数都不在场」
|
|
105
|
+
if (limitRaw === null) {
|
|
172
106
|
sendJson(res, 200, { scope, exportedAt: new Date().toISOString(), entries });
|
|
173
107
|
return;
|
|
174
108
|
}
|
|
175
|
-
// 声明序 = **id 升序**(entry 平面的既有确定序;id 是 PK ⇒ 唯一,单键游标无同值 tiebreak 洞)。
|
|
176
|
-
// 本面自己排,不继承 reader 的顺序:游标的正确性(无重无漏)是本面的承诺,不是上游的巧合。
|
|
177
109
|
const sorted = [...entries].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
178
110
|
const after = decoded?.ok === true ? sorted.filter((e) => e.id > decoded.cursor.id) : sorted;
|
|
179
|
-
const page = after.slice(0, Number(limitRaw));
|
|
111
|
+
const page = after.slice(0, Number(limitRaw));
|
|
180
112
|
const last = page[page.length - 1];
|
|
181
113
|
const hasMore = last !== undefined && after.length > page.length;
|
|
182
114
|
sendJson(res, 200, {
|
|
183
115
|
scope,
|
|
184
116
|
exportedAt: new Date().toISOString(),
|
|
185
117
|
entries: page,
|
|
186
|
-
// `truncated` 与 `nextBefore` 同生同灭,但**两个都要有**:前者回答「这是全量吗」(缺席 limit
|
|
187
|
-
// 的旧契约给的就是全量,所以带 limit 时必须显式说不是),后者回答「从哪儿接着要」。
|
|
188
118
|
...(hasMore ? { truncated: true, nextBefore: encodeExportCursor({ id: last.id }) } : {}),
|
|
189
119
|
});
|
|
190
120
|
}
|
|
@@ -194,14 +124,8 @@ async function handleMemoryPolicyBody(req, res, url, ctx, miss) {
|
|
|
194
124
|
}
|
|
195
125
|
return;
|
|
196
126
|
}
|
|
197
|
-
// 142-S2.5 — POST /v1/memory/sync/:scope: the central-side sync face(设计 §2.5;裁定
|
|
198
|
-
// plan 随数据走——reconcile/nextSyncBaseline 是 core 纯函数,由持数据的本 server 在 performMemorySync
|
|
199
|
-
// (memory-sync.ts) 里直接调用;此处只持 HTTP 半场). Gate ORDER mirrors the export face verbatim:
|
|
200
|
-
// auth (401) → backend posture (501, file 形态诚实拒绝——单用户 file 面自己就是 TOC 侧,没有中心
|
|
201
|
-
// 权威半场可服务) → owner gate (404 zero-oracle: verified principal 的 user 盘逐字匹配 / explicit
|
|
202
|
-
// operator / `org:` 键的目录成员且条目 write:true;userproj: 仍 operator-only) → 验型 (422 typed)。
|
|
203
127
|
if (req.method === "POST" && url.startsWith("/v1/memory/sync/")) {
|
|
204
|
-
const principal = gatedPrincipal(req, deps.config);
|
|
128
|
+
const principal = gatedPrincipal(req, deps.config);
|
|
205
129
|
if (!principal) {
|
|
206
130
|
sendError(res, 401, "auth.unauthorized", "unauthorized");
|
|
207
131
|
return;
|
|
@@ -210,8 +134,6 @@ async function handleMemoryPolicyBody(req, res, url, ctx, miss) {
|
|
|
210
134
|
sendError(res, 501, "capability.memory_store_required", "memory sync requires a DB memory backend (MEMORY_ENGINE_BACKEND=pg|tidb)");
|
|
211
135
|
return;
|
|
212
136
|
}
|
|
213
|
-
// :scope rides the path percent-encoded (scope keys carry `:`/`@`/`/`); route-level `url` is
|
|
214
|
-
// query-stripped but NOT decoded. A malformed escape is a client error, typed 400.
|
|
215
137
|
let scope;
|
|
216
138
|
try {
|
|
217
139
|
scope = decodeURIComponent(url.slice("/v1/memory/sync/".length));
|
|
@@ -224,11 +146,9 @@ async function handleMemoryPolicyBody(req, res, url, ctx, miss) {
|
|
|
224
146
|
sendError(res, 400, "memory_sync_invalid_scope", "missing :scope");
|
|
225
147
|
return;
|
|
226
148
|
}
|
|
227
|
-
// 写面:门与 export 面同一个 `gateMemoryScope`,只在 `need` 上分家 —— org 条目须 `write === true`
|
|
228
|
-
// 才放行(读授予不隐含写授予)。属主/operator 两级与读面逐字同判。
|
|
229
149
|
if (sendScopeGateRefusal(res, await gateMemoryScope(deps, principal, scope, "write")))
|
|
230
150
|
return;
|
|
231
|
-
const body = await readJson(req);
|
|
151
|
+
const body = await readJson(req);
|
|
232
152
|
const parsed = parseMemorySyncRequest(body, scope);
|
|
233
153
|
if (!parsed.ok) {
|
|
234
154
|
sendError(res, 422, "memory_sync_invalid_body", parsed.error);
|
|
@@ -239,22 +159,12 @@ async function handleMemoryPolicyBody(req, res, url, ctx, miss) {
|
|
|
239
159
|
sendJson(res, 200, out);
|
|
240
160
|
}
|
|
241
161
|
catch (err) {
|
|
242
|
-
// applyPatches 可能已落库而 putCursor 失败——500 是诚实信号:客户端按同一 baseRevs 重放即
|
|
243
|
-
// 幂等自愈(identical revs 全程 no-op),绝不静默吞。
|
|
244
162
|
deps.logger?.warn?.("memory_sync_failed", { scope, peer: parsed.value.peer, err: String(err) });
|
|
245
163
|
sendError(res, 500, "internal.error", "memory sync failed");
|
|
246
164
|
}
|
|
247
165
|
return;
|
|
248
166
|
}
|
|
249
|
-
// Effective server-enforced governance (CLI /permissions + /auto-mode). READ-only — a cloud
|
|
250
|
-
// supervisor shell CANNOT change server-side gating (the CLI's "permission modes" are tighten-only client
|
|
251
|
-
// cosmetics; the authoritative policy lives here, applied live in resolveSpec). Worker-global config (same for
|
|
252
|
-
// every principal), non-secret config surface like /v1/capabilities, so a principal is OPTIONAL (only enforced
|
|
253
|
-
// under REQUIRE_PRINCIPAL). Lets the client render the REAL autonomy band + command rules instead of fabricated ones.
|
|
254
167
|
if (url === "/v1/policy" && req.method === "GET") {
|
|
255
|
-
// F-fix: gatedPrincipal (VERIFIED) for the presence gate — on a direct door the legit caller authenticates by
|
|
256
|
-
// JWT (no x-agent-principal header), so a `principalFrom` gate would 401 a valid reader. Worker-global config
|
|
257
|
-
// (no per-tenant leak either way); the correctness/consistency twin of the run-owner gates. (BFF/gated: same.)
|
|
258
168
|
const principal = gatedPrincipal(req, deps.config);
|
|
259
169
|
if (deps.config.requirePrincipal && !principal) {
|
|
260
170
|
sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
|
|
@@ -1,33 +1,17 @@
|
|
|
1
1
|
import { taskNotificationInboxEntry } from "../../orchestration/workflow-completion-inbox.js";
|
|
2
2
|
import { sendJson, sendError } from "../send.js";
|
|
3
3
|
import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
|
|
4
|
-
/**
|
|
5
|
-
* `POST /v1/sessions/:id/wake` 的路由形 —— **单源导出**,因为它有两个消费者:本模块的分发,
|
|
6
|
-
* 和 `server.ts` 的 `isBillableSubmitPath`(wake 会烧模型,必须吃 drain / model-roster-pending /
|
|
7
|
-
* 无 service-token 三道提交门)。
|
|
8
|
-
*
|
|
9
|
-
* 🔴 为什么提成常量:2026-07-31 的缝合审查出这两处**曾经不同步** —— 匹配形写死在本文件里,
|
|
10
|
-
* 名单在 server.ts 里,wake 从来没进过名单,于是同一条 `driveResumeIntoRunLog` 模型腿
|
|
11
|
-
* 从 `/v1/assistant/tasks/:id/resume` 进来受三道门管、从 wake 进来一道都不受。
|
|
12
|
-
* 共用一个常量之后,再想漏就得两处一起改。
|
|
13
|
-
*/
|
|
14
4
|
export const SESSION_WAKE_RE = /^\/v1\/sessions\/[^/]+\/wake$/;
|
|
15
5
|
export async function handleNotifyWake(req, res, url, ctx) {
|
|
16
6
|
const miss = { fell: false };
|
|
17
7
|
await handleNotifyWakeBody(req, res, url, ctx, miss);
|
|
18
8
|
return !miss.fell;
|
|
19
9
|
}
|
|
20
|
-
/** 路由体 = 从 `server.ts` 的 `handle()` 里**整段剪切**过来的原文(唯一改动:统一去缩进两格)。裸 `return;`
|
|
21
|
-
* = 「本域已应答」;走到函数尾才是「没匹配上」。跨域的东西一律在首行解构出来。 */
|
|
22
10
|
async function handleNotifyWakeBody(req, res, url, ctx, miss) {
|
|
23
11
|
const { deps } = ctx;
|
|
24
12
|
const { steerableRuns } = ctx.registry;
|
|
25
13
|
const { readJson, rateLimited, quotaExceeded, leaseDenied } = ctx.helpers;
|
|
26
14
|
const { resumeWake } = ctx.legs;
|
|
27
|
-
// design/144 §2 server 半场(external-notification-bridge 的 HTTP 入站门):把「会话外事件」
|
|
28
|
-
// (daemon job 终态等)注入目标 session。live(本副本活流)→ core `TaskStream.notify()`(task_type=
|
|
29
|
-
// "external" core 铸造,sanitize/围栏/三态投递全在 core);idle → inbox park(external 独立键域,
|
|
30
|
-
// [769]① 防预占)→ 下次开流 drain。帧契约=[446] bg_notification 同族。入站鉴权在此(core 只管注入段)。
|
|
31
15
|
if (req.method === "POST" && /^\/v1\/sessions\/[^/]+\/notify$/.test(url)) {
|
|
32
16
|
if (rateLimited(req, res) || quotaExceeded(req, res))
|
|
33
17
|
return;
|
|
@@ -49,8 +33,6 @@ async function handleNotifyWakeBody(req, res, url, ctx, miss) {
|
|
|
49
33
|
sendError(res, 400, "request.field_invalid", "task_id must be a non-empty string of at most 190 characters");
|
|
50
34
|
return;
|
|
51
35
|
}
|
|
52
|
-
// [5028]/A-002.1 同纪律双向锚:satisfies 保「表内无词表外的词」(core 删/改词 ⇒ 编译红),
|
|
53
|
-
// 穷举断言保「词表无表外的词」(core 加词 ⇒ 编译红)——单靠 satisfies 只有前半,加词会静默 400 拒合法值。
|
|
54
36
|
const NOTIFY_STATUSES = ["completed", "failed", "killed", "cancelled", "event"];
|
|
55
37
|
const _notifyExhaustive = true;
|
|
56
38
|
void _notifyExhaustive;
|
|
@@ -74,13 +56,11 @@ async function handleNotifyWakeBody(req, res, url, ctx, miss) {
|
|
|
74
56
|
sendError(res, 400, "request.field_invalid", "source must be a string of at most 190 characters when present");
|
|
75
57
|
return;
|
|
76
58
|
}
|
|
77
|
-
// 入站 owner 门(fail-closed,exemptions 路由同款):未知 session 404;他人 session 404 无 oracle;
|
|
78
|
-
// store 错误 500 传播绝不放行;无 ownerOf 面(env-only)=501 诚实(入站门没有地基就不开门)。
|
|
79
59
|
if (!deps.sessionStorage?.ownerOf) {
|
|
80
60
|
sendError(res, 501, "capability.session_ownership_required", "external notify requires the session-ownership face (sessionStorage.ownerOf)");
|
|
81
61
|
return;
|
|
82
62
|
}
|
|
83
|
-
const notifyOwner = await deps.sessionStorage.ownerOf(notifySession);
|
|
63
|
+
const notifyOwner = await deps.sessionStorage.ownerOf(notifySession);
|
|
84
64
|
if (notifyOwner === undefined) {
|
|
85
65
|
sendError(res, 404, "not_found.session", "session not found");
|
|
86
66
|
return;
|
|
@@ -98,7 +78,6 @@ async function handleNotifyWakeBody(req, res, url, ctx, miss) {
|
|
|
98
78
|
...(nb.seq !== undefined ? { seq: nb.seq } : {}),
|
|
99
79
|
...(nb.source !== undefined ? { source: nb.source } : {}),
|
|
100
80
|
};
|
|
101
|
-
// live 半场:目标 session 的活流在本副本 → core notify verb(task_type="external" core 铸,不可伪装内部帧)。
|
|
102
81
|
const liveTaskId = await deps.runStore?.getActiveTaskId?.(notifySession).catch(() => undefined);
|
|
103
82
|
const liveStream = liveTaskId !== undefined && liveTaskId !== null ? steerableRuns.get(liveTaskId) : undefined;
|
|
104
83
|
if (liveStream) {
|
|
@@ -108,17 +87,13 @@ async function handleNotifyWakeBody(req, res, url, ctx, miss) {
|
|
|
108
87
|
return;
|
|
109
88
|
}
|
|
110
89
|
catch (e) {
|
|
111
|
-
// 只有「流已结束」竞态才回落 park;core 的明确拒绝(notify.invalid_payload 等)原样 4xx——
|
|
112
|
-
// 把合同拒绝误 park = 无效通知洗成 202(fail-closed 谱系,复审 finding)。
|
|
113
90
|
const code = e.code;
|
|
114
91
|
if (typeof code === "string" && code.startsWith("notify.")) {
|
|
115
92
|
sendError(res, 400, code, e instanceof Error ? e.message : "invalid notification");
|
|
116
93
|
return;
|
|
117
94
|
}
|
|
118
|
-
/* 其余=活流刚结束竞态 → durable park(与 steer 的 live→suspended 同姿势) */
|
|
119
95
|
}
|
|
120
96
|
}
|
|
121
|
-
// idle 半场:park 进 inbox(external 键域;redact/cap 在 taskNotificationInboxEntry 内)→ 下次开流 drain。
|
|
122
97
|
if (!deps.workflowCompletionInbox) {
|
|
123
98
|
sendError(res, 501, "capability.workflow_store_required", "external notify park requires the workflow completion inbox (WORKFLOW_RUN_STORE)");
|
|
124
99
|
return;
|
|
@@ -128,16 +103,13 @@ async function handleNotifyWakeBody(req, res, url, ctx, miss) {
|
|
|
128
103
|
return;
|
|
129
104
|
}
|
|
130
105
|
if (req.method === "POST" && SESSION_WAKE_RE.test(url)) {
|
|
131
|
-
// design/144 §3(core 1.283 wake arm):对 task_done 纯 park 的「消息唤醒」。非门决策——不 allow/deny
|
|
132
|
-
// 任何 pending 动作,只解除 park 并把消息(或已 park 的 pendingSteer)作为续跑首轮输入。有未决门 =
|
|
133
|
-
// core `wake.gate_pending` 拒(永不绕审批);空 park + 无 message = `wake.nothing_to_deliver` 拒。
|
|
134
106
|
if (!deps.checkpointStore) {
|
|
135
107
|
sendError(res, 501, "capability.checkpoint_store_required", "wake requires the checkpoint store");
|
|
136
108
|
return;
|
|
137
109
|
}
|
|
138
110
|
if (rateLimited(req, res) || quotaExceeded(req, res) || (await leaseDenied(req, res)))
|
|
139
|
-
return;
|
|
140
|
-
const principal = gatedPrincipal(req, deps.config);
|
|
111
|
+
return;
|
|
112
|
+
const principal = gatedPrincipal(req, deps.config);
|
|
141
113
|
if (deps.config.requirePrincipal && principal === undefined) {
|
|
142
114
|
sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
|
|
143
115
|
return;
|
|
@@ -155,12 +127,6 @@ async function handleNotifyWakeBody(req, res, url, ctx, miss) {
|
|
|
155
127
|
sendError(res, 400, "request.field_invalid", "message must be a non-empty string when present");
|
|
156
128
|
return;
|
|
157
129
|
}
|
|
158
|
-
// 🕐 **#316 同族登记(本批不改行为)**:wake 与 legacy `/decide` 走同一条 `driveResumeIntoRunLog`
|
|
159
|
-
// 模型腿 —— 响应之前同步跑完整条续跑,时长 = 模型往返、无服务端上限([4664]① 定谳的同族三腿之一)。
|
|
160
|
-
// 本批只把 legacy decide 改成 200 受理形(那是 [4660] 雪崩实测首环);本腿是 wire BREAKING,按能力
|
|
161
|
-
// 显式表态制([3780] 裁 A)候消费方认领后同形改造 —— 改法 = `resumeWake` 透传 `acceptEarly: true`。
|
|
162
|
-
// ⚠️ 本腿多一条判据要一起想:它走 `mintFreshRun`(铸新 run 行),受理体里的 `taskId` 因此是**新铸的**
|
|
163
|
-
// 那个 id —— 改造时受理形必须带上它,否则壳没有可 poll 的锚。
|
|
164
130
|
const out = await resumeWake(wakeSession, wakeBody.message, principal, req);
|
|
165
131
|
sendJson(res, out.status, out.body);
|
|
166
132
|
return;
|
|
@@ -5,23 +5,8 @@ export async function handleObservability(req, res, url, ctx) {
|
|
|
5
5
|
await handleObservabilityBody(req, res, url, ctx, miss);
|
|
6
6
|
return !miss.fell;
|
|
7
7
|
}
|
|
8
|
-
/** 路由体 = 从 `server.ts` 的 `handle()` 里**整段剪切**过来的原文(唯一改动:统一去缩进两格)。裸 `return;`
|
|
9
|
-
* 的语义原样 =「本域已应答」;走到函数尾才是「没匹配上」,置 `miss.fell` 交回装配器继续下一个域。
|
|
10
|
-
* 跨域的东西(deps / 助手 / 单域状态)一律在首行解构出来,于是下面每一行都逐字不变。 */
|
|
11
8
|
async function handleObservabilityBody(req, res, url, ctx, miss) {
|
|
12
9
|
const { deps } = ctx;
|
|
13
|
-
// Per-principal cumulative cost usage (CLI /cost + /usage + status usage tab). The READ twin of
|
|
14
|
-
// the cost-quota GATE (quotaExceeded): `check` is read-only (does NOT consume). Principal-scoped — a caller
|
|
15
|
-
// only ever sees its OWN spend (keyed exactly like the gate). When no cumulative quota is configured
|
|
16
|
-
// (deps.costQuota absent) → enabled:false (NOT an error) so the CLI shows "no cumulative quota; per-task cap
|
|
17
|
-
// = $X" instead of failing. ⚠️ in-memory CostQuota is per-replica (undercounts across replicas) — the TiDB
|
|
18
|
-
// twin (deps.costQuota = TiDBCostQuota) is the cross-replica-accurate backend; both satisfy `.check()`.
|
|
19
|
-
// P2 (design/73 §7.2 dashboard floor): READ-ONLY per-(taskSignature, model) outcome aggregate — pass-rate
|
|
20
|
-
// + mean cost over the graded ledger rows (the (b)-emitter data: make-real/TB harness runs + any future
|
|
21
|
-
// goal面). 🔒 Multi-tenant: operator-only — rows are NOT owner-keyed (mechanical facts keyed by signature
|
|
22
|
-
// ×model), so any-tenant access would leak cross-tenant task/perf shape; single-user turnkey is open (the
|
|
23
|
-
// sole user IS the operator). ?signature= filters to one aggregation key; ?includeAborted=true adds the
|
|
24
|
-
// infra-aborted rows (infra-health view — NEVER the training view, council blocker 3).
|
|
25
10
|
if (url === "/v1/outcomes" && req.method === "GET") {
|
|
26
11
|
if (deps.config.requirePrincipal) {
|
|
27
12
|
const principal = gatedPrincipal(req, deps.config);
|
|
@@ -34,7 +19,7 @@ async function handleObservabilityBody(req, res, url, ctx, miss) {
|
|
|
34
19
|
sendError(res, 501, "capability.outcome_ledger_required", "no queryable outcome ledger on this worker (File sink — read the JSONL dataset directly)");
|
|
35
20
|
return;
|
|
36
21
|
}
|
|
37
|
-
const q = new URL(req.url ?? "/", "http://localhost").searchParams;
|
|
22
|
+
const q = new URL(req.url ?? "/", "http://localhost").searchParams;
|
|
38
23
|
const sig = q.get("signature") ?? undefined;
|
|
39
24
|
try {
|
|
40
25
|
const rows = await deps.outcomeSink.summary({ ...(sig ? { taskSignature: sig } : {}), includeAborted: q.get("includeAborted") === "true" });
|
|
@@ -46,10 +31,8 @@ async function handleObservabilityBody(req, res, url, ctx, miss) {
|
|
|
46
31
|
}
|
|
47
32
|
return;
|
|
48
33
|
}
|
|
49
|
-
// (用量统计查询面 /v1/usage/{summary,series,breakdown} 从这里上移到全局 service-credential 门之前——
|
|
50
|
-
// usage 门自身认 traceToken,但挂在全局门之后时 traceToken-only 调用在全局门就 401=死代码。)
|
|
51
34
|
if (url === "/v1/usage" && req.method === "GET") {
|
|
52
|
-
const principal = gatedPrincipal(req, deps.config);
|
|
35
|
+
const principal = gatedPrincipal(req, deps.config);
|
|
53
36
|
if (!principal) {
|
|
54
37
|
sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
|
|
55
38
|
return;
|