@sema-agent/server 7.151.0 → 7.152.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/MIGRATION.md +266 -0
- package/README.md +15 -3
- package/README.zh-CN.md +10 -2
- package/USAGE.md +93 -64
- package/deploy/sema-up/README.md +5 -4
- package/deploy/sema-up/cluster-up.sh +6 -6
- package/deploy/sema-up/kube-up.sh +5 -5
- package/deploy/sema-up/lib/flagshape.sh +36 -8
- package/deploy/sema-up/machines.example.yaml +3 -1
- package/deploy/sema-up/sema-up.sh +22 -16
- package/dist/approval-ask-audit-store.d.ts +1 -1
- package/dist/approval-ask-machine.d.ts +4 -10
- package/dist/approval-ask-retention.d.ts +1 -1
- package/dist/approval-card.d.ts +10 -12
- package/dist/approval-gate-currency.d.ts +8 -8
- package/dist/approval-gate-currency.js +3 -4
- package/dist/approval-hmac.d.ts +17 -1
- package/dist/approval-hmac.js +7 -1
- package/dist/approval-queue-kind.d.ts +1 -1
- package/dist/approval.d.ts +2 -2
- package/dist/approval.js +2 -2
- package/dist/bind-host.d.ts +1 -3
- package/dist/bind-host.js +2 -3
- package/dist/boot/coordinators.d.ts +1 -1
- package/dist/boot/coordinators.js +9 -10
- package/dist/boot/device-lane.js +2 -0
- package/dist/boot/execution-env.d.ts +1 -0
- package/dist/boot/execution-env.js +31 -5
- package/dist/boot/leader.d.ts +31 -30
- package/dist/boot/leader.js +38 -28
- package/dist/boot/leg-path-adjudication.d.ts +40 -0
- package/dist/boot/leg-path-adjudication.js +18 -0
- package/dist/boot/parked-revive-gate.js +3 -15
- package/dist/boot/reapers.d.ts +1 -1
- package/dist/boot/reapers.js +1 -1
- package/dist/boot/resolve-spec.js +12 -26
- package/dist/boot/runner-deps.d.ts +39 -35
- package/dist/boot/runner-deps.js +92 -89
- package/dist/boot/shutdown.d.ts +6 -0
- package/dist/boot/shutdown.js +10 -4
- package/dist/boot/stage-02-web-admin-pull.js +4 -5
- package/dist/boot/stage-05-execution-env.js +2 -1
- package/dist/boot/stage-06-runners.d.ts +2 -0
- package/dist/boot/stage-06-runners.js +10 -4
- package/dist/boot/stage-07-capability-layer.d.ts +1 -0
- package/dist/boot/stage-07-capability-layer.js +1 -1
- package/dist/boot/stage-08-reapers.d.ts +1 -0
- package/dist/boot/stage-09-leader.d.ts +1 -0
- package/dist/boot/stage-09-leader.js +2 -2
- package/dist/boot/stage-10-http-server.d.ts +8 -0
- package/dist/boot/stage-10-http-server.js +11 -15
- package/dist/boot/stores.js +7 -4
- package/dist/boot/web-admin.js +2 -7
- package/dist/capabilities/approvals-push.d.ts +3 -3
- package/dist/capabilities/scenarios.d.ts +28 -13
- package/dist/capabilities/scenarios.js +2 -2
- package/dist/capability-facts.d.ts +22 -20
- package/dist/capability-facts.js +18 -34
- package/dist/card-rule-offers.d.ts +32 -0
- package/dist/card-rule-offers.js +130 -0
- package/dist/config-catalog.js +12 -11
- package/dist/config-dir-trust.js +4 -2
- package/dist/config-invariants.d.ts +18 -7
- package/dist/config-invariants.js +51 -8
- package/dist/config-lkg.d.ts +5 -19
- package/dist/config-lkg.js +8 -62
- package/dist/config-provider.d.ts +1 -1
- package/dist/config-root-migration.d.ts +2 -2
- package/dist/config-types.d.ts +28 -38
- package/dist/config.d.ts +7 -5
- package/dist/config.js +55 -16
- package/dist/control-socket.d.ts +51 -0
- package/dist/control-socket.js +115 -0
- package/dist/deployment-governance.d.ts +2 -1
- package/dist/device-ws-hub.d.ts +7 -0
- package/dist/device-ws-hub.js +7 -1
- package/dist/elicitation.js +2 -2
- package/dist/entry-problems.d.ts +40 -3
- package/dist/entry-problems.js +3 -1
- package/dist/execution-env-refusal.d.ts +8 -2
- package/dist/execution-env-refusal.js +3 -0
- package/dist/fleet/engine-signal.d.ts +12 -29
- package/dist/fleet/engine-signal.js +1 -7
- package/dist/fleet-client.js +2 -5
- package/dist/gate-settlement.d.ts +1 -8
- package/dist/gate-settlement.js +0 -4
- package/dist/history-anchor.js +1 -1
- package/dist/hitl-delivery.d.ts +2 -3
- package/dist/hitl-delivery.js +0 -2
- package/dist/hooks/hook-llm.js +4 -2
- package/dist/hooks/hook-runner.d.ts +3 -2
- package/dist/hooks/hook-runner.js +16 -1
- package/dist/host-decision.d.ts +11 -11
- package/dist/http/active-run-conflict.d.ts +35 -2
- package/dist/http/active-run-conflict.js +48 -19
- package/dist/http/approval-sweeps.d.ts +1 -1
- package/dist/http/approval-sweeps.js +5 -2
- package/dist/http/checkpoint-ctx.d.ts +2 -0
- package/dist/http/checkpoint-ctx.js +1 -0
- package/dist/http/dispatch.d.ts +6 -2
- package/dist/http/dispatch.js +25 -5
- package/dist/http/durable-run-leg.js +0 -1
- package/dist/http/health-reader.d.ts +16 -9
- package/dist/http/health-reader.js +2 -1
- package/dist/http/health-self-report.js +1 -1
- package/dist/http/owner-access.d.ts +2 -3
- package/dist/http/owner-access.js +3 -4
- package/dist/http/principal-gate.d.ts +49 -21
- package/dist/http/principal-gate.js +61 -11
- package/dist/http/request-channel.d.ts +23 -0
- package/dist/http/request-channel.js +9 -0
- package/dist/http/request-guards.js +17 -2
- package/dist/http/resume-ctx-read.js +20 -3
- package/dist/http/resume-legs.d.ts +2 -2
- package/dist/http/resume-legs.js +111 -41
- package/dist/http/route-ctx.d.ts +73 -7
- package/dist/http/route-ctx.js +17 -0
- package/dist/http/route-table.d.ts +4 -0
- package/dist/http/route-table.js +4 -0
- package/dist/http/routes/admin-config-refresh.js +3 -3
- package/dist/http/routes/admin-drain.d.ts +3 -0
- package/dist/http/routes/admin-drain.js +7 -7
- package/dist/http/routes/admin-memory-optout.js +3 -3
- package/dist/http/routes/adoption.js +3 -5
- package/dist/http/routes/approvals-assistant.d.ts +2 -1
- package/dist/http/routes/approvals-assistant.js +95 -48
- package/dist/http/routes/capabilities.d.ts +4 -0
- package/dist/http/routes/capabilities.js +30 -5
- package/dist/http/routes/config-catalog.js +2 -2
- package/dist/http/routes/devices.js +1 -1
- package/dist/http/routes/diagnostics.d.ts +3 -3
- package/dist/http/routes/diagnostics.js +19 -7
- package/dist/http/routes/images.js +10 -10
- package/dist/http/routes/memory-bundle.js +3 -3
- package/dist/http/routes/memory-compliance.js +3 -3
- package/dist/http/routes/memory-consolidation.js +2 -2
- package/dist/http/routes/memory-origin.js +3 -3
- package/dist/http/routes/memory-policy.js +2 -2
- package/dist/http/routes/observability.js +3 -3
- package/dist/http/routes/retention-ops.js +3 -3
- package/dist/http/routes/rules.js +5 -5
- package/dist/http/routes/run-permission-mode.d.ts +2 -1
- package/dist/http/routes/run-permission-mode.js +2 -2
- package/dist/http/routes/runs.d.ts +4 -9
- package/dist/http/routes/runs.js +73 -33
- package/dist/http/routes/session-sync.d.ts +18 -6
- package/dist/http/routes/session-sync.js +38 -36
- package/dist/http/routes/sessions.js +20 -9
- package/dist/http/routes/tasks.js +28 -37
- package/dist/http/routes/workflows.js +1 -1
- package/dist/http/server.d.ts +9 -3
- package/dist/http/server.js +15 -3
- package/dist/http/submit-ctx.d.ts +1 -1
- package/dist/http/submit-ctx.js +2 -0
- package/dist/human-facing-args.d.ts +33 -0
- package/dist/human-facing-args.js +33 -0
- package/dist/index.d.ts +1 -1
- package/dist/k8s-api-credentials.d.ts +29 -0
- package/dist/k8s-api-credentials.js +25 -0
- package/dist/lane-home-probe.d.ts +1 -1
- package/dist/lane-home-probe.js +2 -1
- package/dist/leader/diffup.d.ts +19 -0
- package/dist/leader/diffup.js +7 -0
- package/dist/leader/leader.d.ts +114 -10
- package/dist/leader/leader.js +69 -5
- package/dist/leader/repair-wire.js +2 -2
- package/dist/leader/run-entry.d.ts +2 -1
- package/dist/leader/run-entry.js +1 -1
- package/dist/leader/wire.d.ts +42 -132
- package/dist/leader/wire.js +81 -67
- package/dist/memory-scope.d.ts +20 -9
- package/dist/memory-scope.js +15 -3
- package/dist/model-entry-headers.d.ts +9 -10
- package/dist/model-entry-headers.js +0 -4
- package/dist/model-entry-refusal.d.ts +7 -4
- package/dist/model-entry-refusal.js +11 -5
- package/dist/model-plane-face.d.ts +1 -1
- package/dist/model-plane-face.js +3 -1
- package/dist/model-route-endpoint.d.ts +13 -7
- package/dist/model-route-endpoint.js +14 -11
- package/dist/model-select.d.ts +22 -0
- package/dist/model-select.js +9 -0
- package/dist/model-unreachable-notice.d.ts +11 -0
- package/dist/model-unreachable-notice.js +4 -4
- package/dist/object-store-buckets.d.ts +640 -6
- package/dist/object-store-buckets.js +166 -0
- package/dist/observability/fail-open.d.ts +20 -16
- package/dist/observability/fail-open.js +20 -16
- package/dist/observability/logger.js +3 -1
- package/dist/operator-roster.d.ts +68 -0
- package/dist/operator-roster.js +32 -0
- package/dist/orchestration/workflow-park-decision.d.ts +1 -1
- package/dist/outbound-dispatcher.d.ts +71 -8
- package/dist/outbound-dispatcher.js +48 -7
- package/dist/owner-faces.d.ts +1 -1
- package/dist/parked-decide.d.ts +2 -2
- package/dist/permission-mode-admission.d.ts +59 -26
- package/dist/permission-mode-admission.js +37 -10
- package/dist/plugins/approval-ask-store-file.d.ts +4 -0
- package/dist/plugins/approval-ask-store-file.js +3 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +4 -0
- package/dist/plugins/approval-ask-store-memory.js +7 -1
- package/dist/plugins/approval-ask-store-sql.d.ts +26 -2
- package/dist/plugins/approval-ask-store-sql.js +12 -1
- package/dist/plugins/blob-backend.d.ts +83 -7
- package/dist/plugins/blob-backend.js +57 -6
- package/dist/plugins/checkpoint-index-columns.d.ts +20 -16
- package/dist/plugins/checkpoint-index-columns.js +3 -6
- package/dist/plugins/checkpoint-store-face.d.ts +119 -61
- package/dist/plugins/checkpoint-store-face.js +134 -55
- package/dist/plugins/landed-attendants.d.ts +2 -2
- package/dist/plugins/local-checkpoint-store.d.ts +13 -3
- package/dist/plugins/local-checkpoint-store.js +38 -49
- package/dist/plugins/one-time-migrations.d.ts +51 -21
- package/dist/plugins/one-time-migrations.js +67 -45
- package/dist/plugins/pg-pool.js +11 -9
- package/dist/plugins/remote-env-host.d.ts +29 -10
- package/dist/plugins/remote-env-host.js +99 -19
- package/dist/plugins/remote-env-k8s.d.ts +68 -2
- package/dist/plugins/remote-env-k8s.js +107 -24
- package/dist/plugins/replica-registration.d.ts +1 -1
- package/dist/plugins/replica-registration.js +1 -1
- package/dist/plugins/s3-presign.d.ts +23 -0
- package/dist/plugins/s3-presign.js +37 -3
- package/dist/plugins/scheduler-support.d.ts +2 -2
- package/dist/plugins/scheduler-support.js +0 -3
- package/dist/plugins/schema-indexes.js +2 -0
- package/dist/plugins/send-file-ledger.d.ts +2 -2
- package/dist/plugins/send-file-ledger.js +2 -2
- package/dist/plugins/send-user-file.d.ts +3 -4
- package/dist/plugins/send-user-file.js +56 -52
- package/dist/plugins/session-blob-claim-token.d.ts +15 -0
- package/dist/plugins/session-blob-claim-token.js +20 -0
- package/dist/plugins/session-blob-namespace.d.ts +46 -0
- package/dist/plugins/session-blob-namespace.js +116 -0
- package/dist/plugins/snapshot-bucket-presence.d.ts +25 -2
- package/dist/plugins/snapshot-bucket-presence.js +8 -2
- package/dist/plugins/sql-json-column.d.ts +1 -1
- package/dist/plugins/store-backend.d.ts +44 -2
- package/dist/plugins/store-backend.js +75 -53
- package/dist/plugins/task-attachment-store.d.ts +2 -1
- package/dist/plugins/task-attachment-store.js +5 -1
- package/dist/plugins/tidb-pool.js +19 -24
- package/dist/plugins/workspace-custody.d.ts +4 -3
- package/dist/question.d.ts +2 -1
- package/dist/question.js +5 -13
- package/dist/request-form.d.ts +6 -1
- package/dist/request-form.js +4 -1
- package/dist/rule-issue-reading.d.ts +1 -1
- package/dist/rules-consent.d.ts +1 -1
- package/dist/run-local.js +8 -9
- package/dist/runs.d.ts +17 -8
- package/dist/runs.js +15 -2
- package/dist/runtime-governance.d.ts +2 -2
- package/dist/sema-config-dir.d.ts +2 -1
- package/dist/session-sync.d.ts +27 -11
- package/dist/session-sync.js +15 -7
- package/dist/stores-sql/blob-index-sql.d.ts +30 -9
- package/dist/stores-sql/blob-index-sql.js +40 -21
- package/dist/stores-sql/checkpoint-store-sql.d.ts +54 -27
- package/dist/stores-sql/checkpoint-store-sql.js +70 -146
- package/dist/stores-sql/file-history-store-sql.d.ts +64 -16
- package/dist/stores-sql/file-history-store-sql.js +100 -28
- package/dist/stores-sql/retention-store-sql.js +3 -9
- package/dist/task-settings.d.ts +6 -5
- package/dist/task-settings.js +3 -3
- package/dist/tool-approval.d.ts +342 -518
- package/dist/tool-approval.js +498 -1207
- package/dist/trace/core-keyset-guard.d.ts +1 -1
- package/dist/trace/ledger-events.d.ts +3 -3
- package/dist/trace/project.d.ts +2 -3
- package/dist/trace/redact.js +3 -2
- package/dist/trace/self-minted-wire.d.ts +287 -157
- package/dist/trace/self-minted-wire.js +93 -48
- package/dist/web-admin-client/apply-effective.js +2 -2
- package/dist/web-admin-client/read-warnings.d.ts +2 -2
- package/dist/web-admin-client/read-warnings.js +1 -1
- package/dist/web-admin-client/types.d.ts +1 -1
- package/dist/workspace-space.d.ts +10 -4
- package/dist/workspace-space.js +4 -1
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +1 -1
- package/package.json +6 -2
- package/scripts/embed-minimal.mjs +3 -3
- package/wire-changes/server@7.152.0.json +900 -0
- package/dist/config-d-migration.d.ts +0 -37
- package/dist/config-d-migration.js +0 -60
package/dist/boot/device-lane.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createDeviceEnrollment, createFixedWindowRateLimiter } from "../device-enrollment.js";
|
|
2
2
|
import { createDeviceWsHub } from "../device-ws-hub.js";
|
|
3
|
+
import { deviceOwnerConfinedTier } from "../http/principal-gate.js";
|
|
3
4
|
export const DEVICE_ENROLL_RATE_POLICY = { limit: 10, windowMs: 60_000 };
|
|
4
5
|
export function createDeviceLane(ctx) {
|
|
5
6
|
const { config, logger, metrics, backend, replicaId, seat } = ctx;
|
|
@@ -20,6 +21,7 @@ export function createDeviceLane(ctx) {
|
|
|
20
21
|
store,
|
|
21
22
|
enrollment,
|
|
22
23
|
replicaId,
|
|
24
|
+
ownerConfinedTier: (owner) => deviceOwnerConfinedTier(owner, config),
|
|
23
25
|
logger: {
|
|
24
26
|
warn: (msg, fields) => logger.warn(msg, fields),
|
|
25
27
|
error: (msg, fields) => logger.error(msg, fields),
|
|
@@ -184,6 +184,7 @@ export declare function createExecutionEnv(ctx: ExecutionEnvCtx): {
|
|
|
184
184
|
sendUserFileTaskEnvs: TaskEnvRegistry | undefined;
|
|
185
185
|
lspManager: import("@sema-agent/core").NodeLspManager | undefined;
|
|
186
186
|
spaceReclaimer: SpaceReclaimer | undefined;
|
|
187
|
+
sandboxApiCredentialsGate: (() => void) | undefined;
|
|
187
188
|
sandboxSecretSeatGate: (() => Promise<void>) | undefined;
|
|
188
189
|
sandboxReaper: SandboxReaper | undefined;
|
|
189
190
|
sandboxGitPlane: SandboxGitPlaneSeat;
|
|
@@ -12,7 +12,7 @@ import { deviceExecutionEnvFactory } from "../plugins/remote-env-device.js";
|
|
|
12
12
|
import { e2bExecutionEnvFactory } from "../plugins/remote-env-e2b.js";
|
|
13
13
|
import { createWorkspaceFill } from "../plugins/workspace-fill.js";
|
|
14
14
|
import { hostExecutionEnvFactory, RemoteHostExecutionEnv } from "../plugins/remote-env-host.js";
|
|
15
|
-
import { boundedStoreCall, checkSandboxEgressPolicy, createSandboxPodApi, createSpaceReclaimer, k8sExecutionEnvFactory, readSandboxSecretSeat, rpcTimeoutMsOf } from "../plugins/remote-env-k8s.js";
|
|
15
|
+
import { boundedStoreCall, checkSandboxEgressPolicy, createSandboxPodApi, createSpaceReclaimer, k8sExecutionEnvFactory, readSandboxApiCredentials, readSandboxSecretSeat, rpcTimeoutMsOf, sandboxApiCredentialFact } from "../plugins/remote-env-k8s.js";
|
|
16
16
|
import { podSpecPatchBrokerRefusal, SANDBOX_SECRET_SEAT_VERBS } from "../plugins/k8s-exec-protocol.js";
|
|
17
17
|
import { createSandboxBrokerSeatOf, createSandboxGitPlaneSeat, sandboxGitPlanePremisesOf } from "../sandbox-git-plane.js";
|
|
18
18
|
import { createSandboxPodReaper } from "../plugins/k8s-pod-reaper.js";
|
|
@@ -38,6 +38,8 @@ import { isCodeToken } from "../terminal.js";
|
|
|
38
38
|
import { createCacheMissMemory } from "../workspace-cache.js";
|
|
39
39
|
import { workspaceSnapshotTimeoutOf } from "../workspace-space.js";
|
|
40
40
|
import { sayEgressPolicyReading } from "../sandbox-egress-policy.js";
|
|
41
|
+
import { K8sApiCredentialsMissingError, k8sCredentialsMissingBootSentence, k8sCredentialsMissingRunSentence, k8sCredentialsRejectedRunSentence } from "../k8s-api-credentials.js";
|
|
42
|
+
import { executionEnvRefusalError } from "../execution-env-refusal.js";
|
|
41
43
|
import { sandboxPathEnvSlots, withSandboxPathEnvSlot } from "./deferred-sandbox-path-env.js";
|
|
42
44
|
import { createLaneHomeAbsenceReporter } from "../lane-home-probe.js";
|
|
43
45
|
import { buildLaneHomeBootNote, buildLaneHomeClaim, capabilitiesFor, laneIsolated, laneSandboxReadFloorState } from "../execution-lane-caps.js";
|
|
@@ -169,6 +171,22 @@ export function ownsItsWorkspace(ctx) {
|
|
|
169
171
|
export function createK8sRunEnvFactory(k8sCfg, seats, deps) {
|
|
170
172
|
const cacheMisses = createCacheMissMemory();
|
|
171
173
|
return async (ctx) => {
|
|
174
|
+
const credentials = await readSandboxApiCredentials(k8sCfg, deps);
|
|
175
|
+
switch (credentials.state) {
|
|
176
|
+
case "missing":
|
|
177
|
+
throw executionEnvRefusalError("env.sandbox_refused", k8sCredentialsMissingRunSentence(credentials.fact));
|
|
178
|
+
case "rejected":
|
|
179
|
+
throw executionEnvRefusalError("env.sandbox_refused", k8sCredentialsRejectedRunSentence());
|
|
180
|
+
case "accepted":
|
|
181
|
+
break;
|
|
182
|
+
case "unproven":
|
|
183
|
+
recordFailOpen("server.sandbox-credentials.probe-unanswered", `namespace=${k8sCfg.namespace} ${credentials.detail}`);
|
|
184
|
+
break;
|
|
185
|
+
default: {
|
|
186
|
+
const unreachable = credentials;
|
|
187
|
+
throw new Error(`unhandled api credential reading: ${String(unreachable.state)}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
172
190
|
const onWorkspaceSnapshot = createRunWorkspaceSnapshotSeat(ctx.sessionId);
|
|
173
191
|
const child = !ownsItsWorkspace(ctx);
|
|
174
192
|
const s3Snapshot = child && k8sCfg.s3Snapshot !== undefined ? spaceOff(k8sCfg.s3Snapshot) : k8sCfg.s3Snapshot;
|
|
@@ -295,6 +313,7 @@ export function createExecutionEnv(ctx) {
|
|
|
295
313
|
let executionEnvFactory;
|
|
296
314
|
let spaceReclaimer;
|
|
297
315
|
let sandboxSecretSeatGate;
|
|
316
|
+
let sandboxApiCredentialsGate;
|
|
298
317
|
let sandboxReaper;
|
|
299
318
|
let sandboxCapacity;
|
|
300
319
|
let sandboxGitPlane = createSandboxGitPlaneSeat((r) => sandboxGitPlanePremisesOf(config, r), {});
|
|
@@ -355,9 +374,16 @@ export function createExecutionEnv(ctx) {
|
|
|
355
374
|
spaceReclaimer = createSpaceReclaimer(k8sCfg);
|
|
356
375
|
sandboxSecretSeatGate = () => ensureSandboxSecretSeat(k8sCfg, logger, undefined, () => readSandboxSecretSeat(k8sCfg, "env"));
|
|
357
376
|
sandboxReaper = createSandboxPodReaper({ api: createSandboxPodApi(k8sCfg), logger, metrics });
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
377
|
+
const missingApiCredential = sandboxApiCredentialFact(k8sCfg, ctx.laneDeps?.k8s);
|
|
378
|
+
sandboxApiCredentialsGate = () => {
|
|
379
|
+
if (missingApiCredential !== undefined)
|
|
380
|
+
throw new K8sApiCredentialsMissingError(k8sCredentialsMissingBootSentence(missingApiCredential));
|
|
381
|
+
};
|
|
382
|
+
if (missingApiCredential === undefined) {
|
|
383
|
+
void checkSandboxEgressPolicy(k8sCfg, ctx.laneDeps?.k8s)
|
|
384
|
+
.then((gaps) => sayEgressPolicyReading(gaps, logger, k8sCfg.namespace))
|
|
385
|
+
.catch((e) => logger.error("sandbox_egress_policy_check_crashed", { error: seatText("operator-log-text", e instanceof Error ? e.message : String(e)), effect: "the sandbox egress self-check did not complete this boot — the lane runs; the egress policy was not read" }));
|
|
386
|
+
}
|
|
361
387
|
}
|
|
362
388
|
else if (config.remoteExec.provider === "ssh") {
|
|
363
389
|
executionEnvFactory = sshExecutionEnvFactory({
|
|
@@ -575,7 +601,7 @@ export function createExecutionEnv(ctx) {
|
|
|
575
601
|
dropSessionScoped: sessionScoped.dropSession,
|
|
576
602
|
dropSessionScopedOf: sessionScoped.dropSessionOf,
|
|
577
603
|
sessionResidentBackground,
|
|
578
|
-
executionEnvFactory, worktreeReap, sendUserFileTaskEnvs, lspManager, spaceReclaimer, sandboxSecretSeatGate, sandboxReaper, sandboxGitPlane,
|
|
604
|
+
executionEnvFactory, worktreeReap, sendUserFileTaskEnvs, lspManager, spaceReclaimer, sandboxApiCredentialsGate, sandboxSecretSeatGate, sandboxReaper, sandboxGitPlane,
|
|
579
605
|
sandboxCapacity,
|
|
580
606
|
};
|
|
581
607
|
}
|
package/dist/boot/leader.d.ts
CHANGED
|
@@ -7,51 +7,35 @@
|
|
|
7
7
|
* (工厂模式的 k8s/host 腿以 `executionEnvFactory` 在场为门;durable 子 worker 挂 `checkpointStore`)。
|
|
8
8
|
*/
|
|
9
9
|
import type { ServiceConfig } from "../config.js";
|
|
10
|
-
import type { createBrain } from "../brain.js";
|
|
11
|
-
import type { buildPricing } from "../budget.js";
|
|
12
10
|
import { createLeaderEndpoint } from "../leader/endpoint.js";
|
|
11
|
+
import type { LeaderBirth } from "../leader/leader.js";
|
|
13
12
|
import type { Logger } from "../observability/logger.js";
|
|
14
13
|
import type { CheckpointStoreFull, StoreBackend, ToolResultStoreFull } from "../plugins/store-backend.js";
|
|
15
14
|
import type { createSessionStore } from "../plugins/session-store.js";
|
|
16
|
-
import type
|
|
15
|
+
import { type RunnerDeps } from "@sema-agent/core";
|
|
16
|
+
import type { DeploymentPlaneDeps, DeploymentPlaneLane } from "./runner-deps.js";
|
|
17
|
+
import { type KeyResolver } from "./side-query-lane.js";
|
|
17
18
|
export interface LeaderCtx {
|
|
18
19
|
config: ServiceConfig;
|
|
19
20
|
logger: Logger;
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
/** 本部署的本地店根(`config.localDataRoot` 缺席时的回落;host 车道上守卫裁决的哨兵目录挂在它下面,与 HTTP 腿同一处)。 */
|
|
22
|
+
localRoot: string;
|
|
22
23
|
executionEnvFactory: RunnerDeps["executionEnvFactory"];
|
|
23
24
|
toolResultStore: ToolResultStoreFull | undefined;
|
|
24
25
|
sessionStore: ReturnType<typeof createSessionStore>;
|
|
25
26
|
checkpointStore: CheckpointStoreFull | undefined;
|
|
26
27
|
/** [ref]:停驻行凭据的封签器(与 HTTP 面 `ServiceDeps.checkpointCtxSealing` 同一只;起服第 6 段已对「店在、它不在」拒启)。必填键,值可为 undefined。 */
|
|
27
28
|
checkpointCtxSealing: import("../http/checkpoint-ctx.js").CheckpointCtxSealing | undefined;
|
|
28
|
-
/** [ref] 件B/C/D([ref],codex R2-F1):部署治理三座席 —— leader 车道的每一只 Runner 都要带上它,
|
|
29
|
-
* 否则开了 leader 端点的部署就有一条在部署治理**之外**的执行面(合规否决/锁都够不着)。 */
|
|
30
|
-
governanceSeams: import("./governance-seams.js").GovernanceSeams;
|
|
31
29
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* 而 `POST /v1/leader` 的准入处**读**的正是它填的那两个桶 —— 缺席 = 门查的桶与花钱的手不是同一只。
|
|
35
|
-
*
|
|
36
|
-
* 🔴 **必填键**(值可为 undefined):这三席都刻意写成非可选,让「main.ts 铸了却忘了递」变成**编译红**
|
|
37
|
-
* 而不是又一次静默漏配([ref]§三族A 病族的机器护栏)。
|
|
30
|
+
* **部署平面层**的铸口(起服第 6 段交出:与主 / 子 Runner 的平面层同一组输入 —— config / brain / 计量 / 治理座 / 按主体准入 resolver / 豁免根 /
|
|
31
|
+
* 错误汇 …)。leader 车道的每一只 Runner 展开它的产物,零逐席搬运;新的部署键进平面层即到达 leader。
|
|
38
32
|
*/
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
*
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
* 且 core 顶层新任务是**静默**落回(无 notice)。必填键,理由同 `tracer`。 */
|
|
46
|
-
promptSource: RunnerDeps["promptSource"];
|
|
47
|
-
/** [ref] 披露半件([ref],codex r2-[high])—— 主车道 `createRunnerDeps` 铸的**同一只**错误汇
|
|
48
|
-
* (main.ts 直引 `runnerDeps.onError`,与 subRunner 的先例同姿势)。core 对治理窗**最终扣账失败**的
|
|
49
|
-
* 唯一出口就是它(`runtask.js` 的 `catch (flushErr) => deps.onError`),所以它与 `usageWindowStore`
|
|
50
|
-
* 席同生共死:挂窗不挂汇 = 治理轴上的静默失败面([ref])。必填键,理由同 `tracer`。 */
|
|
51
|
-
onError: RunnerDeps["onError"];
|
|
52
|
-
/** 部署座 —— 起服第 5 段装配口 `assembleDeploymentMods` 交回的**同一份**(主车道各 Runner 经共享基座吃的就是它)。leader 车道的每一只 Runner 都要带:
|
|
53
|
-
* 缺席 = 运维声明的 mod 包管不到 leader 派出的腿。必填键,理由同 `tracer`。 */
|
|
54
|
-
deploymentMods: NonNullable<RunnerDeps["mods"]>;
|
|
33
|
+
deploymentPlane: (lane: DeploymentPlaneLane) => DeploymentPlaneDeps;
|
|
34
|
+
/**
|
|
35
|
+
* per-model 凭据的**活引用**(`webAdmin.getKeyResolver`:中心每次 apply 整只重铸 resolver ⇒ 递取值口,不递当时的值;任务面 / side-query / WebFetch 摘要面同一只)。
|
|
36
|
+
* leader 车道每条腿的凭据席由它铸({@link createLeaderLegAuth})。必填:没有 web-admin 的装配递 `() => undefined`(每只模型落网关钥,与不配 per-model 钥匙同)。
|
|
37
|
+
*/
|
|
38
|
+
getKeyResolver: () => KeyResolver;
|
|
55
39
|
/** [ref] 车4 件1:durable store 后端(leader-run 登记表从这里取)。缺席 = 无 DB 的 env-only worker。 */
|
|
56
40
|
backend?: StoreBackend | undefined;
|
|
57
41
|
}
|
|
@@ -96,5 +80,22 @@ export declare function buildLeaderObjectStore(env?: NodeJS.ProcessEnv): {
|
|
|
96
80
|
region?: string;
|
|
97
81
|
};
|
|
98
82
|
};
|
|
83
|
+
/**
|
|
84
|
+
* leader 车道每条腿**出生时**的两只部署席({@link LeaderBirth} 的 `govern` / `baseline`)—— 装配处闭包活 config 的唯一铸点:起服 {@link createLeaderFace} 递进
|
|
85
|
+
* wire,部署治理矩阵(`test/operator-knob-client-posture-matrix.test.ts`)的 leader 腿经同一只函数装,矩阵测的就是起服装上的那一份。
|
|
86
|
+
* · `govern` —— 部署治理折叠(R3):每条腿出生时调一次,读活 config(热改的 AUTONOMY / commandPolicy / 守卫集对之后出生的腿生效;续跑腿经 resolveSpec 同读)。
|
|
87
|
+
* 腿的路径裁决经唯一属主:沙盒车道按跑这条腿的会话号取真身;host 车道的 worker 落在工厂才铸的目录里、spec 期不可知 ⇒ 永不创建的哨兵目录
|
|
88
|
+
* (与 HTTP 腿未登记工作目录的形同一处,相对目标解析不出真身 ⇒ 守卫的 fail-closed 臂)。
|
|
89
|
+
* · `baseline` —— 审批基线的两臂(🔴 **函数席**,[ref] 波 2 ①:审批名单会被中心热换 ⇒ 每条腿出生时现调唯一属主 `createApprovalBaselinePolicy`,与 HTTP 腿 /
|
|
90
|
+
* 停驻赎回腿同一只构造):耐久腿取 durable 臂(问答门 + 三张名单),不耐久腿取 live 臂(三张名单,问由无人值守层服 —— 答不了即拒)。
|
|
91
|
+
*/
|
|
92
|
+
export declare function createLeaderLegGovernance(config: ServiceConfig, localRoot: string, logger: Logger): Pick<LeaderBirth, "govern" | "baseline">;
|
|
93
|
+
/**
|
|
94
|
+
* leader 车道每条腿的 **per-model 凭据席**({@link LeaderBirth} 的 `auth`)—— 唯一铸点;席本身是 side-query / WebFetch 摘要面同一只工厂 `createPerModelAuthSeat`
|
|
95
|
+
* (spec 自带的席折成 `upstream`:先问它、它没意见才按模型解;不覆盖。声明了钥匙却拿不到 ⇒ 抛,与任务面同一只毒丸,绝不回落网关钥)。
|
|
96
|
+
* 律(与任务面 `resolveSpec` 同读法:spec 出生那一拍从活引用取值):resolver 在**腿出生那一拍**取一次 —— 那一拍与这条腿的 Runner 构造同一同步段,Runner 构造时把模型目录
|
|
97
|
+
* 收成私有副本,所以钥与目录同代;中心 apply 之后出生的腿取到新的那一只。不按调用现取:在飞腿的目录是上一代,现取会把新家的钥配上旧家的端点。
|
|
98
|
+
*/
|
|
99
|
+
export declare function createLeaderLegAuth(getKeyResolver: () => KeyResolver): LeaderBirth["auth"];
|
|
99
100
|
export declare function createLeaderFace(ctx: LeaderCtx): ReturnType<typeof createLeaderEndpoint> | undefined;
|
|
100
101
|
//# sourceMappingURL=leader.d.ts.map
|
package/dist/boot/leader.js
CHANGED
|
@@ -2,9 +2,15 @@ import { createLeaderEndpoint } from "../leader/endpoint.js";
|
|
|
2
2
|
import { seesRunAs } from "../http/owner-access.js";
|
|
3
3
|
import { claimCallerOf } from "../security.js";
|
|
4
4
|
import { createLeaderParkExpirer, createLeaderRunner } from "../leader/wire.js";
|
|
5
|
-
import {
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { NodeExecutionEnv } from "@sema-agent/core";
|
|
7
|
+
import { applyRuntimeGovernance } from "../runtime-governance.js";
|
|
8
|
+
import { createApprovalBaselinePolicy, createDeploymentGovernanceInputs } from "../deployment-governance.js";
|
|
6
9
|
import { hasApprovalListIntent } from "../approval.js";
|
|
10
|
+
import { createLegPathAdjudication } from "./leg-path-adjudication.js";
|
|
11
|
+
import { sandboxPathEnvSlots } from "./deferred-sandbox-path-env.js";
|
|
7
12
|
import { WORKSPACE_SNAPSHOT_BUCKET_DEFAULT } from "../object-store-buckets.js";
|
|
13
|
+
import { createPerModelAuthSeat } from "./side-query-lane.js";
|
|
8
14
|
export function leaderK8sMinioGap(leaderEnabled, leaderProvider, hasS3) {
|
|
9
15
|
if (!(leaderEnabled && leaderProvider === "k8s" && !hasS3))
|
|
10
16
|
return null;
|
|
@@ -31,8 +37,27 @@ export function buildLeaderObjectStore(env = process.env) {
|
|
|
31
37
|
return {};
|
|
32
38
|
return { s3: { endpoint, bucket: env.MINIO_BUCKET ?? WORKSPACE_SNAPSHOT_BUCKET_DEFAULT, accessKey, secretKey, ...(env.MINIO_REGION ? { region: env.MINIO_REGION } : {}) } };
|
|
33
39
|
}
|
|
40
|
+
export function createLeaderLegGovernance(config, localRoot, logger) {
|
|
41
|
+
return {
|
|
42
|
+
govern: (spec, legSessionId) => applyRuntimeGovernance(spec, createDeploymentGovernanceInputs(config, createLegPathAdjudication(config, {
|
|
43
|
+
sessionId: legSessionId,
|
|
44
|
+
leg: "leader",
|
|
45
|
+
hostArm: () => {
|
|
46
|
+
const gateCwd = join(config.localDataRoot ?? localRoot, "fs-write-gate-unrooted");
|
|
47
|
+
return { env: new NodeExecutionEnv({ cwd: gateCwd }), cwd: gateCwd };
|
|
48
|
+
},
|
|
49
|
+
}, { slots: sandboxPathEnvSlots, logger }))),
|
|
50
|
+
baseline: (kind) => createApprovalBaselinePolicy(config, { kind }),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export function createLeaderLegAuth(getKeyResolver) {
|
|
54
|
+
return (upstream) => {
|
|
55
|
+
const resolver = getKeyResolver();
|
|
56
|
+
return createPerModelAuthSeat(() => resolver, upstream !== undefined ? { upstream } : {});
|
|
57
|
+
};
|
|
58
|
+
}
|
|
34
59
|
export function createLeaderFace(ctx) {
|
|
35
|
-
const { config, logger,
|
|
60
|
+
const { config, logger, executionEnvFactory, toolResultStore, sessionStore, checkpointStore, checkpointCtxSealing } = ctx;
|
|
36
61
|
const leaderMinio = buildLeaderObjectStore();
|
|
37
62
|
const leaderProvider = config.remoteExec.provider;
|
|
38
63
|
const leaderRunStore = ctx.backend?.leaderRun?.();
|
|
@@ -45,22 +70,12 @@ export function createLeaderFace(ctx) {
|
|
|
45
70
|
(leaderProvider === "k8s" && "s3" in leaderMinio && executionEnvFactory) ||
|
|
46
71
|
(leaderProvider === "host" && executionEnvFactory))
|
|
47
72
|
? createLeaderEndpoint(createLeaderRunner({
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
},
|
|
54
|
-
deploymentPosture: () => buildDeploymentPostureSeats(config),
|
|
73
|
+
logger,
|
|
74
|
+
plane: () => ctx.deploymentPlane({ commitCoAuthor: undefined }),
|
|
75
|
+
...createLeaderLegGovernance(config, ctx.localRoot, logger),
|
|
76
|
+
auth: createLeaderLegAuth(ctx.getKeyResolver),
|
|
77
|
+
legSlots: sandboxPathEnvSlots,
|
|
55
78
|
autonomy: () => config.autonomy,
|
|
56
|
-
...(ctx.tracer ? { tracer: ctx.tracer } : {}),
|
|
57
|
-
...(config.usageWindows && ctx.usageWindowStore
|
|
58
|
-
? { usageWindows: config.usageWindows, usageWindowStore: ctx.usageWindowStore }
|
|
59
|
-
: {}),
|
|
60
|
-
...(ctx.promptSource ? { promptSource: ctx.promptSource } : {}),
|
|
61
|
-
...(ctx.onError ? { onError: ctx.onError } : {}),
|
|
62
|
-
mods: ctx.deploymentMods,
|
|
63
|
-
hands: buildHandsBand(config, undefined),
|
|
64
79
|
fanoutEnabled: config.leaderFanoutEnabled,
|
|
65
80
|
...(process.env.MODEL_ROUTER_ID ? { routerModel: process.env.MODEL_ROUTER_ID } : {}),
|
|
66
81
|
...(leaderProvider === "e2b" ? { e2bApiKey: config.remoteExec.apiKey } : {}),
|
|
@@ -69,19 +84,14 @@ export function createLeaderFace(ctx) {
|
|
|
69
84
|
...(toolResultStore ? { toolResultStore } : {}),
|
|
70
85
|
...(leaderProvider === "k8s" ? { workspace: "/workspace" } : {}),
|
|
71
86
|
sessionStore,
|
|
87
|
+
approvalListInForce: () => hasApprovalListIntent(config),
|
|
72
88
|
...(checkpointStore
|
|
73
89
|
? {
|
|
74
|
-
durable: () =>
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
deny: config.approvalDeny,
|
|
80
|
-
autoBudget: config.approvalAutoBudget,
|
|
81
|
-
neverAuto: config.approvalNeverAuto,
|
|
82
|
-
...(config.approvalTimeoutSec > 0 ? { ttlMs: config.approvalTimeoutSec * 1000 } : {}),
|
|
83
|
-
}
|
|
84
|
-
: undefined,
|
|
90
|
+
durable: () => ({
|
|
91
|
+
checkpointStore,
|
|
92
|
+
ctxSealer: checkpointCtxSealing,
|
|
93
|
+
...(config.approvalTimeoutSec > 0 ? { ttlMs: config.approvalTimeoutSec * 1000 } : {}),
|
|
94
|
+
}),
|
|
85
95
|
}
|
|
86
96
|
: {}),
|
|
87
97
|
}), {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **腿的路径裁决**(守卫集 / 写门裁决用的 env 与基准)—— 唯一属主。读者三处同调:HTTP 提交 / 续跑腿(`boot/resolve-spec.ts`)、停驻赎回腿
|
|
3
|
+
* (`boot/parked-revive-gate.ts`)、leader 车道的每一条腿(`boot/leader.ts` 交给出生口的治理折叠)。
|
|
4
|
+
*
|
|
5
|
+
* 律(一句):车道分形只问部署形表一行(`sandboxPathAdjudication`)—— 文件平面不在本进程盘上 ⇒ 代理({@link DeferredSandboxPathEnv})在裁决时刻按
|
|
6
|
+
* **这条腿的会话号**去槽表取那台真沙盒;在本进程盘上 ⇒ 腿自己给的 host 臂(真 fs;cwd / 真任务根是腿的知识,本口不猜)。两臂同带执行环境的 home
|
|
7
|
+
* (`~/` 形规则的基,唯一属主 `executionLaneHomeDir`)。
|
|
8
|
+
*
|
|
9
|
+
* 槽表的写者:部署工厂的装饰器(`withSandboxPathEnvSlot`,工厂铸的 env 按 `ctx.sessionId` 登记),以及**手里握着 env 的腿**自己登记 —— leader 的静态
|
|
10
|
+
* 车道 worker 与集成修复腿的 env 不是部署工厂为那条腿铸的,由 leader 按跑它的会话号登记进**同一只**槽表(`leader/wire.ts`)。没登记 ⇒ 代理取不到真身
|
|
11
|
+
* ⇒ 守卫 fail-closed(每一次结构化写被拒)—— 方向对,但那是把腿瘫痪,所以登记是承重的。
|
|
12
|
+
*/
|
|
13
|
+
import type { ServiceConfig } from "../config.js";
|
|
14
|
+
import type { PathAdjudication } from "../deployment-governance.js";
|
|
15
|
+
import { DEPLOYMENT_SHAPE_PREDICATES } from "../deployment-shape.js";
|
|
16
|
+
import { executionLaneHomeDir } from "../execution-lane-caps.js";
|
|
17
|
+
import { type SandboxPathEnvSlots } from "./deferred-sandbox-path-env.js";
|
|
18
|
+
/** 本口读的部署切面:车道形(部署形表行的输入)+ 执行环境 home 的运维声明。 */
|
|
19
|
+
export type LegPathAdjudicationConfigView = Parameters<typeof DEPLOYMENT_SHAPE_PREDICATES.sandboxPathAdjudication.holds>[0] & Parameters<typeof executionLaneHomeDir>[0] & {
|
|
20
|
+
readonly remoteExec: ServiceConfig["remoteExec"];
|
|
21
|
+
};
|
|
22
|
+
/** 一条腿在裁决面上的位置。 */
|
|
23
|
+
export interface LegPathPlace {
|
|
24
|
+
/** 槽表的键 = 跑这条腿的会话号(沙盒车道上代理按它取真身)。缺席 ⇒ 代理恒 fail-closed。 */
|
|
25
|
+
readonly sessionId: string | undefined;
|
|
26
|
+
/** 槽取不到时那一行 warn 上的腿名(缺席 = 不写这个字段)。 */
|
|
27
|
+
readonly leg?: string;
|
|
28
|
+
/** host 语义车道上这条腿的真 fs 裁决(env + 相对基准 + 真任务根):每条腿各有自己的 cwd 知识,本口只在 host 车道调它。 */
|
|
29
|
+
readonly hostArm: () => Pick<PathAdjudication, "env" | "cwd" | "taskRoot">;
|
|
30
|
+
}
|
|
31
|
+
/** 代理的两件(进程内唯一的槽表 + 留痕面)。 */
|
|
32
|
+
export interface LegPathSeat {
|
|
33
|
+
readonly slots: SandboxPathEnvSlots;
|
|
34
|
+
readonly logger: {
|
|
35
|
+
warn: (event: string, fields?: Record<string, unknown>) => void;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** 活对象(代理 env)⇒ `create*`。每次调用现铸(读活 config;代理每实例留痕一次)。 */
|
|
39
|
+
export declare function createLegPathAdjudication(config: LegPathAdjudicationConfigView, place: LegPathPlace, seat: LegPathSeat): PathAdjudication;
|
|
40
|
+
//# sourceMappingURL=leg-path-adjudication.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { DEPLOYMENT_SHAPE_PREDICATES } from "../deployment-shape.js";
|
|
2
|
+
import { executionLaneHomeDir } from "../execution-lane-caps.js";
|
|
3
|
+
import { DeferredSandboxPathEnv } from "./deferred-sandbox-path-env.js";
|
|
4
|
+
export function createLegPathAdjudication(config, place, seat) {
|
|
5
|
+
const home = executionLaneHomeDir(config);
|
|
6
|
+
const homeBase = home !== undefined ? { taskHome: home } : {};
|
|
7
|
+
if (!DEPLOYMENT_SHAPE_PREDICATES.sandboxPathAdjudication.holds(config))
|
|
8
|
+
return { ...place.hostArm(), ...homeBase };
|
|
9
|
+
return {
|
|
10
|
+
env: new DeferredSandboxPathEnv({
|
|
11
|
+
sessionId: place.sessionId,
|
|
12
|
+
slots: seat.slots,
|
|
13
|
+
logger: { warn: (event, fields) => seat.logger.warn(event, { ...(fields ?? {}), lane: config.remoteExec.provider, ...(place.leg !== undefined ? { leg: place.leg } : {}) }) },
|
|
14
|
+
}),
|
|
15
|
+
...homeBase,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=leg-path-adjudication.js.map
|
|
@@ -4,9 +4,8 @@ import { recordFailOpen } from "../observability/fail-open.js";
|
|
|
4
4
|
import { decodeCheckpointScope } from "../security.js";
|
|
5
5
|
import { createApprovalBaselinePolicy, createDeploymentGovernanceInputs, } from "../deployment-governance.js";
|
|
6
6
|
import { applyRuntimeGovernance } from "../runtime-governance.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { executionLaneHomeDir } from "../execution-lane-caps.js";
|
|
7
|
+
import { sandboxPathEnvSlots } from "./deferred-sandbox-path-env.js";
|
|
8
|
+
import { createLegPathAdjudication } from "./leg-path-adjudication.js";
|
|
10
9
|
import { createSessionExemptionRead } from "../approval.js";
|
|
11
10
|
import { seatText } from "../trace/exit-seat.js";
|
|
12
11
|
function hostGateCwd(config, localRoot) {
|
|
@@ -17,18 +16,7 @@ export function createParkedReviveInheritedGate(deps) {
|
|
|
17
16
|
const slots = deps.slots ?? sandboxPathEnvSlots;
|
|
18
17
|
return async (row, identity) => {
|
|
19
18
|
const metadata = await chainMetadataOf(deps, row, identity);
|
|
20
|
-
const
|
|
21
|
-
const homeBase = laneHome !== undefined ? { taskHome: laneHome } : {};
|
|
22
|
-
const pathAdjudication = DEPLOYMENT_SHAPE_PREDICATES.sandboxPathAdjudication.holds(config)
|
|
23
|
-
? {
|
|
24
|
-
env: new DeferredSandboxPathEnv({
|
|
25
|
-
sessionId: row.sessionId,
|
|
26
|
-
slots,
|
|
27
|
-
logger: { warn: (event, fields) => logger.warn(event, { ...(fields ?? {}), lane: config.remoteExec.provider, leg: "parked_revive" }) },
|
|
28
|
-
}),
|
|
29
|
-
...homeBase,
|
|
30
|
-
}
|
|
31
|
-
: { env: createHostAdjudicationEnv(config, localRoot), ...homeBase };
|
|
19
|
+
const pathAdjudication = createLegPathAdjudication(config, { sessionId: row.sessionId, leg: "parked_revive", hostArm: () => ({ env: createHostAdjudicationEnv(config, localRoot) }) }, { slots, logger });
|
|
32
20
|
const rootSessionId = row.rootSessionId;
|
|
33
21
|
const baseline = createApprovalBaselinePolicy(config, {
|
|
34
22
|
kind: "durable",
|
package/dist/boot/reapers.d.ts
CHANGED
|
@@ -194,7 +194,7 @@ interface ReapSessionsInputs {
|
|
|
194
194
|
* 抛的后果是安全的:core 两条臂的 release 都在 try 内、且计数在 release **之后**递增 ⇒ 抛 = 不计数
|
|
195
195
|
* + 下一 tick 重新枚举重试(失败经济学与 core 的成文一致:泄漏给下一轮,而不是谎报已清)。
|
|
196
196
|
*
|
|
197
|
-
*
|
|
197
|
+
* 抽成具名工厂而不是就地闭包的理由与当年 leader 通告席那只判据函数逐字同一条:判据在**值**上
|
|
198
198
|
* (哪只店被调),源码文本门看不出对错,只有行为测得出来。
|
|
199
199
|
*/
|
|
200
200
|
export declare function createPlacementAwareReapSessions(deps: ReapSessionsInputs): ReapSessionsFacade;
|
package/dist/boot/reapers.js
CHANGED
|
@@ -165,7 +165,7 @@ export function startReapers(ctx) {
|
|
|
165
165
|
let permissionRuleReapInFlight = false;
|
|
166
166
|
let approvalReconcileInFlight = false;
|
|
167
167
|
const approvalReconciler = (() => {
|
|
168
|
-
if (!
|
|
168
|
+
if (!backend)
|
|
169
169
|
return undefined;
|
|
170
170
|
return createApprovalReconciler({
|
|
171
171
|
askStore: backend.approvalAsk(),
|
|
@@ -60,8 +60,9 @@ import { inertToolListEntryOf } from "../rule-entry-list.js";
|
|
|
60
60
|
import { assertAttachmentIdsShape, assertImageProfileLane, assertInheritEnvServed, assertToolListsReadable, assertWriteFaceAdmitted, bodyImageProfileOf, scenarioRequestedBy } from "../request-form.js";
|
|
61
61
|
import { TENANCY_WALL_CLOCK_BASE_SEC, enableForkFromBody, normalizeRetainSubagentSessions, selfOrchestrationFromBody } from "../task-workflow.js";
|
|
62
62
|
import { redactSecrets } from "../trace/redact.js";
|
|
63
|
-
import {
|
|
64
|
-
import {
|
|
63
|
+
import { sandboxPathEnvSlots } from "./deferred-sandbox-path-env.js";
|
|
64
|
+
import { createLegPathAdjudication } from "./leg-path-adjudication.js";
|
|
65
|
+
import { executionLaneOf, laneIsolated, laneWiresExecutionEnv } from "../execution-lane-caps.js";
|
|
65
66
|
import { executionConfigRootOf } from "../execution-config-root.js";
|
|
66
67
|
import { withUnattendedLeg } from "../unattended-permission-mode.js";
|
|
67
68
|
import { bindingsOf } from "../session-bindings.js";
|
|
@@ -255,7 +256,7 @@ export function createResolveSpec(ctx) {
|
|
|
255
256
|
const gateScenarioAndAppend = async (body, auth, root, opts, settingsEnv) => {
|
|
256
257
|
const { parts: subjectParts, person } = identityAxesOf(auth);
|
|
257
258
|
const requestedScenario = scenarioRequestedBy(subjectParts?.scenario, body.scenario);
|
|
258
|
-
const scenarioName = await scenarioGate(person, requestedScenario);
|
|
259
|
+
const { scenario: scenarioName } = await scenarioGate(person, requestedScenario);
|
|
259
260
|
const rootFace = sessionHeadOf === undefined ? undefined : sessionFaceOfHead(root, await sessionHeadOf(root));
|
|
260
261
|
const sessionWitness = { sessionId: root, owner: rootFace?.owner ?? null, incarnation: rootFace?.incarnation ?? null };
|
|
261
262
|
const cap = selectScenario(scenarios, scenarioName)(body, person, { sessionId: sessionWitness.sessionId, delegationRoot: sessionWitness });
|
|
@@ -480,7 +481,7 @@ export function createResolveSpec(ctx) {
|
|
|
480
481
|
...(declaresInteractivePosture(body, req, opts) ? { interactionPosture: "interactive" } : {}),
|
|
481
482
|
...(body.approvalReach !== undefined ? { approvalReach: body.approvalReach } : {}),
|
|
482
483
|
...(typeof body.oneShot === "boolean" ? { oneShot: body.oneShot } : {}),
|
|
483
|
-
resilience: normalizeResilience(body.resilience, explicitOperatorOk(person, config)),
|
|
484
|
+
resilience: normalizeResilience(body.resilience, explicitOperatorOk(person, config, "operate")),
|
|
484
485
|
finalVerification: cap.finalVerification === true || body.finalVerification === true ? true : undefined,
|
|
485
486
|
attachments: normalizeAttachments(body.attachments),
|
|
486
487
|
...(taskHooks.length > 0 ? { mods: [...taskHooks] } : {}),
|
|
@@ -596,29 +597,14 @@ export function createResolveSpec(ctx) {
|
|
|
596
597
|
const foldGovernanceAndSettings = async (body, auth, root, spec, gated, projectBrief, legSessionCwd) => {
|
|
597
598
|
const { acceptedAppend, parsedSettings } = gated;
|
|
598
599
|
const hostSemanticsLane = !DEPLOYMENT_SHAPE_PREDICATES.sandboxPathAdjudication.holds(config);
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
};
|
|
603
|
-
const pathAdjudication = hostSemanticsLane
|
|
604
|
-
? (() => {
|
|
600
|
+
const pathAdjudication = createLegPathAdjudication(config, {
|
|
601
|
+
sessionId: root,
|
|
602
|
+
hostArm: () => {
|
|
605
603
|
const realRoot = realTaskRootOf(legSessionCwd);
|
|
606
604
|
const gateCwd = realRoot ?? join(config.localDataRoot ?? localRoot, "fs-write-gate-unrooted");
|
|
607
|
-
return {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
...(realRoot !== undefined ? { taskRoot: realRoot } : {}),
|
|
611
|
-
...laneHomeBase(),
|
|
612
|
-
};
|
|
613
|
-
})()
|
|
614
|
-
: {
|
|
615
|
-
env: new DeferredSandboxPathEnv({
|
|
616
|
-
sessionId: root,
|
|
617
|
-
slots: sandboxPathEnvSlots,
|
|
618
|
-
logger: { warn: (event, fields) => logger.warn(event, { ...(fields ?? {}), lane: config.remoteExec.provider }) },
|
|
619
|
-
}),
|
|
620
|
-
...laneHomeBase(),
|
|
621
|
-
};
|
|
605
|
+
return { env: new NodeExecutionEnv({ cwd: gateCwd }), cwd: gateCwd, ...(realRoot !== undefined ? { taskRoot: realRoot } : {}) };
|
|
606
|
+
},
|
|
607
|
+
}, { slots: sandboxPathEnvSlots, logger });
|
|
622
608
|
const governedBase = applyRuntimeGovernance(spec, createDeploymentGovernanceInputs(config, pathAdjudication));
|
|
623
609
|
let governed = governedBase;
|
|
624
610
|
let effectiveSettings = parsedSettings.settings;
|
|
@@ -677,7 +663,7 @@ export function createResolveSpec(ctx) {
|
|
|
677
663
|
const resolved = await resolveSandboxImageRef({
|
|
678
664
|
profile: taskImageProfile,
|
|
679
665
|
...(capsNeeded && capsNeeded.length > 0 ? { capabilitiesNeeded: capsNeeded } : {}),
|
|
680
|
-
viewer: { operator: explicitOperatorOk(person, config), tenantId: person ?? null },
|
|
666
|
+
viewer: { operator: explicitOperatorOk(person, config, "operate"), tenantId: person ?? null },
|
|
681
667
|
index: imageIndex,
|
|
682
668
|
});
|
|
683
669
|
if (!resolved.ok)
|
|
@@ -245,7 +245,7 @@ export interface RunnerDepsCtx {
|
|
|
245
245
|
lspManager: RunnerDeps["lspManager"];
|
|
246
246
|
fleetBus: FleetEventBus;
|
|
247
247
|
/** 部署座的 mods 注册(起服第 5 段装配口 `assembleDeploymentMods` 铸:自铸 + 运维声明的包,起服自证过)—— 主 / handless / hookAgent / sub 经本共享基座、
|
|
248
|
-
* leader 车道的路由 / worker /
|
|
248
|
+
* leader 车道的路由 / worker / 修复腿经部署平面层(同一铸点),每一只 Runner 同一份。 */
|
|
249
249
|
deploymentMods: NonNullable<RunnerDeps["mods"]>;
|
|
250
250
|
workflowRunStore: WorkflowRunStore | undefined;
|
|
251
251
|
workflowJournalStore: ServiceWorkflowJournalStore | undefined;
|
|
@@ -253,9 +253,22 @@ export interface RunnerDepsCtx {
|
|
|
253
253
|
workflowNotifyGate: WorkflowNotifyGate | undefined;
|
|
254
254
|
deliverWorkflowCompletion: (p: WorkflowCompletionPayload) => Promise<void>;
|
|
255
255
|
}
|
|
256
|
-
/**
|
|
257
|
-
*
|
|
258
|
-
|
|
256
|
+
/**
|
|
257
|
+
* **部署平面层**:一台部署对**每一条腿说同一句话**的键 —— 收紧面(治理三座 / 按主体准入 / MCP 撤销台账 / 部署姿态座席族 / 豁免根)+ 计量面
|
|
258
|
+
* (tracer / 治理窗两键)+ 服务表(brain / 模型目录 / 定价 / 提示词目录)+ 部署座 / 手带 + 两条披露出口(通告 / 错误)。
|
|
259
|
+
* 律:本进程每一只跑带主体 spec 的 Runner —— 主 / 无手 / hook 代理 / 子代 / run-local,**以及 leader 车道的每一只** —— 展开**同一层**
|
|
260
|
+
* ({@link createDeploymentPlaneDeps},唯一铸点);新的部署键进这一层即到达全部 Runner,没有哪条车道逐席搬运。
|
|
261
|
+
* 不在这一层的是**会话设施**({@link SessionFacilityDeps}):各车道按自己的生命周期自选(leader 的沙盒、停驻店与会话店归 leader)。
|
|
262
|
+
*/
|
|
263
|
+
export type DeploymentPlaneDeps = Pick<RunnerDeps, "brain" | "models" | "roles" | "tiers" | "pricing" | "tracer" | "promptSource" | keyof DeploymentPostureSeats | "mods" | "hands" | "usageWindows" | "usageWindowStore" | "compliancePostureResolver" | "lockedConfig" | "retentionPolicy" | "onNotice" | "onError" | "runtimeCapsResolver" | "mcpRevocations" | "memoryEngineDir">;
|
|
264
|
+
/**
|
|
265
|
+
* **会话设施层**:停驻店、卸载店、后台子代店 / 信箱 / 名册、文件历史、会话规则、记忆三席、LSP、对端座、执行环境工厂 —— 按会话 / 车道的
|
|
266
|
+
* 生命周期选,不是部署对每条腿说的话。起服的主 / 子 Runner 与 run-local 经共享基座取全套;leader 车道不取(它的沙盒、停驻店、会话店按 worker
|
|
267
|
+
* 自己的生命周期给,`leader/wire.ts`)。
|
|
268
|
+
*/
|
|
269
|
+
export type SessionFacilityDeps = Pick<RunnerDeps, "executionEnvFactory" | "lspManager" | "backgroundAgentStore" | "mailboxStore" | "rosterStore" | "toolResultStore" | "checkpointStore" | "fileHistoryStore" | "fileHistoryCapture" | "sessionPolicyStore" | "memoryScopeAdmission" | "deploymentMemoryScopes" | "sharedMemoryStores" | "peerSeat">;
|
|
270
|
+
/** 共享基座 = 部署平面层 + 会话设施层(起服主 / 子 Runner 与 run-local 的两只 Runner 展开它)。 */
|
|
271
|
+
export type SharedRunnerDeps = DeploymentPlaneDeps & SessionFacilityDeps;
|
|
259
272
|
/**
|
|
260
273
|
* [ref] A10 留档发现②(review 2026-07-29,[ref]§三族A 同源修补的延续):main runner 的
|
|
261
274
|
* `RunnerDeps` 字面量(下方 `createRunnerDeps`)与 `main.ts` 里 subRunner 的 `new Runner({...})`
|
|
@@ -281,8 +294,17 @@ export type SharedRunnerDeps = Pick<RunnerDeps, "brain" | "readFace" | "readDeny
|
|
|
281
294
|
* [ref]§三族A 已用「直引 `runnerDeps.X`」修过(main.ts 两处引用同一个 `runnerDeps` 实例的同一
|
|
282
295
|
* 属性,比再抽一层共享基座更强的同源保证),不重复收纳进这里。
|
|
283
296
|
*/
|
|
284
|
-
/**
|
|
285
|
-
|
|
297
|
+
/** 部署平面层的输入(窄面)。`onError` = 本进程的错误汇(起服 = `createRunnerDeps` 铸的那一只,run-local = 它自己的那一只):必填键(值可缺席),
|
|
298
|
+
* 每个平面层调用点都得回答「这条腿的引擎错误交给谁」—— 它是按主体准入 resolver 抛 / 治理窗最终扣账失败的唯一出口,与 resolver、窗席同生共死。 */
|
|
299
|
+
export type DeploymentPlaneCtx = Pick<RunnerDepsCtx, "config" | "brain" | "pricing" | "tracer" | "promptSource" | "deploymentMods" | "usageWindowStore" | "logger" | "governanceSeams" | "mcpRevocations" | "metrics" | "engineDataRoot" | "runtimeCapsResolver"> & {
|
|
300
|
+
readonly onError: RunnerDeps["onError"];
|
|
301
|
+
};
|
|
302
|
+
/** 平面层里**车道给**的一格:提交署名(`hands.commitCoAuthor`;起服 / run-local 各按品牌算一次,leader 车道不署名 ⇒ `undefined`)。 */
|
|
303
|
+
export interface DeploymentPlaneLane {
|
|
304
|
+
readonly commitCoAuthor: RunnerDepsCtx["commitCoAuthor"];
|
|
305
|
+
}
|
|
306
|
+
/** 共享基座的输入 = 平面层的输入 + 会话设施的输入。 */
|
|
307
|
+
export type SharedRunnerDepsCtx = DeploymentPlaneCtx & Pick<RunnerDepsCtx, "executionEnvFactory" | "lspManager" | "backgroundAgentStore" | "mailboxStore" | "rosterStore" | "toolResultStore" | "checkpointStore" | "fileHistoryStore" | "sessionPolicyStore" | "commitCoAuthor" | "orgMemoryAdmission" | "sharedMemoryStores" | "peerDesk">;
|
|
286
308
|
/** 转发席只需要 `inc` —— 刻意窄到这一个动词,免得把整只 Metrics 拖进这条纯转发腿。 */
|
|
287
309
|
export interface EngineNoticeMetricsSeat {
|
|
288
310
|
inc: (name: string, labels?: Record<string, string>) => void;
|
|
@@ -299,39 +321,14 @@ export declare function createEngineNoticeForwarder(logger: {
|
|
|
299
321
|
export declare function installDefaultEngineNoticeSeat(logger: {
|
|
300
322
|
warn: (msg: string, fields?: Record<string, unknown>) => void;
|
|
301
323
|
}, metrics?: EngineNoticeMetricsSeat): void;
|
|
302
|
-
/**
|
|
303
|
-
* `onNotice` **席**的装配判据本身 —— 「`warn` 真在场才铸席」这一条从此是**一个可直接调用的函数**,
|
|
304
|
-
* 不是散在装配点、只能靠扫源码文本去猜的一行三元(codex 交叉复审 R1-[medium],验真后修)。
|
|
305
|
-
*
|
|
306
|
-
* 病(实测):把 leader 的门写成 `cfg.logger ? ((m, x) => cfg.logger?.warn?.(m, x)) : undefined` 时,
|
|
307
|
-
* 「装配点切片里出现过 `warn` 这个词」这类**源码文本**判据一律照绿 —— 而只装了 `info` 的部署会拿到一个
|
|
308
|
-
* truthy 闭包、照样铸席,通告随后被可选链**静默吞掉**,比不铸席更坏(不铸时 core 至少还打它自己的
|
|
309
|
-
* `console.warn`,响亮权留在原处)。任何「在这段文本里找关键字」的门都挡不住这一族变异,因为病灶在
|
|
310
|
-
* **值**(闭包 truthy)而不在字面。⇒ 把判据搬进函数,让它可以被**行为**测:info-only ⇒ 空席;带 warn ⇒
|
|
311
|
-
* 具名转发体。
|
|
312
|
-
*
|
|
313
|
-
* 命名(CLAUDE.md 工厂律):返回的袋子里装的是**活闭包**(有行为)⇒ `create*`,不是 `build*`。
|
|
314
|
-
* 返回**展开形**(`{}` 或 `{ onNotice }`)而不是 `onNotice: undefined`。理由是**键卫生**,不是 core 行为
|
|
315
|
-
* (R3 复扫更正:core 5.28 的 `deliverEngineNotice` 只判 `typeof onNotice === "function"`,显式 `undefined`
|
|
316
|
-
* 与键缺席在 core 那侧行为逐字相同、响亮权都留在 core——旧句「显式 undefined 也算 host 接管」与真码相反):
|
|
317
|
-
* 展开形让 `new Runner({...seat})` 的键集只随真装配变化,EXPECTED_SHARED_KEYS 类的键集绊线才数得准。
|
|
318
|
-
*/
|
|
319
|
-
export declare function createEngineNoticeSeat(logger: {
|
|
320
|
-
warn?: (msg: string, fields?: Record<string, unknown>) => void;
|
|
321
|
-
} | undefined, router?: EngineNoticeRouter): {
|
|
322
|
-
onNotice?: NonNullable<RunnerDeps["onNotice"]>;
|
|
323
|
-
};
|
|
324
324
|
/**
|
|
325
325
|
* [ref](黑板 [ref],[ref] 登记)—— **部署姿态座席族**:运维在配置面写下的姿态(READ 面四席 + 记忆三席 +
|
|
326
326
|
* 委派入口 caps)。core 明写「每个 run 读**准备它的那只 Runner** 的 deps」⇒ 一台部署里跑任务的**每一只**
|
|
327
|
-
* Runner
|
|
328
|
-
*
|
|
329
|
-
* 五处 `...deploymentPostureSeat`)。
|
|
327
|
+
* Runner 都必须带同一份:本进程每一只 Runner 经部署平面层({@link createDeploymentPlaneDeps})展开它 —— 主 runner / subRunner /
|
|
328
|
+
* run-local 经共享基座,leader 车道的四只经 `LeaderWireConfig.plane`。
|
|
330
329
|
*
|
|
331
330
|
* 🔴 本函数是这一族**唯一**的取值点。两处各读一遍 config 就是 [ref]§三族A「主挂了、副漏挂」的病族形——
|
|
332
|
-
* [ref] 正是 leader
|
|
333
|
-
* `test/memory-delegation-evidence-lane.test.ts` ⑤ 组的机器门如实登记,现已翻成正向门)。leader 不许自己
|
|
334
|
-
* 再读 config 铸一份,只吃本函数经 cfg 递过去的产物(`test/leader-deployment-posture-seats.test.ts` ③ 钉)。
|
|
331
|
+
* [ref] 正是 leader 那条腿漏了**全族**。平面层每次铸都现调本函数(中心热换 READ 面之后新构造的 Runner 读新档,[ref])。
|
|
335
332
|
*
|
|
336
333
|
* 纯数据(键值全是 config 透传,无闭包)⇒ `build*`(CLAUDE.md 工厂律)。键**恒在场**、缺席 = `undefined`
|
|
337
334
|
* ([ref] S4 house style;core 消费点全按 `!== undefined` / `??` 判 —— dist
|
|
@@ -359,7 +356,7 @@ export declare function crossBoundaryPostureOf(requirePrincipal: boolean): Cross
|
|
|
359
356
|
/**
|
|
360
357
|
* 手带(`RunnerDeps.hands`)的**唯一**铸点(纯数据 ⇒ `build*`)。两格两个来源:署名由腿给(两条腿判据不同,见 {@link RunnerDepsCtx.commitCoAuthor};
|
|
361
358
|
* leader 车道今天不署名,显式交 `undefined`),前台命令超时转后台读部署旋钮 `AUTO_BACKGROUND_ON_TIMEOUT` —— 旋钮的读点只有这里。
|
|
362
|
-
*
|
|
359
|
+
* 部署平面层调这一只(署名一格由车道给,{@link DeploymentPlaneLane}):
|
|
363
360
|
* 有手的每一只 Runner 都拿到它,`/health.autoBackgroundOnTimeout` 说「生效」时没有哪只 Runner 例外。
|
|
364
361
|
*/
|
|
365
362
|
export declare function buildHandsBand(config: Pick<ServiceConfig, "autoBackgroundOnTimeout">, commitCoAuthor: RunnerDepsCtx["commitCoAuthor"]): NonNullable<RunnerDeps["hands"]>;
|
|
@@ -371,6 +368,13 @@ export declare function buildDeploymentPostureSeats(config: DeploymentPostureCon
|
|
|
371
368
|
* 喂的是 `config.readDenyPatterns`(运维 ∪ 中心 read-face 的行),不是座席族并入 LKG 派生行之后的那一份 —— 派生行是根锚。绝不抛(纯诊断面)。
|
|
372
369
|
*/
|
|
373
370
|
export declare function announceReadDenyRows(config: Pick<ServiceConfig, "readDenyPatterns">, logger: Pick<Logger, "warn">): void;
|
|
371
|
+
/**
|
|
372
|
+
* **部署平面层的唯一铸点**({@link DeploymentPlaneDeps})。纯函数、无副作用(读拒行的进场告知留在共享基座 —— leader 每构造一只 Runner 调本函数一次,
|
|
373
|
+
* 告知不随它重复)。每次调用现读 `config`:部署姿态座席族与模型目录会被中心热换,逐调用现构的 Runner(leader 车道)读到的是构造那一刻的代,
|
|
374
|
+
* 与主车道经 swap 门换代同代([ref]);起服的主 / 子 Runner 各展开一次,同一份 ctx ⇒ 同值同引用。
|
|
375
|
+
* 署名一格车道给({@link DeploymentPlaneLane});其余每一格对每一条腿同一句话。
|
|
376
|
+
*/
|
|
377
|
+
export declare function createDeploymentPlaneDeps(ctx: DeploymentPlaneCtx, lane: DeploymentPlaneLane): DeploymentPlaneDeps;
|
|
374
378
|
export declare function createSharedRunnerDeps(ctx: SharedRunnerDepsCtx): SharedRunnerDeps;
|
|
375
379
|
/**
|
|
376
380
|
* 这台部署**声明本机属主的规则桶**吗(core `RunnerDeps.localOwnerRules`:不带主体的 run 经规则店的 `forLocalOwner()` 读持久规则,而不是读零规则)。
|