@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
package/dist/security.js
CHANGED
|
@@ -1,33 +1,6 @@
|
|
|
1
1
|
import { uuidv7, PROJECT_ID_REGEX } from "@sema-agent/core";
|
|
2
|
-
|
|
3
|
-
// the leaf ./auth-keys.js so config.ts's env-load no longer value-imports this whole auth module (lens2 §F). The
|
|
4
|
-
// verifiers that consume the parsed key-sets now live in ./approval-hmac.js / ./principal-jwt.js (design/158 A13);
|
|
5
|
-
// import all four names from ./auth-keys.js directly ([2354] 兼容面全清 — the compat re-export here is gone).
|
|
6
|
-
import { memoryScopeFor } from "./memory-scope.js"; // design/158 N17: moved to ./memory-scope.js (this file's createAuthorizer still calls it)
|
|
7
|
-
// design/158 A13: the approval-envelope HMAC (integrity) group and the principal-JWT/direct-door (identity)
|
|
8
|
-
// group moved VERBATIM to ./approval-hmac.js and ./principal-jwt.js respectively — this file's own
|
|
9
|
-
// `verifiedPrincipal` still calls verifyPrincipalJwt for the direct-door branch (value import below); every
|
|
10
|
-
// symbol from both groups is re-exported near the bottom of this file so existing importers keep working.
|
|
2
|
+
import { memoryScopeFor } from "./memory-scope.js";
|
|
11
3
|
import { verifyPrincipalJwt } from "./principal-jwt.js";
|
|
12
|
-
/** A SessionStore that may additionally expose tenant-ownership ops (the TiDB store does) AND the
|
|
13
|
-
* `SessionRepo` enumeration/lifecycle seam (§0.5: list/fork/delete homed on the session abstraction's own
|
|
14
|
-
* tables, not the `task_run` runs ledger). All optional — an in-memory dev store implements none. */
|
|
15
|
-
/**
|
|
16
|
-
* E6 fail-closed 守卫的辖域判据:**这个请求是不是一次「破坏性会话写」**。
|
|
17
|
-
*
|
|
18
|
-
* 🔴 2026-08-01 复审逮到:守卫原本是**逐条路由白名单**(`PUT …/policy`、`POST …/sync/import`),
|
|
19
|
-
* 而 `DELETE /v1/sessions/:id`(删整条会话)不在其中 —— 同一威胁形、爆炸半径**更大**,却没门。
|
|
20
|
-
*
|
|
21
|
-
* 威胁形逐字同源(守卫自己的注释):「无 service token + 未开 `ALLOW_UNAUTHED_WRITES`」这一形下
|
|
22
|
-
* `sessionOwnerScopeForWrite` 判 fleet-wide ⇒ 集群内**任意**调用方可对**任意** session 动手。
|
|
23
|
-
* policy 腿担心的是「forged principal 能 tighten/DoS 一个会话的工具」;DELETE 直接把会话删掉。
|
|
24
|
-
*
|
|
25
|
-
* ⇒ 枚举改**族判定**:新增一条破坏性会话路由时自动落进门内,不必记得回来加白名单。
|
|
26
|
-
* 这是本仓「同一条论证只在枚举到的路径上执行」这一族的第 N 次,修法统一为把判据提成函数。
|
|
27
|
-
*
|
|
28
|
-
* 辖域**只含**对**既有会话**的破坏性写:改规则 / 整段替换 / 删除 / 分叉(读源会话全历史)。
|
|
29
|
-
* 不含读面,也不含 `POST /v1/tasks` 那条提交腿 —— 它有自己的同族门(别重复拦,双拦会让错误归因变糊)。
|
|
30
|
-
*/
|
|
31
4
|
export function isDestructiveSessionWrite(method, url) {
|
|
32
5
|
if (method === "DELETE")
|
|
33
6
|
return /^\/v1\/sessions\/[^/]+$/.test(url);
|
|
@@ -37,88 +10,21 @@ export function isDestructiveSessionWrite(method, url) {
|
|
|
37
10
|
return /^\/v1\/sessions\/[^/]+\/(sync\/import|fork)$/.test(url);
|
|
38
11
|
return false;
|
|
39
12
|
}
|
|
40
|
-
/** [4198] 窄互认第二臂的租户键字面量 —— 单机形下 core 写侧 `spec.principal ?? "default"` 铸出的那一个,
|
|
41
|
-
* 也是壳 [4195]G1 翻面后随请求带的那一个。**不是哨兵**([4183] 措辞):它是单机形的真租户键。 */
|
|
42
13
|
export const LEGACY_UNOWNED_AMNESTY_TENANT = "default";
|
|
43
|
-
/**
|
|
44
|
-
* [4198] 窄互认**第二臂**(clay 裁 A / [4225] 下达;cli [4238] 表态即发)—— `legacyShellOwner`
|
|
45
|
-
* (本文件 createAuthorizer 内 + http/server.ts `runOwnerOk`)的对称孪生。
|
|
46
|
-
*
|
|
47
|
-
* 病灶:7.8.0 第一臂承接的是 `owner === "anon:shell-live"` 哨兵行;此后壳把无主表示法归一为
|
|
48
|
-
* **缺席**,于是 1.0.82 及更早留下的存量会话 owner 列是 **null**。壳 1.0.83 起随请求带
|
|
49
|
-
* `default` 主([4195] G1 闸口),`null !== "default"` ⇒ 存量会话在 session 面
|
|
50
|
-
* **列表不出现 / fork 404 / delete 静默 {deleted:false} / workspace 404**(三面矩阵 [4198] 面3;
|
|
51
|
-
* fleet 面与 run 读面天然连续,面1/面2 零施工)。
|
|
52
|
-
*
|
|
53
|
-
* 🔒 **三条件缺一不放行**(与第一臂同源,不是兼容臂):
|
|
54
|
-
* ① 单机形 `!requirePrincipal` —— 多租部署本臂**死路**(header 必到、键必具名);
|
|
55
|
-
* ② 存量行恰为无主 `owner === null` —— 任何具名 owner 的行不受影响;
|
|
56
|
-
* ③ 调用方恰为 `default` 主 —— 非 default 的具名主对无主行仍走原门(404 / deleted:false)。
|
|
57
|
-
* 反向风险有界:①限定的单机形本就单用户,把无主行判给 default 主不产生跨租泄露。
|
|
58
|
-
*
|
|
59
|
-
* 🪦 墓碑注(与第一臂同源):这是两个表示法之间的**过渡承接**。存量随会话生命周期消亡后,本臂
|
|
60
|
-
* 连同 {@link LEGACY_UNOWNED_AMNESTY_TENANT} 与几处调用点可**整删**,不留兼容层。
|
|
61
|
-
*
|
|
62
|
-
* ── 逐面判过、**刻意不放行**的三处(免得后人当遗漏再补一次)────────────────────────────────
|
|
63
|
-
* · `runOwnerOk` 三读面(head / audit 单读 / events):`owner === null` 那一支**本来就放行**
|
|
64
|
-
* (判据是 `owner !== null && principal !== owner`),不需要本臂。[4198] 帖文里「单读 404」一句
|
|
65
|
-
* 经亲读实现行**不成立**,已加现状钉(session-head-probe.test.ts)锁住这个事实。
|
|
66
|
-
* · `PUT /v1/sessions/:id/policy` 的 `owner === null ⇒ 409 conflict.session_ownerless`:那是**有意的
|
|
67
|
-
* fail-closed** —— 无主会话没有稳定的 run-principal,存下去的规则读不到、静默失效。放宽它等于制造
|
|
68
|
-
* 一条 fail-open,不在本臂辖域。
|
|
69
|
-
* · `…/sync/*`(PULL/PUSH/import):PULL 虽是只读、形同 workspace,但这一族**带一条破坏性 import 腿**
|
|
70
|
-
* (`isDestructiveSessionWrite` 明列),而 [4198]/[4225] 裁的是会话**读面与生命周期**四门。放宽一个
|
|
71
|
-
* 写面族超出裁定射程 —— 要做另立一次表态,不在本臂里顺手带过。
|
|
72
|
-
*
|
|
73
|
-
* @param requirePrincipal `config.requirePrincipal`(条件①)
|
|
74
|
-
* @param gateOwner `sessionOwnerScope(ForWrite)` 判出的**已验证**主(条件③;fleet-wide 面不经本臂)
|
|
75
|
-
* @param owner 存量行的 owner 列(条件②)
|
|
76
|
-
*/
|
|
77
14
|
export function legacyUnownedSessionOk(requirePrincipal, gateOwner, owner) {
|
|
78
15
|
return owner === null && legacyUnownedAmnestyOpen(requirePrincipal, gateOwner);
|
|
79
16
|
}
|
|
80
|
-
/**
|
|
81
|
-
* {@link legacyUnownedSessionOk} 的**条件①③**半支 —— 只判「这个部署形 × 这个调用方」是否落在窄臂辖域内。
|
|
82
|
-
*
|
|
83
|
-
* 🔴 单独存在的唯一理由是**列表面**:`GET /v1/sessions` 的第三条件(`owner === null`)不在 JS 里判,
|
|
84
|
-
* 它是 store 侧 `owner = ? OR owner IS NULL` 这一句 SQL 谓词 —— 三条件仍然齐,只是第三条件由 SQL 承载
|
|
85
|
-
* (`listSessions({ owner, includeUnowned })` 的 `includeUnowned` 位即本函数的返回值)。
|
|
86
|
-
* 单实体读/写点(fork/delete/workspace)**一律**用三条件齐的 {@link legacyUnownedSessionOk},不要用本函数。
|
|
87
|
-
*/
|
|
88
17
|
export function legacyUnownedAmnestyOpen(requirePrincipal, gateOwner) {
|
|
89
18
|
return !requirePrincipal && gateOwner === LEGACY_UNOWNED_AMNESTY_TENANT;
|
|
90
19
|
}
|
|
91
|
-
/** The shape core's `uuidv7()` mints: canonical 8-4-4-4-12 lowercase hex with version nibble `7` and the RFC-4122
|
|
92
|
-
* variant (`8`/`9`/`a`/`b`). Used to shape-validate a caller-supplied session id on the §0.5 fork/delete routes so
|
|
93
|
-
* a crafted id (LIKE metacharacters, over-long, non-canonical) is rejected up front — a SECOND layer behind the
|
|
94
|
-
* tool-result LIKE-escape, not the only one. NB: deliberately NOT enforced on the task-submission `body.sessionId`
|
|
95
|
-
* path (the authorizer) — historical callers/tests pass arbitrary short ids there, and the LIKE-escape +
|
|
96
|
-
* VARCHAR(64) column already neutralize the metacharacter risk; tightening that path is a separate, breaking change.
|
|
97
|
-
* ASYMMETRY (GAP #15, INTENTIONAL): a client MAY create a session via submit with an arbitrary id (e.g. run-local
|
|
98
|
-
* `--session foo`, dev anonymous-share ids) yet then CANNOT fork/delete it (those routes 400 a non-uuidv7 id). This
|
|
99
|
-
* is by design — the wire contract types sessionId as an opaque string, so submit stays permissive; fork/delete add
|
|
100
|
-
* the uuidv7 gate only as defense-in-depth for store ops on a caller-supplied `:id`. Do NOT add a uuidv7 check to
|
|
101
|
-
* submit (breaks the contract + run-local + single-tenant dev). The ONE submit-side guard that IS safe and present
|
|
102
|
-
* is a LENGTH cap (≤64, the column width) in prepareSpec — see server.ts `body.sessionId` check — which prevents a
|
|
103
|
-
* 500/truncation at the INSERT without constraining the id shape. */
|
|
104
20
|
const UUIDV7_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
105
|
-
/** True iff `s` matches the canonical uuidv7 shape core mints (see {@link UUIDV7_RE}). */
|
|
106
21
|
export function isUuidV7(s) {
|
|
107
22
|
return UUIDV7_RE.test(s);
|
|
108
23
|
}
|
|
109
|
-
/** Canonical uuid of ANY version (v4 legacy shell sessions included) — the SYNC
|
|
110
|
-
* face's shape gate. Sync stays stricter than submit (arbitrary ≤64 ids) because it is a bulk write
|
|
111
|
-
* face: the shape still shuts out crafted ids (LIKE metacharacters, over-long), but no longer strands a
|
|
112
|
-
* v4-keyed legacy session as unsyncable (it was CREATABLE via the lenient submit yet REJECTED by the v7-only
|
|
113
|
-
* sync gate — a real contradiction). fork/purge/policy keep the v7-only gate (ids we mint ourselves). */
|
|
114
24
|
const UUID_ANY_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
115
25
|
export function isUuidShape(s) {
|
|
116
26
|
return UUID_ANY_RE.test(s);
|
|
117
27
|
}
|
|
118
|
-
/** An HTTP error the server maps to a status code (instead of a generic 500). `code` (optional) is a stable
|
|
119
|
-
* machine-readable identifier the HTTP layer echoes as `{code}` beside the human `{error}` message — the
|
|
120
|
-
* "typed rejection" posture (structured code on the wire, prose stays with the shell/web).
|
|
121
|
-
* `extra` (optional) is folded into the error body verbatim (e.g. the scenario allowlist on a reject). */
|
|
122
28
|
export class HttpError extends Error {
|
|
123
29
|
status;
|
|
124
30
|
code;
|
|
@@ -133,46 +39,12 @@ export class HttpError extends Error {
|
|
|
133
39
|
this.extra = opts.extra;
|
|
134
40
|
}
|
|
135
41
|
}
|
|
136
|
-
/**
|
|
137
|
-
* S6 — close the "sessionId / scope are bearer capabilities" hole.
|
|
138
|
-
*
|
|
139
|
-
* The principal is read from a trusted header set by the authenticated caller (e.g. the OA backend
|
|
140
|
-
* that already holds the service token). It is NOT taken from the request body, so a caller cannot
|
|
141
|
-
* forge identity by passing `scope`/`sessionId` for another tenant.
|
|
142
|
-
*
|
|
143
|
-
* Then:
|
|
144
|
-
* - a supplied `sessionId` is ownership-checked against `session_meta.owner` (403 on mismatch);
|
|
145
|
-
* an unknown id is claimed for this principal; a missing id mints a fresh owned session;
|
|
146
|
-
* - the memory scope is derived from the principal.
|
|
147
|
-
*
|
|
148
|
-
* Returns a function shaped for `ServiceDeps.authorize`. When `sessionStore` is not the TiDB store
|
|
149
|
-
* (e.g. in-memory dev backend) ownership can't be enforced, so it only derives principal + sessionId.
|
|
150
|
-
*/
|
|
151
42
|
export function createAuthorizer(config, sessionStore) {
|
|
152
43
|
return async ({ req, body }) => {
|
|
153
|
-
// F-fix (direct-door principal spoofing, core-steered): the authoritative identity for governance + cost +
|
|
154
|
-
// ownership + memory + checkpoint scope is the VERIFIED principal, never the spoofable header. This swap covers
|
|
155
|
-
// every consumer that flows THROUGH the authorizer (spec.principal + cost/owner/memory/checkpoint scope); the
|
|
156
|
-
// non-authorizer owner/cost sites (runOwnerOk / quotaExceeded / leader / idemKey / the run-route + worker-global
|
|
157
|
-
// presence gates) call `gatedPrincipal` directly, same invariant. BFF/gated = unchanged (no-op there).
|
|
158
44
|
const principal = verifiedPrincipal(req, config);
|
|
159
45
|
if (config.requirePrincipal && !principal) {
|
|
160
46
|
throw new HttpError(401, `missing principal header '${config.principalHeader}'`);
|
|
161
47
|
}
|
|
162
|
-
// 142-S4 projectId 线程化(design/142 §2 行为面):body.projectId 是「哪个项目」的选择器,不是身份 —
|
|
163
|
-
// scope 的 tenant 段永远来自 verified principal(上面),所以它过 SHAPE 门即可。不合形状 = 422 typed
|
|
164
|
-
// fail-loud(注入串/任意路径段绝不进 scope 键或 config.projects 查表)。RESUME 腿不过此门
|
|
165
|
-
// (checkpoint 重建 auth 不跑 authorizer)— 持久化 body 在原始提交时已验过,形状不变。
|
|
166
|
-
//
|
|
167
|
-
// 🔴 A-033.1(#256)单源换装 + **宽读严写**两半:
|
|
168
|
-
// ① 形状词法改从 core 取(`PROJECT_ID_REGEX`,core 自陈「projectId 格式单源」[637]①)。此前取自
|
|
169
|
-
// registry-core 的**自持镜像**,而那份镜像与 core 已实证漂移:core 带 `/i`、registry-core 不带 ⇒
|
|
170
|
-
// 一个 projectId 能过 core 的 scope 面(`parseProjectMarker`/`resolveProjectId` 都认大小写)却被
|
|
171
|
-
// 本仓的门 422。registry-core 自己的 `types.d.ts` 就挂着「core 公开导出后即改从 core 取」的 TODO。
|
|
172
|
-
// ② 收下之后**立即折小写**再往下走 —— core 的铸造面同款纪律(`formatProjectMarker` 亲读:验形用
|
|
173
|
-
// 带 `/i` 的同一条正则,写盘 `projectId.toLowerCase()`)。这一折是承重的:`formatProjScope` 不做
|
|
174
|
-
// 任何归一化,不折的话 `proj:<t>/ABC…` 与 `proj:<t>/abc…` 是**两只不同的记忆盘**,同一个项目按
|
|
175
|
-
// 大小写分家(而 registry-core 的 `ProjectsConfig` 只收小写键 ⇒ 大写形还会在 config.projects 恒 miss)。
|
|
176
48
|
let projectId;
|
|
177
49
|
if (body.projectId !== undefined) {
|
|
178
50
|
if (typeof body.projectId !== "string" || !PROJECT_ID_REGEX.test(body.projectId)) {
|
|
@@ -180,69 +52,26 @@ export function createAuthorizer(config, sessionStore) {
|
|
|
180
52
|
}
|
|
181
53
|
projectId = body.projectId.toLowerCase();
|
|
182
54
|
}
|
|
183
|
-
// design/114 Phase3 (reuse-path warm-resume): the caller requires the session to ALREADY exist. The authorizer
|
|
184
|
-
// must NOT claim-create a missing id — that would materialize an empty session_meta row and DEFEAT core's
|
|
185
|
-
// acquire({requireExisting}) fail-loud, silently turning a resume of a PURGED/EXPIRED session into a fresh empty
|
|
186
|
-
// run (the exact "looks warm, actually fresh" bug the flag prevents). So with it set: a missing requested id
|
|
187
|
-
// FAILS LOUD with a clean 404 HERE (eager — before any SSE headers, so BOTH the sync and stream legs get a real
|
|
188
|
-
// status, not an SSE-buried error) and is NEVER claim-created; a request with NO sessionId is a caller error (the
|
|
189
|
-
// authorizer would otherwise mint a fresh id — there is no existing session to resume). This is the SERVICE-side
|
|
190
|
-
// complement to core's store-level fail-loud (which still covers run-local / no-ownerOf backends / direct core):
|
|
191
|
-
// without it the store never SEES a missing session (the authorizer pre-registers it), so store honoring alone is
|
|
192
|
-
// inert on the HTTP path.
|
|
193
55
|
const requireExisting = body.requireExistingSession === true;
|
|
194
56
|
const ownerOf = sessionStore.ownerOf?.bind(sessionStore);
|
|
195
57
|
const register = sessionStore.register?.bind(sessionStore);
|
|
196
58
|
const requested = typeof body.sessionId === "string" && body.sessionId ? body.sessionId : undefined;
|
|
197
59
|
let sessionId;
|
|
198
60
|
if (requested) {
|
|
199
|
-
// 🔴 [2195]:两个能力**解耦判**(旧码是 `if (ownerOf && register)` 一把抓)。它们回答的是两个
|
|
200
|
-
// 不同的问题:`ownerOf` = 「这个 session 归谁」(判归属),`register` = 「把新 id 记在谁名下」
|
|
201
|
-
// (能记账)。旧码把两者绑成一个开关,于是 `ownerOf` 在、`register` 缺时,**明明判得了归属却
|
|
202
|
-
// 整条不判** —— 仓里有一条特征化测试逐字钉着这个结果(principal 直接叫 `user:intruder`,
|
|
203
|
-
// 注释写「would be a mismatch IF enforced」),它 describe 名却是 "in-memory dev backend",
|
|
204
|
-
// 而配的是 `requirePrincipal: true`(多租)。描述与执行面脱钩,读标题的人以为那是 dev-only 的事。
|
|
205
61
|
if (ownerOf) {
|
|
206
62
|
let owner = await ownerOf(requested);
|
|
207
63
|
if (owner === undefined) {
|
|
208
64
|
if (requireExisting) {
|
|
209
|
-
// design/114 Phase3: the session is genuinely missing (no owner row) AND the caller requires it to
|
|
210
|
-
// EXIST → FAIL LOUD with a clean HTTP 404 on BOTH the sync AND the streaming leg. The authorizer runs
|
|
211
|
-
// (in prepareSpec) BEFORE any SSE headers are committed, so the stream leg gets a real status code — not
|
|
212
|
-
// a `resume.session_not_found` buried in an SSE `done` payload, which a 200-committed stream cannot
|
|
213
|
-
// upgrade. We must NOT claim-create it either (that would defeat the fail-loud). The durable stores ALSO
|
|
214
|
-
// honor `{requireExisting}` (run-local / a no-ownerOf memory backend / direct core), so the fail-loud
|
|
215
|
-
// still holds on the paths that never reach this authorizer.
|
|
216
65
|
throw new HttpError(404, "requireExistingSession: session does not exist (resume.session_not_found)");
|
|
217
66
|
}
|
|
218
67
|
if (register) {
|
|
219
|
-
await register(requested, principal ?? null);
|
|
220
|
-
// Re-read the authoritative stored owner: register is an idempotent upsert that keeps the
|
|
221
|
-
// FIRST owner, so a concurrent first-claim of the same id by another principal could have won
|
|
222
|
-
// the race. Without this re-check the loser would silently attach to the winner's session.
|
|
68
|
+
await register(requested, principal ?? null);
|
|
223
69
|
owner = await ownerOf(requested);
|
|
224
70
|
}
|
|
225
71
|
else if (config.requirePrincipal) {
|
|
226
|
-
// 🔴 [2195] 的第二半:能判归属但**记不了账**。放行的话这个 id 的归属永远不会被写下,
|
|
227
|
-
// 于是下一个租户带同一个 id 来,同样看到「不存在」、同样被放行 —— 两个租户共用一条会话,
|
|
228
|
-
// 第二个人读得到第一个人的对话。多租下这与「归属不匹配」是同一类越权,同样拒。
|
|
229
72
|
throw new HttpError(403, "cannot claim a caller-supplied sessionId on a multi-tenant deployment: this worker's session store cannot record session ownership, so the id would stay unowned and the next caller could attach to it. Configure a durable session backend (SESSION_BACKEND), or omit sessionId to start a new session.");
|
|
230
73
|
}
|
|
231
74
|
}
|
|
232
|
-
// Enforce ownership whenever the session HAS an owner — including against callers that present
|
|
233
|
-
// no principal (audit B — the fix IS this guard, right here in `createAuthorizer`; no line-number
|
|
234
|
-
// anchor on purpose, the old `security.ts:77` pointer had drifted onto an unrelated JSDoc):
|
|
235
|
-
// with the old `principal &&` guard, a caller that
|
|
236
|
-
// omitted the header under requirePrincipal=false could attach to ANY owned session by id.
|
|
237
|
-
// Anonymous callers may still attach to anonymous (owner=null) sessions, so single-tenant dev
|
|
238
|
-
// (no principals anywhere) is unaffected; flipping the requirePrincipal default is NOT needed.
|
|
239
|
-
//
|
|
240
|
-
// [3262] 存量哨兵窄互认([3260] cli live 取证→本仓裁 (a)):cli ≤1.0.70 对本地单用户自发
|
|
241
|
-
// `anon:shell-live` header,存量 session 的 owner 列全是这一个哨兵;cli 停发(表示法归一为
|
|
242
|
-
// 「缺席」,[3239] 复裁)后若仍逐字比对,缺席请求 attach 存量会话恒 401 —— 用户全部既存
|
|
243
|
-
// resume 即断。互认=同一主体(本地单用户)两个表示法的过渡承接,不是兼容臂:三条件缺一
|
|
244
|
-
// 不可(单机形 ∧ owner 恰为哨兵字面量 ∧ 请求 principal 缺席),带任何具名 principal 的请求
|
|
245
|
-
// 对哨兵会话仍走下方 403;多租形 header 必到,本臂不可达。存量随会话生命周期消亡后本臂可整删。
|
|
246
75
|
const legacyShellOwner = !config.requirePrincipal && owner === "anon:shell-live" && principal === undefined;
|
|
247
76
|
if (!legacyShellOwner && owner != null && owner !== principal) {
|
|
248
77
|
throw principal
|
|
@@ -251,31 +80,11 @@ export function createAuthorizer(config, sessionStore) {
|
|
|
251
80
|
}
|
|
252
81
|
}
|
|
253
82
|
else if (config.requirePrincipal) {
|
|
254
|
-
// 🔴 [2195](cli 七版实测:bob 续聊 alice 的 session,期望 403 实测 200)的根因修。
|
|
255
|
-
//
|
|
256
|
-
// 上面整段 owner 门挂在 `ownerOf && register` 上,而 core 的**默认内存店 `TtlSessionStore`
|
|
257
|
-
// 这两个方法都没有**。能力缺席时,旧码不是「拒绝」而是让门**整条消失** —— caller 自报的
|
|
258
|
-
// sessionId 被原样受理,于是多租部署里 bob 直接续聊 alice 的会话。
|
|
259
|
-
//
|
|
260
|
-
// 这也解释了 cli 观察到的 GET/POST 不对称:GET 走 `routes/sessions.ts` 的
|
|
261
|
-
// `deps.sessionStorage.ownerOf`(durable session 面,**另一个对象**)⇒ 判别在场 ⇒ 404;
|
|
262
|
-
// POST 走这里的 runner sessionStore ⇒ 门蒸发。也解释了为什么按路由 diff 定位不到:
|
|
263
|
-
// **路由代码没变**,变的是装配里塞进来的 store 实现有没有这两个方法。
|
|
264
|
-
//
|
|
265
|
-
// 判据改成 fail-closed:多租形下拿不到归属判别能力,就不能允许复用一个 caller 自报的
|
|
266
|
-
// sessionId(拒绝的是「复用」,不是「提交」—— 不带 sessionId 的新会话照常受理)。
|
|
267
|
-
// 单租/dev 形(requirePrincipal=false)零行为变化:那里没有租户边界可越。
|
|
268
|
-
// ⚠️ 消息必须点名「连本 worker 自己刚铸的 id 也一样」:无 `register` 的 store 上,新会话的
|
|
269
|
-
// 归属**根本没被记下**,所以第二个 turn 带回引擎自己给的 id 时同样落到这里。不说清楚的话,
|
|
270
|
-
// 这个 403 从消费端看是纯粹的反直觉(「这 id 是你给我的,你现在说我无权用」)——
|
|
271
|
-
// 实测:客户端「首 turn 不发 sessionId、后续 turn 带回铸值」这一常见形状,第二个 turn 起必拒。
|
|
272
83
|
throw new HttpError(403, "session reuse needs an owner-aware session store on a multi-tenant deployment: this worker's session store cannot attest who owns a session, so a sessionId cannot be authorized — including one this worker itself minted a moment ago (without a register seam it was never recorded as yours). Configure a durable session backend (SESSION_BACKEND), or omit sessionId on every turn to start fresh.");
|
|
273
84
|
}
|
|
274
85
|
sessionId = requested;
|
|
275
86
|
}
|
|
276
87
|
else {
|
|
277
|
-
// design/114 Phase3 (M2 parity): requireExistingSession with NO sessionId is a caller error — the authorizer
|
|
278
|
-
// mints a fresh id otherwise, so core's own no-sessionId M2 check can never fire on the service path.
|
|
279
88
|
if (requireExisting) {
|
|
280
89
|
throw new HttpError(422, "requireExistingSession requires a sessionId — there is no existing session to resume (resume.session_not_found)");
|
|
281
90
|
}
|
|
@@ -286,113 +95,36 @@ export function createAuthorizer(config, sessionStore) {
|
|
|
286
95
|
return { principal, sessionId, memoryScope: memoryScopeFor(config, principal, projectId), resolvedProjectId: projectId };
|
|
287
96
|
};
|
|
288
97
|
}
|
|
289
|
-
/** F1/F3 (codex scope-ledger audit 2026-07-14) — the auth-boundary principal SHAPE gate, applied to every
|
|
290
|
-
* verified identity source (trusted header, SSO-bridge sub, direct-door JWT sub) so no downstream consumer
|
|
291
|
-
* ever sees an ambiguous or over-wide principal:
|
|
292
|
-
* - RESERVED internal identifiers are REJECTED (修1, 三路复审 A 组 HIGH — see {@link RESERVED_PRINCIPALS} /
|
|
293
|
-
* {@link RESERVED_PRINCIPAL_PREFIXES} below): a REAL tenant whose principal string-equals an internal scope
|
|
294
|
-
* sentinel would pass the plain string-compare owner/scope gates downstream and merge with internal rows —
|
|
295
|
-
* e.g. `"_"` merges with the anonymous scope family (the checkpoint scope column `auth?.principal ?? "_"`
|
|
296
|
-
* at suspend, and the decide owner-gate treats `cp.scope === "_"` as anonymous-OPEN, server.ts ~L3710; the
|
|
297
|
-
* SendUserFile ledger scope + the /v1/sendfile-links fleet-wide default; the resource-suspend isolation
|
|
298
|
-
* key), `"_leader"` merges with the leader durable-approval owner scope (a tenant so named could list/
|
|
299
|
-
* decide internal leader approvals), `"_leader-…"` with the leader resource-suspend scope family, and
|
|
300
|
-
* `"__none__"` with the missing-principal placeholder scope on the decide/pending gates.
|
|
301
|
-
* - length > 190 is REJECTED: every scope/owner column the principal lands in VERBATIM is VARCHAR(190)
|
|
302
|
-
* (sendfile_link.scope, approval.owner/scope, memory-engine scope, rate-limit keys, …) — an over-long
|
|
303
|
-
* principal would truncate/500 at the INSERT and split one identity into two.
|
|
304
|
-
* Throws a typed 400 `HttpError` (the HTTP layer maps it); the message names the rule (a reserved identifier /
|
|
305
|
-
* the length cap), never a secret and never the internal scope's semantics. */
|
|
306
98
|
export const PRINCIPAL_MAX_LENGTH = 190;
|
|
307
|
-
/** 修1 — the SINGLE registry of internal sentinel/scope identifiers a tenant principal must never equal.
|
|
308
|
-
* 🔴 新增内部 sentinel scope 必须在此登记(精确值加 RESERVED_PRINCIPALS,前缀族加
|
|
309
|
-
* RESERVED_PRINCIPAL_PREFIXES),否则 assertPrincipalShape 会放行同名租户 = 与内部 scope 行合流的越权面。
|
|
310
|
-
* Current entries (where each is minted): `"_"` = single-user/anonymous sentinel (checkpoint/sendfile/
|
|
311
|
-
* resource-suspend scope family); `"__none__"` = missing-principal placeholder on the decide/pending gates
|
|
312
|
-
* (http/server.ts `principal ?? "__none__"`); `"_leader"` = leader durable-approval scope
|
|
313
|
-
* (src/leader/wire.ts); prefix `"_leader-"` = the leader resource-suspend scope family
|
|
314
|
-
* (`_leader-resource-<workerId>`, src/leader/wire.ts). NB the prefix REQUIRES the hyphen — a tenant named
|
|
315
|
-
* `_leaderX` collides with nothing internal and stays valid (boundary pinned by test). */
|
|
316
99
|
export const RESERVED_PRINCIPALS = ["_", "__none__", "_leader"];
|
|
317
100
|
export const RESERVED_PRINCIPAL_PREFIXES = ["_leader-"];
|
|
318
101
|
export function assertPrincipalShape(principal) {
|
|
319
102
|
if (RESERVED_PRINCIPALS.includes(principal) || RESERVED_PRINCIPAL_PREFIXES.some((p) => principal.startsWith(p))) {
|
|
320
|
-
// Wording deliberately names the RULE (reserved internal scope sentinel), not what the sentinel does.
|
|
321
103
|
throw new HttpError(400, `principal ${JSON.stringify(principal)} is reserved (an internal scope sentinel) — authenticate with a real tenant identifier`, { code: "principal_reserved" });
|
|
322
104
|
}
|
|
323
105
|
if (principal.length > PRINCIPAL_MAX_LENGTH) {
|
|
324
106
|
throw new HttpError(400, `principal exceeds ${PRINCIPAL_MAX_LENGTH} characters (the scope-column width every store pins, e.g. sendfile_link.scope VARCHAR(190)); got ${principal.length}`, { code: "principal_too_long" });
|
|
325
107
|
}
|
|
326
108
|
}
|
|
327
|
-
/** The checkpoint `scope` column's single-user/anonymous sentinel — the same "_" documented above
|
|
328
|
-
* ({@link RESERVED_PRINCIPALS}) as the checkpoint/sendfile/resource-suspend scope family's shared value.
|
|
329
|
-
* Single attested constant + codec pair (naming precedent: {@link SENDFILE_SINGLE_USER_SCOPE} in
|
|
330
|
-
* ./plugins/send-file-ledger.js) so every checkpoint scope read/write goes through one place instead of
|
|
331
|
-
* each call site re-deriving the "_" ⇄ no-principal mapping inline. */
|
|
332
109
|
export const CHECKPOINT_PUBLIC_SCOPE = "_";
|
|
333
|
-
/**
|
|
334
|
-
* 🔴 A-057.44(2026-08-19)—— **后台子代注册簿的匿名租户键**,与上面那只 checkpoint 哨兵是
|
|
335
|
-
* 「同一件事(没有 principal)的两套写法」。
|
|
336
|
-
*
|
|
337
|
-
* 写侧不是本仓能单方面改的:core 的 `treeScope = reviveClaim?.row.scope ?? ctx.principal ??
|
|
338
|
-
* opts.background?.scope` 同时喂**进程内** `defaultTaskRegistry` 与 **durable** 行,而本仓/`core` 全域的
|
|
339
|
-
* 读侧是一整族 `principal ?? "default"`(`runs.ts` markStopSourceForOwner、`boot/session-faces.ts`
|
|
340
|
-
* reapSessionBackground、`http/routes/runs.ts` 的 subagent output/stream 三口、`agents-roster.ts`、
|
|
341
|
-
* `fleet.ts`)。⇒ **把铸点改成 `"_"` 会把这一整族读者一起打瞎**(本车实测:`subagent-tail-content-supply`
|
|
342
|
-
* 的 tail 建连当场 404)。所以归一只能发生在**读侧**:checkpoint 侧拿着 `"_"` 去查 bg 分区时,
|
|
343
|
-
* 显式把这两个键当同一格。
|
|
344
|
-
*/
|
|
345
110
|
export const BACKGROUND_REGISTRY_ANON_SCOPE = "default";
|
|
346
|
-
/**
|
|
347
|
-
* checkpoint 的 scope → 该在哪些 **bg 注册簿分区**里找它的 parked 子代(A-057.44)。
|
|
348
|
-
*
|
|
349
|
-
* 匿名(`"_"`)⇒ 两个都查:`"_"`(将来若统一了写侧)与 `"default"`(今天 core 真写下去的那个)。
|
|
350
|
-
* 其余 ⇒ 原样单查:有 principal 时两套约定折出**同一个**串(`encodeCheckpointScope` 只在 null/undefined
|
|
351
|
-
* 时才替换,core 那侧也只在 `ctx.principal` 缺席时才落兜底),不存在第二个候选;而多租户部署里一个
|
|
352
|
-
* **真名叫 `default`** 的租户必须只匹配自己那格 —— 所以别把别名做成双向。
|
|
353
|
-
*/
|
|
354
111
|
export function backgroundScopesForCheckpointScope(scope) {
|
|
355
112
|
return scope === CHECKPOINT_PUBLIC_SCOPE ? [CHECKPOINT_PUBLIC_SCOPE, BACKGROUND_REGISTRY_ANON_SCOPE] : [scope];
|
|
356
113
|
}
|
|
357
|
-
/** Encode a principal (or none) as the checkpoint `scope` column value. */
|
|
358
114
|
export const encodeCheckpointScope = (principal) => principal ?? CHECKPOINT_PUBLIC_SCOPE;
|
|
359
|
-
/** Decode a checkpoint `scope` column value back to a principal — {@link CHECKPOINT_PUBLIC_SCOPE} maps to
|
|
360
|
-
* `undefined` (no principal), everything else passes through verbatim. */
|
|
361
115
|
export const decodeCheckpointScope = (scope) => (scope === CHECKPOINT_PUBLIC_SCOPE ? undefined : scope);
|
|
362
|
-
/** Read the authenticated principal from the trusted header. Never from the body. */
|
|
363
116
|
export function principalFrom(req, config) {
|
|
364
117
|
if (!config.principalHeader)
|
|
365
|
-
return undefined;
|
|
118
|
+
return undefined;
|
|
366
119
|
const raw = req.headers[config.principalHeader.toLowerCase()];
|
|
367
120
|
const principal = (Array.isArray(raw) ? raw[0] : raw)?.trim() || undefined;
|
|
368
121
|
if (principal !== undefined)
|
|
369
|
-
assertPrincipalShape(principal);
|
|
122
|
+
assertPrincipalShape(principal);
|
|
370
123
|
return principal;
|
|
371
124
|
}
|
|
372
|
-
/** Header carrying the crypto-verified principal JWT on a direct-door worker (the issuer signs it; the worker only
|
|
373
|
-
* verifies). The trusted twin of `config.principalHeader` (the latter is client-spoofable on a direct door). */
|
|
374
125
|
export const PRINCIPAL_TOKEN_HEADER = "x-approval-principal-token";
|
|
375
|
-
/**
|
|
376
|
-
* direct-door 决策证明三件套的另外两个头名(JWT 在上面那个常量里)。
|
|
377
|
-
*
|
|
378
|
-
* 为什么也具名(2026-08-08,#176 二轮扫描 finding):这三个头名此前在
|
|
379
|
-
* `http/routes/approvals-assistant.ts` 的两个 `verifyDirectDoorProof` 调用点、以及 `http/server.ts` 的
|
|
380
|
-
* CORS `access-control-allow-headers` 串里**各手抄一份**——而 CORS 那一行同一句里已经在用
|
|
381
|
-
* `${principalHeader}` 插值另一个头名,证明具名写法在该处本就可用。改任一头名时漏改 CORS 串的后果
|
|
382
|
-
* 是**静默的**:浏览器预检把该头挡下,请求到不了 handler ⇒ 表现成「direct-door 上审批面莫名失效」,
|
|
383
|
-
* 而不是任何一处报错。低危(改头名本身即 wire BREAKING、不太可能单边发生),但零成本可消。
|
|
384
|
-
*/
|
|
385
126
|
export const APPROVAL_MAC_HEADER = "x-approval-mac";
|
|
386
127
|
export const APPROVAL_MAC_KID_HEADER = "x-approval-mac-kid";
|
|
387
|
-
/** The DIRECT-DOOR-SAFE principal — the VERIFIED identity for governance + cost + isolation. On a BFF/gated deployment
|
|
388
|
-
* the header is already verified upstream (return it); on a direct-door worker the `principalHeader` is client-
|
|
389
|
-
* spoofable, so identity comes from the crypto-verified `x-approval-principal-token` JWT (the issuer's private key
|
|
390
|
-
* never lives on a worker). `spec.principal` MUST be this, NEVER `principalFrom` — keying a gate or a bill on a
|
|
391
|
-
* spoofable header is a multi-tenant governance/cost bypass (core's TaskSpec.principal contract). Single source for
|
|
392
|
-
* `createAuthorizer` AND every `gatedPrincipal` call site (F-fix: direct-door principal spoofing, core-steered). */
|
|
393
|
-
/** 鉴权桥:请求入口 async 预认证把验过的 registry-JWT principal 挂在这个 symbol 上——它是
|
|
394
|
-
* request-local 真源(来自签名,非自报头),优先于任何头读取。setSsoPrincipal 在 server.ts 的入口块设,
|
|
395
|
-
* verifiedPrincipal 在此读。 */
|
|
396
128
|
const SSO_PRINCIPAL = Symbol.for("sema.ssoVerifiedPrincipal");
|
|
397
129
|
export function setSsoPrincipal(req, principal) {
|
|
398
130
|
req[SSO_PRINCIPAL] = principal;
|
|
@@ -401,9 +133,6 @@ export function ssoVerifiedPrincipal(req) {
|
|
|
401
133
|
const v = req[SSO_PRINCIPAL];
|
|
402
134
|
return typeof v === "string" && v.length > 0 ? v : undefined;
|
|
403
135
|
}
|
|
404
|
-
/** 鉴权桥 scope 透传:registry-JWT 的 `scope` claim(多租户活动租户,registry-core 0.3.0+
|
|
405
|
-
* `VerifiedIdentity.scope`)也挂 request-local——与 principal 同真源(来自签名,非自报头)。用途=可观测/审计
|
|
406
|
-
* 归因,以及未来 scope 级 quota/过滤的读取地基(单一真源)。ABSENT = pre-scope/默认 token,语义"global"。 */
|
|
407
136
|
const SSO_SCOPE = Symbol.for("sema.ssoVerifiedScope");
|
|
408
137
|
export function setSsoScope(req, scope) {
|
|
409
138
|
req[SSO_SCOPE] = scope;
|
|
@@ -413,15 +142,13 @@ export function ssoVerifiedScope(req) {
|
|
|
413
142
|
return typeof v === "string" && v.length > 0 ? v : undefined;
|
|
414
143
|
}
|
|
415
144
|
export function verifiedPrincipal(req, config) {
|
|
416
|
-
// 鉴权桥优先:registry-JWT 验过的 principal 来自签名,在 directDoor 与 BFF/gated 两种部署都胜出
|
|
417
|
-
// (directDoor 下原走 x-approval-principal-token,SSO principal 在其之前返回,成为下游的可信身份)。
|
|
418
145
|
const sso = ssoVerifiedPrincipal(req);
|
|
419
146
|
if (sso) {
|
|
420
|
-
assertPrincipalShape(sso);
|
|
147
|
+
assertPrincipalShape(sso);
|
|
421
148
|
return sso;
|
|
422
149
|
}
|
|
423
150
|
if (!config.directDoorActive)
|
|
424
|
-
return principalFrom(req, config);
|
|
151
|
+
return principalFrom(req, config);
|
|
425
152
|
const h = req.headers[PRINCIPAL_TOKEN_HEADER];
|
|
426
153
|
const token = (Array.isArray(h) ? h[0] : h) ?? "";
|
|
427
154
|
const sub = verifyPrincipalJwt(token, config.principalJwtPubkeys, {
|
|
@@ -429,20 +156,8 @@ export function verifiedPrincipal(req, config) {
|
|
|
429
156
|
aud: config.principalJwtAud ?? "\0",
|
|
430
157
|
maxTtlSec: config.principalJwtMaxTtlSec,
|
|
431
158
|
})?.sub;
|
|
432
|
-
// F1/F3: gate the direct-door JWT sub as well — a typed 400 naming the rule beats a silent-null 401 (the
|
|
433
|
-
// minter must not be able to issue a "_"-sub token that merges with the anonymous scope family downstream).
|
|
434
159
|
if (sub !== undefined)
|
|
435
160
|
assertPrincipalShape(sub);
|
|
436
161
|
return sub;
|
|
437
162
|
}
|
|
438
|
-
// design/158 N17: memoryScopeFor / memoryEngineRemoteLanePosture / memoryEngineBackendFor / memorySpecForRequest
|
|
439
|
-
// moved VERBATIM to the leaf ./memory-scope.js (security.ts's name says auth; these four resolve memory-plane
|
|
440
|
-
// config, unrelated). Import them from ./memory-scope.js directly ([2354] 兼容面全清 — the compat re-export here
|
|
441
|
-
// is gone); this file's own createAuthorizer below still calls memoryScopeFor.
|
|
442
|
-
// design/158 A13: approval-envelope HMAC (integrity, verifyApprovalHmac/approvalHmacMessage/
|
|
443
|
-
// MAX_APPROVAL_REASON_CHARS) and principal-JWT/direct-door proof (identity, verifyPrincipalJwt/
|
|
444
|
-
// approvalBnd/verifyDirectDoorProof) moved VERBATIM to ./approval-hmac.js and ./principal-jwt.js — this file
|
|
445
|
-
// keeps only the authenticator/principal-shape/SSO-mount group (createAuthorizer, verifiedPrincipal,
|
|
446
|
-
// assertPrincipalShape, principalFrom, setSsoPrincipal/setSsoScope). Import the moved names from their
|
|
447
|
-
// new homes directly ([2354] 兼容面全清 — the compat re-exports here are gone).
|
|
448
163
|
//# sourceMappingURL=security.js.map
|
package/dist/session-leaf-bus.js
CHANGED
|
@@ -1,44 +1,12 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Session leaf-advance bus — the S2 fast path of the session-level SSE subscription
|
|
3
|
-
* ([1196]/[1208]③: an append that lands on THIS replica fans out to watchers synchronously,
|
|
4
|
-
* zero latency on single-replica deployments; cross-replica watchers still ride the probe lane).
|
|
5
|
-
*
|
|
6
|
-
* Deliberately a REPLICA-LOCAL singleton (module state), not a constructor-threaded dependency:
|
|
7
|
-
* the leaf write sits at the bottom of the per-session storage stack (TiDB/Pg `persist()` — a
|
|
8
|
-
* static-factory + N-arg constructor chain away from main.ts), while the one consumer
|
|
9
|
-
* (SessionWatchRegistry.notifyLocal) is replica-local by design. A bus keeps the wiring one line
|
|
10
|
-
* per write site and one line in main; anything smarter re-plumbs four layers for the same effect.
|
|
11
|
-
*
|
|
12
|
-
* Contract:
|
|
13
|
-
* - `emitLeafAdvance` MUST be fire-and-forget from the writer's perspective: it never throws
|
|
14
|
-
* (listener errors are swallowed) and does no async work — a broken watcher must not fail or
|
|
15
|
-
* slow a session write.
|
|
16
|
-
* - Emit AFTER the storage commit succeeds (a rolled-back CAS must not fan out a leaf that never
|
|
17
|
-
* became durable).
|
|
18
|
-
* - Owner IS carried and MUST be the COMMIT-TIME row owner (codex two rounds: without owner the
|
|
19
|
-
* generation fence is skipped; a wake-time snapshot is worse — stale-A over a reclaimed row false-
|
|
20
|
-
* matches an old A entry and leaks the new tenant's leaf into the old stream). Zero-cost source:
|
|
21
|
-
* the persist transaction's own leaf_seq SELECT carries the owner column — the emitted owner is the
|
|
22
|
-
* row's tenant at the moment the write committed.
|
|
23
|
-
* - `leafSeq`(the row's committed leaf_seq)rides every emit: DB commits serialize, but the
|
|
24
|
-
* post-commit continuations don't — without an ordering token a delayed leaf-N delivery after
|
|
25
|
-
* leaf-N+1 would walk the registry BACKWARD (and a stale notification could false-drop a newer
|
|
26
|
-
* generation on owner mismatch). The registry keeps the highest accepted seq and ignores lower.
|
|
27
|
-
* - The local (file/in-mem) lane does not emit: those sessions persist inside core's storage
|
|
28
|
-
* backend with no server-side write hook; the probe lane covers them at its normal cadence.
|
|
29
|
-
*/
|
|
30
1
|
let listener;
|
|
31
|
-
/** main.ts wires this once at boot (→ SessionWatchRegistry.notifyLocal). Last write wins; tests reset with undefined. */
|
|
32
2
|
export function setLeafAdvanceListener(fn) {
|
|
33
3
|
listener = fn;
|
|
34
4
|
}
|
|
35
|
-
/** Called by the SQL session-storage write sites post-commit. Never throws, never awaits. */
|
|
36
5
|
export function emitLeafAdvance(sessionId, leafId, owner, leafSeq) {
|
|
37
6
|
try {
|
|
38
7
|
listener?.(sessionId, leafId, owner, leafSeq);
|
|
39
8
|
}
|
|
40
9
|
catch {
|
|
41
|
-
/* a broken watcher must never fail a session write */
|
|
42
10
|
}
|
|
43
11
|
}
|
|
44
12
|
//# sourceMappingURL=session-leaf-bus.js.map
|
|
@@ -1,70 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* session-sync 的**内容判据**——回答一个 id-集合分类器回答不了的问题:
|
|
3
|
-
* 「两条 entry id 集合相等的日志,**内容**是否也相同?」
|
|
4
|
-
*
|
|
5
|
-
* ── 为什么需要它(深挖第一轮 + 2026-07-26 顺着往下查出的第二半)────────────────────────────────────
|
|
6
|
-
* §7 的 `classifySyncRelationshipByIds` 只比 **id 集合**,而 entry id 是 uuidv7、**不是内容寻址** ⇒
|
|
7
|
-
* 「同 id、异载荷」结构上完全可能(wire 本身就收调用方给的 entries)。第一半的后果已知:Phase A 判 `identical`
|
|
8
|
-
* ⇒ 整段跳过、调用方拿 200 而目的端仍是自己那份内容(真 HTTP 复现过)。
|
|
9
|
-
*
|
|
10
|
-
* 🔴 **第二半是我一开始判错的地方**:我在黑板 [1701] 说「server 单方面修不了」。那句只对 **Phase A** 成立
|
|
11
|
-
* (那时 server 手上没有源端内容,必须靠调用方送摘要)。但 **commit 时 server 同时握着两份日志**
|
|
12
|
-
* (staged + dst),所以内容比较**根本不需要调用方配合** —— 而 4 处 commit/import 路径当时都写着
|
|
13
|
-
* 「`relation === "identical"` ⇒ 跳过 entries 写入」,把「id 集合相等」当成了「内容相同」。
|
|
14
|
-
* 后果:一个**已经把整条日志上传完**的调用方(没送摘要、或送了但不可比),在 commit 处仍然拿到 no-op。
|
|
15
|
-
* ⇒ 两半都要修:Phase A 需要调用方的 `logDigest`(省掉无谓上传),commit 这一半是 server 自己的活。
|
|
16
|
-
*
|
|
17
|
-
* ── 判据来源:core 1.414 导出的 `sessionLogDigest` ──────────────────────────────────────────────────
|
|
18
|
-
* 覆盖每条 entry 的**全部字段**(id / parentId / type / timestamp / 载荷)且**顺序显著**,逐条带长度前缀
|
|
19
|
-
* (所以两份日志不可能被重新切分成同样的字节)。core 明确它可以两端各算 —— 与 `boundInputHash` 不同,
|
|
20
|
-
* 因为这里**分叉的代价是多同步一趟,不是错答案、也不是拒绝**(审批路径上分叉会拒掉一个合法的人类决定,
|
|
21
|
-
* 方向相反)。
|
|
22
|
-
*
|
|
23
|
-
* ── ⚠️ v1 摘要下本判据的**退化行为**(core 黑板 [1709];1.415.0 已修,现行下限已远高于此 ——
|
|
24
|
-
* 承重下限与理由见 `test/core-dependency-floor.test.ts`,别在这里记版本号)────────────────
|
|
25
|
-
* v1 的摘要区分 `{a: undefined}` 与 `{}`,而 core 自己成规模产 own-key=undefined 的 entry ⇒ 同一份日志的
|
|
26
|
-
* 内存形与 JSON 往返形摘要必然不同。本函数在 commit 处比的是 **staged**(刚从 wire 解析,JSON 形)与 **dst**
|
|
27
|
-
* (从存储读回,也是 JSON 形)—— 两侧同形,所以**这一处**不受那条影响;但若某个后端把 staged 留在内存形,
|
|
28
|
-
* 判据会退化成「恒不相等 ⇒ 恒改写」。
|
|
29
|
-
* 🔴 那个退化方向是**安全**的(多写一遍,不会丢数据),这是刻意选的:判据不确定时**宁可改写**,
|
|
30
|
-
* 因为反方向的错(误判"内容相同"而跳过写入)正是本函数要修的那个缺陷。
|
|
31
|
-
* ⇒ 所以这里**不加 scheme 门**。(Phase A 那条路曾加过 scheme **名字门**,后被 tsc 判恒真并撤掉,
|
|
32
|
-
* 换成「依赖下限 + 行为门」—— 名字门只认识旧名字,守不住这条性质;见 server.ts Phase A 段注释与
|
|
33
|
-
* `test/sync-content-digest.test.ts`。)
|
|
34
|
-
*
|
|
35
|
-
* ⚠️ 抽成共享函数而不是在 4 处各写一遍:core [1689] RB-74 的教训(宣布"同族类修完成"而三个实现只落了一个),
|
|
36
|
-
* 本仓今天也刚在 33 处 exec→FileError 包装上吃过同款。判据收成一处,才谈得上机器看守。
|
|
37
|
-
*/
|
|
38
1
|
import { sessionLogDigest } from "@sema-agent/core";
|
|
39
|
-
/**
|
|
40
|
-
* id 集合已判 `identical` 的两条日志,**内容是否真的相同**。
|
|
41
|
-
*
|
|
42
|
-
* `true` ⇒ 内容也相同,跳过 entries 写入是**对的**(只需 owner 重戳)。
|
|
43
|
-
* `false` ⇒ **同 id 异内容**:必须真正改写目的端,否则调用方拿到的是一句"已同步"的谎。
|
|
44
|
-
*
|
|
45
|
-
* 🔴 这里**不抛错、不拒绝**:core 定的消费口径是「digest 不符永远不得变成 error / 409 / 拒绝同步」——
|
|
46
|
-
* 不符只意味着"多干一趟活",而不是"这次同步非法"。
|
|
47
|
-
*/
|
|
48
2
|
export function identicalIdsAlsoIdenticalContent(staged, dst) {
|
|
49
3
|
if (dst === null)
|
|
50
|
-
return false;
|
|
4
|
+
return false;
|
|
51
5
|
return sessionLogDigest(staged) === sessionLogDigest(dst);
|
|
52
6
|
}
|
|
53
|
-
/**
|
|
54
|
-
* `fast_forward` 的**内容判据**([2557] 审计 A5/F2,2-i)——id 分类器的另一格盲区:
|
|
55
|
-
* `dst ⊆ src` 只说明 **id 集合**是子集,共享 id 的**载荷**可能已分叉(目的端把某条 entry
|
|
56
|
-
* 补写/纠正过、id 不变,源端拿着旧版本 + 新追加)。此时按「clean append」整段换装,
|
|
57
|
-
* 目的端那几条纠正会被**静默销毁** —— §7「会丢目的端历史必须 409(除非显式 overwrite-dst)」
|
|
58
|
-
* 在载荷层同样成立。与 `identical` 格的口径**方向不同**是刻意的:identical 格 core 已定
|
|
59
|
-
* 「不符=多干一趟活、照常改写」(那里 src 按 id 完整覆盖 dst,push 语义=以源为准);本格
|
|
60
|
-
* dst 的共享版本与 src 的追加互为对方没有的东西,是真分叉,归 409 那一族。
|
|
61
|
-
*
|
|
62
|
-
* 入参:`stagedShared` = staged 里 id ∈ dst 集合的那部分(**staged 顺序**);`dst` = 目的端全量
|
|
63
|
-
* (**dst 顺序**)。两侧都保留各自顺序 —— 摘要顺序显著,共享段的**乱序**同样是分叉。
|
|
64
|
-
*
|
|
65
|
-
* 返回 `null` = 无分叉(安全 append);返回非空数组 = 分叉的 entry id(dst 顺序):
|
|
66
|
-
* 逐条单 entry 摘要不等的 id;若逐条全等而整段摘要不等(纯乱序),列出位置错开的 id。
|
|
67
|
-
*/
|
|
68
7
|
export function fastForwardSharedContentDiverged(stagedShared, dst) {
|
|
69
8
|
if (sessionLogDigest([...stagedShared]) === sessionLogDigest([...dst]))
|
|
70
9
|
return null;
|
|
@@ -82,13 +21,6 @@ export function fastForwardSharedContentDiverged(stagedShared, dst) {
|
|
|
82
21
|
}
|
|
83
22
|
return diverged;
|
|
84
23
|
}
|
|
85
|
-
/**
|
|
86
|
-
* 把 fast_forward 的内容级分叉铸成 §7 冲突载荷(SyncConflictError.relation,wire 上是 409 的
|
|
87
|
-
* `{relation}` 详情)。用现有 `fork` 成员而不是扩词表:内容层它**就是** fork(双方各握着对方
|
|
88
|
-
* 没有的东西),且 SDK/客户端的关系词表是闭集,扩成员=BREAKING。字段口径(内容级,与 id 级
|
|
89
|
-
* 注释的措辞差异在此登记):`srcExclusive` = 源端要追加的新 id;`dstExclusive` = 目的端将被
|
|
90
|
-
* 销毁的共享版本的 id;`commonAncestor` = null(未逐条定位分叉点 —— 定位成本与收益不配)。
|
|
91
|
-
*/
|
|
92
24
|
export function contentForkRelation(divergedIds, newEntryIds) {
|
|
93
25
|
return { relation: "fork", commonAncestor: null, srcExclusive: [...newEntryIds], dstExclusive: [...divergedIds] };
|
|
94
26
|
}
|