@sema-agent/server 7.43.0 → 7.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/USAGE.md +18 -0
- package/dist/adoption/plan.js +31 -192
- package/dist/adoption/quiesce.js +3 -86
- package/dist/adoption/runner.js +3 -140
- package/dist/adoption/sql.js +0 -74
- package/dist/adoption/wire.js +0 -80
- package/dist/approval-ask-machine.js +0 -75
- package/dist/approval-card.js +0 -323
- package/dist/approval-deny-reasons.js +0 -42
- package/dist/approval-hmac.js +0 -35
- package/dist/approval-reconciler.js +8 -166
- package/dist/approval.js +4 -67
- package/dist/audit.js +1 -44
- package/dist/auth-bridge.js +4 -47
- package/dist/auth-keys.js +0 -23
- package/dist/bake-runner/main.js +4 -65
- package/dist/bake-runner/protocol.js +3 -58
- package/dist/bake-runner/runner.js +5 -91
- package/dist/bench/l8/artifact.js +2 -61
- package/dist/bench/l8/escape.js +0 -25
- package/dist/bench/l8/index.js +0 -14
- package/dist/bench/l8/probes.js +0 -60
- package/dist/bench/l8/run-probes.js +2 -45
- package/dist/bench/s1/arms.js +14 -189
- package/dist/bench/s1/live-deps.js +11 -253
- package/dist/bench/s1/oracle.js +1 -36
- package/dist/bench/s1/repair-oracle-adapter.js +0 -30
- package/dist/bench/s1/reviewer.js +0 -34
- package/dist/bench/s1/row.js +0 -62
- package/dist/bench/s1/run-firm.js +4 -70
- package/dist/bench/s1/runner-ctx.js +0 -40
- package/dist/bench/s1/tasks.js +0 -115
- package/dist/boot/adoption.js +0 -20
- package/dist/boot/budget-tracing.js +3 -55
- package/dist/boot/config-center.js +43 -551
- package/dist/boot/coordinators.js +1 -69
- package/dist/boot/crash-last.js +0 -18
- package/dist/boot/deferred-sandbox-path-env.js +3 -119
- package/dist/boot/execution-env.js +5 -171
- package/dist/boot/governance-seams.js +0 -92
- package/dist/boot/leader.js +0 -69
- package/dist/boot/limit-sync.js +0 -2
- package/dist/boot/memory-boundary.js +3 -91
- package/dist/boot/org-memory.js +1 -25
- package/dist/boot/parked-revive-gate.js +0 -150
- package/dist/boot/permission-rules-audit.js +1 -56
- package/dist/boot/reapers.js +15 -273
- package/dist/boot/resolve-spec.js +9 -768
- package/dist/boot/retention-lane.js +4 -87
- package/dist/boot/runner-deps.js +3 -297
- package/dist/boot/runtime-caps.js +0 -40
- package/dist/boot/session-faces.js +2 -133
- package/dist/boot/shutdown.js +9 -83
- package/dist/boot/side-query-lane.js +2 -137
- package/dist/boot/stores.js +15 -215
- package/dist/boot/task-list-lane.js +0 -18
- package/dist/boot/webfetch-summarize-lane.js +0 -47
- package/dist/boot/workflow-orchestration.js +3 -99
- package/dist/boot-reclaim.js +0 -20
- package/dist/bounded-session-map.js +0 -19
- package/dist/brain.js +2 -139
- package/dist/budget.js +9 -244
- package/dist/capabilities/builtin-tools.js +0 -3
- package/dist/capabilities/center-plugins.js +6 -51
- package/dist/capabilities/center-prompts.js +6 -71
- package/dist/capabilities/code-review-council.js +7 -40
- package/dist/capabilities/collab-workflows.js +1 -44
- package/dist/capabilities/hands-lane.js +0 -65
- package/dist/capabilities/memory-notice.js +0 -70
- package/dist/capabilities/prompt.js +0 -14
- package/dist/capabilities/prompts/code-review.js +0 -14
- package/dist/capabilities/prompts/identity.js +0 -6
- package/dist/capabilities/prompts/team.js +0 -4
- package/dist/capabilities/repo-tools.js +0 -34
- package/dist/capabilities/sandbox-file-send.js +3 -92
- package/dist/capabilities/scenarios.d.ts +0 -1
- package/dist/capabilities/scenarios.js +18 -279
- package/dist/capabilities/select-environment-tool.js +0 -32
- package/dist/capabilities/send-user-file-tool.js +2 -66
- package/dist/capabilities/skills.d.ts +1 -2
- package/dist/capabilities/skills.js +3 -21
- package/dist/capabilities/team.d.ts +3 -11
- package/dist/capabilities/team.js +3 -51
- package/dist/capabilities/tool-defer.js +0 -3
- package/dist/config-center/apply-effective.js +15 -330
- package/dist/config-center/apply-ledger.js +5 -38
- package/dist/config-center/facade.js +0 -41
- package/dist/config-center/hot-keys-registry.js +0 -20
- package/dist/config-center/http-client.js +2 -125
- package/dist/config-center/mcp-revocation.js +2 -34
- package/dist/config-center/read-face.js +0 -59
- package/dist/config-center/restart-signal.js +1 -85
- package/dist/config-center/skills-mcp.d.ts +1 -1
- package/dist/config-center/skills-mcp.js +9 -76
- package/dist/config-center/stage-limits.js +8 -35
- package/dist/config-invariants.js +0 -16
- package/dist/config-lkg.js +0 -42
- package/dist/config-provider.js +3 -186
- package/dist/config-types.js +0 -5
- package/dist/config.js +107 -1145
- package/dist/degenerate-instrument.js +3 -67
- package/dist/deployment-governance.js +0 -124
- package/dist/digest-form.js +0 -12
- package/dist/elicitation.js +3 -86
- package/dist/env-facts.js +7 -75
- package/dist/fleet/fleet-bus.js +34 -507
- package/dist/fleet/fleet-reconciler.js +9 -149
- package/dist/fleet/fleet-terminal-window.js +10 -178
- package/dist/fleet/subagent-tail-bus.js +3 -72
- package/dist/fleet-client.js +10 -70
- package/dist/fleet-lease.js +5 -79
- package/dist/git-api-kind.js +0 -3
- package/dist/governance-ask-marks.js +2 -78
- package/dist/hooks/branch-transcript.js +0 -74
- package/dist/hooks/cc-agent-hook-prompt.js +0 -29
- package/dist/hooks/cc-stop-prompt.js +1 -46
- package/dist/hooks/hook-llm.js +1 -53
- package/dist/hooks/hook-runner.js +20 -414
- package/dist/http/active-run-conflict.js +4 -129
- package/dist/http/cursor-fingerprint.d.ts +5 -0
- package/dist/http/cursor-fingerprint.js +5 -0
- package/dist/http/idempotency.js +0 -37
- package/dist/http/principal-gate.js +3 -40
- package/dist/http/route-ctx.js +0 -9
- package/dist/http/routes/a2a-serve.js +7 -319
- package/dist/http/routes/admin-config-refresh.js +0 -4
- package/dist/http/routes/admin-drain.js +0 -10
- package/dist/http/routes/adoption.js +1 -29
- package/dist/http/routes/agents-roster.js +1 -47
- package/dist/http/routes/approvals-assistant.js +32 -395
- package/dist/http/routes/attachments.js +4 -22
- package/dist/http/routes/capabilities.js +3 -420
- package/dist/http/routes/diagnostics.js +1 -81
- package/dist/http/routes/fleet.js +9 -185
- package/dist/http/routes/images.js +14 -239
- package/dist/http/routes/leader.js +0 -13
- package/dist/http/routes/memory-bundle.js +1 -61
- package/dist/http/routes/memory-policy.js +9 -99
- package/dist/http/routes/notify-wake.js +3 -37
- package/dist/http/routes/observability.js +2 -19
- package/dist/http/routes/retention-ops.js +2 -34
- package/dist/http/routes/rules.js +1 -93
- package/dist/http/routes/runs.js +64 -873
- package/dist/http/routes/session-sync.js +19 -258
- package/dist/http/routes/sessions-list.js +17 -43
- package/dist/http/routes/sessions.js +34 -226
- package/dist/http/routes/shared-memory.js +5 -36
- package/dist/http/routes/side-query.js +1 -87
- package/dist/http/routes/tasks.js +52 -722
- package/dist/http/routes/trace-usage.js +32 -195
- package/dist/http/routes/workflows.js +19 -195
- package/dist/http/run-meta.js +0 -6
- package/dist/http/send.js +0 -32
- package/dist/http/server.js +92 -1597
- package/dist/http/sse-lifecycle.js +2 -13
- package/dist/http/sse-log.js +3 -48
- package/dist/http/tar.js +5 -21
- package/dist/http/verify-rounds.js +0 -5
- package/dist/http/wire-gate.js +0 -9
- package/dist/http/workspace-content.js +0 -10
- package/dist/images/bake-validate.js +1 -70
- package/dist/images/manifest.js +1 -6
- package/dist/index.js +0 -21
- package/dist/key-resolver.js +2 -17
- package/dist/leader/diffout.js +1 -20
- package/dist/leader/diffup.js +0 -47
- package/dist/leader/endpoint.js +2 -57
- package/dist/leader/fanout.js +3 -45
- package/dist/leader/grader-env-factory.js +3 -72
- package/dist/leader/leader.js +5 -156
- package/dist/leader/merge.js +8 -115
- package/dist/leader/planner.js +3 -54
- package/dist/leader/repair-oracle.js +1 -60
- package/dist/leader/repair-wire.js +2 -79
- package/dist/leader/wire.js +8 -307
- package/dist/lsp/e2b-bridge.js +4 -64
- package/dist/lsp/e2b-manager.js +6 -94
- package/dist/lsp/lsp-frames.js +0 -12
- package/dist/lsp/manager.js +4 -96
- package/dist/lsp/ws-transport.js +5 -55
- package/dist/lsp-evict.js +1 -15
- package/dist/main.js +57 -765
- package/dist/memory-bundle-engine.js +0 -55
- package/dist/memory-export.js +0 -4
- package/dist/memory-posture.js +1 -15
- package/dist/memory-scope.js +11 -148
- package/dist/memory-sync-client.js +2 -44
- package/dist/memory-sync.js +1 -80
- package/dist/model-select.js +3 -80
- package/dist/observability/cost-quota.js +1 -17
- package/dist/observability/cost-taxonomy.js +0 -34
- package/dist/observability/fail-open.js +7 -86
- package/dist/observability/logger.js +0 -6
- package/dist/observability/metrics.js +0 -94
- package/dist/observability/otel-exporter.js +3 -13
- package/dist/observability/principal-context.js +0 -9
- package/dist/observability/prompt-manifest.js +1 -37
- package/dist/observability/rate-limit.js +0 -4
- package/dist/observability/secret-env-scrub.js +2 -56
- package/dist/observability/tool-trace.js +1 -70
- package/dist/orchestration/hardened-vm-runner.js +4 -118
- package/dist/orchestration/hardened-vm-worker-runner.js +1 -26
- package/dist/orchestration/hardened-vm-worker.js +0 -27
- package/dist/orchestration/subagent-steer.js +1 -45
- package/dist/orchestration/workflow-agent-steer.js +1 -80
- package/dist/orchestration/workflow-completion-inbox.js +32 -285
- package/dist/orchestration/workflow-notify-journal.js +16 -259
- package/dist/org-memory-admission.js +3 -47
- package/dist/parent-watch.js +2 -48
- package/dist/parked-decide.js +1 -109
- package/dist/per-task-image.js +0 -57
- package/dist/plan-cache-probe.js +3 -27
- package/dist/plugins/adoption-log-sql.js +2 -119
- package/dist/plugins/approval-ask-store-memory.js +3 -38
- package/dist/plugins/approval-ask-store-sql.js +6 -188
- package/dist/plugins/approval-exemption-store.js +2 -28
- package/dist/plugins/background-agent-store-sql.js +4 -105
- package/dist/plugins/background-shell-support.js +14 -122
- package/dist/plugins/blob-backend.js +6 -169
- package/dist/plugins/breaker-state-sql.js +8 -46
- package/dist/plugins/caching-session-store.js +4 -106
- package/dist/plugins/checkpoint-store-sql.js +18 -505
- package/dist/plugins/e2b-orphan-reclaim.js +0 -45
- package/dist/plugins/file-outcome-sink.js +0 -9
- package/dist/plugins/file-resume-anchor-store.js +4 -43
- package/dist/plugins/file-run-store.js +26 -364
- package/dist/plugins/file-snapshot-store-sql.js +10 -181
- package/dist/plugins/fork-routing-session-store.js +8 -111
- package/dist/plugins/host-platform.js +2 -91
- package/dist/plugins/image-bake-store-sql.js +7 -250
- package/dist/plugins/image-index-sql.js +4 -123
- package/dist/plugins/k8s-bg-scripts.js +4 -88
- package/dist/plugins/k8s-exec-protocol.js +0 -41
- package/dist/plugins/leader-run-store-sql.js +0 -101
- package/dist/plugins/local-checkpoint-store.js +8 -128
- package/dist/plugins/local-session-store.js +34 -296
- package/dist/plugins/local-task-attachment-store.js +2 -16
- package/dist/plugins/mailbox-store-sql.js +8 -65
- package/dist/plugins/memory-embedder-fingerprint.js +5 -166
- package/dist/plugins/memory-embedder.js +4 -70
- package/dist/plugins/memory-engine-pg.js +6 -170
- package/dist/plugins/memory-engine-tidb.js +7 -155
- package/dist/plugins/memory-engine-vector-util.js +0 -10
- package/dist/plugins/memory-key-guards.js +0 -34
- package/dist/plugins/memory-origin-law.js +0 -187
- package/dist/plugins/memory-resume-anchor-store.js +0 -17
- package/dist/plugins/memory-run-store.js +12 -89
- package/dist/plugins/memory-session-policy-store.js +0 -17
- package/dist/plugins/memory-sync-store-pg.js +4 -49
- package/dist/plugins/memory-sync-store-tidb.js +3 -35
- package/dist/plugins/outcome-ledger-sql.js +3 -97
- package/dist/plugins/permission-rule-store-file.js +5 -133
- package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
- package/dist/plugins/permission-rule-store-sql.js +50 -307
- package/dist/plugins/pg-cost-quota.js +0 -7
- package/dist/plugins/pg-pool.js +0 -92
- package/dist/plugins/pg-rate-limiter.js +2 -13
- package/dist/plugins/pg-safe-json.js +4 -40
- package/dist/plugins/pg-session-storage.js +25 -189
- package/dist/plugins/posix-shell-fs.js +1 -31
- package/dist/plugins/remote-env-adb.js +12 -101
- package/dist/plugins/remote-env-e2b.js +40 -371
- package/dist/plugins/remote-env-file-error.js +0 -37
- package/dist/plugins/remote-env-host.js +63 -483
- package/dist/plugins/remote-env-k8s.js +32 -326
- package/dist/plugins/remote-env-local-docker.js +24 -157
- package/dist/plugins/remote-env-ssh.js +20 -128
- package/dist/plugins/remote-scratchpad.js +2 -32
- package/dist/plugins/remote-shell.js +1 -32
- package/dist/plugins/resume-anchor-store-sql.js +0 -11
- package/dist/plugins/retention-lane-store-sql.js +0 -108
- package/dist/plugins/retention-store-sql.js +4 -383
- package/dist/plugins/roster-store-sql.js +0 -55
- package/dist/plugins/run-store-sql.js +14 -251
- package/dist/plugins/s3-presign.js +2 -49
- package/dist/plugins/scheduler-support.js +3 -80
- package/dist/plugins/send-file-ledger.js +4 -53
- package/dist/plugins/send-user-file.js +4 -94
- package/dist/plugins/session-placement.js +1 -89
- package/dist/plugins/session-policy-store-sql.js +3 -81
- package/dist/plugins/session-store.js +0 -57
- package/dist/plugins/shared-memory-store-sql.js +4 -167
- package/dist/plugins/sql-driver.js +0 -17
- package/dist/plugins/sql-errors.js +0 -7
- package/dist/plugins/sql-escape.js +0 -8
- package/dist/plugins/sql-row-helpers.js +0 -25
- package/dist/plugins/store-backend.js +42 -222
- package/dist/plugins/store-contracts.js +2 -46
- package/dist/plugins/task-attachment-store.js +3 -49
- package/dist/plugins/task-list-store-sql.js +0 -79
- package/dist/plugins/tidb-cost-quota.js +1 -4
- package/dist/plugins/tidb-pool.js +1 -207
- package/dist/plugins/tidb-rate-limiter.js +3 -9
- package/dist/plugins/tidb-session-storage.js +4 -70
- package/dist/plugins/tidb-session-store.js +19 -341
- package/dist/plugins/tool-result-store-sql.js +4 -179
- package/dist/plugins/usage-window-store-sql.js +0 -8
- package/dist/plugins/web-search.js +10 -132
- package/dist/plugins/workflow-journal-store-sql.js +2 -58
- package/dist/plugins/workflow-run-store-sql.js +6 -91
- package/dist/plugins/worktree-isolation.js +6 -126
- package/dist/plugins/write-behind-counter.js +16 -75
- package/dist/principal-jwt.js +5 -60
- package/dist/project-memory.js +15 -146
- package/dist/prompts-domain-validate.js +1 -51
- package/dist/question.js +2 -111
- package/dist/resource-suspend.js +0 -18
- package/dist/router/route-orchestration.js +0 -77
- package/dist/rules-consent.d.ts +44 -5
- package/dist/rules-consent.js +75 -215
- package/dist/run-local.js +14 -381
- package/dist/runs.js +29 -617
- package/dist/runtime-caps-resolver.js +6 -132
- package/dist/runtime-governance.js +1 -232
- package/dist/sandbox-pkg-source.js +0 -37
- package/dist/sealed-key.js +3 -68
- package/dist/security.js +6 -291
- package/dist/session-leaf-bus.js +0 -32
- package/dist/session-sync-content.js +1 -69
- package/dist/session-sync-kernel.js +3 -60
- package/dist/session-sync.js +3 -66
- package/dist/session-titler.js +7 -41
- package/dist/session-watch.js +9 -73
- package/dist/shared-memory-scope-authorizer.js +0 -18
- package/dist/sighup-idle.js +1 -12
- package/dist/spec-fields.js +4 -143
- package/dist/store-live-probe.js +3 -49
- package/dist/task-a2a.js +1 -126
- package/dist/task-cwd.js +1 -103
- package/dist/task-mcp.js +1 -89
- package/dist/task-settings.js +10 -300
- package/dist/task-workflow.js +8 -75
- package/dist/tool-approval.d.ts +28 -1
- package/dist/tool-approval.js +70 -1430
- package/dist/trace/artifacts.js +5 -20
- package/dist/trace/engine-notice-wire.js +6 -138
- package/dist/trace/ledger-sink.js +7 -96
- package/dist/trace/project.js +4 -448
- package/dist/trace/redact.js +9 -77
- package/dist/turn-activity.js +1 -27
- package/dist/usage-analytics.js +4 -34
- package/dist/wall-clock-jump-guard.js +1 -68
- package/package.json +3 -3
- package/dist/capabilities/scenario-alias.d.ts +0 -27
- package/dist/capabilities/scenario-alias.js +0 -61
|
@@ -1,40 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* #322 —— **世代账**(design/322 §2 世代账,codex 设计审 F4)。
|
|
3
|
-
*
|
|
4
|
-
* 身份标 = 上游 `EffectiveConfig.version`,**刻意不造跨进程 epoch**(codex F4 语义不变):
|
|
5
|
-
* · 同版本幂等重放(etag 未推进的下一拍)不该伪增世代;
|
|
6
|
-
* · 双副本对账「是不是同一代」必须能跨进程判 —— 同 source version = 同一代。
|
|
7
|
-
*
|
|
8
|
-
* 🔴 #325(test [4809],[4810] 阻断裁定):**序轴与身份标分离**。source version 在
|
|
9
|
-
* `CONFIG_PROVIDER=local` 下是 registry-core file-store 的内容哈希(u53,上游契约只承诺
|
|
10
|
-
* 「同 files ⇒ 同 number」,从未承诺序)——把它当序数消费,哈希更小的合法新代会被永久
|
|
11
|
-
* 静默忽略且本账双盲(target/applied 都不动)。修后:**推进判断吃收货处铸的进程内单调
|
|
12
|
-
* ordinal**(config-center `generationOf`;等版本重放沿用同 ordinal=幂等语义保留),
|
|
13
|
-
* `version` 只做对账身份标——configTargetVersion 语义从「max(见过)」变为「**最后**见过的
|
|
14
|
-
* 源代」(remote 单调腿二者等价;local 腿这才是真相)。
|
|
15
|
-
*
|
|
16
|
-
* 读面两个:`/health` 的三键(target / applied / 失败持续时长;#325 起 additive 补
|
|
17
|
-
* targetOrdinal/appliedOrdinal 两键=推进/停滞的机器判据)给编排器判「这个副本要不要摘」;
|
|
18
|
-
* 诊断端点的 `configApply` 段给运维看**按组**的细节(哪一组停在哪一代、上一次为什么被拒)。
|
|
19
|
-
*
|
|
20
|
-
* 🔴 原子承诺的**域**是单进程(§2):本账记的是「本副本的收敛状态」,不是 fleet 的。fleet 级判据 = 各副本
|
|
21
|
-
* /health 的 applied == target,拉齐工具归运维面。
|
|
22
|
-
*/
|
|
23
1
|
export function createConfigApplyLedger(now = () => Date.now()) {
|
|
24
2
|
let targetOrd = 0;
|
|
25
3
|
let appliedOrd = 0;
|
|
26
4
|
let targetVersion = 0;
|
|
27
5
|
let appliedVersion = 0;
|
|
28
|
-
/**
|
|
29
|
-
* 🔴 codex R1-F3(真 finding,红先):**有组被拒的世代不算「本副本应用了这一代」**。
|
|
30
|
-
*
|
|
31
|
-
* applyEffective 对一个 limits 被整组拒的候选仍返回 true(其余面照常提交),生产腿随后无条件调
|
|
32
|
-
* `applied(...)` —— 修前于是 `/health` 报 applied==target、stale 时长消失,而限额面其实停在旧代。
|
|
33
|
-
* 世代账存在的全部理由就是让这种漂移可见,所以拒过的世代在这里被记下,`applied()` 拒绝为它收敛。
|
|
34
|
-
* #325 起按 **ordinal** 记(local 腿同 version 可能隔代重现,序数才是「这一次候选」的唯一键)。
|
|
35
|
-
*/
|
|
36
6
|
const rejectedOrds = new Set();
|
|
37
|
-
/** 首次分歧的时刻 —— **持续**时长的锚(每拍刷新会把「卡了一小时」写成「卡了 0 秒」)。 */
|
|
38
7
|
let divergedSince;
|
|
39
8
|
const groups = {};
|
|
40
9
|
const reconcile = () => {
|
|
@@ -53,10 +22,9 @@ export function createConfigApplyLedger(now = () => Date.now()) {
|
|
|
53
22
|
},
|
|
54
23
|
applied(ordinal, version) {
|
|
55
24
|
if (ordinal > targetOrd) {
|
|
56
|
-
targetOrd = ordinal;
|
|
25
|
+
targetOrd = ordinal;
|
|
57
26
|
targetVersion = version;
|
|
58
27
|
}
|
|
59
|
-
// 本世代有组被拒 ⇒ 只推进 target(诚实的「见过、没能完整应用」),applied 停在上一个完整世代。
|
|
60
28
|
if (!rejectedOrds.has(ordinal) && ordinal > appliedOrd) {
|
|
61
29
|
appliedOrd = ordinal;
|
|
62
30
|
appliedVersion = version;
|
|
@@ -65,14 +33,14 @@ export function createConfigApplyLedger(now = () => Date.now()) {
|
|
|
65
33
|
},
|
|
66
34
|
recordApply(report) {
|
|
67
35
|
const prev = groups.limitsHttp;
|
|
68
|
-
const ord = report.ordinal ?? report.version;
|
|
36
|
+
const ord = report.ordinal ?? report.version;
|
|
69
37
|
if (report.limits.rejected)
|
|
70
38
|
rejectedOrds.add(ord);
|
|
71
39
|
else
|
|
72
|
-
rejectedOrds.delete(ord);
|
|
40
|
+
rejectedOrds.delete(ord);
|
|
73
41
|
if (report.limits.rejected) {
|
|
74
42
|
groups.limitsHttp = {
|
|
75
|
-
appliedVersion: prev?.appliedVersion ?? 0,
|
|
43
|
+
appliedVersion: prev?.appliedVersion ?? 0,
|
|
76
44
|
...(prev?.deferredKeys ? { deferredKeys: prev.deferredKeys } : {}),
|
|
77
45
|
lastRejected: { keys: [...report.limits.rejected.keys], reason: report.limits.rejected.reason, version: report.version, at: now() },
|
|
78
46
|
...(report.limits.rejected.restartReason
|
|
@@ -83,9 +51,8 @@ export function createConfigApplyLedger(now = () => Date.now()) {
|
|
|
83
51
|
}
|
|
84
52
|
groups.limitsHttp = {
|
|
85
53
|
appliedVersion: report.version,
|
|
86
|
-
// 逐键 defer(值合法但键不热):每代重算 —— 不再被发布的 defer 自然消失,不粘。
|
|
87
54
|
...(report.limits.deferred && report.limits.deferred.length > 0 ? { deferredKeys: report.limits.deferred.map((d) => ({ key: d.key, reason: d.reason })) } : {}),
|
|
88
|
-
...(prev?.lastRejected ? { lastRejected: prev.lastRejected } : {}),
|
|
55
|
+
...(prev?.lastRejected ? { lastRejected: prev.lastRejected } : {}),
|
|
89
56
|
};
|
|
90
57
|
},
|
|
91
58
|
view() {
|
|
@@ -1,44 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Config-center adapter — pull the effective config from the config-center control plane and
|
|
3
|
-
* apply it OVER the env-derived defaults (env = fallback, center = override). Follows the
|
|
4
|
-
* "universal internal schema → translate at the boundary" pattern, with our security boundary kept:
|
|
5
|
-
*
|
|
6
|
-
* (name history: this module tracks the control plane's product name, which has changed over time
|
|
7
|
-
* — registry → sema-web → planned sema-admin. The module itself keeps a neutral, product-name-free
|
|
8
|
-
* identifier ("config-center") and does not chase the rename.)
|
|
9
|
-
*
|
|
10
|
-
* - the CENTER owns the LOGICAL config: the model roster (names/capabilities/tier), the role map,
|
|
11
|
-
* and team templates;
|
|
12
|
-
* - the SERVICE env still owns the SECRETS: API keys stay in env (the center never stores a secret);
|
|
13
|
-
* keys are per-model via `apiKeyEnv` → `config.modelApiKeyEnv` → the spec's `getApiKeyAndHeaders`
|
|
14
|
-
* (core 1.45), so each model/cascade-rung authenticates with its own upstream key.
|
|
15
|
-
*
|
|
16
|
-
* Per-model `baseUrl` IS transported (a catalog model may live on a different gateway;
|
|
17
|
-
* core brain honors model.baseUrl, absent = "" = boot-env endpoint).
|
|
18
|
-
*
|
|
19
|
-
* Hot-reload status (复审 2026-07-29 P1-11 — 亲读判定,取代此处旧的 "models/roles are restart-to-apply"
|
|
20
|
-
* TODO, which went stale when `mutateInPlace` landed):
|
|
21
|
-
* - models/roles/roster/teams/projects/autonomy — **HOT**. `applyEffective` mutates `config.models`/
|
|
22
|
-
* `config.roles` IN PLACE, and core's Runner reads `this.deps.models/roles` per task off that very
|
|
23
|
-
* reference. Both brains re-resolve `model.baseUrl || config.baseUrl` inside `buildRequest()` on every
|
|
24
|
-
* call (core 2.1.0 `brain/openai.js` + `brain/anthropic.js`), so a moved gateway takes effect on the
|
|
25
|
-
* next task with no restart.
|
|
26
|
-
* - the same plane under a **tier-frozen** Runner — deferred, not hot: core expands a PRIVATE catalog copy
|
|
27
|
-
* at construction, so `main` defers the whole plane and the `models-tiers` restart slice signals.
|
|
28
|
-
* - **`degrade-route`** — the one genuinely boot-frozen catalog consumer left: reactive degrade
|
|
29
|
-
* (`MODEL_DEGRADE_REACTIVE`) bakes the target Model + its gateway + its key into the brain composition
|
|
30
|
-
* at boot. It cannot be hot-applied without rebuilding the brain, so it is registered on the existing
|
|
31
|
-
* restartRequired /health contract instead (see `restart-signal.ts`). Fail-loud beats serving a stale
|
|
32
|
-
* gateway silently on the rate-limit path.
|
|
33
|
-
* - skills/mcp/scenarios/runtime-gates — restart-to-apply by construction (baked into the boot wiring).
|
|
34
|
-
*
|
|
35
|
-
* (design/158 A13, internal-lossless) This module is now a FACADE: the implementation lives
|
|
36
|
-
* alongside it in `src/config-center/` split by responsibility group (HTTP client / EffectiveConfig
|
|
37
|
-
* application / restart-signal detection / skills+MCP consumption). Every symbol below is
|
|
38
|
-
* re-exported UNCHANGED — existing `from ".../config-center/facade.js"` imports need zero changes
|
|
39
|
-
* across a rename of this file's *contents*; a path rename (this move, 2026-07-29) does require the
|
|
40
|
-
* importers to update, which was done in the same commit.
|
|
41
|
-
*/
|
|
42
1
|
export { fetchEffective, fetchPrincipalCaps, fetchPrincipalOrgMemory, ConfigCenterHttpError, fetchSkillContent, fetchPromptArtifact, fetchPromptBlob, } from "./http-client.js";
|
|
43
2
|
export { mutateInPlace, applyEffective, applyRuntimeGates, applyRuntimeHot, resolveDefaultModelName, logEffectiveDiff, runtimeHasActiveGate, } from "./apply-effective.js";
|
|
44
3
|
export { restartReasons, planeHasActiveTiers, modelPlaneChanged, } from "./restart-signal.js";
|
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
const cases = (key) => ({ effect: `HK-${key}-effect`, refuse: `HK-${key}-refuse`, rollback: `HK-${key}-rollback` });
|
|
2
|
-
/**
|
|
3
|
-
* 批1(cost 族)真正接线的 14 键 —— **stage 消费的单一真源**:`stage-limits.ts` 按本表遍历,
|
|
4
|
-
* 所以「表里有、stage 不写」或「stage 写、表里没有」都不可能悄悄发生。
|
|
5
|
-
*
|
|
6
|
-
* 前 3 个是 B 簇1(限额座 setter);其余 11 个是 limitsHttp 组的 A′ 接线键。
|
|
7
|
-
*/
|
|
8
2
|
export const BATCH1_LIMIT_KEYS = [
|
|
9
3
|
"rateLimitPerMin",
|
|
10
4
|
"maxPrincipalCostUsd",
|
|
@@ -22,19 +16,6 @@ export const BATCH1_LIMIT_KEYS = [
|
|
|
22
16
|
"toolResultTtlSec",
|
|
23
17
|
];
|
|
24
18
|
const B_SETTER_KEYS = ["rateLimitPerMin", "maxPrincipalCostUsd"];
|
|
25
|
-
/**
|
|
26
|
-
* 🔴 **stage 消费但不热**的键(codex R2-F2,验真后降档)。
|
|
27
|
-
*
|
|
28
|
-
* `costQuotaWindowSec` 在设计稿里归 B 簇(「加个 setter 就好」),那个判断的前提是「限额是比较参数,不是
|
|
29
|
-
* 行状态」—— 对**限额**成立,对**窗长**不成立:SQL 腿的窗长是**桶键的一部分**(`floor(now/windowMs)`,
|
|
30
|
-
* 进 DB 主键)。热切窗长于是有两个真后果:①A→B→A 会在原 A 窗内**复活**切换前的花费(旧桶还在,桶号又
|
|
31
|
-
* 撞上);②B 期间 A 的高编号旧桶不被 `window_bucket < current` 的 reap 清理。内存腿只能靠「换窗即清」
|
|
32
|
-
* 才安全,于是两腿语义必然分裂 —— 同一条发布在不同后端给出相反的成本判决(后端相关的 fail-open)。
|
|
33
|
-
*
|
|
34
|
-
* 处置:窗长**降为 restart-to-apply**(值仍逐次校验,变更逐次铸因、进世代账的 deferredKeys + 一条响亮
|
|
35
|
-
* warn),而不是给一个「在某些后端上会复活旧账」的热能力。真做热要给桶键加**世代隔离**(把窗长世代并进
|
|
36
|
-
* 持久化主键 + 跨副本一致的世代来源),那是独立的一件,归后续批。
|
|
37
|
-
*/
|
|
38
19
|
export const DEFERRED_LIMIT_KEYS = [
|
|
39
20
|
{
|
|
40
21
|
key: "costQuotaWindowSec",
|
|
@@ -43,7 +24,6 @@ export const DEFERRED_LIMIT_KEYS = [
|
|
|
43
24
|
},
|
|
44
25
|
];
|
|
45
26
|
const DEFERRED_KEYS = new Set(DEFERRED_LIMIT_KEYS.map((d) => d.key));
|
|
46
|
-
/** 热键表 = 批1 消费键集**减去**显式 deferred 的那些(表只登记真的热的键)。 */
|
|
47
27
|
export const HOT_KEYS_REGISTRY = BATCH1_LIMIT_KEYS.filter((key) => !DEFERRED_KEYS.has(key)).map((key) => ({
|
|
48
28
|
key,
|
|
49
29
|
group: "limitsHttp",
|
|
@@ -1,25 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* config-center HTTP client — the transport calls the service makes against the config-center
|
|
3
|
-
* (`fetchEffective`/`fetchPrincipalCaps`/`fetchPrincipalOrgMemory`/`fetchSkillContent`/`fetchPromptArtifact`/
|
|
4
|
-
* `fetchPromptBlob`) plus the shared HTTP-error type. 后两条 `?principal=` 腿共用 `requestPrincipalView`
|
|
5
|
-
* 传输半场与 `assertPrincipalEcho` 回声核,只在**解析投影**上分家(见 `fetchPrincipalOrgMemory` 头注)。 Split out of `facade.ts` (design/158 A13, internal-lossless — the
|
|
6
|
-
* facade re-exports every symbol below unchanged).
|
|
7
|
-
*/
|
|
8
1
|
import { createHash } from "node:crypto";
|
|
9
2
|
import { readEffectiveWire, PrincipalCapsWire } from "@sema-agent/registry-core";
|
|
10
|
-
import { centerPromptsFromEffective } from "../prompts-domain-validate.js";
|
|
11
|
-
/** GET the effective config (Bearer + ETag). null = 304 (unchanged). Throws on transport/HTTP error, and on a
|
|
12
|
-
* payload that is not an effective config at all(非对象 / version 非有限数 / gate 域坏形——[2281] 裁B)。
|
|
13
|
-
* `domainErrors`:坏 catalog 域(schema default 已落)逐域单列——与本地腿 `FetchEffectiveResult` 同键同义,
|
|
14
|
-
* boot/refresh 的候选门直接消费。 */
|
|
3
|
+
import { centerPromptsFromEffective } from "../prompts-domain-validate.js";
|
|
15
4
|
export async function fetchEffective(baseUrl, token, etag, fetchImpl = fetch, worker) {
|
|
16
|
-
// Defensive scheme guard: Node's fetch supports file:// — a mis-set
|
|
17
|
-
// SEMA_REGISTRY_URL must not turn into a local-file read. Reject anything but http(s).
|
|
18
5
|
const scheme = new URL(baseUrl).protocol;
|
|
19
6
|
if (scheme !== "http:" && scheme !== "https:")
|
|
20
7
|
throw new Error(`SEMA_REGISTRY_URL must be http(s), got "${scheme}"`);
|
|
21
|
-
// worker-scoped pull: `?worker=<name>` returns that worker's roster; omitted → the global roster
|
|
22
|
-
// (backward compatible — the center returns today's global config when no worker is given).
|
|
23
8
|
const url = `${baseUrl.replace(/\/+$/, "")}/api/config/effective${worker ? `?worker=${encodeURIComponent(worker)}` : ""}`;
|
|
24
9
|
const res = await fetchImpl(url, {
|
|
25
10
|
headers: { authorization: `Bearer ${token}`, ...(etag ? { "if-none-match": etag } : {}) },
|
|
@@ -29,24 +14,13 @@ export async function fetchEffective(baseUrl, token, etag, fetchImpl = fetch, wo
|
|
|
29
14
|
return null;
|
|
30
15
|
if (!res.ok)
|
|
31
16
|
throw new Error(`config-center HTTP ${res.status}`);
|
|
32
|
-
// [2281] 裁B(§M1):wire 载荷在**这个消费端边界**过 registry-core `readEffectiveWire`(0.12.0)真校验,
|
|
33
|
-
// 不再裸断言——「两个契约共用一个拼写不是一个被检查的契约」。判据与本地腿同源(parseDomain +
|
|
34
|
-
// DOMAIN_READ_FALLBACK):catalog 域坏形 → 该域 schema default + 下面折进 domainErrors(候选门在
|
|
35
|
-
// refresh 拒候选、boot 逐域 warn);gate 域坏形/垃圾载荷 → throw(caller 整包 catch 回落 env/LKG)。
|
|
36
|
-
// 未知顶层键 verbatim 透传(open-world:新 center 新域、legacy teams 键都不丢)。grandfather 类
|
|
37
|
-
// 警告(值已被收编接受)不算 error,不进 domainErrors——与本地店同口径。
|
|
38
17
|
const warnings = [];
|
|
39
18
|
const wire = readEffectiveWire(await res.json(), (w) => warnings.push(w));
|
|
40
|
-
// #100 边界单点归一(§V2-B):worker 腿 prompts 键在**这里**定形——0.13.0 起 wire 值 verbatim 到手,
|
|
41
|
-
// 存储原文(global lane 合法下发形/emptyEffective 底座)归一为「键缺席」或 catalog 投影,下游
|
|
42
|
-
// boot/adopt/refresh/deferred/LKG 存取全部只见「缺席或消费形」(裸 `.prompts` 直读有棘轮钉拦)。
|
|
43
19
|
const centerPrompts = centerPromptsFromEffective(wire);
|
|
44
20
|
if (centerPrompts === undefined)
|
|
45
21
|
delete wire.prompts;
|
|
46
22
|
else
|
|
47
23
|
wire.prompts = centerPrompts;
|
|
48
|
-
// registry-core 0.19.0 响亮化三 kind 同批收编(与 local 腿 config-provider.ts 同映射,LANDING-7.38 件③):
|
|
49
|
-
// dropped/unread=写了没生效(defaulted 同级),carried=承运待键表方判——全入诊断面,禁静默丢。
|
|
50
24
|
const domainErrors = warnings
|
|
51
25
|
.map((w) => w.kind === "domain-defaulted"
|
|
52
26
|
? { domain: w.domain, error: (w.error instanceof Error ? w.error.message : String(w.error)).slice(0, 600) }
|
|
@@ -58,38 +32,13 @@ export async function fetchEffective(baseUrl, token, etag, fetchImpl = fetch, wo
|
|
|
58
32
|
? { domain: w.domain ?? "config.d", error: `unread config file ${w.file} (${w.why})`.slice(0, 600) }
|
|
59
33
|
: undefined)
|
|
60
34
|
.filter((r) => r !== undefined);
|
|
61
|
-
// 经真校验后的 wire 值到 service 拼写副本的换装:两型同一契约面(EffectiveWire 是 service 型的同源超集,
|
|
62
|
-
// service 型只声明自己消费的域且全 optional)——此断言的前提正是上面那次校验,不再是裸信任。
|
|
63
35
|
const effective = wire;
|
|
64
36
|
return { effective, etag: res.headers.get("etag") ?? undefined, ...(domainErrors.length > 0 ? { domainErrors } : {}) };
|
|
65
37
|
}
|
|
66
|
-
/**
|
|
67
|
-
* design/99 §K (core 1.157 `RunnerDeps.runtimeCapsResolver`) — GET the PER-PRINCIPAL runtime
|
|
68
|
-
* ENTITLEMENTS the engine enforces server-side, from center's `GET /api/config/effective?principal=&worker=`.
|
|
69
|
-
* Center pre-resolves the tighten-only intersection (fleet defaults ∩ tier ⊕ roles + overrides) and returns a
|
|
70
|
-
* `runtimeCaps` block — the service passes it through verbatim (no further tightening, per center's ruling).
|
|
71
|
-
*
|
|
72
|
-
* 🔐 TOKEN/WORKER (center ruling, sema-registry `aeb4979`): two valid postures —
|
|
73
|
-
* • FULL `SERVICE_PULL_TOKEN` + `?principal=` → full per-user body (caps + per-user config).
|
|
74
|
-
* • worker-scoped `wpt_<self>` + `?principal=&worker=<self>` → **caps-only** body (`{principal, configured,
|
|
75
|
-
* runtimeCaps, budget}`; never other tenants' config) — a worker MAY resolve its OWN worker's principals.
|
|
76
|
-
* A `wpt_` with NO `?worker=` (or a mismatched worker) still 403s. So we ALWAYS pass `?worker=` when the
|
|
77
|
-
* deployment has one (`SEMA_REGISTRY_WORKER`): it authorizes the wpt_ path AND is harmless on the full token
|
|
78
|
-
* (the caps value is worker-independent — `resolveEntitlement` resolves from the principal alone; worker only
|
|
79
|
-
* scopes the config view we ignore). This re-adds the `worker` arg dropped in 1.14.0 (L2-3) — center's ruling
|
|
80
|
-
* makes it load-bearing for AUTHORIZATION, not for the caps value.
|
|
81
|
-
* `null` = 304 (caller keeps its cached value). THROWS on transport / non-304 HTTP error → the resolver
|
|
82
|
-
* fail-closes (core then DENIES the optional `allowWorkflows` cap — an outage must never silently GRANT). ETag is
|
|
83
|
-
* content-hashed (`p-<hash>`); an `if-none-match` poll is cheap. Mirrors `fetchEffective` (Bearer + 8s + scheme guard).
|
|
84
|
-
*/
|
|
85
38
|
export async function fetchPrincipalCaps(baseUrl, token, principal, etag, fetchImpl = fetch, worker) {
|
|
86
39
|
const raw = await requestPrincipalView(baseUrl, token, principal, etag, fetchImpl, worker);
|
|
87
40
|
if (raw === null)
|
|
88
41
|
return null;
|
|
89
|
-
// 边界读=schema(clay 宪法 [2704] §2,同源锚=registry-core PrincipalCapsWire):治理面
|
|
90
|
-
// runtimeCaps/budget 严格+passthrough(值漂移整体拒 ⇒ 本函数 throw ⇒ resolver fail-close,
|
|
91
|
-
// 「outage 绝不静默 GRANT」同向);[565]/[580] fail-open 面与 orgMemory(C3 失败域隔离)在
|
|
92
|
-
// schema 里保持宽读,归一/校验仍在下方与目录腿(极性不因 schema 化翻转)。
|
|
93
42
|
const parsedBody = PrincipalCapsWire.safeParse(raw.json);
|
|
94
43
|
if (!parsedBody.success) {
|
|
95
44
|
throw new Error(`config-center principal-caps body failed schema validation: ${parsedBody.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ").slice(0, 600)}`);
|
|
@@ -99,10 +48,6 @@ export async function fetchPrincipalCaps(baseUrl, token, principal, etag, fetchI
|
|
|
99
48
|
return {
|
|
100
49
|
runtimeCaps: body.runtimeCaps ?? null,
|
|
101
50
|
configured: Boolean(body.configured),
|
|
102
|
-
// The caps view carries the RESOLVED per-principal scenario ruling (two keys, shape pinned
|
|
103
|
-
// by center tests). An older center has NO such keys → undefined (no governance, legacy passthrough) —
|
|
104
|
-
// distinct from the "assigned nothing" ruling {scenario:null, allowlist:[]} a newer center returns.
|
|
105
|
-
// Defensive on shape (scenario face is fail-open — see gateScenarioRequest): non-string entries dropped.
|
|
106
51
|
...("scenario" in body || "allowlist" in body
|
|
107
52
|
? {
|
|
108
53
|
scenario: {
|
|
@@ -111,17 +56,7 @@ export async function fetchPrincipalCaps(baseUrl, token, principal, etag, fetchI
|
|
|
111
56
|
},
|
|
112
57
|
}
|
|
113
58
|
: {}),
|
|
114
|
-
// (stage7 P5, registry-core 0.8 execution 域): the caps view's per-principal execution
|
|
115
|
-
// ruling `{required, allowedLanes}` — same car, same no-cache semantics as the scenario ruling. An
|
|
116
|
-
// older center has no key → undefined (no enforcement). allowedLanes is an OPEN value domain
|
|
117
|
-
// (adding a lane is never BREAKING) — tolerate unknown names, drop non-string ELEMENTS defensively.
|
|
118
|
-
// shape DRIFT (required non-boolean / allowedLanes non-array) must NOT be
|
|
119
|
-
// coerced — the old coercion turned "allowedLanes came as a comma string" + required=true into
|
|
120
|
-
// allowedLanes=[] = fleet-wide deny-all (fail-CLOSED on a wire glitch, opposite of the fail-open posture)
|
|
121
|
-
// and a drifted `required` into silent non-enforcement. Now: drift ⇒ DROP the whole ruling (fail-open,
|
|
122
|
-
// gate passes) + `executionDrift` so the caller's audit line fires (the resolver routes it to onError).
|
|
123
59
|
...((() => {
|
|
124
|
-
// [2704] §1:unknown 面的窄化走运行时类型谓词(真检查),不走断言。
|
|
125
60
|
const isRecord = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
126
61
|
const exRaw = body.execution;
|
|
127
62
|
if (exRaw === undefined || exRaw === null)
|
|
@@ -133,10 +68,6 @@ export async function fetchPrincipalCaps(baseUrl, token, principal, etag, fetchI
|
|
|
133
68
|
if (!Array.isArray(exRaw.allowedLanes))
|
|
134
69
|
return { executionDrift: `execution.allowedLanes is ${typeof exRaw.allowedLanes}, expected string[] — ruling dropped (fail-open)` };
|
|
135
70
|
const execution = { required: exRaw.required, allowedLanes: exRaw.allowedLanes.filter((s) => typeof s === "string") };
|
|
136
|
-
// registry-core 0.10.1-0.10.2:execution.sessionMirror 宽读。observation-only 子面
|
|
137
|
-
// (server 非执法端,见 ExecutionRuling.sessionMirror 注),所以形状漂移只丢**本子面**并走 executionDrift
|
|
138
|
-
// 审计线(resolver 路由到 onError),**绝不**连带丢 lane ruling(那是真执法面,不能被观测子面拖垮)。
|
|
139
|
-
// required 缺省 false = registry-core zod 默认(便利态);engineUrl 必须非空 string。
|
|
140
71
|
if (exRaw.sessionMirror !== undefined && exRaw.sessionMirror !== null) {
|
|
141
72
|
const sm = exRaw.sessionMirror;
|
|
142
73
|
if (isRecord(sm) && typeof sm.engineUrl === "string" && sm.engineUrl.length > 0 && (sm.required === undefined || typeof sm.required === "boolean")) {
|
|
@@ -148,31 +79,19 @@ export async function fetchPrincipalCaps(baseUrl, token, principal, etag, fetchI
|
|
|
148
79
|
}
|
|
149
80
|
return { execution };
|
|
150
81
|
})()),
|
|
151
|
-
// design/170 件A:org 记忆解析段**原值透传**(键在场保在场、缺席保缺席——缺席=旧 center 能力握手,
|
|
152
|
-
// org 目录腿据此判 section_absent 瞬时;形状校验是 org-memory-admission 模块用 PrincipalOrgMemoryWire
|
|
153
|
-
// 的事,本腿保持薄透传不预判)。
|
|
154
82
|
...("orgMemory" in body ? { orgMemory: body.orgMemory } : {}),
|
|
155
|
-
etag: raw.etag,
|
|
83
|
+
etag: raw.etag,
|
|
156
84
|
};
|
|
157
85
|
}
|
|
158
|
-
/** design/170 件A C8(响应 principal 回声核):center 两条 `?principal=` 腿的 body 都回带 principal——
|
|
159
|
-
* 在场且与请求不符 = 中间层/LB 把别的 principal 的视图递了过来,弃用整响应(throw ⇒ caps 腿
|
|
160
|
-
* fail-close、org 目录腿记 unavailable)。键缺席 = 旧 center 形,按无证据容忍(回声核只拦「有回声
|
|
161
|
-
* 且对不上」的确定性错递,不把旧形升级成故障)。两条腿共用同一道闸,不各写一遍。 */
|
|
162
86
|
function assertPrincipalEcho(echoed, principal) {
|
|
163
87
|
if (echoed !== undefined && echoed !== principal) {
|
|
164
88
|
throw new Error(`config-center principal-caps response echoes principal ${JSON.stringify(echoed)} but ${JSON.stringify(principal)} was requested — discarding the whole response`);
|
|
165
89
|
}
|
|
166
90
|
}
|
|
167
|
-
/** 两条 `?principal=` 腿共用的**传输**半场:scheme 闸 / `?principal=[&worker=]` / Bearer / 8s 超时 /
|
|
168
|
-
* 304 语义。逐字来自原 {@link fetchPrincipalCaps} 函数体,提成共用件是为了第二条腿(org 段读取)
|
|
169
|
-
* 不必抄一遍传输纪律——抄一遍就等于给同一条边界立两套会各自漂移的规矩。
|
|
170
|
-
* 返回 `null` = 合法的 304(仅当我们真的发了条件请求);否则是**解析前**的 body 原值。 */
|
|
171
91
|
async function requestPrincipalView(baseUrl, token, principal, etag, fetchImpl, worker) {
|
|
172
92
|
const scheme = new URL(baseUrl).protocol;
|
|
173
93
|
if (scheme !== "http:" && scheme !== "https:")
|
|
174
94
|
throw new Error(`SEMA_REGISTRY_URL must be http(s), got "${scheme}"`);
|
|
175
|
-
// `?principal=<id>[&worker=<self>]` — worker authorizes the wpt_<self> caps-only path; harmless on a full token.
|
|
176
95
|
const params = new URLSearchParams({ principal });
|
|
177
96
|
if (worker)
|
|
178
97
|
params.set("worker", worker);
|
|
@@ -182,9 +101,6 @@ async function requestPrincipalView(baseUrl, token, principal, etag, fetchImpl,
|
|
|
182
101
|
signal: AbortSignal.timeout(8000),
|
|
183
102
|
});
|
|
184
103
|
if (res.status === 304) {
|
|
185
|
-
// A 304 is only valid as a reply to OUR conditional. If we sent no etag and still got 304, center is
|
|
186
|
-
// misbehaving — DON'T treat it as "unchanged" (that surfaces undefined caps = no restriction = fail OPEN).
|
|
187
|
-
// Throw so the resolver fail-closes (review B). With an etag, 304 = genuine "unchanged" → null.
|
|
188
104
|
if (!etag)
|
|
189
105
|
throw new Error("config-center returned 304 to a non-conditional principal-caps request");
|
|
190
106
|
return null;
|
|
@@ -194,31 +110,9 @@ async function requestPrincipalView(baseUrl, token, principal, etag, fetchImpl,
|
|
|
194
110
|
const responseEtag = res.headers.get("etag");
|
|
195
111
|
return { json: await res.json(), ...(responseEtag !== null ? { etag: responseEtag } : {}) };
|
|
196
112
|
}
|
|
197
|
-
/** 件A C3 失败域隔离的读取投影:org 目录腿只消费两个字段——`principal`(回声核)与 `orgMemory` 段
|
|
198
|
-
* 原值。用 `PrincipalCapsWire.pick()` 取子集(schema 属主仍是 registry-core,这里不复制形状定义,
|
|
199
|
-
* `.passthrough()` 随 `_def` 一并继承)。 */
|
|
200
113
|
const PrincipalOrgMemoryEnvelope = PrincipalCapsWire.pick({ principal: true, orgMemory: true });
|
|
201
|
-
/**
|
|
202
|
-
* design/170 件A —— 读 `?principal=` 响应里的 org 记忆解析段(org 目录腿的取数口)。
|
|
203
|
-
*
|
|
204
|
-
* 🔬 **为什么不复用 {@link fetchPrincipalCaps}**(复审车 D-2 修):C3「两张表失败域互不干扰」是在档
|
|
205
|
-
* 裁定,registry-core 的 `PrincipalCapsWire` 也正是为此把 org 段留成 `unknown`(「在本 schema 里校验
|
|
206
|
-
* 会把 org 段坏形连坐进 caps 腿」)。但隔离此前只做了**单向**:org 段坏不连坐 caps,caps 段坏却整体
|
|
207
|
-
* 连坐 org —— 治理面 `runtimeCaps`/`budget` 的一次值漂移会让整封 safeParse 失败、函数 throw,于是一个
|
|
208
|
-
* **形状完全正确**的 org 授权段读不出来,目录记 `fetch_failed`、准入铸瞬时拒。治理面的 fail-closed
|
|
209
|
-
* 极性是对的(它管的是「别静默放松治理」),但它不该决定 org 授权面的可用性。
|
|
210
|
-
*
|
|
211
|
-
* 所以这条腿用 {@link PrincipalOrgMemoryEnvelope} 只读它真正消费的两个字段:治理面漂移在这里是被
|
|
212
|
-
* `.passthrough()` 原样放过的未知键,不构成本腿的故障。段本身的形状校验仍在目录腿
|
|
213
|
-
* (`PrincipalOrgMemoryWire`),本腿保持薄透传不预判。
|
|
214
|
-
*
|
|
215
|
-
* 返回值三值语义:段键缺席 ⇒ `undefined`(旧 center 能力握手 ⇒ 目录判 `section_absent` 瞬时);
|
|
216
|
-
* 段在场 ⇒ 原值(空表 = 取数成功的负结果,与缺席不同格)。条件请求不适用(本腿恒不带 etag),
|
|
217
|
-
* 因此 304 在传输层就是 fail-loud。
|
|
218
|
-
*/
|
|
219
114
|
export async function fetchPrincipalOrgMemory(baseUrl, token, principal, fetchImpl = fetch, worker) {
|
|
220
115
|
const raw = await requestPrincipalView(baseUrl, token, principal, undefined, fetchImpl, worker);
|
|
221
|
-
// etag 恒缺席 ⇒ 304 已在传输层 throw;这一支只为类型收口(不可达)。
|
|
222
116
|
if (raw === null)
|
|
223
117
|
throw new Error("config-center returned 304 to a non-conditional org-directory fetch");
|
|
224
118
|
const parsed = PrincipalOrgMemoryEnvelope.safeParse(raw.json);
|
|
@@ -228,8 +122,6 @@ export async function fetchPrincipalOrgMemory(baseUrl, token, principal, fetchIm
|
|
|
228
122
|
assertPrincipalEcho(parsed.data.principal, principal);
|
|
229
123
|
return parsed.data.orgMemory;
|
|
230
124
|
}
|
|
231
|
-
/** An HTTP error from a config-center fetch that carries the response status so callers can branch on it
|
|
232
|
-
* (e.g. translate a 404 to "unknown hash → undefined") WITHOUT parsing the message string. */
|
|
233
125
|
export class ConfigCenterHttpError extends Error {
|
|
234
126
|
status;
|
|
235
127
|
constructor(message, status) {
|
|
@@ -238,14 +130,6 @@ export class ConfigCenterHttpError extends Error {
|
|
|
238
130
|
this.name = "ConfigCenterHttpError";
|
|
239
131
|
}
|
|
240
132
|
}
|
|
241
|
-
/**
|
|
242
|
-
* Fetch one skill's body by content hash (`GET /api/config/skills/content/<hash>`, Bearer) and VERIFY its
|
|
243
|
-
* sha256 matches the manifest hash (integrity + tamper check). The content is immutable per hash, so the
|
|
244
|
-
* caller caches by hash and never re-fetches an unchanged skill. Throws a {@link ConfigCenterHttpError}
|
|
245
|
-
* (carrying `.status`) on an HTTP error — INCLUDING 404 — or a plain Error on hash mismatch. Callers that
|
|
246
|
-
* want "unknown hash → undefined" parity with the local backend inspect `.status === 404` and swallow it;
|
|
247
|
-
* the legacy `applyCenterSkills` path keeps treating every throw as "skip this skill, keep baseline".
|
|
248
|
-
*/
|
|
249
133
|
export async function fetchSkillContent(baseUrl, token, contentHash, fetchImpl = fetch) {
|
|
250
134
|
const url = `${baseUrl.replace(/\/+$/, "")}/api/config/skills/content/${encodeURIComponent(contentHash)}`;
|
|
251
135
|
const res = await fetchImpl(url, { headers: { authorization: `Bearer ${token}` }, signal: AbortSignal.timeout(8000) });
|
|
@@ -258,10 +142,6 @@ export async function fetchSkillContent(baseUrl, token, contentHash, fetchImpl =
|
|
|
258
142
|
throw new Error(`skill content hash mismatch for ${contentHash}: computed sha256:${got}`);
|
|
259
143
|
return content;
|
|
260
144
|
}
|
|
261
|
-
/** [985]b / [1285]① — §9.4 by-digest 补拉二函(fetchSkillContent 同姿势:HTTP+Bearer+8s 超时+
|
|
262
|
-
* 消费侧自证)。artifact=envelope JSON(验真=调用方过 core verifyPromptArtifact,canonical 重算——
|
|
263
|
-
* 本函数只做传输与形读,不越权解释);blob=段文本 raw-byte(sha256 逐位自证,fetchSkillContent 同款)。
|
|
264
|
-
* digest 参数收带前缀/裸 hex 两形([1285] 参数口径)。 */
|
|
265
145
|
export async function fetchPromptArtifact(baseUrl, token, artifactDigest, fetchImpl = fetch) {
|
|
266
146
|
const url = `${baseUrl.replace(/\/+$/, "")}/api/config/prompts/epoch-artifacts/${encodeURIComponent(artifactDigest)}`;
|
|
267
147
|
const res = await fetchImpl(url, { headers: { authorization: `Bearer ${token}` }, signal: AbortSignal.timeout(8000) });
|
|
@@ -274,9 +154,6 @@ export async function fetchPromptBlob(baseUrl, token, contentDigest, fetchImpl =
|
|
|
274
154
|
const res = await fetchImpl(url, { headers: { authorization: `Bearer ${token}` }, signal: AbortSignal.timeout(8000) });
|
|
275
155
|
if (!res.ok)
|
|
276
156
|
throw new ConfigCenterHttpError(`prompt blob HTTP ${res.status} for ${contentDigest}`, res.status);
|
|
277
|
-
// codex F2(补拉批):哈希必须打在**响应原始字节**上(res.text() 的 UTF-8 宽容解码会剥 BOM/替换坏序列
|
|
278
|
-
// ——验的就不再是送来的字节,content-addressed 完整性破功);验过再严格解码(fatal:true,坏 UTF-8
|
|
279
|
-
// 掷错而非静默替换)。
|
|
280
157
|
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
281
158
|
const want = contentDigest.replace(/^sha256:/, "").toLowerCase();
|
|
282
159
|
const got = createHash("sha256").update(bytes).digest("hex");
|
|
@@ -1,45 +1,17 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* #324(core 5.50 design/338 提货件)—— mid-turn MCP 撤销台账:`RunnerDeps.mcpRevocations` 席的宿主实现。
|
|
3
|
-
*
|
|
4
|
-
* 语义(与 core 契约对表,types.d.ts `RunnerDeps.mcpRevocations` 注):
|
|
5
|
-
* · 权威单点在宿主 —— core 每次 MCP dispatch 前**同步**探 `isRevoked(serverName)`,从不缓存答案。
|
|
6
|
-
* · 本台账记「中心**曾发布为 enabled、后从 enabled 集移除**」的服务器名。MCP mount 是 boot-baked
|
|
7
|
-
* (`resolveMcpServers` 只在启动期跑),所以**删除向**在 5.50 前只能等 restart 才生效——这正是
|
|
8
|
-
* design/338 要消的窗:治理面最要紧的动作(撤销一条对外通道)现在即时生效,restart 信号的
|
|
9
|
-
* 删除向豁免见 `restart-signal.ts` 的 mcp 特判(`mcpRevocationLive` 前提,fail-closed:席没接线
|
|
10
|
-
* 的部署保持旧的 restart 语义)。
|
|
11
|
-
* · **重现恢复是指纹判,不是名字判**(codex F1,confirmed):被撤的名字回到 enabled 集时,只有当
|
|
12
|
-
* 它的条目指纹与 **baseline(boot mount 真身)逐字相同**才解除撤销——重加同名**不同址/不同配置**
|
|
13
|
-
* 的条目,复活的会是 boot-frozen 的旧 mount(新配置要 restart 才装上),那是把已撤下的旧端点在
|
|
14
|
-
* restart 窗口内重新放行(fail-open)。指纹不匹配 ⇒ 名字保持 revoked 直到 restart 收敛。
|
|
15
|
-
* · **baseline 只由 boot bake 拍铸**(codex F2,confirmed):它必须是「实际 mount 的集合」——迟到
|
|
16
|
-
* boot 采用/refresh 拍的候选集都不是 mount 真身,不得铸 baseline。`hasBaseline()` 是 restart 豁免
|
|
17
|
-
* 的第二道门(boot 超时形:mount=env 集,中心撤销语义整个不适用,豁免必须关死走旧 restart 语义)。
|
|
18
|
-
* · **域缺席(null)不动**:中心停止发布 mcp 域 ≠ 逐服务器撤销指令(fingerprint 上 null↔非 null
|
|
19
|
-
* 自会签 restart);域在场但 `servers: []` 是明确的「清空」指令 ⇒ 全撤。
|
|
20
|
-
* · baseline 之后新增再删除的名字照 revoke(它从未 mount,isRevoked=true 无 mount 可拒=无害,
|
|
21
|
-
* fail-closed 向);这类名字重加时因无 baseline 指纹**不解除**——同样无害,restart 收敛。
|
|
22
|
-
*
|
|
23
|
-
* 纯内存、同步、无 I/O —— 每副本各自持有(mount 也是副本各自的,天然同界)。进程重启即清零:
|
|
24
|
-
* 重启后按新配置 mount,台账语义自洽。
|
|
25
|
-
*/
|
|
26
1
|
export function createMcpRevocationLedger(onChange) {
|
|
27
|
-
/** boot mount 真身(name→指纹);undefined = baseline 未铸(hasBaseline=false)。铸后不变。 */
|
|
28
2
|
let baseline;
|
|
29
|
-
let seen;
|
|
3
|
+
let seen;
|
|
30
4
|
const revoked = new Set();
|
|
31
5
|
return {
|
|
32
6
|
isRevoked: (serverName) => revoked.has(serverName),
|
|
33
7
|
hasBaseline: () => baseline !== undefined,
|
|
34
8
|
applyEnabledEntries: (entries, opts) => {
|
|
35
9
|
if (entries === null)
|
|
36
|
-
return;
|
|
10
|
+
return;
|
|
37
11
|
if (opts?.isBaseline === true && baseline === undefined)
|
|
38
12
|
baseline = new Map(entries);
|
|
39
13
|
const restored = [];
|
|
40
14
|
for (const [n, fp] of entries) {
|
|
41
|
-
// codex F1:恢复=指纹判——只有回归到 baseline(mount 真身)的逐字同形才解除;
|
|
42
|
-
// 同名不同形(改址/改配置)保持 revoked,新配置候 restart 装上(fail-closed)。
|
|
43
15
|
if (revoked.has(n) && baseline?.get(n) === fp) {
|
|
44
16
|
revoked.delete(n);
|
|
45
17
|
restored.push(n);
|
|
@@ -62,10 +34,6 @@ export function createMcpRevocationLedger(onChange) {
|
|
|
62
34
|
view: () => ({ revoked: [...revoked].sort() }),
|
|
63
35
|
};
|
|
64
36
|
}
|
|
65
|
-
/** 一拍 EffectiveConfig 的 ENABLED MCP 服务器条目表(`resolveMcpServers` 同判据:`enabled !== false`;
|
|
66
|
-
* 值=整条目的 key 排序 JSON 指纹,Map 插入序=发布序);mcp 域缺席 ⇒ `null`(台账不动)。
|
|
67
|
-
* 指纹口径与 restart-signal 的 stableStringify 同精神(key 序归一),但**刻意独立实现**:两处指纹
|
|
68
|
-
* 只在各自域内自比,不跨域比对,无同源约束。 */
|
|
69
37
|
export function enabledMcpEntries(mcp) {
|
|
70
38
|
if (!mcp)
|
|
71
39
|
return null;
|
|
@@ -1,33 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* #A4([4147] 窗三件批 件1;[4073]/[4083] 承诺件)—— READ 容纳面四键的 **config-center 消费腿**。
|
|
3
|
-
*
|
|
4
|
-
* 部署 env 的四键(`READ_FACE` / `READ_DENY_PATTERNS` / `READ_DENY_BUILTIN_TIERS` /
|
|
5
|
-
* `READ_DENY_BUILTIN_EXCLUDE`,见 config.ts)此前是这条轴上的唯一入口:一个用 config-center 管理整片
|
|
6
|
-
* worker 的组织,想统一收紧 READ 面只能逐机改 env。本模块把 `EffectiveConfig.readFace`(见 types.ts
|
|
7
|
-
* {@link CenterReadFace})投影到同样的四个 `ServiceConfig` 键上,**restart-to-apply**(与 mcp/a2a 同线:
|
|
8
|
-
* `createRunnerDeps` 在 boot 期捕获这四键;改动由 `read-face` restart 片通告)。
|
|
9
|
-
*
|
|
10
|
-
* ── 三条规则,每条都有失败方向的论证 ──────────────────────────────────────────────────────────
|
|
11
|
-
*
|
|
12
|
-
* ① **env 显式恒赢,逐键判**(operator-knob 律)。env 是**这台机器**的部署主权表达,center 是组织下发;
|
|
13
|
-
* 冲突时保留 env 并留一条点名 warn。为什么不照 6 闸的「center 覆盖 env」:那六闸是**限额/治理**面
|
|
14
|
-
* (组织比单机更知道该收多紧),而 READ 容纳面描述的是**这台机器的磁盘上有什么** —— 运维在这台机器上
|
|
15
|
-
* 写下 `READ_FACE=roots` 时知道的事情,组织的下发面不知道。静默盖掉它是本仓「部署级旋钮」纪律的反面。
|
|
16
|
-
* 逐键而非整域:env 只写了 face 时,center 的三个 deny 键照常落(否则一个 env 键会把整片组织策略挡掉)。
|
|
17
|
-
* 判据 = **键在场**(`!== undefined`):`readDenyBuiltinTiers: []` 是 env `none` 的显式空姿态,
|
|
18
|
-
* 不是「没设过」—— 用真值判会把「一档内建都不要」这个显式表态判成缺席。
|
|
19
|
-
*
|
|
20
|
-
* ② **坏值整域拒 + 响亮**(#157 安全轴)。任何一个成员过不了引擎自己的校验器 ⇒ 整个 readFace 域不落,
|
|
21
|
-
* 保留 env 前值 + warn 点名。两个方向都要:半应用一份 READ 姿态(收紧了一半)会让运维以为策略生效;
|
|
22
|
-
* 而 throw 会让一次坏发布把常驻进程炸掉(env 腿 throw 是对的 —— 那是启动期、由本机运维自己写的值)。
|
|
23
|
-
* 与 `stageApprovalRequire` 的域级拒绝先例同形。
|
|
24
|
-
*
|
|
25
|
-
* ③ **校验一律交给引擎**(`compileReadDeny` / `resolveReadDenyBuiltins`)。宿主不自造第二份判定语义,
|
|
26
|
-
* 与 env 腿逐字同款;且校验跑在**合成后的最终姿态**上(env 键 + center 键混合形),否则
|
|
27
|
-
* 「env 选了档位、center 删了一个不在该档里的行名」这类跨腿坏组合会漏过。
|
|
28
|
-
*/
|
|
29
1
|
import { compileReadDeny, resolveReadDenyBuiltins } from "@sema-agent/core";
|
|
30
|
-
/** center 域成员 → `ServiceConfig` 键的对照表(单一真源:warn 文案、env-wins 判定、落配三处同吃)。 */
|
|
31
2
|
const KEY_MAP = [
|
|
32
3
|
["face", "readFace"],
|
|
33
4
|
["denyPatterns", "readDenyPatterns"],
|
|
@@ -36,9 +7,6 @@ const KEY_MAP = [
|
|
|
36
7
|
];
|
|
37
8
|
class ReadFaceDomainError extends Error {
|
|
38
9
|
}
|
|
39
|
-
/** `denyPatterns` 的归一:裸串简写 ≡ `{pattern, caseSensitive:false}`(core `ReadDenyEntry` 同宽,
|
|
40
|
-
* 与 env 腿 `parseReadDenyPatternsEnv` 逐字同词表)。形坏一律抛 ⇒ 整域拒(绝不静默丢元素:丢光会
|
|
41
|
-
* 塌成空表,而空表的语义恰好是「没有追加保护」)。 */
|
|
42
10
|
function normalizeDenyPatterns(raw) {
|
|
43
11
|
if (!Array.isArray(raw))
|
|
44
12
|
throw new ReadFaceDomainError("readFace.denyPatterns must be an array of \"pattern\" strings or {pattern, caseSensitive?} objects");
|
|
@@ -54,20 +22,17 @@ function normalizeDenyPatterns(raw) {
|
|
|
54
22
|
return { pattern: it.pattern, caseSensitive: cs === true };
|
|
55
23
|
});
|
|
56
24
|
}
|
|
57
|
-
/** `face` 的词表校验(闭集两词;坏词抛 ⇒ 整域拒)。 */
|
|
58
25
|
function assertFace(raw) {
|
|
59
26
|
if (raw !== "open" && raw !== "roots")
|
|
60
27
|
throw new ReadFaceDomainError(`readFace.face must be "open" or "roots" — got ${JSON.stringify(raw)}`);
|
|
61
28
|
return raw;
|
|
62
29
|
}
|
|
63
|
-
/** `resolveReadDenyBuiltins` 的入参拼装(两处调用同源:发布件自校验 + 合成后终校验)。 */
|
|
64
30
|
function builtinsArg(tiers, exclude) {
|
|
65
31
|
return {
|
|
66
32
|
...(tiers !== undefined ? { tiers } : {}),
|
|
67
33
|
...(exclude !== undefined ? { exclude } : {}),
|
|
68
34
|
};
|
|
69
35
|
}
|
|
70
|
-
/** 名字表(档位 / 行名)的形校验:必须是非空串数组;`[]` 合法(显式空姿态)。 */
|
|
71
36
|
function normalizeNameList(field, raw) {
|
|
72
37
|
if (!Array.isArray(raw))
|
|
73
38
|
throw new ReadFaceDomainError(`readFace.${field} must be an array of names`);
|
|
@@ -77,17 +42,7 @@ function normalizeNameList(field, raw) {
|
|
|
77
42
|
return n.trim();
|
|
78
43
|
});
|
|
79
44
|
}
|
|
80
|
-
/**
|
|
81
|
-
* 应用 center 的 readFace 域(**boot-only**:调用点在 boot 拉取之后、`createRunnerDeps` 之前)。
|
|
82
|
-
* 缺席 ⇒ 零触碰。规则见文件头注 ①②③。
|
|
83
|
-
*
|
|
84
|
-
* 返回值 = **本机 env 显式占住的 center 键名**(与本次发布内容无关的部署事实)。调用方把它喂给
|
|
85
|
-
* `RestartSliceCtx.readFaceEnvHeld`,`read-face` restart 片于是只指纹「真会 boot-bake 的那部分」——
|
|
86
|
-
* 否则 center 改一个本机永远用不上的键,整片 fleet 会为一次对自己毫无影响的发布滚动重启一遍
|
|
87
|
-
* (codex 对抗复审 finding 2,验真后采纳的那一半)。
|
|
88
|
-
*/
|
|
89
45
|
export function applyCenterReadFace(config, eff, logger) {
|
|
90
|
-
// 部署事实先算(与发布件在不在场无关):env 占住哪几个键,是这台机器的属性。
|
|
91
46
|
const envHeldKeys = KEY_MAP.filter(([, cfgKey]) => config[cfgKey] !== undefined).map(([centerKey]) => centerKey);
|
|
92
47
|
const domain = eff.readFace;
|
|
93
48
|
if (domain === undefined || domain === null)
|
|
@@ -100,33 +55,21 @@ export function applyCenterReadFace(config, eff, logger) {
|
|
|
100
55
|
return envHeldKeys;
|
|
101
56
|
}
|
|
102
57
|
const d = domain;
|
|
103
|
-
// ── ① env 恒赢(逐键):env 写过的键,center 的同名成员只留声、不落配 ────────────────────────
|
|
104
58
|
const envHeld = KEY_MAP.filter(([centerKey, cfgKey]) => d[centerKey] !== undefined && config[cfgKey] !== undefined).map(([centerKey, cfgKey]) => `${cfgKey} (readFace.${centerKey})`);
|
|
105
|
-
// ── ②③ stage ────────────────────────────────────────────────────────────────────────────────
|
|
106
59
|
let staged;
|
|
107
60
|
try {
|
|
108
|
-
// ② **全域预校验先行,不看 env 是否覆盖**(codex 对抗复审 finding 1,验真属实后重排的次序)。
|
|
109
|
-
// 旧序把校验挂在「取 center 值」之后,而被 env 占住的键根本不取值 ⇒ 那个成员**完全不过校验**:
|
|
110
|
-
// 「env 钉死 face × center 把 face 写错一个字母 + 同域发一个放宽的 denyBuiltinTiers:[]」这一形下,
|
|
111
|
-
// 非法成员静默无声、放宽成员照落 —— 本文件承诺的「任一坏值整域拒」被证否,且偏离方向是 fail-open。
|
|
112
|
-
// 现在:域里**每一个在场成员**都先过形/词表/引擎校验(且校验的是**这份发布自己的**姿态),
|
|
113
|
-
// 任一不合法 ⇒ 抛 ⇒ 整域拒;合法之后才做 env-wins 逐键合成。
|
|
114
61
|
const normalized = {
|
|
115
62
|
...(d.face !== undefined ? { readFace: assertFace(d.face) } : {}),
|
|
116
63
|
...(d.denyPatterns !== undefined ? { readDenyPatterns: normalizeDenyPatterns(d.denyPatterns) } : {}),
|
|
117
64
|
...(d.denyBuiltinTiers !== undefined ? { readDenyBuiltinTiers: normalizeNameList("denyBuiltinTiers", d.denyBuiltinTiers) } : {}),
|
|
118
65
|
...(d.denyBuiltinExclude !== undefined ? { readDenyBuiltinExclude: normalizeNameList("denyBuiltinExclude", d.denyBuiltinExclude) } : {}),
|
|
119
66
|
};
|
|
120
|
-
// 发布件自身的引擎校验(与 env 覆盖无关 —— 一个坏发布就是坏发布,不因本机恰好用不上它而变合法)。
|
|
121
67
|
if (normalized.readDenyPatterns !== undefined)
|
|
122
68
|
compileReadDeny(normalized.readDenyPatterns);
|
|
123
69
|
if (normalized.readDenyBuiltinTiers !== undefined || normalized.readDenyBuiltinExclude !== undefined) {
|
|
124
70
|
resolveReadDenyBuiltins(builtinsArg(normalized.readDenyBuiltinTiers, normalized.readDenyBuiltinExclude));
|
|
125
71
|
}
|
|
126
|
-
// ③ env-wins 逐键合成:只保留**没被 env 占住**的键。
|
|
127
72
|
staged = Object.fromEntries(KEY_MAP.filter(([, cfgKey]) => config[cfgKey] === undefined).map(([, cfgKey]) => [cfgKey, normalized[cfgKey]]).filter(([, v]) => v !== undefined));
|
|
128
|
-
// 引擎规则再验一次,这次跑在**最终合成姿态**上(env 键 + 本次 center 键):跨腿坏组合
|
|
129
|
-
// (env 选档位 × center 删一个不在该档里的行名)只有在合成后才看得见。
|
|
130
73
|
const finalPatterns = staged.readDenyPatterns ?? config.readDenyPatterns;
|
|
131
74
|
const finalTiers = staged.readDenyBuiltinTiers ?? config.readDenyBuiltinTiers;
|
|
132
75
|
const finalExclude = staged.readDenyBuiltinExclude ?? config.readDenyBuiltinExclude;
|
|
@@ -142,7 +85,6 @@ export function applyCenterReadFace(config, eff, logger) {
|
|
|
142
85
|
});
|
|
143
86
|
return envHeldKeys;
|
|
144
87
|
}
|
|
145
|
-
// ── commit:纯赋值 ────────────────────────────────────────────────────────────────────────────
|
|
146
88
|
if (staged.readFace !== undefined)
|
|
147
89
|
config.readFace = staged.readFace;
|
|
148
90
|
if (staged.readDenyPatterns !== undefined)
|
|
@@ -159,7 +101,6 @@ export function applyCenterReadFace(config, eff, logger) {
|
|
|
159
101
|
}
|
|
160
102
|
if (Object.keys(staged).length > 0) {
|
|
161
103
|
logger?.info("sema_registry_read_face", {
|
|
162
|
-
// 落的是**哪些键**,不是 deny 表的内容(策略内容不进日志面,与能力位的最小披露同判)。
|
|
163
104
|
applied: Object.keys(staged),
|
|
164
105
|
...(staged.readFace !== undefined ? { face: staged.readFace } : {}),
|
|
165
106
|
note: "restart-to-apply — these keys are captured when the runner deps are assembled at boot",
|