@sema-agent/server 7.135.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 +237 -2
- package/USAGE.md +146 -41
- package/deploy/sema-up/README.md +15 -1
- package/deploy/sema-up/chart/templates/NOTES.txt +1 -1
- package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +7 -1
- package/deploy/sema-up/chart/values.yaml +1 -1
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +7 -1
- package/dist/approval-card.d.ts +60 -4
- package/dist/approval-card.js +18 -1
- package/dist/approval-content-kind.d.ts +7 -0
- package/dist/approval-content-kind.js +8 -1
- 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.d.ts +34 -5
- package/dist/auth-bridge.js +22 -5
- package/dist/boot/budget-tracing.d.ts +1 -2
- package/dist/boot/budget-tracing.js +2 -2
- package/dist/boot/execution-env.d.ts +24 -1
- package/dist/boot/execution-env.js +69 -6
- package/dist/boot/org-memory.js +1 -1
- 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 +24 -22
- package/dist/boot/runner-deps.js +4 -2
- package/dist/boot/stage-03-store-backend.d.ts +1 -1
- package/dist/boot/stage-04-stores.d.ts +2 -2
- package/dist/boot/stage-05-execution-env.d.ts +4 -2
- package/dist/boot/stage-05-execution-env.js +11 -2
- package/dist/boot/stage-06-runners.d.ts +4 -2
- package/dist/boot/stage-06-runners.js +5 -1
- package/dist/boot/stage-07-capability-layer.d.ts +5 -2
- package/dist/boot/stage-07-capability-layer.js +1 -1
- package/dist/boot/stage-08-reapers.d.ts +5 -2
- package/dist/boot/stage-08-reapers.js +2 -1
- package/dist/boot/stage-09-leader.d.ts +5 -2
- package/dist/boot/stage-10-http-server.d.ts +5 -2
- package/dist/boot/stage-10-http-server.js +3 -2
- package/dist/boot/stores.d.ts +1 -1
- package/dist/boot/stores.js +2 -3
- package/dist/boot/web-admin.js +10 -17
- package/dist/brain.js +12 -3
- 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/budget.d.ts +3 -3
- package/dist/budget.js +4 -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/mcp-probe.d.ts +5 -4
- 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/capability-facts.d.ts +3 -7
- package/dist/capability-facts.js +3 -5
- package/dist/center-credential-renewer.js +5 -1
- package/dist/center-credential.d.ts +18 -4
- package/dist/center-credential.js +18 -3
- 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 +21 -16
- package/dist/config-d-migration.js +1 -1
- package/dist/config-invariants.d.ts +1 -1
- package/dist/config-invariants.js +8 -0
- package/dist/config-lkg.d.ts +0 -38
- package/dist/config-lkg.js +1 -83
- package/dist/config-types.d.ts +21 -4
- package/dist/config.d.ts +1 -1
- package/dist/config.js +60 -16
- package/dist/deployment-governance.d.ts +2 -2
- package/dist/deployment-governance.js +7 -9
- package/dist/deployment-shape.d.ts +1 -1
- package/dist/device-ws-protocol.d.ts +1 -0
- package/dist/device-ws-protocol.js +2 -1
- package/dist/elicitation.d.ts +8 -7
- package/dist/elicitation.js +9 -8
- package/dist/file-history-disclosure.d.ts +8 -4
- package/dist/file-history-disclosure.js +24 -6
- package/dist/fleet-client.d.ts +32 -1
- package/dist/fleet-client.js +24 -4
- package/dist/fleet-lease.d.ts +7 -3
- package/dist/fleet-lease.js +31 -23
- package/dist/hooks/classic-stations.d.ts +11 -54
- package/dist/hooks/hook-llm.js +14 -8
- package/dist/hooks/hook-runner.d.ts +4 -4
- package/dist/hooks/hook-runner.js +2 -2
- 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 +76 -16
- package/dist/http/checkpoint-ctx.js +45 -1
- package/dist/http/dispatch.js +24 -19
- 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-ctx-read.d.ts +2 -16
- package/dist/http/resume-ctx-read.js +2 -3
- 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.d.ts +0 -2
- package/dist/http/routes/approvals-assistant.js +65 -16
- 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 +4 -3
- package/dist/http/routes/session-sync.js +70 -12
- package/dist/http/routes/sessions.js +20 -15
- 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/submit-ctx.js +1 -1
- package/dist/http/wire-types.d.ts +57 -30
- package/dist/identity-source.d.ts +65 -0
- package/dist/identity-source.js +45 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -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-route-gap.d.ts +49 -4
- package/dist/model-route-gap.js +34 -1
- 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/object-store-buckets.d.ts +13 -0
- package/dist/object-store-buckets.js +3 -0
- package/dist/observability/fail-open.d.ts +19 -7
- package/dist/observability/fail-open.js +19 -7
- package/dist/observability/metrics.js +2 -2
- package/dist/observability/principal-context.d.ts +4 -1
- package/dist/observability/principal-context.js +6 -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.d.ts +4 -3
- package/dist/parked-decide.js +29 -9
- package/dist/peer-directory.d.ts +2 -1
- package/dist/peer-directory.js +7 -5
- package/dist/permission-rule-list.d.ts +2 -2
- package/dist/plugins/approval-ask-store-sql.d.ts +8 -0
- package/dist/plugins/approval-ask-store-sql.js +3 -0
- package/dist/plugins/blob-backend.js +2 -1
- package/dist/plugins/checkpoint-index-columns.d.ts +50 -1
- package/dist/plugins/checkpoint-index-columns.js +28 -0
- package/dist/plugins/checkpoint-store-sql.d.ts +46 -12
- package/dist/plugins/checkpoint-store-sql.js +64 -35
- package/dist/plugins/file-history-store-sql.d.ts +6 -0
- package/dist/plugins/file-history-store-sql.js +37 -37
- 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 +7 -6
- package/dist/plugins/memory-run-store.js +2 -0
- package/dist/plugins/one-time-migrations.d.ts +49 -37
- package/dist/plugins/one-time-migrations.js +87 -128
- 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 +154 -14
- package/dist/plugins/remote-env-k8s.js +300 -21
- 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/s3-answer.d.ts +15 -0
- package/dist/plugins/s3-answer.js +5 -0
- package/dist/plugins/session-ownership.d.ts +17 -3
- package/dist/plugins/session-ownership.js +19 -5
- package/dist/plugins/snapshot-bucket-presence.d.ts +39 -4
- package/dist/plugins/snapshot-bucket-presence.js +35 -7
- package/dist/plugins/sql-errors.d.ts +60 -0
- package/dist/plugins/sql-errors.js +64 -1
- package/dist/plugins/store-contracts.d.ts +10 -1
- package/dist/plugins/store-fault.d.ts +4 -0
- package/dist/plugins/store-fault.js +10 -0
- package/dist/plugins/tidb-pool.js +8 -1
- package/dist/plugins/tidb-session-store.js +2 -2
- package/dist/plugins/web-search.d.ts +1 -1
- package/dist/plugins/web-search.js +2 -2
- package/dist/request-declaration-refusal.d.ts +94 -0
- package/dist/request-declaration-refusal.js +78 -0
- package/dist/request-image-urls.d.ts +6 -0
- package/dist/request-image-urls.js +27 -0
- package/dist/request-key-closure.d.ts +5 -0
- package/dist/request-key-closure.js +5 -2
- 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-caps-resolver.js +1 -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/sandbox-pkg-source.d.ts +28 -1
- package/dist/sandbox-pkg-source.js +1 -2
- package/dist/sealed-key.d.ts +19 -0
- package/dist/sealed-key.js +47 -4
- 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-incarnation.d.ts +16 -5
- package/dist/session-incarnation.js +7 -4
- package/dist/session-policy-wire.d.ts +1 -1
- package/dist/session-policy-wire.js +2 -2
- package/dist/session-row.d.ts +3 -17
- package/dist/session-row.js +4 -0
- package/dist/session-sync.d.ts +15 -3
- package/dist/session-sync.js +5 -2
- 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-a2a.d.ts +52 -81
- package/dist/task-a2a.js +219 -89
- package/dist/task-mcp.d.ts +7 -15
- package/dist/task-mcp.js +16 -51
- package/dist/task-settings.d.ts +2 -2
- package/dist/tool-approval.d.ts +35 -12
- package/dist/tool-approval.js +59 -39
- package/dist/tool-rule-names.d.ts +5 -0
- package/dist/trace/core-keyset-guard.d.ts +5 -5
- 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 +37 -13
- 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 +40 -12
- package/dist/trace/redact.js +332 -128
- package/dist/trace/self-minted-wire.d.ts +641 -0
- package/dist/trace/self-minted-wire.js +201 -0
- package/dist/trace/wire-projection-faces.d.ts +5 -3
- package/dist/trace/wire-projection-faces.js +7 -3
- package/dist/url-credential-refusal.d.ts +52 -0
- package/dist/url-credential-refusal.js +14 -0
- package/dist/url-refusal.d.ts +13 -4
- package/dist/url-refusal.js +8 -2
- 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/dist/workspace-space.d.ts +27 -0
- package/dist/workspace-space.js +26 -0
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +11 -5
- package/package.json +4 -3
- package/scripts/drain-check-local.mjs +2 -1
- package/scripts/drain-check.mysql.sql +3 -1
- package/scripts/drain-check.pg.sql +3 -1
- package/skills/schedule.md +1 -1
- package/wire-changes/server@7.104.0.json +134 -0
- package/wire-changes/server@7.105.0.json +53 -0
- package/wire-changes/server@7.106.0.json +475 -0
- package/wire-changes/server@7.107.0.json +64 -0
- package/wire-changes/server@7.108.0.json +153 -0
- package/wire-changes/server@7.109.0.json +46 -0
- package/wire-changes/server@7.110.0.json +90 -0
- package/wire-changes/server@7.111.0.json +86 -0
- package/wire-changes/server@7.112.0.json +60 -0
- package/wire-changes/server@7.113.0.json +26 -0
- package/wire-changes/server@7.114.0.json +26 -0
- package/wire-changes/server@7.115.0.json +97 -0
- package/wire-changes/server@7.116.0.json +218 -0
- package/wire-changes/server@7.116.1.json +26 -0
- package/wire-changes/server@7.117.0.json +65 -0
- package/wire-changes/server@7.118.0.json +129 -0
- package/wire-changes/server@7.119.0.json +65 -0
- package/wire-changes/server@7.120.0.json +89 -0
- package/wire-changes/server@7.121.0.json +219 -0
- package/wire-changes/server@7.122.0.json +54 -0
- package/wire-changes/server@7.123.0.json +26 -0
- package/wire-changes/server@7.124.0.json +336 -0
- package/wire-changes/server@7.124.1.json +26 -0
- package/wire-changes/server@7.125.0.json +111 -0
- package/wire-changes/server@7.126.0.json +285 -0
- package/wire-changes/server@7.127.0.json +77 -0
- package/wire-changes/server@7.127.1.json +26 -0
- package/wire-changes/server@7.127.2.json +53 -0
- package/wire-changes/server@7.128.0.json +26 -0
- package/wire-changes/server@7.129.0.json +106 -0
- package/wire-changes/server@7.129.1.json +26 -0
- package/wire-changes/server@7.130.0.json +26 -0
- package/wire-changes/server@7.131.0.json +118 -0
- package/wire-changes/server@7.132.0.json +226 -0
- package/wire-changes/server@7.133.0.json +1155 -0
- package/wire-changes/server@7.134.0.json +107 -0
- package/wire-changes/server@7.135.0.json +174 -0
- package/wire-changes/server@7.136.0.json +448 -0
- package/wire-changes/server@7.137.0.json +825 -0
- package/dist/url-credential.d.ts +0 -52
- package/dist/url-credential.js +0 -49
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
import type { Pool as MySqlPool } from "mysql2/promise";
|
|
2
2
|
import type { Pool as PgPool } from "pg";
|
|
3
3
|
import { type ExecutionOutcomeRecordWord, type GateOutcome, type Checkpoint, type CheckpointGate, type CheckpointState, type CheckpointStore, type CheckpointSummary, type CheckpointToken, type PendingSteerInput, type ResumeOutcome, type ResolvedOutcome, type ResolveExpectation, type ReopenReason, type RiskDescriptor, type StoreDurability, type StoreFidelity, type ToolApprovalCard } from "@sema-agent/core";
|
|
4
|
-
import { type ParkRowFacts, type RuleOfferProjection } from "../approval-card.js";
|
|
4
|
+
import { type ParkRowFacts, type RuleOfferProjection, type SkillCardFacts } from "../approval-card.js";
|
|
5
5
|
import { type SqlDialect, type SqlDriver } from "./sql-driver.js";
|
|
6
6
|
import type { SealedCheckpointCtx } from "../http/checkpoint-ctx.js";
|
|
7
|
-
/**
|
|
8
|
-
export interface
|
|
7
|
+
/** ctx 行改写店腿的读数(两孪生同形):扫到的可读行 · 回写成的行 · 回写成的行里改写函数报的改动数 · 读不出的行 · 改写之后一行装不下的行。 */
|
|
8
|
+
export interface CtxRowsRewrite {
|
|
9
9
|
readonly scanned: number;
|
|
10
10
|
readonly rewritten: number;
|
|
11
11
|
readonly valuesRewritten: number;
|
|
12
12
|
readonly unreadable: number;
|
|
13
13
|
readonly tooLarge: number;
|
|
14
14
|
}
|
|
15
|
+
/** 一行的改写:交回新的落盘形(只有封口模块的铸点产得出 —— 品牌型)与这一行的改动数;`undefined` = 这一行不写。 */
|
|
16
|
+
export type CtxRowRewrite = (sessionId: string, ctx: object) => {
|
|
17
|
+
readonly ctx: SealedCheckpointCtx;
|
|
18
|
+
readonly values: number;
|
|
19
|
+
} | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* 🔴 **一次性迁移的 ctx 行改写店腿**(两孪生:`SqlCheckpointStore` / `LocalCheckpointStore` 的 `rewriteCtxRows`;逐行判词两孪生同):扫全部 `checkpoint_ctx` 行、
|
|
22
|
+
* 交给 `rewrite`、交回新体的行原地回写(续跑时钟不动)。店不知道改写是什么 —— 用它的迁移各自带行改写:M28(凭据位明文封好)、M30(退役请求键改写)。
|
|
23
|
+
* 窗与删法逐条在 `plugins/one-time-migrations.ts` 登记表;登记表里最后一条用它的迁移删码时,本腿、读数型与扫描页一起删。
|
|
24
|
+
*/
|
|
25
|
+
export interface CheckpointCtxRowsRewriter {
|
|
26
|
+
rewriteCtxRows(rewrite: CtxRowRewrite): Promise<CtxRowsRewrite>;
|
|
27
|
+
}
|
|
15
28
|
export { approvalPayloadFingerprint } from "./approval-payload-fingerprint.js";
|
|
16
29
|
/**
|
|
17
30
|
* [ref] D-1 (§3 invariant #3 — crash-safe reaper backstop): an ABSOLUTE upper bound on a pending
|
|
@@ -43,8 +56,11 @@ export declare function tokenFingerprint(token: string): string;
|
|
|
43
56
|
*
|
|
44
57
|
* 🔴 [ref]:行**顶层**另带 {@link ParkRowFacts}(`requiresRealApproval` / `ruleOffersAbsence`,真才带、缺席 ≠ false)——
|
|
45
58
|
* 型与值都只有一个来源:`approval-card.ts` 的 {@link parkRowFacts}(两只店各读自己手里的那只寄存行 `pendingAction`,
|
|
46
|
-
* 交给它、原样展开)。继承而不是在这里逐键再写一遍:core 给这一臂再加一位同类事实时,改的只有那张 Pick 表。
|
|
47
|
-
|
|
59
|
+
* 交给它、原样展开)。继承而不是在这里逐键再写一遍:core 给这一臂再加一位同类事实时,改的只有那张 Pick 表。
|
|
60
|
+
*
|
|
61
|
+
* 技能卡三位({@link SkillCardFacts}:`boundGrantDigest` / `boundGrants` / `classifierNoRuling`)另有一只投影 {@link skillCardFacts},输入是 core 的检查点摘要而不是
|
|
62
|
+
* 寄存行原值(清单只在哈希等于摘要时才配上 wire,校验点单点在 core —— 见 {@link skillCardFacts} 顶注);同一张行上与收件箱行逐字同值,在场才带。 */
|
|
63
|
+
export interface PendingCheckpoint extends ParkRowFacts, SkillCardFacts {
|
|
48
64
|
sessionId: string;
|
|
49
65
|
/**
|
|
50
66
|
* 这道停驻门的**非秘密稳定身份**(core `Checkpoint.checkpointId`,引擎每次停驻铸)—— 三处 durable 读面(本行 = `GET /v1/approvals` 的 `pending[]`
|
|
@@ -660,6 +676,12 @@ export declare class SqlCheckpointStore implements CheckpointStore {
|
|
|
660
676
|
* approval gate (facet `B`) is not here: core refuses `sla_timeout` on it pre-CAS, and reapExpired voids it. The filter is
|
|
661
677
|
* in SQL, before the LIMIT: `limit` bounds the per-tick deny-resume work (each is a model run), never hides a row behind
|
|
662
678
|
* rows the sweep does not own.
|
|
679
|
+
*
|
|
680
|
+
* Order ({@link CHECKPOINT_SWEEP_ORDER_SQL}): rows whose `approval_card` column carries a word come first, then rows whose card
|
|
681
|
+
* is NULL, each by deadline. A NULL card on an approval gate is either a gate the engine gave no card (a real SLA row) or a
|
|
682
|
+
* row a replica that does not know the column wrote during a mixed-version window — possibly a question, which the engine
|
|
683
|
+
* refuses to deny (`sla_timeout`) and which the next boot's M29 backfill settles. The column cannot tell the two apart, so the
|
|
684
|
+
* order does: rows whose facet is certain take the window first and a straggler cannot crowd them out.
|
|
663
685
|
*/
|
|
664
686
|
listExpiredApprovalGates(cutoff: number, limit?: number): Promise<Array<{
|
|
665
687
|
sessionId: string;
|
|
@@ -676,6 +698,21 @@ export declare class SqlCheckpointStore implements CheckpointStore {
|
|
|
676
698
|
* 真正需要它的读面是**已决行**的那一条:`findDecidedApprovalsForBinding`(decide 幂等回放),见那里。
|
|
677
699
|
*/
|
|
678
700
|
listPending(scope?: string): Promise<PendingCheckpoint[]>;
|
|
701
|
+
/**
|
|
702
|
+
* 🔴 [ref] —— 运维队列行的**技能卡三位**({@link SkillCardFacts})。值与收件箱行同源:core `summarizeCheckpoint` 对同一行投的摘要,经 {@link skillCardFacts} 这一只投影。
|
|
703
|
+
*
|
|
704
|
+
* 为什么不像 {@link parkRowFacts} 那几位一样直接读 `pendingAction` 子树原值:三位里的**清单** `boundGrants` 在寄存行上是未经哈希校验的原值,core 只在摘要里「哈希等于摘要」
|
|
705
|
+
* 才发布它(「never a list beside a digest that binds another」);校验读口与哈希函数都不在 core 的公开导出面上,本仓既算不出也不该再写一份 ——
|
|
706
|
+
* 于是要拿到校验过的清单,只有对这一行跑 core 的 `summarizeCheckpoint`,而它吃的是整只 {@link Checkpoint}(不是子树)。
|
|
707
|
+
*
|
|
708
|
+
* 读放大(如实):**只对行上真带三位之一的行**(摘要是串,或成因词键在场 —— {@link parkRowCarriesSkillCardFacts})多读该行一次整只快照;普通卡(绝大多数行)零额外读,
|
|
709
|
+
* 主 SELECT 仍只取 `pendingAction` 子树(NP-3 不退)。这是一个**只增不改的代价**:带三位之一的待决行数 × 读次数(`/stream` 每连接每 3s 一拍)。
|
|
710
|
+
* 🚧 清单校验读口成为 core 公开导出的那一版,本读口与门一并退役,三位改走 `parkRowFacts` 读原值经 core 的读口 —— 零额外读。
|
|
711
|
+
*
|
|
712
|
+
* 失败方向与 {@link readParkRowActionCell} 逐字同一条判据(展示/分诊材料、行级容忍 + 响亮、同一个 tag):整只快照在场却不是一只合法的检查点(读回的体坏了,或 core 的摘要读不动它)
|
|
713
|
+
* ⇒ 只丢**这一行**的三位;行已不在待决(两次读之间被决/过期)⇒ 三位缺席;`not-text`(读腿漏了投影)原样抛。
|
|
714
|
+
*/
|
|
715
|
+
private readParkedSkillCardFacts;
|
|
679
716
|
/**
|
|
680
717
|
* [ref] #2 (scheduler seam, core 1.105): a scope's PENDING checkpoints as lightweight
|
|
681
718
|
* {@link CheckpointSummary} rows in ONE query — the cross-task triage view a supervisor scheduler needs
|
|
@@ -808,17 +845,14 @@ export declare class SqlCheckpointStore implements CheckpointStore {
|
|
|
808
845
|
putCtx(sessionId: string, ctx: SealedCheckpointCtx): Promise<void>;
|
|
809
846
|
getCtx<T extends object = object>(sessionId: string): Promise<T | null>;
|
|
810
847
|
/**
|
|
811
|
-
* 🔴
|
|
812
|
-
*
|
|
848
|
+
* 🔴 **一次性迁移的 ctx 行改写店腿**({@link CheckpointCtxRowsRewriter};用它的迁移与各自的行改写见那里,窗与删法见 `plugins/one-time-migrations.ts` 登记表;
|
|
849
|
+
* 本店不知道改写是什么,只负责扫与回写):按 `session_id` 键集分页扫 `checkpoint_ctx`
|
|
813
850
|
* (每页 {@link CTX_REWRITE_PAGE} 行,只取会话 id 与 ctx 文本),行体经读腿**同一只**判形({@link decodeCtxColumn})—— 读不出 ⇒ 计数、跳过、不动;
|
|
814
851
|
* `rewrite` 交回新体 ⇒ 经写腿同一只编码与形闸,**按原文 CAS** 回写(MySQL 协议两边 `CAST(… AS BINARY)`,与 M17 / M18 同一条理由:连接排序规则大小写
|
|
815
852
|
* 不敏感;PG `text` 等值)。`updated_at_ms` **不动**(收割按它判,迁移不许延长或缩短一行的寿命)。CAS 打空(扫描与回写之间这一行被续跑腿 / 别的副本写过)
|
|
816
|
-
* ⇒ 不计、不重试(
|
|
853
|
+
* ⇒ 不计、不重试(新写者是本版写下的、已是新形;是旧版副本就由之后的起服再扫到)。一行装不下(如封签比明文长)⇒ 计 `tooLarge`、那一行留原样,其余照扫。
|
|
817
854
|
*/
|
|
818
|
-
|
|
819
|
-
readonly ctx: SealedCheckpointCtx;
|
|
820
|
-
readonly values: number;
|
|
821
|
-
} | undefined): Promise<CtxRowsRewriteM28>;
|
|
855
|
+
rewriteCtxRows(rewrite: CtxRowRewrite): Promise<CtxRowsRewrite>;
|
|
822
856
|
/**
|
|
823
857
|
* GC ctx rows whose lifecycle is over (bound to the checkpoint, don't leak):
|
|
824
858
|
* a session with NO pending checkpoint, NO active run, AND not touched since `cutoff`.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { parkedApprovalCard } from "../approval-content-kind.js";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
-
import { CHECKPOINT_FACET_SQL,
|
|
4
|
-
import { CheckpointError, isGateKind, MAX_RULE_TEXT_CHARS, validatePendingSteer, appendPendingSteer, winnerFromOutcome, summarizeCheckpoint, MAX_SUPPORTED_CHECKPOINT_VERSION, ExecutionOutcomeConflictError, executionRecordDisposition, executionVerdict, } from "@sema-agent/core";
|
|
3
|
+
import { CHECKPOINT_FACET_SQL, CHECKPOINT_SWEEP_ORDER_SQL, CheckpointIndexRefusedError, judgeCheckpointIndexCells } from "./checkpoint-index-columns.js";
|
|
4
|
+
import { CheckpointError, isGateKind, MAX_RULE_TEXT_CHARS, validatePendingSteer, appendPendingSteer, winnerFromOutcome, summarizeCheckpoint, checkpointRevOf, MAX_SUPPORTED_CHECKPOINT_VERSION, ExecutionOutcomeConflictError, executionRecordDisposition, executionVerdict, } from "@sema-agent/core";
|
|
5
5
|
import { redactDeep, redactSecrets } from "../trace/redact.js";
|
|
6
|
-
import { MAX_RULE_OFFERS, MAX_RULE_OFFER_BATCH_MEMBERS, parkRowFacts, readRuleOfferList } from "../approval-card.js";
|
|
6
|
+
import { MAX_RULE_OFFERS, MAX_RULE_OFFER_BATCH_MEMBERS, parkRowCarriesSkillCardFacts, parkRowFacts, readRuleOfferList, skillCardFacts } from "../approval-card.js";
|
|
7
7
|
import { assertJsonColumnWritable, createObjectColumnReader, decodeJsonColumn, decodeNullableJsonColumn, foldJsonColumn, isJsonColumnBodyProblem, jsonColumnText, readJsonArrayColumn, readJsonObjectColumn, readNonNullJsonValueColumn, } from "./sql-json-column.js";
|
|
8
8
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
9
9
|
import { durableRowIdentity, withoutCheckpointToken } from "../checkpoint-identity.js";
|
|
@@ -170,18 +170,16 @@ function readRiskDescriptorCell(cell) {
|
|
|
170
170
|
return null;
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
|
-
function
|
|
174
|
-
let pendingAction;
|
|
173
|
+
function readParkRowActionCell(cell) {
|
|
175
174
|
try {
|
|
176
|
-
|
|
175
|
+
return decodeNullableJsonColumn(cell, readPendingActionMemberBody, "checkpoint.checkpoint $.pendingAction (operator queue row)");
|
|
177
176
|
}
|
|
178
177
|
catch (e) {
|
|
179
178
|
if (!isJsonColumnBodyProblem(e))
|
|
180
179
|
throw e;
|
|
181
180
|
recordFailOpen("server.approvals.park-row-blob-unreadable");
|
|
182
|
-
return
|
|
181
|
+
return undefined;
|
|
183
182
|
}
|
|
184
|
-
return parkRowFacts(pendingAction);
|
|
185
183
|
}
|
|
186
184
|
const PARK_ROW_PENDING_ACTION = { key: "pendingAction", as: "pending_action" };
|
|
187
185
|
const PARK_ROW_CHECKPOINT_ID = { key: "checkpointId", as: "checkpoint_id" };
|
|
@@ -383,7 +381,10 @@ export class SqlCheckpointStore {
|
|
|
383
381
|
return { sql: `SELECT ${select} FROM checkpoint WHERE ${where.join(" AND ")} ORDER BY created_at_ms ${dir}, token ${dir} LIMIT 1`, params };
|
|
384
382
|
}
|
|
385
383
|
async put(token, cp) {
|
|
386
|
-
const
|
|
384
|
+
const judged = judgeCheckpointIndexCells(cp, this.db.dialect);
|
|
385
|
+
if (judged.kind !== "cells")
|
|
386
|
+
throw new CheckpointIndexRefusedError(cp.sessionId, judged);
|
|
387
|
+
const cells = judged.cells;
|
|
387
388
|
const pa = cp.pendingAction;
|
|
388
389
|
const toolInput = boundedToolInput(pa?.args);
|
|
389
390
|
const ruleOffers = boundedRuleOffers(pa?.ruleOffers);
|
|
@@ -411,7 +412,7 @@ export class SqlCheckpointStore {
|
|
|
411
412
|
cells.risk_descriptor === null ? null : this.json(cells.risk_descriptor, "risk descriptor"),
|
|
412
413
|
ruleOffers === undefined ? null : this.json(ruleOffers, "rule offers"),
|
|
413
414
|
cells.has_bidi_controls,
|
|
414
|
-
cp
|
|
415
|
+
checkpointRevOf(cp),
|
|
415
416
|
cp.reopenReason ?? null,
|
|
416
417
|
executionOutcome,
|
|
417
418
|
executionAtMs,
|
|
@@ -543,7 +544,7 @@ export class SqlCheckpointStore {
|
|
|
543
544
|
return res.affected;
|
|
544
545
|
}
|
|
545
546
|
async listExpiredApprovalGates(cutoff, limit = 100) {
|
|
546
|
-
const { rows } = await this.db.query(this.q(`SELECT session_id, scope FROM checkpoint WHERE status = 'pending' AND deadline IS NOT NULL AND deadline <= ? AND ${CHECKPOINT_FACET_SQL.tidb} = 'A' ORDER BY
|
|
547
|
+
const { rows } = await this.db.query(this.q(`SELECT session_id, scope FROM checkpoint WHERE status = 'pending' AND deadline IS NOT NULL AND deadline <= ? AND ${CHECKPOINT_FACET_SQL.tidb} = 'A' ORDER BY ${CHECKPOINT_SWEEP_ORDER_SQL} LIMIT ?`, `SELECT session_id, scope FROM checkpoint WHERE status = 'pending' AND deadline IS NOT NULL AND deadline <= $1 AND ${CHECKPOINT_FACET_SQL.pg} = 'A' ORDER BY ${CHECKPOINT_SWEEP_ORDER_SQL} LIMIT $2`), [cutoff, limit]);
|
|
547
548
|
return rows.map((r) => ({ sessionId: String(r.session_id), scope: String(r.scope) }));
|
|
548
549
|
}
|
|
549
550
|
async listPending(scope) {
|
|
@@ -555,7 +556,7 @@ export class SqlCheckpointStore {
|
|
|
555
556
|
const { rows } = scope
|
|
556
557
|
? await this.db.query(`${base}${this.q(" AND c.scope=?", " AND c.scope=$1")} ORDER BY c.created_at_ms ASC`, [scope])
|
|
557
558
|
: await this.db.query(`${base} ORDER BY c.created_at_ms ASC`);
|
|
558
|
-
const
|
|
559
|
+
const entries = rows.flatMap((r) => {
|
|
559
560
|
const gateKind = queueRowGateKind(r.gate_kind);
|
|
560
561
|
if (gateKind === undefined)
|
|
561
562
|
return [];
|
|
@@ -564,28 +565,56 @@ export class SqlCheckpointStore {
|
|
|
564
565
|
const boundInputHash = r.bound_input_hash ?? null;
|
|
565
566
|
const ruleOffers = readRuleOffersCell(r.rule_suggestions);
|
|
566
567
|
const approvalCard = parkedApprovalCard({ kind: gateKind, approvalCard: r.approval_card ?? undefined });
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
568
|
+
const pendingAction = readParkRowActionCell(r[PARK_ROW_PENDING_ACTION.as]);
|
|
569
|
+
const row = {
|
|
570
|
+
sessionId: String(r.session_id),
|
|
571
|
+
...durableRowIdentity("approvals-queue", readCheckpointIdCell(r[PARK_ROW_CHECKPOINT_ID.as]), r.token),
|
|
572
|
+
scope: String(r.scope),
|
|
573
|
+
toolName,
|
|
574
|
+
toolCallId,
|
|
575
|
+
...(toolCallId !== null ? { boundCallId: toolCallId } : {}),
|
|
576
|
+
...(boundInputHash !== null ? { boundInputHash } : {}),
|
|
577
|
+
gateKind,
|
|
578
|
+
taskId: r.task_id ?? null,
|
|
579
|
+
input: refusing("a tool input the operator would be approving unseen", () => decodeToolInputColumn(r.tool_input)) ?? null,
|
|
580
|
+
createdAt: Number(r.created_at_ms),
|
|
581
|
+
deadline: r.deadline == null ? null : Number(r.deadline),
|
|
582
|
+
riskDescriptor: readRiskDescriptorCell(r.risk_descriptor),
|
|
583
|
+
...(ruleOffers !== undefined ? { ruleOffers } : {}),
|
|
584
|
+
...(Number(r.has_bidi_controls) === 1 ? { hasBidiControls: true } : {}),
|
|
585
|
+
...(approvalCard !== undefined ? { approvalCard } : {}),
|
|
586
|
+
...parkRowFacts(pendingAction),
|
|
587
|
+
};
|
|
588
|
+
return [{ row, token: String(r.token), readSkillCard: parkRowCarriesSkillCardFacts(pendingAction) }];
|
|
586
589
|
});
|
|
590
|
+
const out = [];
|
|
591
|
+
for (const e of entries)
|
|
592
|
+
out.push(e.readSkillCard ? { ...e.row, ...(await this.readParkedSkillCardFacts(e.token)) } : e.row);
|
|
587
593
|
return out.sort((a, b) => (b.riskDescriptor?.severity ?? 0) - (a.riskDescriptor?.severity ?? 0) || a.createdAt - b.createdAt);
|
|
588
594
|
}
|
|
595
|
+
async readParkedSkillCardFacts(token) {
|
|
596
|
+
const { rows } = await this.db.query(this.q(`SELECT ${jsonColumnText("tidb", "checkpoint")} FROM checkpoint WHERE token = ? AND status = 'pending'`, `SELECT ${jsonColumnText("pg", "checkpoint")} FROM checkpoint WHERE token = $1 AND status = 'pending'`), [token]);
|
|
597
|
+
const r = rows[0];
|
|
598
|
+
if (r === undefined)
|
|
599
|
+
return {};
|
|
600
|
+
let cp;
|
|
601
|
+
try {
|
|
602
|
+
cp = decodeCheckpointColumn(r.checkpoint, "(operator queue row, skill-card facts)");
|
|
603
|
+
}
|
|
604
|
+
catch (e) {
|
|
605
|
+
if (!isJsonColumnBodyProblem(e))
|
|
606
|
+
throw e;
|
|
607
|
+
recordFailOpen("server.approvals.park-row-blob-unreadable");
|
|
608
|
+
return {};
|
|
609
|
+
}
|
|
610
|
+
try {
|
|
611
|
+
return skillCardFacts(summarizeCheckpoint(cp));
|
|
612
|
+
}
|
|
613
|
+
catch {
|
|
614
|
+
recordFailOpen("server.approvals.park-row-blob-unreadable");
|
|
615
|
+
return {};
|
|
616
|
+
}
|
|
617
|
+
}
|
|
589
618
|
async listByScope(scope) {
|
|
590
619
|
const { rows } = await this.db.query(this.q(`SELECT ${jsonColumnText("tidb", "checkpoint")}, status FROM checkpoint WHERE status = 'pending' AND scope = ? ORDER BY created_at_ms ASC LIMIT 500`, `SELECT ${jsonColumnText("pg", "checkpoint")}, status FROM checkpoint WHERE status = 'pending' AND scope = $1 ORDER BY created_at_ms ASC LIMIT 500`), [scope]);
|
|
591
620
|
const out = [];
|
|
@@ -709,7 +738,7 @@ export class SqlCheckpointStore {
|
|
|
709
738
|
const { rows } = await this.db.query(this.q(`SELECT ${jsonColumnText("tidb", "ctx")} FROM checkpoint_ctx WHERE session_id=?`, `SELECT ${jsonColumnText("pg", "ctx")} FROM checkpoint_ctx WHERE session_id=$1`), [sessionId]);
|
|
710
739
|
return rows[0] ? refusing("a resume context nobody can read", () => decodeCtxColumn(rows[0].ctx, `for session ${JSON.stringify(sessionId)}`)) : null;
|
|
711
740
|
}
|
|
712
|
-
async
|
|
741
|
+
async rewriteCtxRows(rewrite) {
|
|
713
742
|
const out = { scanned: 0, rewritten: 0, valuesRewritten: 0, unreadable: 0, tooLarge: 0 };
|
|
714
743
|
let after = "";
|
|
715
744
|
for (;;) {
|
|
@@ -719,7 +748,7 @@ export class SqlCheckpointStore {
|
|
|
719
748
|
after = sessionId;
|
|
720
749
|
let ctx;
|
|
721
750
|
try {
|
|
722
|
-
ctx = decodeCtxColumn(row.ctx, `for session ${JSON.stringify(sessionId)} (
|
|
751
|
+
ctx = decodeCtxColumn(row.ctx, `for session ${JSON.stringify(sessionId)} (ctx row rewrite scan)`);
|
|
723
752
|
}
|
|
724
753
|
catch {
|
|
725
754
|
out.unreadable += 1;
|
|
@@ -730,13 +759,13 @@ export class SqlCheckpointStore {
|
|
|
730
759
|
if (next === undefined)
|
|
731
760
|
continue;
|
|
732
761
|
const text = this.json(next.ctx, "checkpoint ctx");
|
|
733
|
-
assertJsonColumnWritable(text, createObjectColumnReader(), `checkpoint_ctx.ctx for session ${JSON.stringify(sessionId)} (
|
|
762
|
+
assertJsonColumnWritable(text, createObjectColumnReader(), `checkpoint_ctx.ctx for session ${JSON.stringify(sessionId)} (ctx row rewrite)`);
|
|
734
763
|
let affected;
|
|
735
764
|
try {
|
|
736
765
|
({ affected } = await this.db.query(this.q("UPDATE checkpoint_ctx SET ctx = ? WHERE session_id = ? AND CAST(CAST(ctx AS CHAR) AS BINARY) = CAST(? AS BINARY)", "UPDATE checkpoint_ctx SET ctx = $1::jsonb WHERE session_id = $2 AND ctx::text = $3"), [text, sessionId, String(row.ctx)]));
|
|
737
766
|
}
|
|
738
767
|
catch (e) {
|
|
739
|
-
const folded = rowTooLargeOrSelf(this.db.dialect, e, `checkpoint_ctx.ctx for session ${JSON.stringify(sessionId)} (
|
|
768
|
+
const folded = rowTooLargeOrSelf(this.db.dialect, e, `checkpoint_ctx.ctx for session ${JSON.stringify(sessionId)} (ctx row rewrite)`);
|
|
740
769
|
if (!(folded instanceof StoreRowTooLargeError))
|
|
741
770
|
throw folded;
|
|
742
771
|
out.tooLarge += 1;
|
|
@@ -165,6 +165,12 @@ export declare class SqlFileHistoryStore implements FileHistoryStore {
|
|
|
165
165
|
/** core 7.55.0 `FileHistoryExportResult`:本店的图在 scope 锁内一事务物化,读得出就是整图(`{ ok: true, history }`;空 scope ⇒ `history: null`);
|
|
166
166
|
* 库读失败照常抛(读故障不是答案)—— 本店没有「撕裂 / 读不出的边界」这一形(边界与映射同事务落),所以不答 `{ ok: false }`。 */
|
|
167
167
|
exportHistory(scope: string): Promise<FileHistoryExportResult>;
|
|
168
|
+
/**
|
|
169
|
+
* Fetch + content-address verify EVERY blob the envelope references from the source, one at a time (memory bounded by one blob) —— 首推与同图重推共用的**唯一**取值校验;
|
|
170
|
+
* 任何一枚取不出 / 核不过 ⇒ 整信封典型拒,什么都不发布。`onVerified` = 校验过的这一枚拿来做什么:首推传「续租式 upsert」(delete-in-use 租约 —— 发布前的孤儿清扫不许收走
|
|
171
|
+
* 这次导入正要引用的字节);同图重推不传 —— 已发布的图的版本行本身就是引用,租约无事可护,而回写会把每只对象重传一遍(MinIO 的 `putBlob` 是整体 PUT)。
|
|
172
|
+
*/
|
|
173
|
+
private verifySourceBlobs;
|
|
168
174
|
importHistory(scope: string, data: FileHistoryExport, srcGetBlob: (hash: string) => Promise<Uint8Array | undefined>): Promise<FileHistoryResult>;
|
|
169
175
|
}
|
|
170
176
|
/** MySQL-protocol (TiDB) binding. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { captureFileBackupViaEnv, applyFileRestoreViaEnv, fileHistoryModeProvenDifferent, worldStillMatchesMint, boundaryPublishVerdict, fileHistoryDescribeTrackKey, fileHistoryTrackKeyOf, fileHistoryResolveTrackKey, fileHistoryExportDigest, validateFileHistoryExport, resolveFileHistoryRetention, fileHistoryBoundariesToKeep, } from "@sema-agent/core";
|
|
2
|
+
import { captureFileBackupViaEnv, applyFileRestoreViaEnv, fileHistoryModeProvenDifferent, worldStillMatchesMint, annulProofFailure, boundaryPublishVerdict, fileHistoryDescribeTrackKey, fileHistoryTrackKeyOf, fileHistoryResolveTrackKey, fileHistoryExportDigest, validateFileHistoryExport, resolveFileHistoryRetention, fileHistoryBoundariesToKeep, } from "@sema-agent/core";
|
|
3
3
|
import { BlobTooLargeError, SqlBlobBackend } from "./blob-backend.js";
|
|
4
4
|
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
5
5
|
import { isMissingColumnError } from "./sql-errors.js";
|
|
@@ -149,15 +149,8 @@ export class SqlFileHistoryStore {
|
|
|
149
149
|
const baseAvailable = truthy(t.rows[0].base_available);
|
|
150
150
|
const mintedBlobHash = v1.rows[0]?.blob_hash != null ? String(v1.rows[0].blob_hash) : null;
|
|
151
151
|
const proof = await worldStillMatchesMint(verify.env, absPath, root, baseAvailable, mintedBlobHash, verify.signal);
|
|
152
|
-
if (
|
|
153
|
-
return
|
|
154
|
-
ok: false,
|
|
155
|
-
error: {
|
|
156
|
-
code: "conflict",
|
|
157
|
-
message: `first-touch record for ${absPath} is NOT retractable: ${proof.reason} — the write this retraction compensates for may have changed the file, and deleting the only baseline of a modified file is worse than keeping a stale record`,
|
|
158
|
-
},
|
|
159
|
-
};
|
|
160
|
-
}
|
|
152
|
+
if (proof.verdict !== "unchanged")
|
|
153
|
+
return annulProofFailure(proof, absPath);
|
|
161
154
|
}
|
|
162
155
|
const conn = await this.db.connect();
|
|
163
156
|
try {
|
|
@@ -441,7 +434,7 @@ export class SqlFileHistoryStore {
|
|
|
441
434
|
let targetVersion = boundary.get(t.keyHash);
|
|
442
435
|
if (targetVersion === undefined) {
|
|
443
436
|
if (!t.baseAvailable) {
|
|
444
|
-
ledger.refused.push({ path: t.trackKey, reason: "base_unavailable" });
|
|
437
|
+
ledger.refused.push({ path: t.trackKey, reason: "base_unavailable", class: "base_unavailable" });
|
|
445
438
|
continue;
|
|
446
439
|
}
|
|
447
440
|
targetVersion = 1;
|
|
@@ -452,12 +445,12 @@ export class SqlFileHistoryStore {
|
|
|
452
445
|
continue;
|
|
453
446
|
}
|
|
454
447
|
if (rec.mode !== undefined && !(Number.isInteger(rec.mode) && rec.mode >= 0 && rec.mode <= 0o7777)) {
|
|
455
|
-
ledger.
|
|
448
|
+
ledger.failed.push({ path: t.trackKey, reason: `recorded mode ${String(rec.mode)} is not a valid POSIX permission value (store integrity) — refusing to apply a corrupted permission record` });
|
|
456
449
|
continue;
|
|
457
450
|
}
|
|
458
451
|
const resolved = fileHistoryResolveTrackKey(root, t.trackKey, t.keyDesc);
|
|
459
452
|
if (!resolved.ok) {
|
|
460
|
-
ledger.refused.push({ path: t.trackKey, reason: resolved.reason });
|
|
453
|
+
ledger.refused.push({ path: t.trackKey, reason: resolved.reason, class: resolved.class });
|
|
461
454
|
continue;
|
|
462
455
|
}
|
|
463
456
|
let applied;
|
|
@@ -488,7 +481,7 @@ export class SqlFileHistoryStore {
|
|
|
488
481
|
else if (applied.outcome === "identical")
|
|
489
482
|
ledger.identicalSkipped.push(t.trackKey);
|
|
490
483
|
else if (applied.outcome === "refused")
|
|
491
|
-
ledger.refused.push({ path: t.trackKey, reason: applied.reason });
|
|
484
|
+
ledger.refused.push({ path: t.trackKey, reason: applied.reason, class: applied.class });
|
|
492
485
|
else
|
|
493
486
|
ledger.failed.push({ path: t.trackKey, reason: applied.reason });
|
|
494
487
|
if ((applied.outcome === "applied" || applied.outcome === "identical") && applied.modeNotPreserved === true)
|
|
@@ -716,6 +709,29 @@ export class SqlFileHistoryStore {
|
|
|
716
709
|
conn.release();
|
|
717
710
|
}
|
|
718
711
|
}
|
|
712
|
+
async verifySourceBlobs(data, srcGetBlob, onVerified) {
|
|
713
|
+
const needed = new Set();
|
|
714
|
+
for (const t of data.tracked)
|
|
715
|
+
for (const v of t.versions)
|
|
716
|
+
if (v.blobHash !== null)
|
|
717
|
+
needed.add(v.blobHash);
|
|
718
|
+
for (const hash of needed) {
|
|
719
|
+
let bytes;
|
|
720
|
+
try {
|
|
721
|
+
bytes = await srcGetBlob(hash);
|
|
722
|
+
}
|
|
723
|
+
catch (err) {
|
|
724
|
+
return { ok: false, error: { code: "adopt_failed", message: `source blob ${hash} fetch failed: ${foldCaughtFailure(err).text}` } };
|
|
725
|
+
}
|
|
726
|
+
if (bytes === undefined)
|
|
727
|
+
return { ok: false, error: { code: "adopt_failed", message: `source blob ${hash} is missing — the envelope's graph cannot be completed, so nothing is published` } };
|
|
728
|
+
if (sha256hex(bytes) !== hash) {
|
|
729
|
+
return { ok: false, error: { code: "unsupported_format", message: `source blob ${hash} fails content-address integrity — refused, nothing published` } };
|
|
730
|
+
}
|
|
731
|
+
await onVerified?.(hash, bytes);
|
|
732
|
+
}
|
|
733
|
+
return { ok: true };
|
|
734
|
+
}
|
|
719
735
|
async importHistory(scope, data, srcGetBlob) {
|
|
720
736
|
try {
|
|
721
737
|
const shaped = validateFileHistoryExport(data);
|
|
@@ -733,31 +749,15 @@ export class SqlFileHistoryStore {
|
|
|
733
749
|
if (!exported.ok)
|
|
734
750
|
return exported;
|
|
735
751
|
const existing = exported.history;
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
return { ok: true };
|
|
752
|
+
const sameGraph = existing !== null && fileHistoryExportDigest(existing) === fileHistoryExportDigest(data);
|
|
753
|
+
if (existing !== null && !sameGraph) {
|
|
739
754
|
return { ok: false, error: { code: "conflict", message: `importHistory target "${scope}" already holds a DIFFERENT history — a first import requires an empty target and a retry is legal only for the identical envelope (atomic visibility prevents half-graphs; this law prevents publishing the wrong complete graph over locally-grown history)` } };
|
|
740
755
|
}
|
|
741
|
-
const
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
for (const hash of needed) {
|
|
747
|
-
let bytes;
|
|
748
|
-
try {
|
|
749
|
-
bytes = await srcGetBlob(hash);
|
|
750
|
-
}
|
|
751
|
-
catch (err) {
|
|
752
|
-
return { ok: false, error: { code: "adopt_failed", message: `source blob ${hash} fetch failed: ${foldCaughtFailure(err).text}` } };
|
|
753
|
-
}
|
|
754
|
-
if (bytes === undefined)
|
|
755
|
-
return { ok: false, error: { code: "adopt_failed", message: `source blob ${hash} is missing — the envelope's graph cannot be completed, so nothing is published` } };
|
|
756
|
-
if (sha256hex(bytes) !== hash) {
|
|
757
|
-
return { ok: false, error: { code: "unsupported_format", message: `source blob ${hash} fails content-address integrity — refused, nothing published` } };
|
|
758
|
-
}
|
|
759
|
-
await this.blobs.putBlob(hash, bytes);
|
|
760
|
-
}
|
|
756
|
+
const verified = await this.verifySourceBlobs(data, srcGetBlob, sameGraph ? undefined : (hash, bytes) => this.blobs.putBlob(hash, bytes));
|
|
757
|
+
if (!verified.ok)
|
|
758
|
+
return verified;
|
|
759
|
+
if (sameGraph)
|
|
760
|
+
return { ok: true };
|
|
761
761
|
const conn = await this.db.connect();
|
|
762
762
|
try {
|
|
763
763
|
await conn.begin();
|
|
@@ -9,6 +9,7 @@ import { recordFailOpen } from "../observability/fail-open.js";
|
|
|
9
9
|
import { replayJsonlSkipping } from "../observability/corrupt-read-seat.js";
|
|
10
10
|
import { RUN_HOLDER_DEATH_CODES, RUN_ORPHANED_ERROR, RUN_STALLED_ERROR, StoreBootRefusalError, buildRunOrphanedError, readLegacyHolderDeathCode } from "./store-contracts.js";
|
|
11
11
|
import { notifyRunTerminal } from "../observability/run-terminal.js";
|
|
12
|
+
import { readRunIdentityCells, runIdentityCells } from "../identity-source.js";
|
|
12
13
|
import { createInProcessSpaceCommitTable, fenceSpaceCommitAtTerminal, SPACE_COMMIT_RECORD_SCHEMA } from "../workspace-space-commit.js";
|
|
13
14
|
const ownerEq = (a, b) => (a ?? null) === (b ?? null);
|
|
14
15
|
const isParseableDate = (v) => typeof v === "string" && !Number.isNaN(Date.parse(v));
|
|
@@ -140,16 +141,19 @@ export class FileRunStore {
|
|
|
140
141
|
return this.spaceCommits.abandonSpaceCommit(spaceKey, taskId);
|
|
141
142
|
}
|
|
142
143
|
toRow(d) {
|
|
144
|
+
const { identitySource, identityScope, identityPrincipalInc, ...rest } = d;
|
|
143
145
|
return {
|
|
144
|
-
...
|
|
146
|
+
...rest,
|
|
145
147
|
idemKey: typeof d.idemKey === "string" ? d.idemKey : null,
|
|
146
148
|
idemBodyFingerprint: typeof d.idemBodyFingerprint === "string" ? d.idemBodyFingerprint : null,
|
|
149
|
+
identity: readRunIdentityCells(identitySource, identityScope, identityPrincipalInc),
|
|
147
150
|
createdAt: new Date(d.createdAt),
|
|
148
151
|
updatedAt: new Date(d.updatedAt),
|
|
149
152
|
};
|
|
150
153
|
}
|
|
151
154
|
toDisk(r) {
|
|
152
|
-
|
|
155
|
+
const { identity, ...rest } = r;
|
|
156
|
+
return { ...rest, ...runIdentityCells(identity), createdAt: r.createdAt.toISOString(), updatedAt: r.updatedAt.toISOString() };
|
|
153
157
|
}
|
|
154
158
|
toRecord(r) {
|
|
155
159
|
return {
|
|
@@ -162,6 +166,7 @@ export class FileRunStore {
|
|
|
162
166
|
errorCode: r.errorCode,
|
|
163
167
|
jobId: r.jobId,
|
|
164
168
|
source: r.source,
|
|
169
|
+
identity: r.identity,
|
|
165
170
|
instanceId: r.instanceId,
|
|
166
171
|
cancelRequested: r.cancelRequested,
|
|
167
172
|
objectivePreview: r.objectivePreview,
|
|
@@ -376,6 +381,7 @@ export class FileRunStore {
|
|
|
376
381
|
objectivePreview: meta.objectivePreview ?? null,
|
|
377
382
|
idemKey,
|
|
378
383
|
idemBodyFingerprint: meta.idem?.bodyFingerprint ?? null,
|
|
384
|
+
identity: meta.identity ?? null,
|
|
379
385
|
createdAt: now,
|
|
380
386
|
updatedAt: now,
|
|
381
387
|
cancelRequested: false,
|
|
@@ -1,9 +1,51 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
3
|
-
*
|
|
4
|
-
* k8s adapter's protocol/patch layer, independent of pod lifecycle. Split out of `remote-env-k8s.ts` ([ref]
|
|
5
|
-
* A13, internal-lossless — `remote-env-k8s.ts` re-exports every symbol below unchanged).
|
|
2
|
+
* The identity labels every sandbox pod carries — the ONE spelling (RBAC selectors, the reaper's listing and the egress self-check key on
|
|
3
|
+
* them; a pod-spec patch can add labels, never set or replace one of these).
|
|
6
4
|
*/
|
|
5
|
+
export declare const SANDBOX_POD_LABELS: {
|
|
6
|
+
readonly app: "sema-sandbox";
|
|
7
|
+
readonly "managed-by": "sema-server";
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* The pod's owning run: label key, value = the server run id (the task id) of the TOP-LEVEL run whose env created the pod. Minted only by the
|
|
11
|
+
* adapter from `K8sEnvConfig.runId` (the per-run binding decides who has one); absent ⇒ the pod names no run, and the reaper removes it only once
|
|
12
|
+
* its containers have ended. An identity label: a patch can neither set nor replace it.
|
|
13
|
+
*/
|
|
14
|
+
export declare const SANDBOX_RUN_LABEL = "sema.io/run";
|
|
15
|
+
/**
|
|
16
|
+
* The pod's own reaper hold: annotation key, value = the decimal milliseconds the reaper must wait after the owning run turned terminal before it
|
|
17
|
+
* removes the pod (`k8s-pod-reaper.ts` `sandboxRunEndHoldMs` of the CREATING replica's data bound). Written once, by the adapter, on every pod that
|
|
18
|
+
* carries {@link SANDBOX_RUN_LABEL} (the hold is only read for those); the reaper reads this and nothing else — never its own replica's config, so a
|
|
19
|
+
* rolling change of the snapshot bound never shortens another replica's pod. A pod-spec patch reaches labels only, never annotations.
|
|
20
|
+
*/
|
|
21
|
+
export declare const SANDBOX_RUN_END_HOLD_ANNOTATION = "sema.io/run-end-hold-ms";
|
|
22
|
+
/** The two per-pod Secret seats: `env` = the pod's env Secret (the values `K8S_SANDBOX_ENV` names); `lease` = the member-credential broker's lease Secret. */
|
|
23
|
+
export type SandboxSecretSeatKind = "env" | "lease";
|
|
24
|
+
/**
|
|
25
|
+
* Per seat, the secrets verbs the adapter sends for it in the sandbox namespace — the ONE table, and each row is exactly that seat's calls:
|
|
26
|
+
* `env` = `create` (the env Secret, POSTed after its pod, never touched again); `lease` = `create` (the lease Secret) + `patch` (the lease rewrite,
|
|
27
|
+
* a merge-patch of `broker.json`). Nothing else: the adapter never reads a Secret back (no `get` — which would also let the service account read
|
|
28
|
+
* every Secret in the namespace) and never deletes one (no `delete`) — each Secret is owned by its pod (`ownerReferences`, `blockOwnerDeletion:
|
|
29
|
+
* false`), so the cluster's garbage collector removes it with the pod under its own identity. A seat in play asks only its own row: the boot gate
|
|
30
|
+
* (`K8S_SANDBOX_ENV` non-empty) asks `env`, the member-credential plane judge asks `lease` — an env-only deployment is never asked for `patch`.
|
|
31
|
+
* Readers: the SelfSubjectAccessReview (`readSandboxSecretSeat`), the refusal sentences that name the Role rule, the plane's off-reason text, the
|
|
32
|
+
* catalog sentence. `test/k8s-sandbox-secret.test.ts` pins row ≡ calls per seat, both directions.
|
|
33
|
+
*/
|
|
34
|
+
export declare const SANDBOX_SECRET_SEAT_VERBS: {
|
|
35
|
+
readonly env: readonly ["create"];
|
|
36
|
+
readonly lease: readonly ["create", "patch"];
|
|
37
|
+
};
|
|
38
|
+
export type SandboxSecretVerb = (typeof SANDBOX_SECRET_SEAT_VERBS)[SandboxSecretSeatKind][number];
|
|
39
|
+
export declare function isLabelValue(v: string): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* The volume names the adapter itself mints on a sandbox pod — the ONE table (a patch volume / volumeMount must not reuse one: it would collide
|
|
42
|
+
* with, or mount, the adapter's own volume). Today: the space's build-cache claim and the member-credential broker's lease volume. A volume the
|
|
43
|
+
* adapter adds later is added HERE, and the patch judge below refuses it with no second guard.
|
|
44
|
+
*/
|
|
45
|
+
export declare const ADAPTER_VOLUME_NAMES: {
|
|
46
|
+
readonly cache: "ws-cache";
|
|
47
|
+
readonly brokerLease: "sema-broker-lease";
|
|
48
|
+
};
|
|
7
49
|
/**
|
|
8
50
|
* The confined, ADDITIVE pod-spec patch. Every field is purely additive —
|
|
9
51
|
* none can override the hard isolation invariants. This is the seam the RSI L8 immutable-mount probe + the
|
|
@@ -22,19 +64,32 @@ export interface PodSpecPatch {
|
|
|
22
64
|
/** Extra labels merged into metadata.labels (e.g. a grader/probe tag). */
|
|
23
65
|
labels?: Record<string, string>;
|
|
24
66
|
}
|
|
25
|
-
|
|
26
|
-
* Apply a {@link PodSpecPatch} to a sandbox pod object IN PLACE — purely ADDITIVE. Volumes + volumeMounts are
|
|
27
|
-
* APPENDED (never replaced); securityContext objects are shallow-MERGED onto any existing; labels are merged. It
|
|
28
|
-
* deliberately touches ONLY spec.volumes / spec.securityContext / containers[0].{volumeMounts,securityContext} /
|
|
29
|
-
* metadata.labels — so the hard isolation invariants (runtimeClassName, automountServiceAccountToken,
|
|
30
|
-
* restartPolicy, activeDeadlineSeconds, image, command) are preserved by construction. Pure + exported for testing.
|
|
31
|
-
*/
|
|
32
|
-
export declare function applyPodSpecPatch(pod: {
|
|
67
|
+
type SandboxPodShape = {
|
|
33
68
|
metadata: {
|
|
34
69
|
labels?: Record<string, string>;
|
|
35
70
|
};
|
|
36
71
|
spec: Record<string, unknown>;
|
|
37
|
-
}
|
|
72
|
+
};
|
|
73
|
+
/** Which pod a patch lands on: one that carries the broker container or not. */
|
|
74
|
+
export interface PodPatchPresence {
|
|
75
|
+
readonly broker: boolean;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Apply a {@link PodSpecPatch} to a sandbox pod object IN PLACE — purely ADDITIVE, through the one field table
|
|
79
|
+
* ({@link POD_SPEC_PATCH_FIELDS}): a key outside the table is refused; EVERY present field is judged before ANY lands (a refusal leaves the pod
|
|
80
|
+
* byte-identical). Volumes + volumeMounts are APPENDED (never replaced); securityContext objects are shallow-MERGED onto any existing; labels are
|
|
81
|
+
* merged under the identity keys. It touches ONLY spec.volumes / spec.securityContext / containers[0].{volumeMounts,securityContext} /
|
|
82
|
+
* metadata.labels — so the hard isolation invariants (runtimeClassName, automountServiceAccountToken, restartPolicy, activeDeadlineSeconds, image,
|
|
83
|
+
* command) are preserved by construction. Throws (the caller surfaces it as a provisioning failure). Pure + exported for testing.
|
|
84
|
+
* `presence.broker` ⇒ the table's broker column judges first (see {@link POD_SPEC_PATCH_FIELDS}).
|
|
85
|
+
*/
|
|
86
|
+
export declare function applyPodSpecPatch(pod: SandboxPodShape, patch: PodSpecPatch, presence?: PodPatchPresence): void;
|
|
87
|
+
/**
|
|
88
|
+
* The patch judge alone — the same one {@link applyPodSpecPatch} runs — for a pod that carries the broker: the refusal sentence, or `undefined`
|
|
89
|
+
* when the patch may land. The boot reads it on the deployment's static patch (a refusal turns the member-credential plane off,
|
|
90
|
+
* `pod_patch_reaches_broker`); the adapter runs it again on every pod it builds with the broker.
|
|
91
|
+
*/
|
|
92
|
+
export declare function podSpecPatchBrokerRefusal(patch: PodSpecPatch | undefined): string | undefined;
|
|
38
93
|
/**
|
|
39
94
|
* TLS options for the exec WebSocket, shaped for BOTH runtimes (2026-06-11 in-cluster drill finding):
|
|
40
95
|
* Bun's `ws` shim IGNORES node-ws's top-level `ca`/`rejectUnauthorized` (handshake fails against a
|
|
@@ -50,4 +105,5 @@ export declare function exitCodeFromStatus(statusJson: string): {
|
|
|
50
105
|
} | {
|
|
51
106
|
error: string;
|
|
52
107
|
};
|
|
108
|
+
export {};
|
|
53
109
|
//# sourceMappingURL=k8s-exec-protocol.d.ts.map
|