@sema-agent/server 7.136.0 → 7.137.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 +173 -0
- package/USAGE.md +84 -16
- package/deploy/sema-up/README.md +14 -0
- package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +7 -1
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +7 -1
- package/dist/approval-content-kind.d.ts +6 -0
- package/dist/approval-content-kind.js +7 -0
- package/dist/approval-gate-currency.d.ts +48 -6
- package/dist/approval-gate-currency.js +16 -9
- package/dist/approval-hmac.d.ts +13 -1
- package/dist/approval-hmac.js +1 -1
- package/dist/approval.js +3 -3
- package/dist/auth-bridge.js +2 -2
- package/dist/boot/execution-env.d.ts +24 -1
- package/dist/boot/execution-env.js +69 -6
- package/dist/boot/reapers.d.ts +3 -0
- package/dist/boot/reapers.js +15 -1
- package/dist/boot/resolve-spec.d.ts +4 -0
- package/dist/boot/resolve-spec.js +16 -17
- package/dist/boot/stage-03-store-backend.d.ts +1 -1
- package/dist/boot/stage-04-stores.d.ts +1 -1
- package/dist/boot/stage-05-execution-env.d.ts +3 -1
- package/dist/boot/stage-05-execution-env.js +11 -2
- package/dist/boot/stage-06-runners.d.ts +3 -1
- package/dist/boot/stage-06-runners.js +5 -1
- package/dist/boot/stage-07-capability-layer.d.ts +4 -1
- package/dist/boot/stage-07-capability-layer.js +1 -1
- package/dist/boot/stage-08-reapers.d.ts +4 -1
- package/dist/boot/stage-08-reapers.js +2 -1
- package/dist/boot/stage-09-leader.d.ts +4 -1
- package/dist/boot/stage-10-http-server.d.ts +4 -1
- package/dist/boot/stage-10-http-server.js +3 -2
- package/dist/boot/stores.d.ts +1 -1
- package/dist/broker/broker.d.ts +89 -0
- package/dist/broker/broker.js +287 -0
- package/dist/broker/constants.d.ts +39 -0
- package/dist/broker/constants.js +15 -0
- package/dist/broker/http1.d.ts +111 -0
- package/dist/broker/http1.js +256 -0
- package/dist/broker/lease-book.d.ts +87 -0
- package/dist/broker/lease-book.js +142 -0
- package/dist/broker/lease-file.d.ts +60 -0
- package/dist/broker/lease-file.js +127 -0
- package/dist/broker/main.d.ts +2 -0
- package/dist/broker/main.js +20 -0
- package/dist/capabilities/composite-members.d.ts +2 -1
- package/dist/capabilities/composite-members.js +2 -1
- package/dist/capabilities/engine-capabilities.js +1 -0
- package/dist/capabilities/scenarios.d.ts +23 -8
- package/dist/capabilities/scenarios.js +13 -8
- package/dist/capabilities/skills.d.ts +28 -5
- package/dist/capabilities/skills.js +8 -0
- package/dist/center-credential-renewer.js +5 -1
- package/dist/checkpoint-ctx-seal-m28.d.ts +3 -10
- package/dist/checkpoint-ctx-seal-m28.js +1 -1
- package/dist/checkpoint-identity.d.ts +3 -3
- package/dist/config-catalog.js +9 -6
- package/dist/config-types.d.ts +9 -0
- package/dist/config.d.ts +1 -1
- package/dist/config.js +34 -8
- package/dist/deployment-governance.d.ts +2 -2
- package/dist/deployment-governance.js +7 -9
- package/dist/device-ws-protocol.d.ts +1 -0
- package/dist/device-ws-protocol.js +2 -1
- package/dist/file-history-disclosure.d.ts +4 -2
- package/dist/file-history-disclosure.js +2 -0
- package/dist/hooks/classic-stations.d.ts +11 -54
- package/dist/http/admission.js +10 -7
- package/dist/http/approval-sweeps.js +4 -3
- package/dist/http/caller-trust.d.ts +2 -1
- package/dist/http/caller-trust.js +1 -3
- package/dist/http/checkpoint-ctx.d.ts +48 -7
- package/dist/http/checkpoint-ctx.js +40 -0
- package/dist/http/dispatch.js +15 -10
- package/dist/http/durable-run-leg.d.ts +5 -1
- package/dist/http/durable-run-leg.js +1 -1
- package/dist/http/health-self-report.d.ts +2 -2
- package/dist/http/mcp-remote-text.d.ts +5 -0
- package/dist/http/mcp-remote-text.js +7 -0
- package/dist/http/principal-gate.d.ts +15 -10
- package/dist/http/principal-gate.js +48 -9
- package/dist/http/resume-legs.d.ts +2 -20
- package/dist/http/resume-legs.js +52 -38
- package/dist/http/route-ctx.d.ts +8 -2
- package/dist/http/routes/approvals-assistant.js +18 -4
- package/dist/http/routes/capabilities.js +11 -1
- package/dist/http/routes/mcp-reconnect.d.ts +1 -1
- package/dist/http/routes/mcp-reconnect.js +5 -7
- package/dist/http/routes/runs.js +2 -1
- package/dist/http/routes/session-sync.js +42 -6
- package/dist/http/routes/sessions.js +2 -1
- package/dist/http/routes/tasks.js +8 -6
- package/dist/http/server.d.ts +4 -0
- package/dist/http/stored-body-replay.d.ts +3 -3
- package/dist/http/stored-body-replay.js +6 -1
- package/dist/http/wire-types.d.ts +52 -27
- package/dist/identity-source.d.ts +65 -0
- package/dist/identity-source.js +45 -0
- package/dist/memory-store-refusal.d.ts +3 -3
- package/dist/model-entry-refusal.d.ts +2 -0
- package/dist/model-entry-refusal.js +11 -0
- package/dist/model-select.d.ts +6 -17
- package/dist/model-select.js +0 -3
- package/dist/namespaced-rule-name.d.ts +1 -1
- package/dist/observability/fail-open.d.ts +13 -1
- package/dist/observability/fail-open.js +13 -1
- package/dist/observability/metrics.js +1 -1
- package/dist/observability/run-terminal.js +1 -0
- package/dist/operator-ask.d.ts +2 -2
- package/dist/org-delivery-gate.js +1 -1
- package/dist/org-memory-admission.d.ts +2 -2
- package/dist/org-memory-admission.js +39 -2
- package/dist/parked-decide.js +3 -2
- package/dist/peer-directory.js +2 -0
- package/dist/permission-rule-list.d.ts +2 -2
- package/dist/plugins/checkpoint-store-sql.d.ts +19 -9
- package/dist/plugins/checkpoint-store-sql.js +6 -6
- package/dist/plugins/file-history-store-sql.js +7 -14
- package/dist/plugins/file-run-store.js +8 -2
- package/dist/plugins/k8s-exec-protocol.d.ts +69 -13
- package/dist/plugins/k8s-exec-protocol.js +116 -28
- package/dist/plugins/k8s-pod-delete-failure.d.ts +25 -0
- package/dist/plugins/k8s-pod-delete-failure.js +17 -0
- package/dist/plugins/k8s-pod-reaper.d.ts +112 -0
- package/dist/plugins/k8s-pod-reaper.js +122 -0
- package/dist/plugins/local-checkpoint-store.d.ts +3 -6
- package/dist/plugins/local-checkpoint-store.js +3 -3
- package/dist/plugins/memory-run-store.js +2 -0
- package/dist/plugins/one-time-migrations.d.ts +28 -9
- package/dist/plugins/one-time-migrations.js +48 -5
- package/dist/plugins/permission-rule-store-file.js +2 -2
- package/dist/plugins/permission-rule-store-sql.d.ts +17 -17
- package/dist/plugins/permission-rule-store-sql.js +16 -17
- package/dist/plugins/pg-pool.js +4 -0
- package/dist/plugins/pg-session-storage.js +2 -2
- package/dist/plugins/remote-env-adb.d.ts +3 -0
- package/dist/plugins/remote-env-adb.js +1 -0
- package/dist/plugins/remote-env-device.d.ts +3 -0
- package/dist/plugins/remote-env-device.js +4 -0
- package/dist/plugins/remote-env-e2b.d.ts +3 -0
- package/dist/plugins/remote-env-e2b.js +1 -0
- package/dist/plugins/remote-env-file-error.d.ts +8 -1
- package/dist/plugins/remote-env-file-error.js +7 -1
- package/dist/plugins/remote-env-host.d.ts +7 -1
- package/dist/plugins/remote-env-host.js +7 -1
- package/dist/plugins/remote-env-k8s.d.ts +137 -10
- package/dist/plugins/remote-env-k8s.js +276 -14
- package/dist/plugins/remote-env-local-docker.d.ts +3 -0
- package/dist/plugins/remote-env-local-docker.js +1 -0
- package/dist/plugins/remote-env-ssh.d.ts +3 -0
- package/dist/plugins/remote-env-ssh.js +1 -0
- package/dist/plugins/run-store-sql.js +10 -4
- package/dist/plugins/session-ownership.d.ts +17 -3
- package/dist/plugins/session-ownership.js +19 -5
- package/dist/plugins/sql-errors.d.ts +23 -2
- package/dist/plugins/sql-errors.js +15 -0
- package/dist/plugins/store-contracts.d.ts +10 -1
- package/dist/plugins/store-fault.d.ts +2 -2
- package/dist/plugins/store-fault.js +4 -2
- package/dist/plugins/tidb-pool.js +7 -0
- package/dist/plugins/tidb-session-store.js +2 -2
- package/dist/request-key-closure.js +1 -1
- package/dist/request-value-closure.d.ts +31 -2
- package/dist/request-value-closure.js +7 -1
- package/dist/rule-entry-list.d.ts +3 -2
- package/dist/rule-entry-list.js +2 -2
- package/dist/rules-consent.js +7 -4
- package/dist/run-local.d.ts +2 -2
- package/dist/runs.js +2 -1
- package/dist/runtime-governance.d.ts +5 -5
- package/dist/sandbox-git-lease.d.ts +49 -0
- package/dist/sandbox-git-lease.js +60 -0
- package/dist/sandbox-git-plane.d.ts +84 -0
- package/dist/sandbox-git-plane.js +105 -0
- package/dist/security.d.ts +16 -4
- package/dist/security.js +8 -3
- package/dist/server-secret-env.d.ts +6 -0
- package/dist/server-secret-env.js +14 -0
- package/dist/session-error-wire.d.ts +14 -6
- package/dist/session-error-wire.js +2 -1
- package/dist/session-policy-wire.d.ts +1 -1
- package/dist/session-policy-wire.js +2 -2
- package/dist/session-sync.d.ts +1 -1
- package/dist/spec-fields.d.ts +43 -13
- package/dist/spec-fields.js +33 -12
- package/dist/stored-request-prompt-profile-m30.d.ts +49 -0
- package/dist/stored-request-prompt-profile-m30.js +25 -0
- package/dist/task-mcp.d.ts +1 -1
- package/dist/task-settings.d.ts +2 -2
- package/dist/tool-approval.d.ts +12 -2
- package/dist/tool-approval.js +8 -3
- package/dist/tool-rule-names.d.ts +5 -0
- package/dist/trace/core-keyset-guard.d.ts +4 -4
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +2 -0
- package/dist/trace/exit-seat.js +2 -2
- package/dist/trace/ledger-sink.d.ts +4 -5
- package/dist/trace/ledger-sink.js +3 -7
- package/dist/trace/project.d.ts +37 -3
- package/dist/trace/project.js +20 -6
- package/dist/trace/redact.d.ts +18 -6
- package/dist/trace/redact.js +118 -53
- package/dist/trace/self-minted-wire.d.ts +377 -3
- package/dist/trace/self-minted-wire.js +122 -0
- package/dist/trace/wire-projection-faces.d.ts +5 -3
- package/dist/trace/wire-projection-faces.js +7 -3
- package/dist/web-admin-client/apply-effective.js +0 -1
- package/dist/web-admin-client/center-request.d.ts +20 -2
- package/dist/web-admin-client/center-request.js +40 -5
- package/dist/web-admin-client/http-client.js +6 -6
- package/dist/web-admin-client/types.d.ts +5 -5
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +11 -5
- package/package.json +2 -2
- package/wire-changes/server@7.137.0.json +825 -0
|
@@ -1,35 +1,125 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { BROKER_VOLUME_NAME } from "../broker/constants.js";
|
|
2
|
+
export const SANDBOX_POD_LABELS = { app: "sema-sandbox", "managed-by": "sema-server" };
|
|
3
|
+
export const SANDBOX_RUN_LABEL = "sema.io/run";
|
|
4
|
+
export const SANDBOX_RUN_END_HOLD_ANNOTATION = "sema.io/run-end-hold-ms";
|
|
5
|
+
export const SANDBOX_SECRET_SEAT_VERBS = { env: ["create"], lease: ["create", "patch"] };
|
|
6
|
+
const IDENTITY_LABEL_KEYS = new Set([...Object.keys(SANDBOX_POD_LABELS), SANDBOX_RUN_LABEL]);
|
|
7
|
+
const LABEL_VALUE = /^(?:[A-Za-z0-9](?:[-A-Za-z0-9_.]{0,61}[A-Za-z0-9])?)?$/;
|
|
8
|
+
export function isLabelValue(v) {
|
|
9
|
+
return LABEL_VALUE.test(v);
|
|
10
|
+
}
|
|
11
|
+
export const ADAPTER_VOLUME_NAMES = { cache: "ws-cache", brokerLease: BROKER_VOLUME_NAME };
|
|
12
|
+
const RESERVED_VOLUME_NAMES = new Set(Object.values(ADAPTER_VOLUME_NAMES));
|
|
13
|
+
const agentContainerOf = (pod) => pod.spec.containers?.[0];
|
|
14
|
+
const POD_SPEC_PATCH_FIELDS = {
|
|
15
|
+
volumes: {
|
|
16
|
+
judge: (volumes) => {
|
|
17
|
+
assertVolumesPodLocal(volumes, "volumes");
|
|
18
|
+
for (const v of volumes)
|
|
19
|
+
assertPatchVolumeName(v?.name, "volumes");
|
|
20
|
+
},
|
|
21
|
+
apply: (pod, volumes) => {
|
|
22
|
+
if (volumes.length > 0)
|
|
23
|
+
pod.spec.volumes = [...(pod.spec.volumes ?? []), ...volumes];
|
|
24
|
+
},
|
|
25
|
+
withBroker: "allowed",
|
|
26
|
+
},
|
|
27
|
+
volumeMounts: {
|
|
28
|
+
judge: (mounts) => {
|
|
29
|
+
for (const m of mounts)
|
|
30
|
+
assertPatchVolumeName(m?.name, "volumeMounts");
|
|
31
|
+
},
|
|
32
|
+
apply: (pod, mounts) => {
|
|
33
|
+
const container = agentContainerOf(pod);
|
|
34
|
+
if (container && mounts.length > 0)
|
|
35
|
+
container.volumeMounts = [...(container.volumeMounts ?? []), ...mounts];
|
|
36
|
+
},
|
|
37
|
+
withBroker: "allowed",
|
|
38
|
+
},
|
|
39
|
+
podSecurityContext: {
|
|
40
|
+
judge: (sc) => {
|
|
41
|
+
assertHardeningOnly(sc, "podSecurityContext");
|
|
42
|
+
if ("sysctls" in sc) {
|
|
43
|
+
throw new Error("podSpecPatch.podSecurityContext rejected — `sysctls` is not a patch key (the pod's sysctls are minted only by the lane's IPv6 posture, K8S_SANDBOX_IPV6)");
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
apply: (pod, sc) => {
|
|
47
|
+
pod.spec.securityContext = { ...(pod.spec.securityContext ?? {}), ...sc };
|
|
48
|
+
},
|
|
49
|
+
withBroker: {
|
|
50
|
+
refused: "podSpecPatch.podSecurityContext is not allowed on a pod that carries the member-credential broker — every pod-level key would reach the broker container too; put the hardening into containerSecurityContext (it lands on the agent container only)",
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
containerSecurityContext: {
|
|
54
|
+
judge: (sc) => assertHardeningOnly(sc, "containerSecurityContext"),
|
|
55
|
+
apply: (pod, sc) => {
|
|
56
|
+
const container = agentContainerOf(pod);
|
|
57
|
+
if (container)
|
|
58
|
+
container.securityContext = { ...(container.securityContext ?? {}), ...sc };
|
|
59
|
+
},
|
|
60
|
+
withBroker: "allowed",
|
|
61
|
+
},
|
|
62
|
+
labels: {
|
|
63
|
+
judge: () => { },
|
|
64
|
+
apply: (pod, labels) => {
|
|
65
|
+
const merged = { ...pod.metadata.labels };
|
|
66
|
+
for (const [k, v] of Object.entries(labels))
|
|
67
|
+
if (!IDENTITY_LABEL_KEYS.has(k))
|
|
68
|
+
merged[k] = v;
|
|
69
|
+
pod.metadata.labels = merged;
|
|
70
|
+
},
|
|
71
|
+
withBroker: "allowed",
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
const PATCH_FIELD_ORDER = Object.keys(POD_SPEC_PATCH_FIELDS);
|
|
75
|
+
export function applyPodSpecPatch(pod, patch, presence = { broker: false }) {
|
|
76
|
+
const present = judgePodSpecPatch(patch, presence);
|
|
77
|
+
for (const k of present)
|
|
78
|
+
applyField(k, pod, patch);
|
|
79
|
+
}
|
|
80
|
+
export function podSpecPatchBrokerRefusal(patch) {
|
|
81
|
+
if (patch === undefined)
|
|
82
|
+
return undefined;
|
|
83
|
+
try {
|
|
84
|
+
judgePodSpecPatch(patch, { broker: true });
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
return e instanceof Error ? e.message : String(e);
|
|
5
89
|
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (v !== undefined)
|
|
12
|
-
identity[k] = v;
|
|
90
|
+
}
|
|
91
|
+
function judgePodSpecPatch(patch, presence) {
|
|
92
|
+
for (const key of Object.keys(patch)) {
|
|
93
|
+
if (!Object.hasOwn(POD_SPEC_PATCH_FIELDS, key)) {
|
|
94
|
+
throw new Error(`podSpecPatch rejected — ${JSON.stringify(key)} is not a patch field (the patch fields are ${PATCH_FIELD_ORDER.join(" / ")}; any other key is refused, never ignored)`);
|
|
13
95
|
}
|
|
14
|
-
pod.metadata.labels = { ...pod.metadata.labels, ...patch.labels, ...identity };
|
|
15
96
|
}
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
97
|
+
const present = PATCH_FIELD_ORDER.filter((k) => patch[k] !== undefined);
|
|
98
|
+
if (presence.broker) {
|
|
99
|
+
for (const k of present) {
|
|
100
|
+
const column = POD_SPEC_PATCH_FIELDS[k].withBroker;
|
|
101
|
+
if (column !== "allowed")
|
|
102
|
+
throw new Error(column.refused);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
for (const k of present)
|
|
106
|
+
judgeField(k, patch);
|
|
107
|
+
return present;
|
|
108
|
+
}
|
|
109
|
+
function judgeField(key, patch) {
|
|
110
|
+
POD_SPEC_PATCH_FIELDS[key].judge(patch[key]);
|
|
111
|
+
}
|
|
112
|
+
function applyField(key, pod, patch) {
|
|
113
|
+
POD_SPEC_PATCH_FIELDS[key].apply(pod, patch[key]);
|
|
114
|
+
}
|
|
115
|
+
function assertPatchVolumeName(name, where) {
|
|
116
|
+
if (typeof name !== "string" || name === "")
|
|
117
|
+
throw new Error(`podSpecPatch.${where} rejected — every entry needs a non-empty string \`name\``);
|
|
118
|
+
if (RESERVED_VOLUME_NAMES.has(name)) {
|
|
119
|
+
throw new Error(`podSpecPatch.${where} rejected — volume name ${JSON.stringify(name)} is reserved (the adapter mints it on the pod itself: ${[...RESERVED_VOLUME_NAMES].join(" / ")}); name the patch volume something else`);
|
|
28
120
|
}
|
|
29
121
|
}
|
|
30
122
|
function assertHardeningOnly(sc, where) {
|
|
31
|
-
if (!sc)
|
|
32
|
-
return;
|
|
33
123
|
const bad = (msg) => {
|
|
34
124
|
throw new Error(`podSpecPatch.${where} rejected — it may only HARDEN, never weaken isolation: ${msg}`);
|
|
35
125
|
};
|
|
@@ -53,8 +143,6 @@ function assertHardeningOnly(sc, where) {
|
|
|
53
143
|
bad('seccompProfile.type:"Unconfined" (disables syscall filtering)');
|
|
54
144
|
}
|
|
55
145
|
function assertVolumesPodLocal(volumes, where) {
|
|
56
|
-
if (!volumes?.length)
|
|
57
|
-
return;
|
|
58
146
|
const POD_LOCAL = new Set(["emptyDir", "configMap", "secret", "projected", "downwardAPI", "ephemeral"]);
|
|
59
147
|
for (const v of volumes) {
|
|
60
148
|
const vol = v;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** A pod DELETE the API answered with something other than "deleted" (2xx) or "not there" (404): a Role without `delete`, a 5xx — the pod is still in the namespace. */
|
|
2
|
+
export declare class PodDeleteRefused extends Error {
|
|
3
|
+
readonly status: number;
|
|
4
|
+
constructor(status: number);
|
|
5
|
+
}
|
|
6
|
+
/** 删失败的原因码(闭集两形):API 回了 2xx / 404 以外的答复 ⇒ `http_<状态>`;请求本身抛(传输失败 / 超时)⇒ `request_failed`。 */
|
|
7
|
+
export declare function podDeleteCauseOf(error: unknown): string;
|
|
8
|
+
/** 一次删失败的读数(座自己填 `why` 与 `effect`)。 */
|
|
9
|
+
export interface PodDeleteFailure {
|
|
10
|
+
readonly namespace: string;
|
|
11
|
+
readonly pod: string;
|
|
12
|
+
/** 这只 pod 为什么要删(每个座一个闭集词)。 */
|
|
13
|
+
readonly why: string;
|
|
14
|
+
/** 被抛出的那一只(只读它的类型取原因码,其文字不上任何一行)。 */
|
|
15
|
+
readonly error: unknown;
|
|
16
|
+
/** 留下什么(运维行的后果句,英文,不带值)。 */
|
|
17
|
+
readonly effect: string;
|
|
18
|
+
/** 运维行座:可缺 / 可没有 `error` 档(计数不依赖座)。 */
|
|
19
|
+
readonly logger?: {
|
|
20
|
+
error?(event: string, fields?: Record<string, unknown>): void;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** 上报一次删 pod 失败(计数 + 一行 error)。不抛。 */
|
|
24
|
+
export declare function reportPodDeleteFailed(f: PodDeleteFailure): void;
|
|
25
|
+
//# sourceMappingURL=k8s-pod-delete-failure.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { recordFailOpen } from "../observability/fail-open.js";
|
|
2
|
+
export class PodDeleteRefused extends Error {
|
|
3
|
+
status;
|
|
4
|
+
constructor(status) {
|
|
5
|
+
super(`pod delete answered HTTP ${status}`);
|
|
6
|
+
this.status = status;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export function podDeleteCauseOf(error) {
|
|
10
|
+
return error instanceof PodDeleteRefused ? `http_${error.status}` : "request_failed";
|
|
11
|
+
}
|
|
12
|
+
export function reportPodDeleteFailed(f) {
|
|
13
|
+
const cause = podDeleteCauseOf(f.error);
|
|
14
|
+
recordFailOpen("server.sandbox-pod.delete-failed", `namespace=${f.namespace} pod=${f.pod} why=${f.why} cause=${cause}`);
|
|
15
|
+
f.logger?.error?.("sandbox_pod_delete_failed", { pod: f.pod, namespace: f.namespace, why: f.why, cause, effect: f.effect });
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=k8s-pod-delete-failure.js.map
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* k8s 沙箱 Pod 的 reaper —— server 建出来、却没被自己的 run 收尾删掉的 Pod 由它删(该 Pod 的 Secret 以 `ownerReferences` 属于它,随它由集群 GC)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么要它:`activeDeadlineSeconds` 只让容器到点终止、Pod 进 `Failed`,**不删 Pod 对象**;run 收尾的 `destroy()` 是删 Pod 的正常路,
|
|
5
|
+
* 而 server 进程崩溃 / 收尾里删失败(`destroy` 的删是吞错的)时 Pod 对象(连同它的 Secret)就一直留着。
|
|
6
|
+
*
|
|
7
|
+
* 法(只删两形;判决链的缺省臂是「不动作」,删必须被显式选中 —— 销毁不可逆,拿不准恒不删,与 E2B 孤儿回收同一方向纪律):
|
|
8
|
+
* ② `status.phase ∈ {Succeeded, Failed}` —— 容器已全部结束,删它不掐断任何东西;不看 run、不看标签;
|
|
9
|
+
* ① 带 run 标签(`sema.io/run`,只有顶层 run 的 Pod 带)、run 行在 server 店里**已是终态**,且自 run 行**转终态的时刻**(行上的 `updatedAt` ——
|
|
10
|
+
* 三只 run 店的终态写当拍盖它)起超过**这只 Pod 自带的**上界:`max(租约上限 + 宽限 = 6 min, 工作区快照时限 + 引擎拆环境放行 15 s)`
|
|
11
|
+
* ({@link sandboxRunEndHoldMs};租约上限 = 凭据代理的租约律那一只 `LEASE_TTL_MAX_MS`,本模块不另立)。
|
|
12
|
+
* 第二项是 run 收尾的 `destroy()` 在终态之后还可能在 Pod 上存快照的最长时间(引擎的拆环境腿 15 s 放行、run 随即落终态,快照按数据时限接着跑)——
|
|
13
|
+
* 那个数据时限是**建这只 Pod 的副本**的配置,所以上界**随 Pod 走、写一次读一次**:适配器建 Pod 时按自己的快照时限算一次、写进注解
|
|
14
|
+
* `sema.io/run-end-hold-ms`(`k8s-exec-protocol.ts` `SANDBOX_RUN_END_HOLD_ANNOTATION`),回收器只读 Pod 自带的这一格,**从不**读回收器所在副本的配置
|
|
15
|
+
* (列举是整个命名空间的 Pod:滚动改 `WORKSPACE_SNAPSHOT_TIMEOUT_MS` 时,一个时限短的副本不得替时限长的副本判它的 Pod)。
|
|
16
|
+
* 带 run 标签的 Pod 上注解缺席(不是本服务写的:标签与注解由建 Pod 的适配器在同一个条件下一起写)、不是十进制整数、不在 [租约上限 + 宽限, 快照时限上限 + 引擎拆环境放行] 里
|
|
17
|
+
* ⇒ 不按 ① 删(`hold_unreadable`;这只 Pod 由自己的 `activeDeadlineSeconds` 到点后命中 ② 收)。7.136.x 及以前建的 Pod 没有 run 标签(标签与注解都是本版起写)⇒ 落在下面
|
|
18
|
+
* 「不带 run 标签」一类(`no_run_label`),同样只按 ② 收。持久化开 / 关两种部署同一条律。
|
|
19
|
+
* **活 run 的 Pod 不由本模块判**:run 行非终态(活 / 停驻)、查不到、读抛、词表外 ⇒ 不删 —— 它由自己的截止(`activeDeadlineSeconds` 到点
|
|
20
|
+
* ⇒ 之后命中 ②)与取消 / 收尾路径收;本模块从不按 Pod 存在时长判活。
|
|
21
|
+
* 不带 run 标签的 Pod(委派子代 / 服务自己的 run / 没有 run id)只按 ② 删:会话级后台子代刻意活过父 run 的回合,「会话里没有非终态 run」
|
|
22
|
+
* 推不出「这只 Pod 没人用」。
|
|
23
|
+
*
|
|
24
|
+
* 没有 run 店(无存储后端的部署)⇒ ② 照删;带 run 标签的 Pod 跳过 `run_store_absent`(查不了 run ⇒ 不删)。
|
|
25
|
+
* 每轮一行结构化日志 `sandbox_pod_reap_round`(扫了几只、按闭集原因分的删 / 跳过计数),每删一只一行 `sandbox_pod_reaped`;**列举失败**这一轮一只都删不了 ——
|
|
26
|
+
* F 类兜底口 `server.sandbox-pod-reaper.list-failed` 计一次 + 一行 warn `sandbox_pod_reap_list_failed`(下一轮一个周期后照常再列);**某一只删失败**不挡其余 ——
|
|
27
|
+
* 与连接清理同一只上报口 `reportPodDeleteFailed`(F 类计数 `server.sandbox-pod.delete-failed` + 一行 error `sandbox_pod_delete_failed`,`why=reaper_<删它的理由>`,下一轮再判再删)。
|
|
28
|
+
* 每副本起服后第一个维护 tick 跑一次、此后每 {@link SANDBOX_POD_REAP_PERIOD_MS} 一次(`boot/reapers.ts`);多副本同时删同一只 ⇒ 404 计 `gone`。
|
|
29
|
+
*/
|
|
30
|
+
import type { Logger } from "../observability/logger.js";
|
|
31
|
+
import type { Metrics } from "../observability/metrics.js";
|
|
32
|
+
/** ① 的宽限(在租约上限之外再等的一段)。常量。 */
|
|
33
|
+
export declare const SANDBOX_POD_REAP_GRACE_MS = 60000;
|
|
34
|
+
/**
|
|
35
|
+
* 引擎在 run 收尾时给「拆执行环境」那条腿的放行时限:超过它 run 照样落终态,`destroy()` 脱离着继续跑。值 = core 的 `TEARDOWN_LEG_MAX_MS`
|
|
36
|
+
* (`dist/core/runner/teardown-bounded.js`)—— core 不从包根导出它(包的 exports 只有 `.` / `./bench` / `./fixtures`),本仓只能照抄这一个数;
|
|
37
|
+
* 收口 = 向 core 要一个包根读口后改读、删本常量。
|
|
38
|
+
*/
|
|
39
|
+
export declare const ENGINE_TEARDOWN_RELEASE_MS = 15000;
|
|
40
|
+
/** ① 的上界:`max(租约上限 + 宽限, 快照时限 + 引擎拆环境放行)`。唯一的调用方 = 适配器建 Pod 的那一拍(快照时限 = 建它的副本的 `dataTimeoutMs`),结果写进 Pod 的注解。 */
|
|
41
|
+
export declare function sandboxRunEndHoldMs(snapshotTimeoutMs: number): number;
|
|
42
|
+
/** 一个副本两轮回收之间的间隔。常量。 */
|
|
43
|
+
export declare const SANDBOX_POD_REAP_PERIOD_MS: number;
|
|
44
|
+
/** 列举出来的一只沙箱 Pod(只带判官要读的几样;时刻是毫秒)。 */
|
|
45
|
+
export interface SandboxPodListing {
|
|
46
|
+
readonly name: string;
|
|
47
|
+
/** 标签 `sema.io/run`:顶层 run 的 id;缺席 = 这只 Pod 不点名 run。 */
|
|
48
|
+
readonly runId?: string;
|
|
49
|
+
/** 注解 `sema.io/run-end-hold-ms` 的原串(建它的副本写的 ① 上界);缺席 = Pod 上没有这一格(或不是串)。判官自己读它({@link judgeSandboxPod})。 */
|
|
50
|
+
readonly runEndHold?: string;
|
|
51
|
+
readonly phase?: string;
|
|
52
|
+
/** `metadata.deletionTimestamp` 在场:已在删除中。 */
|
|
53
|
+
readonly terminating: boolean;
|
|
54
|
+
}
|
|
55
|
+
/** reaper 对沙箱命名空间的窄面(真实装配 = `remote-env-k8s.ts` 的 `createSandboxPodApi`;测试注入假面)。 */
|
|
56
|
+
export interface SandboxPodApi {
|
|
57
|
+
readonly namespace: string;
|
|
58
|
+
/** 按身份标签列本命名空间的沙箱 Pod。失败抛。 */
|
|
59
|
+
list(): Promise<SandboxPodListing[]>;
|
|
60
|
+
/** 删一只;404 ⇒ `gone`。其余失败抛。 */
|
|
61
|
+
delete(name: string): Promise<"deleted" | "gone">;
|
|
62
|
+
}
|
|
63
|
+
/** 一行 run 在判官眼里的两位。状态词按**串**读:滚动升级里新副本写下的词本进程可能不认(`runStatusClassOf` 答 `undefined` ⇒ 不删)。
|
|
64
|
+
* `updatedAt` = 行最后一次被写的时刻(ISO);终态行上它就是转终态那一拍(之后改它的写只会让 reaper 等得更久)。 */
|
|
65
|
+
export interface SandboxPodRunRow {
|
|
66
|
+
readonly status: string;
|
|
67
|
+
readonly updatedAt: string;
|
|
68
|
+
}
|
|
69
|
+
/** run 店的窄读口(三只 run 店共有的 `getRun`)。 */
|
|
70
|
+
export interface SandboxPodRunReader {
|
|
71
|
+
getRun(taskId: string): Promise<SandboxPodRunRow | null | undefined>;
|
|
72
|
+
}
|
|
73
|
+
/** 一只 Pod 的 run 读数(闭集):读到行 / 店答没有这一行 / 读抛 / 本部署没有 run 店可问。 */
|
|
74
|
+
export type SandboxPodRunReading = {
|
|
75
|
+
readonly found: SandboxPodRunRow;
|
|
76
|
+
} | {
|
|
77
|
+
readonly absent: true;
|
|
78
|
+
} | {
|
|
79
|
+
readonly failed: true;
|
|
80
|
+
} | {
|
|
81
|
+
readonly storeAbsent: true;
|
|
82
|
+
};
|
|
83
|
+
export type SandboxPodDeleteReason = "containers_ended" | "run_ended_past_hold";
|
|
84
|
+
export type SandboxPodSkipReason = "terminating" | "no_run_label" | "run_store_absent" | "run_unread" | "run_read_failed" | "run_status_unknown" | "run_live" | "run_end_unreadable" | "hold_unreadable" | "within_run_end_hold";
|
|
85
|
+
export type SandboxPodVerdict = {
|
|
86
|
+
readonly delete: SandboxPodDeleteReason;
|
|
87
|
+
} | {
|
|
88
|
+
readonly skip: SandboxPodSkipReason;
|
|
89
|
+
};
|
|
90
|
+
/** 一只 Pod 删不删(纯判官;法见文件头)。`now` 毫秒;① 的上界只读 Pod 自带的注解(`pod.runEndHold`),没有第二个来源。 */
|
|
91
|
+
export declare function judgeSandboxPod(pod: SandboxPodListing, run: SandboxPodRunReading, now: number): SandboxPodVerdict;
|
|
92
|
+
/** 一轮的读数(轮次行与计数的来源)。`deleted` / `skipped` 只列出现过的原因。 */
|
|
93
|
+
export interface SandboxPodReapRound {
|
|
94
|
+
readonly listed: boolean;
|
|
95
|
+
readonly scanned: number;
|
|
96
|
+
readonly deleted: Partial<Record<SandboxPodDeleteReason, number>>;
|
|
97
|
+
readonly skipped: Partial<Record<SandboxPodSkipReason, number>>;
|
|
98
|
+
/** 判了删、删时已不在(404)。 */
|
|
99
|
+
readonly gone: number;
|
|
100
|
+
readonly deleteFailed: number;
|
|
101
|
+
}
|
|
102
|
+
export interface SandboxPodReaper {
|
|
103
|
+
/** 一轮:列举 → 逐只判 → 删被判删的。不抛(列举失败 ⇒ `listed: false` + F 计数 + 一行 warn)。`runs` 缺席 = 本部署没有 run 店(② 照删)。 */
|
|
104
|
+
reapOnce(runs: SandboxPodRunReader | undefined): Promise<SandboxPodReapRound>;
|
|
105
|
+
}
|
|
106
|
+
export declare function createSandboxPodReaper(deps: {
|
|
107
|
+
readonly api: SandboxPodApi;
|
|
108
|
+
readonly logger?: Pick<Logger, "info" | "warn" | "error">;
|
|
109
|
+
readonly metrics?: Pick<Metrics, "inc">;
|
|
110
|
+
readonly now?: () => number;
|
|
111
|
+
}): SandboxPodReaper;
|
|
112
|
+
//# sourceMappingURL=k8s-pod-reaper.d.ts.map
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { recordFailOpen } from "../observability/fail-open.js";
|
|
2
|
+
import { reportPodDeleteFailed } from "./k8s-pod-delete-failure.js";
|
|
3
|
+
import { runStatusClassOf } from "./store-contracts.js";
|
|
4
|
+
import { LEASE_TTL_MAX_MS } from "../broker/lease-file.js";
|
|
5
|
+
import { WORKSPACE_SNAPSHOT_TIMEOUT_MS_RANGE } from "../workspace-space.js";
|
|
6
|
+
export const SANDBOX_POD_REAP_GRACE_MS = 60_000;
|
|
7
|
+
export const ENGINE_TEARDOWN_RELEASE_MS = 15_000;
|
|
8
|
+
export function sandboxRunEndHoldMs(snapshotTimeoutMs) {
|
|
9
|
+
return Math.max(LEASE_TTL_MAX_MS + SANDBOX_POD_REAP_GRACE_MS, snapshotTimeoutMs + ENGINE_TEARDOWN_RELEASE_MS);
|
|
10
|
+
}
|
|
11
|
+
const RUN_END_HOLD_READABLE = { min: LEASE_TTL_MAX_MS + SANDBOX_POD_REAP_GRACE_MS, max: WORKSPACE_SNAPSHOT_TIMEOUT_MS_RANGE.max + ENGINE_TEARDOWN_RELEASE_MS };
|
|
12
|
+
function runEndHoldOf(raw) {
|
|
13
|
+
if (raw === undefined || !/^[0-9]{1,16}$/.test(raw))
|
|
14
|
+
return undefined;
|
|
15
|
+
const ms = Number(raw);
|
|
16
|
+
return Number.isSafeInteger(ms) && ms >= RUN_END_HOLD_READABLE.min && ms <= RUN_END_HOLD_READABLE.max ? ms : undefined;
|
|
17
|
+
}
|
|
18
|
+
export const SANDBOX_POD_REAP_PERIOD_MS = 5 * 60_000;
|
|
19
|
+
const CLASS_VERDICT = {
|
|
20
|
+
live: "live",
|
|
21
|
+
parked: "live",
|
|
22
|
+
succeeded: "terminal",
|
|
23
|
+
unsuccessful: "terminal",
|
|
24
|
+
};
|
|
25
|
+
export function judgeSandboxPod(pod, run, now) {
|
|
26
|
+
if (pod.terminating)
|
|
27
|
+
return { skip: "terminating" };
|
|
28
|
+
if (pod.phase === "Succeeded" || pod.phase === "Failed")
|
|
29
|
+
return { delete: "containers_ended" };
|
|
30
|
+
if (pod.runId === undefined)
|
|
31
|
+
return { skip: "no_run_label" };
|
|
32
|
+
if ("storeAbsent" in run)
|
|
33
|
+
return { skip: "run_store_absent" };
|
|
34
|
+
if ("failed" in run)
|
|
35
|
+
return { skip: "run_read_failed" };
|
|
36
|
+
if ("absent" in run)
|
|
37
|
+
return { skip: "run_unread" };
|
|
38
|
+
const cls = runStatusClassOf(run.found.status);
|
|
39
|
+
if (cls === undefined)
|
|
40
|
+
return { skip: "run_status_unknown" };
|
|
41
|
+
if (CLASS_VERDICT[cls] === "live")
|
|
42
|
+
return { skip: "run_live" };
|
|
43
|
+
const endedAt = Date.parse(run.found.updatedAt);
|
|
44
|
+
if (!Number.isFinite(endedAt))
|
|
45
|
+
return { skip: "run_end_unreadable" };
|
|
46
|
+
const hold = runEndHoldOf(pod.runEndHold);
|
|
47
|
+
if (hold === undefined)
|
|
48
|
+
return { skip: "hold_unreadable" };
|
|
49
|
+
if (now - endedAt <= hold)
|
|
50
|
+
return { skip: "within_run_end_hold" };
|
|
51
|
+
return { delete: "run_ended_past_hold" };
|
|
52
|
+
}
|
|
53
|
+
export function createSandboxPodReaper(deps) {
|
|
54
|
+
const now = deps.now ?? Date.now;
|
|
55
|
+
return {
|
|
56
|
+
async reapOnce(runs) {
|
|
57
|
+
const deleted = {};
|
|
58
|
+
const skipped = {};
|
|
59
|
+
let gone = 0;
|
|
60
|
+
let deleteFailed = 0;
|
|
61
|
+
let pods;
|
|
62
|
+
try {
|
|
63
|
+
pods = await deps.api.list();
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
const error = e instanceof Error ? e.message : String(e);
|
|
67
|
+
recordFailOpen("server.sandbox-pod-reaper.list-failed", `namespace=${deps.api.namespace} ${error}`);
|
|
68
|
+
deps.logger?.warn("sandbox_pod_reap_list_failed", {
|
|
69
|
+
namespace: deps.api.namespace,
|
|
70
|
+
error,
|
|
71
|
+
effect: "sandbox pods were not deleted this round — ended pods and pods of ended runs (with their Secrets) stay until a later round lists them (one period later)",
|
|
72
|
+
});
|
|
73
|
+
return { listed: false, scanned: 0, deleted, skipped, gone, deleteFailed };
|
|
74
|
+
}
|
|
75
|
+
const reads = new Map();
|
|
76
|
+
const readRun = (runId) => {
|
|
77
|
+
if (runs === undefined)
|
|
78
|
+
return Promise.resolve({ storeAbsent: true });
|
|
79
|
+
let r = reads.get(runId);
|
|
80
|
+
if (r === undefined) {
|
|
81
|
+
r = runs.getRun(runId).then((row) => (row === undefined || row === null ? { absent: true } : { found: row }), () => ({ failed: true }));
|
|
82
|
+
reads.set(runId, r);
|
|
83
|
+
}
|
|
84
|
+
return r;
|
|
85
|
+
};
|
|
86
|
+
const at = now();
|
|
87
|
+
for (const pod of pods) {
|
|
88
|
+
const reading = pod.runId === undefined ? { absent: true } : await readRun(pod.runId);
|
|
89
|
+
const verdict = judgeSandboxPod(pod, reading, at);
|
|
90
|
+
if ("skip" in verdict) {
|
|
91
|
+
skipped[verdict.skip] = (skipped[verdict.skip] ?? 0) + 1;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const outcome = await deps.api.delete(pod.name);
|
|
96
|
+
if (outcome === "gone") {
|
|
97
|
+
gone++;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
deleted[verdict.delete] = (deleted[verdict.delete] ?? 0) + 1;
|
|
101
|
+
deps.metrics?.inc("sandbox_pods_reaped_total", { reason: verdict.delete });
|
|
102
|
+
deps.logger?.info("sandbox_pod_reaped", { namespace: deps.api.namespace, pod: pod.name, reason: verdict.delete, ...(pod.runId !== undefined ? { run: pod.runId } : {}) });
|
|
103
|
+
}
|
|
104
|
+
catch (e) {
|
|
105
|
+
deleteFailed++;
|
|
106
|
+
reportPodDeleteFailed({
|
|
107
|
+
namespace: deps.api.namespace,
|
|
108
|
+
pod: pod.name,
|
|
109
|
+
why: `reaper_${verdict.delete}`,
|
|
110
|
+
error: e,
|
|
111
|
+
effect: "the pod, and the Secret it owns, stay in the sandbox namespace; the reaper tries this pod again next round (one period later), until then it holds static data only",
|
|
112
|
+
logger: deps.logger,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const round = { listed: true, scanned: pods.length, deleted, skipped, gone, deleteFailed };
|
|
117
|
+
deps.logger?.info("sandbox_pod_reap_round", { namespace: deps.api.namespace, ...round });
|
|
118
|
+
return round;
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=k8s-pod-reaper.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Checkpoint, type CheckpointSummary, type CheckpointToken, type PendingSteerInput, type ReopenReason, type ResolveExpectation, type ExecutionOutcomeRecordWord, type GateOutcome, type ResumeOutcome, type StoreDurability, type StoreFidelity } from "@sema-agent/core";
|
|
2
|
-
import { type CheckpointAskCandidate, type
|
|
2
|
+
import { type CheckpointAskCandidate, type CtxRowRewrite, type CtxRowsRewrite, type DecidedApprovalRecord, type PendingCheckpoint, type PendingRowBinding } from "./checkpoint-store-sql.js";
|
|
3
3
|
import type { SealedCheckpointCtx } from "../http/checkpoint-ctx.js";
|
|
4
4
|
export interface LocalCheckpointStoreOptions {
|
|
5
5
|
/** taskId join for the pending card (the local FileRunStore's `getActiveTaskId`). Absent ⇒ `taskId: null`. */
|
|
@@ -213,15 +213,12 @@ export declare class LocalCheckpointStore {
|
|
|
213
213
|
*/
|
|
214
214
|
getCtx<T extends object = object>(sessionId: string): Promise<T | null>;
|
|
215
215
|
/**
|
|
216
|
-
* 🔴
|
|
216
|
+
* 🔴 **一次性迁移的 ctx 行改写店腿**(local 孪生;`checkpoint-store-sql.ts` `CheckpointCtxRowsRewriter`,判词与 SQL 孪生 `rewriteCtxRows` 逐条同):
|
|
217
217
|
* 逐档读(信封经读腿**同一只**判形 —— 读不出 ⇒ 计数、跳过、**不删**),`rewrite` 交回新体 ⇒ 信封原样(`sessionId` / `updatedAt` 不动:收割按 `updatedAt` 判)
|
|
218
218
|
* 只换 `ctx`,经写腿同一只形闸、同一只原子写落回原档。封签绑的是信封里的 `sessionId`(与 `getCtx` 认行的那一格同一个)。
|
|
219
219
|
* 数据根独占 + 起服在任何读写者之前跑 ⇒ 本车道没有并发写者,不需要 CAS;没有一行装不下这回事(`tooLarge` 恒 0)。
|
|
220
220
|
*/
|
|
221
|
-
|
|
222
|
-
readonly ctx: SealedCheckpointCtx;
|
|
223
|
-
readonly values: number;
|
|
224
|
-
} | undefined): Promise<CtxRowsRewriteM28>;
|
|
221
|
+
rewriteCtxRows(rewrite: CtxRowRewrite): Promise<CtxRowsRewrite>;
|
|
225
222
|
/** GC ctx files whose lifecycle is over — same three guards as the SQL twins (stale ∧ no pending ∧ no active run). */
|
|
226
223
|
reapCtx(cutoff: number): Promise<number>;
|
|
227
224
|
/**
|
|
@@ -327,7 +327,7 @@ export class LocalCheckpointStore {
|
|
|
327
327
|
const rec = refusingCtx(sessionId, () => decodeJsonColumn(readFileSync(p, "utf8"), createCtxRecordReader(), `checkpoint-ctx ${ctxFileOf(sessionId)} for session ${JSON.stringify(sessionId)}`));
|
|
328
328
|
return rec.sessionId === sessionId ? rec.ctx : null;
|
|
329
329
|
}
|
|
330
|
-
async
|
|
330
|
+
async rewriteCtxRows(rewrite) {
|
|
331
331
|
const out = { scanned: 0, rewritten: 0, valuesRewritten: 0, unreadable: 0, tooLarge: 0 };
|
|
332
332
|
for (const f of readdirSync(this.ctxDir)) {
|
|
333
333
|
if (!f.endsWith(".json"))
|
|
@@ -335,7 +335,7 @@ export class LocalCheckpointStore {
|
|
|
335
335
|
const p = join(this.ctxDir, f);
|
|
336
336
|
let rec;
|
|
337
337
|
try {
|
|
338
|
-
rec = decodeJsonColumn(readFileSync(p, "utf8"), createCtxRecordReader(), `checkpoint-ctx ${f} (
|
|
338
|
+
rec = decodeJsonColumn(readFileSync(p, "utf8"), createCtxRecordReader(), `checkpoint-ctx ${f} (ctx row rewrite scan)`);
|
|
339
339
|
}
|
|
340
340
|
catch {
|
|
341
341
|
out.unreadable += 1;
|
|
@@ -346,7 +346,7 @@ export class LocalCheckpointStore {
|
|
|
346
346
|
if (next === undefined)
|
|
347
347
|
continue;
|
|
348
348
|
const text = JSON.stringify({ sessionId: rec.sessionId, updatedAt: rec.updatedAt, ctx: next.ctx });
|
|
349
|
-
assertJsonColumnWritable(text, createCtxRecordReader(), `checkpoint ctx for session ${JSON.stringify(rec.sessionId)} (local
|
|
349
|
+
assertJsonColumnWritable(text, createCtxRecordReader(), `checkpoint ctx for session ${JSON.stringify(rec.sessionId)} (local ctx row rewrite)`);
|
|
350
350
|
atomicWriteFile(this.tmpDir, p, text);
|
|
351
351
|
out.rewritten += 1;
|
|
352
352
|
out.valuesRewritten += next.values;
|
|
@@ -43,6 +43,7 @@ export class MemoryRunStore {
|
|
|
43
43
|
errorCode: r.errorCode,
|
|
44
44
|
jobId: r.jobId,
|
|
45
45
|
source: r.source,
|
|
46
|
+
identity: r.identity,
|
|
46
47
|
instanceId: r.instanceId,
|
|
47
48
|
cancelRequested: r.cancelRequested,
|
|
48
49
|
objectivePreview: r.objectivePreview,
|
|
@@ -75,6 +76,7 @@ export class MemoryRunStore {
|
|
|
75
76
|
objectivePreview: meta.objectivePreview ?? null,
|
|
76
77
|
idemKey,
|
|
77
78
|
idemBodyFingerprint: meta.idem?.bodyFingerprint ?? null,
|
|
79
|
+
identity: meta.identity ?? null,
|
|
78
80
|
createdAt: now,
|
|
79
81
|
updatedAt: now,
|
|
80
82
|
cancelRequested: false,
|
|
@@ -109,11 +109,12 @@
|
|
|
109
109
|
* 改名之后它们找不到那一列:已在跑的旧副本每一条规则语句响亮失败(新任务起不来、规则面 500,在跑的腿留着它已执行的快照),旧二进制起服被列册探针拒启 ——
|
|
110
110
|
* 没有一只副本按旧文法读到新文法的行。行里不带文法版本(一个旧写者维护不了的第二个事实,恰在混跑窗里说谎),也不读规则体猜版本。
|
|
111
111
|
* MySQL 协议方言 `CHANGE COLUMN`(MariaDB 10.5 以前没有 `RENAME COLUMN`;定义内插同一串),PG `RENAME COLUMN`;成功判据是后置条件(列名换过来了)。
|
|
112
|
-
* ②
|
|
112
|
+
* ② **改名那一拍的改写:每一行在它自己的文法里由引擎读,改写成文法 1 里的同一句话**(core 7.51.2 [ref],行自带规则文法戳 `grammar`:写口判官盖、店整只存、
|
|
113
113
|
* 引擎按行自带的戳读;无戳行 = 戳之前写的行,引擎读成文法 0)。改名那一刻库里每一行都是无戳行(≤7.128.x 写的)—— 判据只有引擎的:行(体 + `rev`)经
|
|
114
114
|
* `readSessionRulesShape` 读成它自己的文法,`compileSessionRuleBucket` 在那种文法下编译 `toolAllow`,归 `earlier_grammar` 的条目(文法 0 里拼写不是它所指名字的条目
|
|
115
|
-
* —— `Bash(rm:*)` / `Edit(src/**)` / `Bash(*)`:什么都不放行,文法 1 里是一条放行规则)删掉,其余原样,盖 `grammar:
|
|
116
|
-
*
|
|
115
|
+
* —— `Bash(rm:*)` / `Edit(src/**)` / `Bash(*)`:什么都不放行,文法 1 里是一条放行规则)删掉,其余原样,盖 `grammar: 1` —— 文法 0 与 1 在引擎里只差
|
|
116
|
+
* 这一条子句,所以改写后的行在文法 1 里是原行的逐调用同义重述(名单保持在场:删到空 = 在场的空名单,什么都不放行);之后几次修订由引擎按行戳读、保住写者的意思
|
|
117
|
+
* (7.137.0 起;此前盖的是当时的当前文法,引擎 7.56.0 把名内 `*` 的意思改了之后那一盖会越过修订、静默把 `toolDeny: ["*"]` 变成拒每一只工具)。**每一行**都改写(只盖戳的也写):改写之后
|
|
117
118
|
* 部署里零无戳行,除下一条留下的行(引擎 7.52.0 [ref] 起文法 0 里的 `*` 也是 `earlier_grammar` 惰性条目、只作废它自己 —— 与别的读不出的 allow 条目同一条判:删、盖戳;7.129.0–7.131.x 引擎把带 `*` 的名单整张读成作废,本步对那种行曾不改写、只点名,那一臂随引擎修正退役)。
|
|
118
119
|
* **盖戳前先问引擎读者**([ref],引擎 7.52.0 [ref]):行的 `toolDeny` 里有 `unreadableDenyEntries` 答的条目(引擎在这一行的文法里读成「不是任何一条规则」:空串、`-deploy`、非 ASCII 名、`(` 前带不可见空白 ……
|
|
119
120
|
* 7.129.0–7.131.x 的写口收下过)⇒ 引擎对这一行的**每一次工具调用都拒**(读成更少的规则是 fail-open,引擎不这么读),并按行自带的旧文法通告(`config.session_rules_earlier_grammar`,只对戳早于当前文法的行发)。
|
|
@@ -252,7 +253,7 @@
|
|
|
252
253
|
* `http/checkpoint-ctx.ts` 末段「凭据位密封」;本处不重列)在 `checkpoint_ctx` 里只以封签存在,续跑上下文读口对凭据位上的明文答 409 `conflict.checkpoint_ctx_unsealable`(`reason:"plaintext"`)。升级之前写下的
|
|
253
254
|
* 行是明文 ⇒ **起服扫一遍、原地封好**:不在本文件的迁移段里(封要保管店的封签器,而本段跑在建店的 web-admin 运行时能交给它之前的店层 ——
|
|
254
255
|
* 与 M10 / M19 / M25 同为在本段之外跑的条目),编排在 `checkpoint-ctx-seal-m28.ts`(起服第 6 段,checkpoint 店建成之后、任何路由装配之前调一次),
|
|
255
|
-
*
|
|
256
|
+
* 店腿是两孪生共有的 ctx 行改写腿 `rewriteCtxRows`(`checkpoint-store-sql.ts` `CheckpointCtxRowsRewriter`;M30 用同一只;SQL:键集分页 + 按原文 CAS、`updated_at_ms` 不动;local:逐档原子重写、信封的 `updatedAt` 不动),
|
|
256
257
|
* 行改写是读写两侧同一只封口(`sealStoredCheckpointCtxM28`),封的是「读口会答 `plaintext` 的每一个值」(与开口同一只判词)—— 7.135.0 闭集加了一位
|
|
257
258
|
* (command 条目的 `plugin.options`)之后,7.134.0 写下的带记号行在那一位上的明文也落在这一条里、同窗补封(本条没有新窗);7.136.0 再加四位(子进程的命令行:MCP stdio 传输与
|
|
258
259
|
* command 条目各自的 `command` / `args`,第 3 代)同理 —— 无记号行与 7.134.0 / 7.135.0 写下的第 1 / 2 代行上的明文命令行同窗补封。不删行、不改续跑语义;读不出的行计数不动;已封与不带凭据的行零写。读数不进
|
|
@@ -271,6 +272,18 @@
|
|
|
271
272
|
* 迁移段先于起服列册对账,缺列表上的扫描会抛裸的「未知列」、盖住随后的拒启句)。到期 facet 的生成谓词与运维队列行的卡读这一列 —— 本条跑完之前它们不运行
|
|
272
273
|
* (迁移段先于任何路由与收割器装配)。**够不到的一格(如实登记)**:已决 / 已作废的行不扫(没有读者读它们的卡);local 车道没有这一列(停驻行在引擎自己的 File 店,
|
|
273
274
|
* 卡从文档读)。窗 = 发布日 + 30 天(与 M21 同一条论证:窗关时缺省部署上升级前停驻的行已被终局兜底收走)。
|
|
275
|
+
* · **M30 停驻行请求体里的退役键 `promptProfile` 原地改写**(7.137.0,引擎 7.56.0)—— 提示词形收成布尔 `promptLean`,双形词 `promptProfile` 整键退役(新提交在键闭集门 400)。
|
|
276
|
+
* 7.136.x 及以前停驻的任务,`checkpoint_ctx` 存的请求体带旧键 —— 续跑腿重放它而没有读者 ⇒ `"classic"`(要长形)静默回到模型声明的短形。⇒ **起服扫一遍、原地改写**
|
|
277
|
+
* (与 M28 同形同处:不在本文件的迁移段里 —— 那一段跑在 checkpoint 店建成之前;编排在 `stored-request-prompt-profile-m30.ts`,起服第 6 段 M28 之后调一次,
|
|
278
|
+
* 早于任何路由装配;店腿是两孪生共有的 `rewriteCtxRows`,与 M28 同一只;行改写 = `http/checkpoint-ctx.ts` `rewriteStoredBodyKeys`,只动体的这两个顶层键,
|
|
279
|
+
* 读法记号与凭据位原样随行)。映射与 MIGRATION 写给调用方的同一张:`"classic"` 且体里没有布尔 `promptLean` ⇒ 删旧键、写 `promptLean: false`;`"simple"` / 词表外 /
|
|
280
|
+
* 体里已有布尔 `promptLean` ⇒ 只删旧键。已改写与不带旧键的行零写;读不出的行计数不动;续跑时钟不动。读数不进 {@link OneTimeMigrationReport}(`reportKeys` 空,与 M28 同形),
|
|
281
|
+
* 由起服 info 一行 `checkpoint_ctx_prompt_profile_rewritten` 说(行数,不带值)。窗外不扫。**续跑腿不读旧键**(没有按日历分的第二种读法);存体里仍带它的行
|
|
282
|
+
* (窗内从没起过本版的部署;滚动升级里旧副本在本版起服扫描之后才写下的行)由续跑判据响亮拒(`http/stored-body-replay.ts` 第四问,409 `checkpoint.unsupported_version`),
|
|
283
|
+
* 窗内再起一次本版即被改写。窗 = 发布日 + 30 天(与 M28 / M29 同一条论证:窗关时缺省部署上升级前停驻的行已被终局兜底收走)。**本条不覆盖混跑(如实登记)**:
|
|
284
|
+
* ≤7.136.x 的读者只认 `promptProfile`,读到本条改写过的体(旧键已删、换成 `promptLean`)回到缺省形 —— 本版副本起服之后仍在续跑的 7.136.x 副本、回滚到 7.136.x 之后,
|
|
285
|
+
* 被改写过的 `"classic"` 行静默回短形;也不能给旧读者留旧键(那是同一事实的两份表示,本版读者对仍带旧键的体响亮拒)⇒ 升级序是**先停光 ≤7.136.x 副本、再起第一个 7.137.0 副本**,
|
|
286
|
+
* 回滚之前先决完或取消停驻的任务(随包 MIGRATION 7.137.0 第 4 条;与 M28 的混跑登记同形)。
|
|
274
287
|
*
|
|
275
288
|
* ## 收据账(一次起服一只,每一条迁移同一只)
|
|
276
289
|
* 每一步把它**已提交**的改写 / 删除与点名读数当场记进同一只收据账({@link MigrationReceipts}):计数随每次提交累加、行随每次提交追加、形读数在那一步落定时写。走完 ⇒
|
|
@@ -460,9 +473,15 @@ export declare const ONE_TIME_MIGRATIONS: readonly OneTimeMigrationWindow[];
|
|
|
460
473
|
/**
|
|
461
474
|
* 一条登记行的窗**此刻**开着没有(`today` = UTC 日历日 YYYY-MM-DD):未发布形(`removableAfter: null`)⇒ 开;否则今天不晚于 `removableAfter` ⇒ 开
|
|
462
475
|
* (窗关当天仍开,与到期门「今天严格晚于才红」同一条边界)。
|
|
463
|
-
*
|
|
476
|
+
* 给**窗内才生效的行为臂**(M10 的认领、M19 / M20 的续跑重读)与本文件迁移段之外的起服扫描(M28 / M30)用:过了窗就停,不等删码车 —— 删码因此是零行为变化,而窗只写在本表这一处。
|
|
464
477
|
*/
|
|
465
478
|
export declare function migrationWindowOpen(window: Pick<OneTimeMigrationWindow, "removableAfter">, today: string): boolean;
|
|
479
|
+
/**
|
|
480
|
+
* 一条登记行的窗的**最后一天**(= `removableAfter`,UTC 日历日 YYYY-MM-DD;与 {@link migrationWindowOpen} 同一条边界:这一天仍开)—— 给**要在句子里写绝对日期**的读者读
|
|
481
|
+
* (续跑拒句「在这一天之前重启即被改写」):窗只写在本表这一处,句子不手写第二份。行不在(读者活过了它的登记行,该随那一行一起删)或未发布形(`removableAfter: null`,
|
|
482
|
+
* 还没有日期可写)⇒ 响亮抛,不铸一句带空日期的句子。
|
|
483
|
+
*/
|
|
484
|
+
export declare function migrationWindowEnd(id: string): string;
|
|
466
485
|
/** local 车道上 SQL 独有的几条(M8 / M9 / M11 / M17 / M18 / M21 / M23 / M24 / M26 / M27 / M29)的读数(M6 两车道都有,local 腿的读数由 `LocalBackend.runOneTimeMigrations` 现算):那几张表 / 列在文件底座上从来不存在 ⇒ 如实答 absent / 0。 */
|
|
467
486
|
export declare const NO_SQL_MIGRATIONS: Omit<OneTimeMigrationReport, "holderDeathCodeRowsBackfilled">;
|
|
468
487
|
/** 启动期的日志口(logger 的子集)。两级各有其用,见 {@link logOneTimeMigrations}。 */
|
|
@@ -520,26 +539,26 @@ export declare function moveWorkflowJournalScope(db: MigrationDb, dialect: "tidb
|
|
|
520
539
|
* 不是对象 / 没有这两个键 / 条目文字里出现同名字样 ⇒ 空。不改写:新文法写不出旧命令桶的拒(头注 M17 段 ③)。
|
|
521
540
|
*/
|
|
522
541
|
export declare function retiredSessionRuleBucketsOf(body: unknown): readonly RetiredSessionRuleBucket[];
|
|
523
|
-
/** M17 ——
|
|
542
|
+
/** M17 —— 一份存量规则体按引擎在它自己的文法里的读法改写成文法 1 里的同一句话({@link oldGrammarEntriesOf} 的读数)。 */
|
|
524
543
|
export interface OldGrammarEntries {
|
|
525
544
|
/** toolAllow 里删掉的条目(引擎的 `earlier_grammar` 读数;见 {@link SessionRuleRowOldGrammarEntries.toolAllowDropped})。行因 {@link toolDenyUnreadable} 整行不动时恒空(没写 ⇒ 没删)。 */
|
|
526
545
|
readonly toolAllowDropped: readonly string[];
|
|
527
546
|
/** toolDeny 里引擎 `unreadableDenyEntries` 读成「不是任何一条规则」的条目(条目 + 引擎读者的原因词)。**非空 ⇒ 这一行不改写、不盖戳**({@link rewritten} 缺席):
|
|
528
547
|
* 这种行引擎对每一次工具调用都拒(读成更少的规则是 fail-open,引擎不这么读),并按行自带的旧文法通告;盖成当前文法戳只会让通告消失、行照旧拒一切。缺席 = 没有这样的条目。 */
|
|
529
548
|
readonly toolDenyUnreadable?: readonly UnreadableDenyEntry[];
|
|
530
|
-
/** 这一行要落的新体 —— 被删条目去掉、其余键原样(退役命令桶也原样)、`grammar:
|
|
549
|
+
/** 这一行要落的新体 —— 被删条目去掉、其余键原样(退役命令桶也原样)、`grammar: 1`(本步证过同义的那一版);在场 = 要写。缺席 = 零动作:行已带戳且没有可删的条目
|
|
531
550
|
* (上一次没走完的判读已改写过的行)。 */
|
|
532
551
|
readonly rewritten?: Record<string, unknown>;
|
|
533
552
|
}
|
|
534
553
|
/**
|
|
535
554
|
* M17 —— 一行存量规则(体 + 它的 `rev`)的改写判读(纯函数,判据只有引擎的):
|
|
536
555
|
* · 行按引擎的形读法在**它自己的文法**里读 —— `readSessionRulesShape` 对与 `rev` 并排的值按戳读,无戳 ⇒ 文法 0(戳之前写的行);退役命令桶先放一边(形读法拒它们,
|
|
537
|
-
* 而改写的是同一行其余的桶 ——
|
|
556
|
+
* 而改写的是同一行其余的桶 —— 人按出路删掉退役桶的那一刻,行已是文法 1 的真话);
|
|
538
557
|
* · `toolAllow` 经 `compileSessionRuleBucket` 在那种文法下编译,归 `earlier_grammar` 的条目删掉(文法 0 与 1 在引擎里只差这一条子句:拼写不是它所指名字的 allow 条目
|
|
539
558
|
* 在文法 0 里什么都不放行、在文法 1 里是一条放行规则),名单**保持在场**(在场的空 `toolAllow` 引擎读作「什么都不放行」);别的桶一个字节不动;
|
|
540
559
|
* · **盖戳前先问引擎读者**([ref]):行的 `toolDeny` 里有 `unreadableDenyEntries` 答的条目(引擎在这一行的文法里读成「不是任何一条规则」)⇒ **整行不改写、不盖戳、不删任何条目**,
|
|
541
560
|
* 条目与读者的原因词随 {@link OldGrammarEntries.toolDenyUnreadable} 交出(收据点名)。引擎对这种行本来就拒一切 —— 本函数只问、不判:不复刻引擎的条目读者,不替运维删条目或改写。
|
|
542
|
-
* · 新体盖 `grammar:
|
|
561
|
+
* · 新体盖 `grammar: 1`({@link M17_PROVEN_GRAMMAR}:本步证过同义的那一版):无戳行每一行都写(改写之后部署里零无戳行 —— 除上一条留下的行);已带戳且没有可删条目的行零动作(幂等)。
|
|
543
562
|
* 读不出形(不是对象、有别的非桶键、桶不是一列字符串、坏戳 / 比引擎新的戳)⇒ `undefined`,不判(读回照旧 `corrupt`)。本函数零处读 / 比较戳的数值:
|
|
544
563
|
* 「有没有戳」是形读法自己判戳之前的同一问;条目在哪种文法下算什么只有引擎读。
|
|
545
564
|
*/
|