@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,15 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Restart-to-apply CHANGE detection — the structured `RestartSignal` an orchestrator consumes (GET /health →
|
|
3
|
-
* `restart`), computed against the process's BOOT config (not presence, else a refresh would re-fire the same
|
|
4
|
-
* diff and loop-restart). Also owns the model-plane deferral predicates (`planeHasActiveTiers`/
|
|
5
|
-
* `modelPlaneChanged`) main.ts uses to decide whether a hot-apply is safe under a tier-frozen Runner. Split out
|
|
6
|
-
* of `facade.ts` (design/158 A13, internal-lossless — the facade re-exports every symbol below unchanged).
|
|
7
|
-
*/
|
|
8
1
|
import { resolveActiveTiers } from "@sema-agent/registry-core";
|
|
9
2
|
import { RUNTIME_GATE_KEYS, HOT_RUNTIME_GATE_KEYS, runtimeGatePresent, resolveDefaultModelName } from "./apply-effective.js";
|
|
10
3
|
const RESTART_SLICES = ["skills", "mcp", "a2a", "scenarios", "runtime-gates", "models-tiers", "degrade-route", "read-face"];
|
|
11
|
-
/** Canonical, key-sorted JSON (array order preserved) so two semantically-equal effective slices fingerprint
|
|
12
|
-
* identically regardless of object key order from the center serializer. */
|
|
13
4
|
function stableStringify(v) {
|
|
14
5
|
if (v === null || typeof v !== "object")
|
|
15
6
|
return JSON.stringify(v) ?? "null";
|
|
@@ -21,43 +12,15 @@ function stableStringify(v) {
|
|
|
21
12
|
.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`)
|
|
22
13
|
.join(",")}}`;
|
|
23
14
|
}
|
|
24
|
-
/** The comparable value of one restart-to-apply slice (undefined config / unpublished slice → null, so
|
|
25
|
-
* "center started/stopped publishing skills" is itself a detectable change). Skills/MCP/scenarios fingerprint
|
|
26
|
-
* ONLY the ENABLED entries — the runtime consumers (applyCenterSkills / resolveMcpServers / centerScenarios) skip
|
|
27
|
-
* `enabled:false`, so a disabled entry's content has zero runtime effect and must NOT trigger a restart.
|
|
28
|
-
* Every enable/disable FLIP still changes the filtered set → still triggers, correctly. */
|
|
29
15
|
function enabledOnly(rows) {
|
|
30
16
|
if (!rows)
|
|
31
17
|
return null;
|
|
32
18
|
return rows.filter((r) => r.enabled !== false);
|
|
33
19
|
}
|
|
34
|
-
/** One catalog entry, reduced to the fields whose change is genuinely restart-to-apply. Consumers of a
|
|
35
|
-
* boot-frozen catalog entry read almost every field on the FULLY-DERIVED Model (`toModel`) — id → body.model,
|
|
36
|
-
* baseUrl → the endpoint, provider → which brain, maxTokens/compat/extraBody/input → the request body,
|
|
37
|
-
* apiKeyEnv/sealedApiKey → the credential — so we fingerprint the WHOLE entry minus the three provably HOT
|
|
38
|
-
* fields:
|
|
39
|
-
* - `cost` — refreshed by `mutateInPlace(pricing, buildPricing(config.models))`, and core prefers
|
|
40
|
-
* `deps.pricing[model.id]` over the (possibly frozen) `Model.cost`; `toModel` always emits
|
|
41
|
-
* a cost object, so every center-lane model has a live pricing entry.
|
|
42
|
-
* - `quotaWeight` — refreshed by `mutateInPlace(config.modelQuotaWeights, …)`, read live per burn.
|
|
43
|
-
* - `enabled` — `enabledOnly` already filtered on it, so among the entries that reach here it is
|
|
44
|
-
* `true`-or-absent: two encodings of ONE state. Keeping it would let a center serializer
|
|
45
|
-
* that starts/stops emitting the explicit `true` manufacture a restart out of nothing.
|
|
46
|
-
* Every real enable/disable FLIP changes the filtered SET, so it still signals.
|
|
47
|
-
* A DENY-list (not an allow-list) on purpose: a catalog field added later rides the fingerprint automatically,
|
|
48
|
-
* so the failure mode of drift is a spurious restart, never a silently missed one.
|
|
49
|
-
*
|
|
50
|
-
* Shared by BOTH catalog-entry fingerprints (`degrade-route`, `models-tiers` + `modelPlaneChanged`) since the
|
|
51
|
-
* 复审 2026-07-29 留档: models-tiers had kept all three, so on a tiers-active deployment a pure re-pricing /
|
|
52
|
-
* weight tweak / serializer writing `enabled:true` rolling-restarted the fleet. The two fingerprints must
|
|
53
|
-
* subtract the SAME set — under active tiers `modelPlaneChanged` defers the plane and the refresh loop then
|
|
54
|
-
* FORCE-pushes the `models-tiers` reason for a deferred candidate (boot/config-center.ts R15/R22), so
|
|
55
|
-
* subtracting in only one of them is either no-op (restart still fires) or incoherent (applied yet signalled). */
|
|
56
20
|
function catalogEntryFingerprint(entry) {
|
|
57
21
|
const { cost: _cost, quotaWeight: _quotaWeight, enabled: _enabled, ...route } = entry;
|
|
58
22
|
return route;
|
|
59
23
|
}
|
|
60
|
-
/** The enabled entries of a model plane, each reduced by `catalogEntryFingerprint` (null = no plane published). */
|
|
61
24
|
function planeEntriesFingerprint(rows) {
|
|
62
25
|
const enabled = enabledOnly(rows);
|
|
63
26
|
return enabled ? enabled.map((m) => catalogEntryFingerprint(m)) : null;
|
|
@@ -71,9 +34,6 @@ function restartSliceValue(eff, slice, ctx) {
|
|
|
71
34
|
case "mcp":
|
|
72
35
|
return enabledOnly(eff.mcp?.servers);
|
|
73
36
|
case "a2a":
|
|
74
|
-
// 与 mcp 逐字同形:只指纹 ENABLED 条目(`resolveA2aPeers` 跳过 `enabled:false`,所以被禁条目的
|
|
75
|
-
// 内容零运行期效果),整条记录进指纹(url/cardUrl/headerRefs/principalHeader/allowSkills/scenarios
|
|
76
|
-
// 全是运行期相关字段;DENY-list 都不需要——本域没有 cost/quotaWeight 那种热字段)。
|
|
77
37
|
return enabledOnly(eff.a2a?.peers);
|
|
78
38
|
case "scenarios":
|
|
79
39
|
return enabledOnly(eff.scenarios?.scenarios);
|
|
@@ -82,38 +42,21 @@ function restartSliceValue(eff, slice, ctx) {
|
|
|
82
42
|
if (!rt)
|
|
83
43
|
return null;
|
|
84
44
|
const present = {};
|
|
85
|
-
// #322 批1:限额五闸已转热(stage-limits 双腿写入)⇒ **不再进指纹**。留在里面的话,一次纯限额发布
|
|
86
|
-
// 会让整片 fleet 为一个已经生效的变更滚动重启一遍——而重启后的姿态与重启前逐字相同(read-face 的
|
|
87
|
-
// env-held 剔除同族论证)。剩下的 approvalRequire 仍是 boot-baked,归批2。
|
|
88
45
|
for (const k of RUNTIME_GATE_KEYS)
|
|
89
46
|
if (!HOT_RUNTIME_GATE_KEYS.has(k) && runtimeGatePresent(rt, k))
|
|
90
47
|
present[k] = rt[k];
|
|
91
48
|
return Object.keys(present).length ? present : null;
|
|
92
49
|
}
|
|
93
50
|
case "models-tiers": {
|
|
94
|
-
// Fingerprint the models+tiers pair ONLY when this side resolves an active tier table (the Runner-copy
|
|
95
|
-
// decoupling only exists under tiers). Tier-less side → null: both sides tier-less never signals (hot-apply
|
|
96
|
-
// is real there); a tiers on/off FLIP is itself a detectable change (null vs object) — correctly signals,
|
|
97
|
-
// because the flip only takes effect at Runner construction.
|
|
98
51
|
const active = eff.models ? resolveActiveTiers(eff.models) : undefined;
|
|
99
52
|
if (!active || Object.keys(active).length === 0)
|
|
100
53
|
return null;
|
|
101
54
|
const enabled = enabledOnly(eff.models?.models);
|
|
102
|
-
// codex R8: the resolved DEFAULT rides the fingerprint too — the Runner's frozen copy carries its own
|
|
103
|
-
// boot-time `default` entry, so a refresh that ONLY moves the default source (models.default /
|
|
104
|
-
// roles.default / tier binding) re-points config.models.default while spec.model="default" keeps routing
|
|
105
|
-
// the stale Runner entry. SAME resolver as applyEffective (resolveDefaultModelName, single source);
|
|
106
|
-
// dangling candidates fall through silently here — applyEffective owns the warn.
|
|
107
55
|
const names = new Set((enabled ?? []).map((m) => m.name));
|
|
108
56
|
const def = resolveDefaultModelName(eff, (n) => names.has(n), enabled?.[0]?.name ?? "");
|
|
109
|
-
// Entries reduced by the shared deny-list (cost/quotaWeight/enabled are hot — see catalogEntryFingerprint).
|
|
110
57
|
return { models: planeEntriesFingerprint(eff.models?.models), tiers: active, default: def.name };
|
|
111
58
|
}
|
|
112
59
|
case "read-face": {
|
|
113
|
-
// 指纹的是**真会 boot-bake 的那部分**:域里被本机 env 占住的成员剔除(codex finding 2 —— 旧实现
|
|
114
|
-
// 指纹原始域,于是 center 改一个本机永远用不上的键也会让整片 fleet 滚动重启一遍,而重启后姿态
|
|
115
|
-
// 逐字不变)。域缺席、或全部成员都被 env 占住 ⇒ null:两者对**本机**是同一件事(这条腿不存在),
|
|
116
|
-
// 于是「组织开始/停止发布 READ 面」在真会生效的那些键上仍然可检测。
|
|
117
60
|
const d = eff.readFace;
|
|
118
61
|
if (!d)
|
|
119
62
|
return null;
|
|
@@ -124,30 +67,18 @@ function restartSliceValue(eff, slice, ctx) {
|
|
|
124
67
|
case "degrade-route": {
|
|
125
68
|
const to = ctx?.reactiveDegradeTo;
|
|
126
69
|
if (to === undefined)
|
|
127
|
-
return null;
|
|
128
|
-
// Present↔absent is itself a change: with no enabled entry named `to`, createBrain composes NO
|
|
129
|
-
// degrading shell at all, so the flip only takes effect at the next boot.
|
|
70
|
+
return null;
|
|
130
71
|
const target = (enabledOnly(eff.models?.models) ?? []).find((m) => m.name === to);
|
|
131
72
|
return target ? catalogEntryFingerprint(target) : null;
|
|
132
73
|
}
|
|
133
74
|
}
|
|
134
75
|
}
|
|
135
|
-
/** Restart-to-apply slices that DIFFER between this process's BOOT config and a freshly-pulled one. Empty =
|
|
136
|
-
* nothing baked-at-boot changed (only hot-apply slices moved, or nothing) → no restart needed. An orchestrator
|
|
137
|
-
* can auto rolling-restart on a non-empty result WITHOUT a restart loop, because the comparison is always
|
|
138
|
-
* against boot (a refresh that re-fires the same diff is idempotent, not a fresh trigger). */
|
|
139
76
|
export function restartReasons(boot, current, ctx) {
|
|
140
77
|
return RESTART_SLICES.filter((s) => {
|
|
141
78
|
const b = restartSliceValue(boot, s, ctx);
|
|
142
79
|
const c = restartSliceValue(current, s, ctx);
|
|
143
80
|
if (stableStringify(b) === stableStringify(c))
|
|
144
81
|
return false;
|
|
145
|
-
// #324:撤销台账腿在场时,mcp 片的**纯删除向**不签 —— 删除已被台账即时承接(core 每次 dispatch 前
|
|
146
|
-
// 探 isRevoked),签了就是为一个已生效的变更滚动重启 fleet。判据=**保序严格子序列**(codex F3,
|
|
147
|
-
// confirmed:旧判据用 Set 成员测,[A,B]→[B,A] 纯重排两侧指纹不等、每条又都在 boot 集 ⇒ 被误豁免,
|
|
148
|
-
// 而服务器顺序是 mount 语义〔工具串接序〕,重排只能 restart 生效;删除+重排混合同理——存留条目的
|
|
149
|
-
// 相对顺序必须与 boot 逐字一致才是「纯删除」)。任何新增/改形/重排照签;域整体出现/消失(null↔数组)
|
|
150
|
-
// 不是删除向,保守照签。ctx 键缺席 = 席没接线 ⇒ 旧语义照签(fail-closed,#157)。
|
|
151
82
|
if (s === "mcp" && ctx?.mcpRevocationLive === true && Array.isArray(b) && Array.isArray(c) && c.length < b.length) {
|
|
152
83
|
const bootFps = b.map(stableStringify);
|
|
153
84
|
let bi = 0;
|
|
@@ -163,18 +94,6 @@ export function restartReasons(boot, current, ctx) {
|
|
|
163
94
|
return true;
|
|
164
95
|
});
|
|
165
96
|
}
|
|
166
|
-
/** codex R10 (models-tiers 窗收口): TRUE when the MODEL PLANE (enabled models + active tier table + resolved
|
|
167
|
-
* default) of `next` differs from the last-APPLIED plane `prev`. Under a tier-frozen Runner (tiers non-empty at
|
|
168
|
-
* construction → private expanded copy) main.ts DEFERS the whole plane mutation until restart when this is true —
|
|
169
|
-
* admission (/v1/models, the catalog gates) and the Runner then stay on ONE generation through the restart
|
|
170
|
-
* window, so a same-key retarget can never silently route/bill the stale Model object. `prev === undefined`
|
|
171
|
-
* (env-boot plane, no EffectiveConfig to compare) is conservatively "changed" — one deferred adoption + restart
|
|
172
|
-
* converges. Deliberately NOT gated on tier activity of either side (unlike the restart slice): a frozen Runner
|
|
173
|
-
* goes stale on ANY plane change, including a tiers-off flip. */
|
|
174
|
-
/** codex R12: TRUE when this candidate resolves an ACTIVE tier table. Used by main.ts's defer predicate — a
|
|
175
|
-
* tier-less-booted Runner never re-runs expandTiers, so hot-applying an ARRIVING tier table splits admission
|
|
176
|
-
* (tier words pass the expanded gate) from execution (core throws "Unknown model ref"). Hot-apply of the model
|
|
177
|
-
* plane is safe only when BOTH the constructed generation and the candidate are tier-less. */
|
|
178
97
|
export function planeHasActiveTiers(eff) {
|
|
179
98
|
const active = eff.models ? resolveActiveTiers(eff.models) : undefined;
|
|
180
99
|
return active !== undefined && Object.keys(active).length > 0;
|
|
@@ -187,9 +106,6 @@ export function modelPlaneChanged(prev, next) {
|
|
|
187
106
|
const active = e.models ? (resolveActiveTiers(e.models) ?? null) : null;
|
|
188
107
|
const names = new Set((enabled ?? []).map((m) => m.name));
|
|
189
108
|
const def = resolveDefaultModelName(e, (n) => names.has(n), enabled?.[0]?.name ?? "");
|
|
190
|
-
// Same reduction as the models-tiers slice — the two MUST subtract the same hot fields (see
|
|
191
|
-
// catalogEntryFingerprint): a deferred candidate force-pushes that slice's reason, so any divergence
|
|
192
|
-
// either re-manufactures the restart we just subtracted or signals a plane that was applied hot.
|
|
193
109
|
return stableStringify({ models: planeEntriesFingerprint(e.models?.models), tiers: active, default: def.name });
|
|
194
110
|
};
|
|
195
111
|
return fp(prev) !== fp(next);
|
|
@@ -28,7 +28,7 @@ export declare function resolveMcpServers(mcp: {
|
|
|
28
28
|
servers: CenterMcpServer[];
|
|
29
29
|
}, logger?: Logger): ScopedMcpServer[];
|
|
30
30
|
/** The MCP servers applicable to a scenario: tagged with it, or untagged (global). undefined when none.
|
|
31
|
-
* #
|
|
31
|
+
* #342:alias 窗撤销,单键命中(同 `skillsForScenario`)。 */
|
|
32
32
|
export declare function mcpForScenario(servers: ScopedMcpServer[] | undefined, scenario: string): McpServerSpec[] | undefined;
|
|
33
33
|
/**
|
|
34
34
|
* DESIGN-269 §2.1 —— resolve center A2A peers (`/effective.a2a`) into core `A2aServerSpec`s + the scenarios
|
|
@@ -1,30 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Small consumption lanes that all project a center manifest domain onto a per-request runtime shape:
|
|
3
|
-
* skills application (`applyCenterSkills` — merge center skills OVER the image baseline, content-addressed
|
|
4
|
-
* fetch+verify+cache), MCP server resolution (`resolveMcpServers`/`mcpForScenario` — env-NAME ref
|
|
5
|
-
* resolution + per-scenario filtering) and — since DESIGN-269 车1 — A2A peer resolution
|
|
6
|
-
* (`resolveA2aPeers`/`a2aForScenario`, the same two rules on the agent-to-agent lane; it lives HERE rather
|
|
7
|
-
* than in a new file because it shares `resolveRefs`, and a secret-boundary rule with two implementations
|
|
8
|
-
* is a rule with one that will drift). Split out of `facade.ts` (design/158 A13, internal-lossless —
|
|
9
|
-
* the facade re-exports every symbol below unchanged).
|
|
10
|
-
*/
|
|
11
1
|
import { skillContentHash } from "@sema-agent/registry-core";
|
|
12
|
-
import { scenarioConfigKeys } from "../capabilities/scenario-alias.js";
|
|
13
2
|
import { fetchSkillContent } from "./http-client.js";
|
|
14
3
|
import { SHA256_HEX_RE } from "../digest-form.js";
|
|
15
|
-
|
|
16
|
-
* Merge center skills OVER the image baseline (design/41-sibling B1; `sema-registry docs/MCP-SKILLS.md`). Load order =
|
|
17
|
-
* `loadSkills(SKILLS_DIR)` baseline → center skills lazily fetched by hash and overlaid BY NAME (center
|
|
18
|
-
* wins on a clash → can override a built-in; guarded center-side by publisher RBAC + the publish gate).
|
|
19
|
-
* `enabled:false` is skipped. Content is verified + cached by hash (same hash fetched once). A skill whose
|
|
20
|
-
* content fetch/verify fails is skipped (the baseline version, if any, stays) — never blocks startup.
|
|
21
|
-
* Restart-to-apply: this runs at boot, before `buildScenarios` bakes skills into scenarios.
|
|
22
|
-
*/
|
|
23
|
-
export async function applyCenterSkills(baseline, manifest, baseUrl, token, logger, fetchImpl = fetch,
|
|
24
|
-
/** LKG 姊妹面(clay 拍 2026-07-18):skill 正文的 content-addressed 盘缓存目录。读序=内存→盘(验
|
|
25
|
-
* hash,坏文件忽略)→中心(成功后 best-effort 回写)。让 LKG-boot 在中心不可达时不再于正文拉取上
|
|
26
|
-
* 挂数秒(live 实测黑洞 +5.3s/skill 批),顺带省正常 refresh 的重复拉取。undefined=无盘缓存(原行为)。 */
|
|
27
|
-
diskCacheDir) {
|
|
4
|
+
export async function applyCenterSkills(baseline, manifest, baseUrl, token, logger, fetchImpl = fetch, diskCacheDir) {
|
|
28
5
|
const { promises: fsp } = await import("node:fs");
|
|
29
6
|
const { join: joinPath } = await import("node:path");
|
|
30
7
|
const diskRead = async (hash) => {
|
|
@@ -32,21 +9,17 @@ diskCacheDir) {
|
|
|
32
9
|
return undefined;
|
|
33
10
|
const hex = hash.replace(/^sha256:/, "");
|
|
34
11
|
if (!SHA256_HEX_RE.test(hex))
|
|
35
|
-
return undefined;
|
|
12
|
+
return undefined;
|
|
36
13
|
try {
|
|
37
14
|
const text = await fsp.readFile(joinPath(diskCacheDir, hex), "utf8");
|
|
38
15
|
if (skillContentHash(text) === hash)
|
|
39
|
-
return text;
|
|
16
|
+
return text;
|
|
40
17
|
logger?.warn("sema_registry_skill_cache_corrupt", { hash, note: "on-disk body fails its own hash — ignored, refetching from center" });
|
|
41
18
|
}
|
|
42
19
|
catch {
|
|
43
|
-
/* absent/unreadable → center */
|
|
44
20
|
}
|
|
45
21
|
return undefined;
|
|
46
22
|
};
|
|
47
|
-
// F5(LKG 复审三轮):写盘 promises 收集、函数返回前 await——fire-and-forget 会让 persistLkgDurable
|
|
48
|
-
// 的「先落正文再发信号」序失效(await 只等到 fetch,rename 还在飞,窗口内重启=离线 boot 缺 skill)。
|
|
49
|
-
// 单写失败仍 warn 软降级(收集的是 catch 过的 promise,Promise.all 不会炸)。
|
|
50
23
|
const pendingWrites = [];
|
|
51
24
|
const diskWrite = (hash, content) => {
|
|
52
25
|
if (!diskCacheDir)
|
|
@@ -60,8 +33,8 @@ diskCacheDir) {
|
|
|
60
33
|
})().catch((err) => logger?.warn("sema_registry_skill_cache_write_failed", { hash, err: String(err) })));
|
|
61
34
|
};
|
|
62
35
|
const byName = new Map(baseline.map((s) => [s.spec.name, s]));
|
|
63
|
-
const contentCache = new Map();
|
|
64
|
-
const seenNames = new Set();
|
|
36
|
+
const contentCache = new Map();
|
|
37
|
+
const seenNames = new Set();
|
|
65
38
|
for (const m of manifest.skills ?? []) {
|
|
66
39
|
if (m.enabled === false)
|
|
67
40
|
continue;
|
|
@@ -74,7 +47,7 @@ diskCacheDir) {
|
|
|
74
47
|
content = await diskRead(m.contentHash);
|
|
75
48
|
if (content === undefined) {
|
|
76
49
|
content = await fetchSkillContent(baseUrl, token, m.contentHash, fetchImpl);
|
|
77
|
-
diskWrite(m.contentHash, content);
|
|
50
|
+
diskWrite(m.contentHash, content);
|
|
78
51
|
}
|
|
79
52
|
contentCache.set(m.contentHash, content);
|
|
80
53
|
const overrodeBuiltin = byName.has(m.name);
|
|
@@ -85,16 +58,9 @@ diskCacheDir) {
|
|
|
85
58
|
logger?.warn("sema_registry_skill_failed", { name: m.name, hash: m.contentHash, err: String(err), note: "keeping baseline if any; skipping center version" });
|
|
86
59
|
}
|
|
87
60
|
}
|
|
88
|
-
await Promise.all(pendingWrites);
|
|
61
|
+
await Promise.all(pendingWrites);
|
|
89
62
|
return [...byName.values()];
|
|
90
63
|
}
|
|
91
|
-
/** Resolve env-NAME refs (target-key → ENV_NAME) to real values from THIS service's env. Returns null when
|
|
92
|
-
* ANY referenced env var is unset → the caller skips the whole server (fail-safe: never wire a half-credentialed
|
|
93
|
-
* endpoint). Mirrors the missing-apiKeyEnv handling for models (the secret boundary is unchanged).
|
|
94
|
-
*
|
|
95
|
-
* `event`/`skipNote` are per-lane so an operator reading logs sees WHICH lane dropped an entry (MCP server vs
|
|
96
|
-
* A2A peer) — the resolution rule itself is one function on purpose (two copies of a secret-boundary rule is
|
|
97
|
-
* how one of them drifts open). */
|
|
98
64
|
function resolveRefs(refs, server, kind, logger, event = "sema_registry_mcp_env_missing", skipNote = "skipping this MCP server — referenced env var is unset in this service") {
|
|
99
65
|
if (!refs)
|
|
100
66
|
return {};
|
|
@@ -109,20 +75,12 @@ function resolveRefs(refs, server, kind, logger, event = "sema_registry_mcp_env_
|
|
|
109
75
|
}
|
|
110
76
|
return out;
|
|
111
77
|
}
|
|
112
|
-
/**
|
|
113
|
-
* Resolve center MCP servers (`/effective.mcp`) into core `McpServerSpec`s + the scenarios each applies to.
|
|
114
|
-
* env-NAME refs → real values from THIS service's env (secret boundary unchanged); a server with any missing
|
|
115
|
-
* ref is skipped (`resolveRefs` → null). `enabled:false` skipped. Restart-to-apply (filtered per scenario in
|
|
116
|
-
* `resolveSpec` via `mcpForScenario`).
|
|
117
|
-
*/
|
|
118
78
|
export function resolveMcpServers(mcp, logger) {
|
|
119
79
|
const out = [];
|
|
120
80
|
for (const s of mcp.servers ?? []) {
|
|
121
81
|
if (s.enabled === false)
|
|
122
82
|
continue;
|
|
123
83
|
const allow = s.allowTools && s.allowTools.length > 0 ? { allowTools: s.allowTools } : {};
|
|
124
|
-
// E23 (shell-host contract): carry the per-server inbound-elicitation opt-in onto McpServerSpec.elicitation. Only when
|
|
125
|
-
// explicitly true (default OFF / fail-closed) — core advertises the capability only when this AND onElicit hold.
|
|
126
84
|
const elicit = s.elicitation === true ? { elicitation: true } : {};
|
|
127
85
|
if (s.transport.kind === "stdio") {
|
|
128
86
|
const env = resolveRefs(s.transport.envRefs, s.name, "env", logger);
|
|
@@ -147,32 +105,12 @@ export function resolveMcpServers(mcp, logger) {
|
|
|
147
105
|
logger?.info("sema_registry_mcp", { servers: out.map((s) => s.spec.name) });
|
|
148
106
|
return out;
|
|
149
107
|
}
|
|
150
|
-
/** The MCP servers applicable to a scenario: tagged with it, or untagged (global). undefined when none.
|
|
151
|
-
* #273 alias 窗:标签写**旧场景名**的条目同样命中(`scenarioConfigKeys`,同 `skillsForScenario`)。 */
|
|
152
108
|
export function mcpForScenario(servers, scenario) {
|
|
153
109
|
if (!servers || servers.length === 0)
|
|
154
110
|
return undefined;
|
|
155
|
-
const
|
|
156
|
-
const hit = servers.filter((s) => s.scenarios.length === 0 || keys.some((n) => s.scenarios.includes(n))).map((s) => s.spec);
|
|
111
|
+
const hit = servers.filter((s) => s.scenarios.length === 0 || s.scenarios.includes(scenario)).map((s) => s.spec);
|
|
157
112
|
return hit.length > 0 ? hit : undefined;
|
|
158
113
|
}
|
|
159
|
-
/**
|
|
160
|
-
* DESIGN-269 §2.1 —— resolve center A2A peers (`/effective.a2a`) into core `A2aServerSpec`s + the scenarios
|
|
161
|
-
* each applies to. Exact sibling of {@link resolveMcpServers}, same three rules:
|
|
162
|
-
* · header-NAME refs → real values from THIS service's env (the secret boundary is unchanged — the center
|
|
163
|
-
* never holds the bearer). A peer with ANY missing ref is SKIPPED wholesale (`resolveRefs` → null):
|
|
164
|
-
* dialing a remote AGENT with half the credentials is the wrong failure direction.
|
|
165
|
-
* · `enabled:false` skipped.
|
|
166
|
-
* · restart-to-apply (filtered per scenario in `resolveSpec` via {@link a2aForScenario}).
|
|
167
|
-
*
|
|
168
|
-
* ⚠️ **No `toolAxes` on the center face, deliberately** (this is where the A2A lane STOPS mirroring MCP):
|
|
169
|
-
* core's ruling is that a caller override is the ONLY trusted way down from the fail-closed
|
|
170
|
-
* `egress:true`+`effect:"write"` default, and it names the CALLER as that trust root. An operator lowering
|
|
171
|
-
* an axis in center config would be vouching, on behalf of every future task, that a REMOTE agent's skill
|
|
172
|
-
* only reads — a claim the operator cannot actually verify and which core's own doc-comment reserves for
|
|
173
|
-
* the caller. Absent face > a face that quietly launders trust. (`allowSkills` is the operator's real
|
|
174
|
-
* knob here: narrow WHAT mounts, never re-label what it does.)
|
|
175
|
-
*/
|
|
176
114
|
export function resolveA2aPeers(a2a, logger) {
|
|
177
115
|
const out = [];
|
|
178
116
|
for (const p of a2a.peers ?? []) {
|
|
@@ -197,15 +135,10 @@ export function resolveA2aPeers(a2a, logger) {
|
|
|
197
135
|
logger?.info("sema_registry_a2a", { peers: out.map((p) => p.spec.name) });
|
|
198
136
|
return out;
|
|
199
137
|
}
|
|
200
|
-
/** The A2A peers applicable to a scenario: tagged with it, or untagged (global). undefined when none.
|
|
201
|
-
* Twin of {@link mcpForScenario} — `undefined` (not `[]`) is load-bearing: an empty array would put an
|
|
202
|
-
* `a2a: []` key on the spec, i.e. "this deployment declares an empty peer set", where absence means
|
|
203
|
-
* "this leg does not exist here". */
|
|
204
138
|
export function a2aForScenario(peers, scenario) {
|
|
205
139
|
if (!peers || peers.length === 0)
|
|
206
140
|
return undefined;
|
|
207
|
-
const
|
|
208
|
-
const hit = peers.filter((p) => p.scenarios.length === 0 || keys.some((n) => p.scenarios.includes(n))).map((p) => p.spec);
|
|
141
|
+
const hit = peers.filter((p) => p.scenarios.length === 0 || p.scenarios.includes(scenario)).map((p) => p.spec);
|
|
209
142
|
return hit.length > 0 ? hit : undefined;
|
|
210
143
|
}
|
|
211
144
|
//# sourceMappingURL=skills-mcp.js.map
|
|
@@ -5,18 +5,12 @@ const INFRA_RATE_KEYS = ["toolCallMicroUsd", "sandboxSecMicroUsd", "egressGbMicr
|
|
|
5
5
|
const okNum = (v) => typeof v === "number" && Number.isFinite(v);
|
|
6
6
|
const atLeast = (key, v, min) => okNum(v) && v >= min ? { ok: true, value: v } : { ok: false, reason: `${key} must be a finite number >= ${min}` };
|
|
7
7
|
const positive = (key, v) => okNum(v) && v > 0 ? { ok: true, value: v } : { ok: false, reason: `${key} must be a finite number > 0` };
|
|
8
|
-
/** 逐键判据。**坏值一律拒整批**(不夹取、不丢弃)——见文件头注纪律 3。 */
|
|
9
8
|
const VALIDATORS = {
|
|
10
|
-
rateLimitPerMin: (v) => atLeast("rateLimitPerMin", v, 0),
|
|
11
|
-
maxPrincipalCostUsd: (v) => atLeast("maxPrincipalCostUsd", v, 0),
|
|
12
|
-
costQuotaWindowSec: (v) => positive("costQuotaWindowSec", v),
|
|
9
|
+
rateLimitPerMin: (v) => atLeast("rateLimitPerMin", v, 0),
|
|
10
|
+
maxPrincipalCostUsd: (v) => atLeast("maxPrincipalCostUsd", v, 0),
|
|
11
|
+
costQuotaWindowSec: (v) => positive("costQuotaWindowSec", v),
|
|
13
12
|
maxTaskCostUsd: (v) => atLeast("maxTaskCostUsd", v, 0),
|
|
14
13
|
maxTaskTokens: (v) => atLeast("maxTaskTokens", v, 0),
|
|
15
|
-
// 🔴 codex R1-F2(真 finding,红先钉在 batch1-codex-r1.test.ts):**部分发布必须按基线补齐**。
|
|
16
|
-
// 修前这里从**空对象**重建,于是只发 `toolCallMicroUsd` 的一次发布会把 sandbox/egress 费率变成
|
|
17
|
-
// undefined —— 而消费端(routes/runs.ts 的 supervisorCost)把它们当 0 算,结果是**静默漏计费**:
|
|
18
|
-
// 账面看起来正常,只是少了两条轴。同一条 finding 的另一半:未知成员(拼错)修前被**忽略**,
|
|
19
|
-
// 极端情形下提交一个空费率对象;现在拼错=整批响亮拒(一个拼错的费率名等于「这条轴没在计费」)。
|
|
20
14
|
infraCostRates: (v, baseline) => {
|
|
21
15
|
if (v === null || typeof v !== "object" || Array.isArray(v))
|
|
22
16
|
return { ok: false, reason: "infraCostRates must be an object of non-negative micro-USD rates" };
|
|
@@ -29,7 +23,7 @@ const VALIDATORS = {
|
|
|
29
23
|
for (const k of INFRA_RATE_KEYS) {
|
|
30
24
|
const raw = src[k];
|
|
31
25
|
if (raw === undefined) {
|
|
32
|
-
out[k] = base[k] ?? 0;
|
|
26
|
+
out[k] = base[k] ?? 0;
|
|
33
27
|
continue;
|
|
34
28
|
}
|
|
35
29
|
if (!okNum(raw) || raw < 0)
|
|
@@ -38,7 +32,7 @@ const VALIDATORS = {
|
|
|
38
32
|
}
|
|
39
33
|
return { ok: true, value: out };
|
|
40
34
|
},
|
|
41
|
-
attachmentOrphanGraceMs: (v) => atLeast("attachmentOrphanGraceMs", v, 0),
|
|
35
|
+
attachmentOrphanGraceMs: (v) => atLeast("attachmentOrphanGraceMs", v, 0),
|
|
42
36
|
workspaceFileMaxBytes: (v) => positive("workspaceFileMaxBytes", v),
|
|
43
37
|
attachmentMaxBytes: (v) => atLeast("attachmentMaxBytes", v, LIMITS_HTTP_FLOORS.attachmentMaxBytes),
|
|
44
38
|
attachmentMimeAllowlist: (v) => {
|
|
@@ -51,10 +45,9 @@ const VALIDATORS = {
|
|
|
51
45
|
},
|
|
52
46
|
attachmentUnboundTtlMs: (v) => atLeast("attachmentUnboundTtlMs", v, LIMITS_HTTP_FLOORS.attachmentUnboundTtlMs),
|
|
53
47
|
drainGraceMs: (v) => atLeast("drainGraceMs", v, LIMITS_HTTP_FLOORS.drainGraceMs),
|
|
54
|
-
runStaleSec: (v) => positive("runStaleSec", v),
|
|
55
|
-
toolResultTtlSec: (v) => positive("toolResultTtlSec", v),
|
|
48
|
+
runStaleSec: (v) => positive("runStaleSec", v),
|
|
49
|
+
toolResultTtlSec: (v) => positive("toolResultTtlSec", v),
|
|
56
50
|
};
|
|
57
|
-
/** env 派生基线(**首次** stage 时快照;boot 序 = loadConfig → applyEffective,此刻 config 尚是纯 env)。 */
|
|
58
51
|
const limitsEnvBaseline = new WeakMap();
|
|
59
52
|
function baselineOf(config) {
|
|
60
53
|
const known = limitsEnvBaseline.get(config);
|
|
@@ -67,21 +60,13 @@ function baselineOf(config) {
|
|
|
67
60
|
return snap;
|
|
68
61
|
}
|
|
69
62
|
const same = (a, b) => (typeof a === "object" || typeof b === "object" ? JSON.stringify(a ?? null) === JSON.stringify(b ?? null) : a === b);
|
|
70
|
-
/** 候选视图 = 现役 + 本批 staged 赋值(跨键不变量判的就是这个「如果落地会怎样」)。 */
|
|
71
63
|
function candidateView(config, assignments) {
|
|
72
64
|
const view = { ...config };
|
|
73
|
-
// 写口窄化到「本批键 → unknown」的可选记录:值的真类型已在 VALIDATORS 里逐键判过,这里只是把判过的值
|
|
74
|
-
// 放回同名槽位(窄 `as`,不是 `as unknown as` —— ServiceConfig 本就可赋给这个形)。
|
|
75
65
|
const writable = view;
|
|
76
66
|
for (const [k, v] of assignments)
|
|
77
67
|
writable[k] = v;
|
|
78
68
|
return view;
|
|
79
69
|
}
|
|
80
|
-
/**
|
|
81
|
-
* STAGE:算出本世代 limitsHttp 组要 COMMIT 的全部赋值(纯计算,零变异)。
|
|
82
|
-
*
|
|
83
|
-
* 返回 `rejected` 时 `assignments`/`deletions` 恒为空 —— 整批拒的物理保证在返回值形上,不靠调用方自律。
|
|
84
|
-
*/
|
|
85
70
|
export function stageLimits(config, eff, logger) {
|
|
86
71
|
const baseline = baselineOf(config);
|
|
87
72
|
const legacy = eff.legacyGates ?? {};
|
|
@@ -91,10 +76,6 @@ export function stageLimits(config, eff, logger) {
|
|
|
91
76
|
const deferred = [];
|
|
92
77
|
const badKeys = [];
|
|
93
78
|
let reason;
|
|
94
|
-
// 🔴 codex R1-F1(真 finding,红先):`limits` 是 **open-world wire 数据**(无 schema 保证),所以
|
|
95
|
-
// 容器本身必须先判形。修前只对它做可选索引 ⇒ `"bad"` / 数组 / null 让**每个键都看起来缺席** ⇒ 全体
|
|
96
|
-
// 回 env 底,而且该世代被当作合法提交:中心刚把 rate/cost 门从 0 打开的部署上,一次畸形发布就把保护
|
|
97
|
-
// 一声不响地关回去了。方向与本域其余判据一致 —— 读不懂就更严不更松(整批拒,旧值继续服务)。
|
|
98
79
|
const rawLimits = eff.limits;
|
|
99
80
|
if (rawLimits !== undefined && (rawLimits === null || typeof rawLimits !== "object" || Array.isArray(rawLimits))) {
|
|
100
81
|
const rejected = { keys: [...BATCH1_LIMIT_KEYS], reason: `the published limits domain must be a JSON object — got ${rawLimits === null ? "null" : Array.isArray(rawLimits) ? "array" : typeof rawLimits}` };
|
|
@@ -106,9 +87,6 @@ export function stageLimits(config, eff, logger) {
|
|
|
106
87
|
return { assignments: [], deletions: [], changed: [], deferred: [], rejected };
|
|
107
88
|
}
|
|
108
89
|
const limits = rawLimits;
|
|
109
|
-
// LANDING-7.38 接线件④:limits 域是 open-world(registry-core 0.19.0 承运未知键并点名),但**键表
|
|
110
|
-
// 属主在这里**——发布了、本批却不消费的键(拼错/超前批次)至少要 warn,否则「registry-core 点了名、
|
|
111
|
-
// 部署里没人听」= 配置写了不生效的静默形。DEFERRED_LIMIT_KEYS(如 costQuotaWindowSec)不算未知。
|
|
112
90
|
if (limits !== undefined) {
|
|
113
91
|
const known = new Set([...BATCH1_LIMIT_KEYS, ...DEFERRED_LIMIT_KEYS.map((d) => d.key)]);
|
|
114
92
|
const unconsumed = Object.keys(limits).filter((k) => !known.has(k));
|
|
@@ -121,12 +99,10 @@ export function stageLimits(config, eff, logger) {
|
|
|
121
99
|
}
|
|
122
100
|
}
|
|
123
101
|
for (const key of BATCH1_LIMIT_KEYS) {
|
|
124
|
-
// 🔴 用**存在性**判新位优先级,不用 `??`(codex R1-F1 的另一半):`{rateLimitPerMin: null}` 在 `??`
|
|
125
|
-
// 下会当成「缺席」而回底/落旧位,一个显式的坏值就这样变成了静默撤回。存在即判 ⇒ null 走坏值拒。
|
|
126
102
|
const published = limits !== undefined && Object.prototype.hasOwnProperty.call(limits, key) ? limits[key] : legacy[key];
|
|
127
103
|
let next;
|
|
128
104
|
if (published === undefined) {
|
|
129
|
-
next = baseline[key];
|
|
105
|
+
next = baseline[key];
|
|
130
106
|
}
|
|
131
107
|
else {
|
|
132
108
|
const verdict = VALIDATORS[key](published, baseline[key]);
|
|
@@ -140,8 +116,6 @@ export function stageLimits(config, eff, logger) {
|
|
|
140
116
|
const current = config[key];
|
|
141
117
|
if (same(current, next))
|
|
142
118
|
continue;
|
|
143
|
-
// 🔴 codex R2-F2:**显式 deferred 的键不落地** —— 值已验过(坏值照样整批拒),但它的变更只能重启生效,
|
|
144
|
-
// 所以这里逐键 defer(不是整批拒:同批的热键该生效还得生效)+ 铸因进世代账 + 一条响亮 warn。
|
|
145
119
|
const deferral = DEFERRED_LIMIT_KEYS.find((d) => d.key === key);
|
|
146
120
|
if (deferral) {
|
|
147
121
|
deferred.push({ key, reason: deferral.restartReason });
|
|
@@ -162,7 +136,6 @@ export function stageLimits(config, eff, logger) {
|
|
|
162
136
|
});
|
|
163
137
|
return { assignments: [], deletions: [], changed: [], deferred: [], rejected };
|
|
164
138
|
}
|
|
165
|
-
// 跨键不变量:本批任一键触发**全表**校验(design/322 §2 A′,codex F2)。
|
|
166
139
|
const verdict = checkCrossInvariants({ live: config, next: candidateView(config, assignments) });
|
|
167
140
|
if (!verdict.ok) {
|
|
168
141
|
const involved = changed.length > 0 ? changed : BATCH1_LIMIT_KEYS.slice();
|
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
import { assertGuardPatternsUsable } from "./deployment-governance.js";
|
|
2
|
-
/**
|
|
3
|
-
* 活体心跳周期(ms)——`runs.ts` 的 `HEARTBEAT_MS` 就是它(那边 re-export,单源在此)。
|
|
4
|
-
*
|
|
5
|
-
* 为什么常量住在不变量文件里:它唯一的**判据用途**是「陈旧窗必须大于心跳」这条不变量,而该不变量现在
|
|
6
|
-
* 有 boot 与 stage 两个消费面。放在这里,两面读的是同一个数,`runs.ts` 的心跳环也读同一个数。
|
|
7
|
-
*/
|
|
8
2
|
export const LIVENESS_HEARTBEAT_MS = 30_000;
|
|
9
3
|
const anyServiceAuth = (v) => Boolean(v.authToken) || Object.keys(v.authTokens ?? {}).length > 0;
|
|
10
|
-
/** 与 `resolveBindHost` 逐字同判:无鉴权写面 + 零凭据 ⇒ boot 期把监听地址收窄到 loopback。 */
|
|
11
4
|
const mustNarrowBind = (v) => Boolean(v.allowUnauthedWrites) && !anyServiceAuth(v);
|
|
12
|
-
/** 直连门的合取项(config.ts:2788 的同一组);任一项翻转 = 门姿态变更。 */
|
|
13
5
|
const directDoorConjuncts = (v) => Boolean(v.directApprovalDoor) && Boolean(v.durableApproval) && v.approvalHmacKeys.length > 0 && v.principalJwtPubkeys.length > 0 && Boolean(v.principalJwtIss) && Boolean(v.principalJwtAud);
|
|
14
6
|
export const CONFIG_CROSS_INVARIANTS = [
|
|
15
7
|
{
|
|
@@ -22,9 +14,6 @@ export const CONFIG_CROSS_INVARIANTS = [
|
|
|
22
14
|
: undefined,
|
|
23
15
|
},
|
|
24
16
|
{
|
|
25
|
-
// 转换型:boot 期算一次的绑址收窄(resolveBindHost → server.listen)在运行期无法重算,所以任何
|
|
26
|
-
// **让收窄条件从不成立变成成立**的热切都是谎:写门确实收紧了,监听面却还在原来的接口上。
|
|
27
|
-
// 反向(窄→宽,例如加了凭据)不拒:那是收紧姿态,已绑的 loopback 只会更严。
|
|
28
17
|
id: "bind-posture",
|
|
29
18
|
keys: ["authToken", "authTokens", "allowUnauthedWrites"],
|
|
30
19
|
restartReason: "bind-posture",
|
|
@@ -42,8 +31,6 @@ export const CONFIG_CROSS_INVARIANTS = [
|
|
|
42
31
|
: undefined,
|
|
43
32
|
},
|
|
44
33
|
{
|
|
45
|
-
// 状态 + 转换两面都判:半开的直连门(boot 会拒启的那些组合)绝不许经热路径造出来;门**姿态**
|
|
46
|
-
// (激活/未激活)的翻转同样只许重启 —— 消费面(/health directDoorActive、鉴权腿)在 boot 期定形。
|
|
47
34
|
id: "direct-door-anchors",
|
|
48
35
|
keys: ["directApprovalDoor", "durableApproval", "approvalHmacKeys", "principalJwtPubkeys", "principalJwtIss", "principalJwtAud", "requirePrincipal", "operatorPrincipals"],
|
|
49
36
|
restartReason: "direct-door",
|
|
@@ -83,8 +70,6 @@ export const CONFIG_CROSS_INVARIANTS = [
|
|
|
83
70
|
: undefined,
|
|
84
71
|
},
|
|
85
72
|
{
|
|
86
|
-
// bake 门是 build-host-RCE 面,空 operator 名单让 isOperator 变 true-for-all = world-writable RCE。
|
|
87
|
-
// 与上一条同族但**判据不同**(那条判多租户 durable 审批的读面),故各自登记、各自锚。
|
|
88
73
|
id: "bake-operator-principals",
|
|
89
74
|
keys: ["imageBakes", "operatorPrincipals"],
|
|
90
75
|
restartReason: "operator-principals",
|
|
@@ -127,7 +112,6 @@ export const CONFIG_CROSS_INVARIANTS = [
|
|
|
127
112
|
: undefined,
|
|
128
113
|
},
|
|
129
114
|
];
|
|
130
|
-
/** 全表校验(**第一条不过即拒**:候选是整批的,报第一个成因足够行动,继续跑其余条目只会淹掉它)。 */
|
|
131
115
|
export function checkCrossInvariants(input) {
|
|
132
116
|
for (const inv of CONFIG_CROSS_INVARIANTS) {
|
|
133
117
|
const reason = inv.check(input);
|
package/dist/config-lkg.js
CHANGED
|
@@ -1,44 +1,16 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LKG 落盘(clay 拍,2026-07-18)——干净配置候选的本机持久化,boot 的第三级来源:
|
|
3
|
-
* 中心(预算窗内)> 盘上 LKG > env 兜底。
|
|
4
|
-
*
|
|
5
|
-
* 为什么存在(1.222.0 发版复审的残余环,clay 重裁量证据):boot 抢答窗让慢中心不再阻塞启动,但
|
|
6
|
-
* skills/mcp/scenarios/runtime-gates 四个 restart-to-apply 面是 boot 烤进进程的——配置迟到只能发
|
|
7
|
-
* restartRequired 信号。center 发布了这些面 + 中心**持续**慢于预算 + orchestrator 自动重启 ⇒ 每个
|
|
8
|
-
* 新进程都为真缺失的面发一次信号 = 反复重启环。盘上 LKG 让下一进程 boot 直读上一进程验证过的干净
|
|
9
|
-
* 候选(全部面进 buildScenarios),到货后与之比较通常零差异 ⇒ 无信号无环;中心恢复自然收敛。
|
|
10
|
-
* 白拿的第二收益:重启秒起且配置齐全(不再有「起服后几秒 roster pending」窗口)。
|
|
11
|
-
*
|
|
12
|
-
* 安全边界:config plane 恒只携带 env-NAME 引用(apiKeyEnv/tokenEnv/…),真实秘密从不入 effective
|
|
13
|
-
* ——落盘物按同一纪律无秘密;文件 0600 + 原子写(tmp+rename)。**只存干净候选**(候选门通过的:
|
|
14
|
-
* boot clean / refresh LKG advance / deferred clean),所以读侧只需防「文件坏形/被改」:JSON+信封
|
|
15
|
-
* 判形+prompts 面复判(validatePromptsDomain,与候选门同函数),任何不对 ⇒ 忽略文件走 env(warn
|
|
16
|
-
* 点名,绝不让坏缓存挡启动)。
|
|
17
|
-
*
|
|
18
|
-
* 范围:**remote lane only**——local lane 的 FileConfigStore 本来就是盘上即时读,无此需要。
|
|
19
|
-
* 文件按 worker 键控(`config-lkg-<worker|global>.json`),同宿主多 worker 不互踩。
|
|
20
|
-
*/
|
|
21
1
|
import { promises as fs } from "node:fs";
|
|
22
2
|
import { homedir } from "node:os";
|
|
23
3
|
import { join, dirname } from "node:path";
|
|
24
4
|
import { randomBytes } from "node:crypto";
|
|
25
5
|
import { validatePromptsDomain } from "./prompts-domain-validate.js";
|
|
26
|
-
/** skill 正文的 content-addressed 盘缓存目录(LKG 的姊妹面):LKG 只含 skills MANIFEST(正文按
|
|
27
|
-
* hash 另拉)——黑洞中心下 LKG-boot 会在 skill 正文拉取上再挂数秒(live 实测 +5.3s)。正文按
|
|
28
|
-
* `<dir>/<sha-hex>` 落盘,读侧**必验 hash**(content-addressed 的全部意义;坏文件=忽略重拉)。 */
|
|
29
6
|
export function defaultSkillCacheDir() {
|
|
30
7
|
return join(process.env.SEMA_CONFIG_DIR ?? join(homedir(), ".sema"), "skill-cache");
|
|
31
8
|
}
|
|
32
|
-
/** 默认路径(scheduler store 同门:SEMA_CONFIG_DIR → ~/.sema)。`CONFIG_LKG_PATH` 覆写;
|
|
33
|
-
* `CONFIG_LKG_ENABLED=false` 关闭(读写双关)。🪦 正名前的 `CONFIG_LKG_DISABLED` 自 server 3.0.0 起是
|
|
34
|
-
* **fail-loud 墓碑**——设了(任何值)即拒启并指路新名,不再被读取。 */
|
|
35
9
|
export function defaultLkgPath(worker) {
|
|
36
10
|
const dir = process.env.SEMA_CONFIG_DIR ?? join(homedir(), ".sema");
|
|
37
11
|
const key = (worker ?? "global").replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 64);
|
|
38
12
|
return join(dir, `config-lkg-${key}.json`);
|
|
39
13
|
}
|
|
40
|
-
/** 原子落盘一个干净候选(tmp+rename,0600)。BEST-EFFORT:失败=一条 warn 的素材(调用方发),绝不
|
|
41
|
-
* 影响在飞的应用链——LKG 缓存丢失的代价只是下次冷启动回到 env 兜底。 */
|
|
42
14
|
export async function saveLkg(path, worker, effective, etag) {
|
|
43
15
|
const body = { formatVersion: 1, savedAt: new Date().toISOString(), worker: worker ?? "global", ...(etag !== undefined ? { etag } : {}), effective };
|
|
44
16
|
const tmp = `${path}.${randomBytes(6).toString("hex")}.tmp`;
|
|
@@ -46,10 +18,6 @@ export async function saveLkg(path, worker, effective, etag) {
|
|
|
46
18
|
await fs.writeFile(tmp, JSON.stringify(body), { mode: 0o600 });
|
|
47
19
|
await fs.rename(tmp, path);
|
|
48
20
|
}
|
|
49
|
-
/**
|
|
50
|
-
* 读盘上 LKG。返回 undefined 的所有情形都「安静地回 env 兜底」(调用方 warn 点名 reason):
|
|
51
|
-
* 文件缺席/JSON 坏/信封形不对/worker 不匹配(防同名文件跨 worker 串包)/prompts 面复判失败。
|
|
52
|
-
*/
|
|
53
21
|
export async function loadLkg(path, worker) {
|
|
54
22
|
let text;
|
|
55
23
|
try {
|
|
@@ -76,18 +44,9 @@ export async function loadLkg(path, worker) {
|
|
|
76
44
|
return { reason: "bad effective" };
|
|
77
45
|
if (p.etag !== undefined && typeof p.etag !== "string")
|
|
78
46
|
return { reason: "bad etag" };
|
|
79
|
-
// 浅域判形(LKG 复审 F4):applyEffective 是 in-place mutate——半程炸=混态。载荷信任面与 live 远端
|
|
80
|
-
// 相同(config plane、本机文件=operator 权限),这里防的是**跨版本 schema 漂移/文件被改**的结构性坏形:
|
|
81
|
-
// 各域存在时必须是对象、其主数组存在时必须是数组、version 必须是数;更深的语义仍归 apply 层
|
|
82
|
-
// (live 载荷同待遇——LKG 不引入第二套 schema)。
|
|
83
47
|
const eff = p.effective;
|
|
84
48
|
if (typeof eff.version !== "number")
|
|
85
49
|
return { reason: "bad effective.version" };
|
|
86
|
-
// codex R24(真存量 bug):teams 的真形=信封 `{ teams: CenterTeam[] }`(EffectiveConfig 声明形,
|
|
87
|
-
// applyEffective 消费 `eff.teams?.teams`)——旧判形要求裸数组,任何带 teams 的候选保存成功、下次 boot
|
|
88
|
-
// 被拒("bad effective.teams")→ env 兜底 → 慢中心下 defer 再签,正是交棒门要消的环。teams 改与其他域
|
|
89
|
-
// 同姿势:对象 + 主数组存在时必须是数组。(旧夹具 `teams: []` 曾遮住此形;裸数组从未被真实 save 产出,
|
|
90
|
-
// 拒掉无害:unusable → env 兜底 → 下个干净候选重写。)
|
|
91
50
|
for (const [domain, arrayKey] of [["models", "models"], ["teams", "teams"], ["skills", "skills"], ["mcp", "servers"], ["scenarios", "scenarios"], ["runtime", null]]) {
|
|
92
51
|
const v = eff[domain];
|
|
93
52
|
if (v === undefined)
|
|
@@ -100,7 +59,6 @@ export async function loadLkg(path, worker) {
|
|
|
100
59
|
return { reason: `bad effective.${domain}.${arrayKey}` };
|
|
101
60
|
}
|
|
102
61
|
}
|
|
103
|
-
// prompts 面复判(候选门同函数):坏 prompts 若放行,boot 采用后每个任务 prepare 都会炸。
|
|
104
62
|
const promptsRaw = p.effective.prompts;
|
|
105
63
|
if (promptsRaw !== undefined && !validatePromptsDomain(promptsRaw).ok)
|
|
106
64
|
return { reason: "prompts face fails re-validation" };
|