@sema-agent/server 7.135.0 → 7.136.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 +64 -2
- package/USAGE.md +62 -25
- package/deploy/sema-up/README.md +1 -1
- package/deploy/sema-up/chart/templates/NOTES.txt +1 -1
- package/deploy/sema-up/chart/values.yaml +1 -1
- package/dist/approval-card.d.ts +60 -4
- package/dist/approval-card.js +18 -1
- package/dist/approval-content-kind.d.ts +1 -0
- package/dist/approval-content-kind.js +1 -1
- package/dist/auth-bridge.d.ts +34 -5
- package/dist/auth-bridge.js +20 -3
- package/dist/boot/budget-tracing.d.ts +1 -2
- package/dist/boot/budget-tracing.js +2 -2
- package/dist/boot/org-memory.js +1 -1
- package/dist/boot/resolve-spec.js +10 -7
- package/dist/boot/runner-deps.js +4 -2
- package/dist/boot/stage-04-stores.d.ts +1 -1
- package/dist/boot/stage-05-execution-env.d.ts +1 -1
- package/dist/boot/stage-06-runners.d.ts +1 -1
- package/dist/boot/stage-07-capability-layer.d.ts +1 -1
- package/dist/boot/stage-08-reapers.d.ts +1 -1
- package/dist/boot/stage-09-leader.d.ts +1 -1
- package/dist/boot/stage-10-http-server.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/budget.d.ts +3 -3
- package/dist/budget.js +4 -0
- package/dist/capabilities/mcp-probe.d.ts +5 -4
- package/dist/capability-facts.d.ts +3 -7
- package/dist/capability-facts.js +3 -5
- package/dist/center-credential.d.ts +18 -4
- package/dist/center-credential.js +18 -3
- package/dist/config-catalog.js +12 -10
- 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 +12 -4
- package/dist/config.js +26 -8
- package/dist/deployment-shape.d.ts +1 -1
- package/dist/elicitation.d.ts +8 -7
- package/dist/elicitation.js +9 -8
- package/dist/file-history-disclosure.d.ts +6 -4
- package/dist/file-history-disclosure.js +22 -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/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/checkpoint-ctx.d.ts +28 -9
- package/dist/http/checkpoint-ctx.js +5 -1
- package/dist/http/dispatch.js +9 -9
- package/dist/http/resume-ctx-read.d.ts +2 -16
- package/dist/http/resume-ctx-read.js +2 -3
- package/dist/http/routes/approvals-assistant.d.ts +0 -2
- package/dist/http/routes/approvals-assistant.js +47 -12
- package/dist/http/routes/runs.js +2 -2
- package/dist/http/routes/session-sync.js +31 -9
- package/dist/http/routes/sessions.js +18 -14
- package/dist/http/submit-ctx.js +1 -1
- package/dist/http/wire-types.d.ts +5 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/model-route-gap.d.ts +49 -4
- package/dist/model-route-gap.js +34 -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 +6 -6
- package/dist/observability/fail-open.js +6 -6
- package/dist/observability/metrics.js +1 -1
- package/dist/observability/principal-context.d.ts +4 -1
- package/dist/observability/principal-context.js +6 -1
- package/dist/parked-decide.d.ts +4 -3
- package/dist/parked-decide.js +26 -7
- package/dist/peer-directory.d.ts +2 -1
- package/dist/peer-directory.js +5 -5
- 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 +27 -3
- package/dist/plugins/checkpoint-store-sql.js +58 -29
- package/dist/plugins/file-history-store-sql.d.ts +6 -0
- package/dist/plugins/file-history-store-sql.js +30 -23
- package/dist/plugins/local-checkpoint-store.js +4 -3
- package/dist/plugins/one-time-migrations.d.ts +21 -28
- package/dist/plugins/one-time-migrations.js +39 -123
- package/dist/plugins/remote-env-k8s.d.ts +18 -5
- package/dist/plugins/remote-env-k8s.js +24 -7
- package/dist/plugins/s3-answer.d.ts +15 -0
- package/dist/plugins/s3-answer.js +5 -0
- 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 +39 -0
- package/dist/plugins/sql-errors.js +49 -1
- package/dist/plugins/store-fault.d.ts +4 -0
- package/dist/plugins/store-fault.js +8 -0
- package/dist/plugins/tidb-pool.js +1 -1
- 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 +4 -1
- package/dist/runtime-caps-resolver.js +1 -1
- 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/session-incarnation.d.ts +16 -5
- package/dist/session-incarnation.js +7 -4
- 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/task-a2a.d.ts +52 -81
- package/dist/task-a2a.js +219 -89
- package/dist/task-mcp.d.ts +6 -14
- package/dist/task-mcp.js +16 -51
- package/dist/tool-approval.d.ts +23 -10
- package/dist/tool-approval.js +51 -36
- package/dist/trace/core-keyset-guard.d.ts +2 -2
- package/dist/trace/exit-seat.js +37 -13
- package/dist/trace/redact.d.ts +28 -12
- package/dist/trace/redact.js +233 -94
- package/dist/trace/self-minted-wire.d.ts +267 -0
- package/dist/trace/self-minted-wire.js +79 -0
- 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/workspace-space.d.ts +27 -0
- package/dist/workspace-space.js +26 -0
- package/package.json +3 -2
- 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/dist/url-credential.d.ts +0 -52
- package/dist/url-credential.js +0 -49
|
@@ -36,6 +36,33 @@ export const APPROVAL_CARD_COLUMN_MAX_BYTES = {
|
|
|
36
36
|
tidb: 65_535,
|
|
37
37
|
pg: Number.POSITIVE_INFINITY,
|
|
38
38
|
};
|
|
39
|
+
export function judgeCheckpointIndexCells(cp, dialect) {
|
|
40
|
+
const gate = cp.gate;
|
|
41
|
+
if (typeof gate !== "object" || gate === null)
|
|
42
|
+
return { kind: "unreadable", member: "gate" };
|
|
43
|
+
const pendingAction = cp.pendingAction;
|
|
44
|
+
if (typeof pendingAction !== "object" || pendingAction === null)
|
|
45
|
+
return { kind: "unreadable", member: "pendingAction" };
|
|
46
|
+
const cells = buildCheckpointIndexCells(cp);
|
|
47
|
+
if (cells.approval_card !== null) {
|
|
48
|
+
const bytes = Buffer.byteLength(cells.approval_card, "utf8");
|
|
49
|
+
const maxBytes = APPROVAL_CARD_COLUMN_MAX_BYTES[dialect];
|
|
50
|
+
if (bytes > maxBytes)
|
|
51
|
+
return { kind: "card_too_long", bytes, maxBytes };
|
|
52
|
+
}
|
|
53
|
+
return { kind: "cells", cells };
|
|
54
|
+
}
|
|
55
|
+
export class CheckpointIndexRefusedError extends Error {
|
|
56
|
+
refusal;
|
|
57
|
+
constructor(sessionId, refusal) {
|
|
58
|
+
const where = `the checkpoint row for session ${JSON.stringify(sessionId) ?? "(none)"} was not written`;
|
|
59
|
+
super(refusal.kind === "unreadable"
|
|
60
|
+
? `${where}: its document carries no \`${refusal.member}\` object — the index columns are derived from the gate and the pending action, so a document without one cannot be parked`
|
|
61
|
+
: `${where}: the approval card word is ${refusal.bytes} UTF-8 bytes and the approval_card column holds at most ${refusal.maxBytes} on this database`);
|
|
62
|
+
this.name = "CheckpointIndexRefusedError";
|
|
63
|
+
this.refusal = refusal;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
39
66
|
function groupByFacet(pairs, column) {
|
|
40
67
|
const groups = new Map();
|
|
41
68
|
for (const [word, facet] of pairs) {
|
|
@@ -59,4 +86,5 @@ export const CHECKPOINT_FACET_SQL = Object.freeze({
|
|
|
59
86
|
tidb: renderFacetCase((column) => `CAST(${column} AS BINARY)`),
|
|
60
87
|
pg: renderFacetCase((column) => column),
|
|
61
88
|
});
|
|
89
|
+
export const CHECKPOINT_SWEEP_ORDER_SQL = "CASE WHEN approval_card IS NULL THEN 1 ELSE 0 END ASC, deadline ASC, token ASC";
|
|
62
90
|
//# sourceMappingURL=checkpoint-index-columns.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
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
7
|
/** M28 店腿的读数(两孪生同形;随 M28 删):扫到的可读行 · 回写成的行 · 回写成的行里被改写的值 · 读不出的行 · 封好之后一行装不下的行。 */
|
|
@@ -43,8 +43,11 @@ export declare function tokenFingerprint(token: string): string;
|
|
|
43
43
|
*
|
|
44
44
|
* 🔴 [ref]:行**顶层**另带 {@link ParkRowFacts}(`requiresRealApproval` / `ruleOffersAbsence`,真才带、缺席 ≠ false)——
|
|
45
45
|
* 型与值都只有一个来源:`approval-card.ts` 的 {@link parkRowFacts}(两只店各读自己手里的那只寄存行 `pendingAction`,
|
|
46
|
-
* 交给它、原样展开)。继承而不是在这里逐键再写一遍:core 给这一臂再加一位同类事实时,改的只有那张 Pick 表。
|
|
47
|
-
|
|
46
|
+
* 交给它、原样展开)。继承而不是在这里逐键再写一遍:core 给这一臂再加一位同类事实时,改的只有那张 Pick 表。
|
|
47
|
+
*
|
|
48
|
+
* 技能卡三位({@link SkillCardFacts}:`boundGrantDigest` / `boundGrants` / `classifierNoRuling`)另有一只投影 {@link skillCardFacts},输入是 core 的检查点摘要而不是
|
|
49
|
+
* 寄存行原值(清单只在哈希等于摘要时才配上 wire,校验点单点在 core —— 见 {@link skillCardFacts} 顶注);同一张行上与收件箱行逐字同值,在场才带。 */
|
|
50
|
+
export interface PendingCheckpoint extends ParkRowFacts, SkillCardFacts {
|
|
48
51
|
sessionId: string;
|
|
49
52
|
/**
|
|
50
53
|
* 这道停驻门的**非秘密稳定身份**(core `Checkpoint.checkpointId`,引擎每次停驻铸)—— 三处 durable 读面(本行 = `GET /v1/approvals` 的 `pending[]`
|
|
@@ -660,6 +663,12 @@ export declare class SqlCheckpointStore implements CheckpointStore {
|
|
|
660
663
|
* approval gate (facet `B`) is not here: core refuses `sla_timeout` on it pre-CAS, and reapExpired voids it. The filter is
|
|
661
664
|
* in SQL, before the LIMIT: `limit` bounds the per-tick deny-resume work (each is a model run), never hides a row behind
|
|
662
665
|
* rows the sweep does not own.
|
|
666
|
+
*
|
|
667
|
+
* Order ({@link CHECKPOINT_SWEEP_ORDER_SQL}): rows whose `approval_card` column carries a word come first, then rows whose card
|
|
668
|
+
* 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
|
|
669
|
+
* row a replica that does not know the column wrote during a mixed-version window — possibly a question, which the engine
|
|
670
|
+
* refuses to deny (`sla_timeout`) and which the next boot's M29 backfill settles. The column cannot tell the two apart, so the
|
|
671
|
+
* order does: rows whose facet is certain take the window first and a straggler cannot crowd them out.
|
|
663
672
|
*/
|
|
664
673
|
listExpiredApprovalGates(cutoff: number, limit?: number): Promise<Array<{
|
|
665
674
|
sessionId: string;
|
|
@@ -676,6 +685,21 @@ export declare class SqlCheckpointStore implements CheckpointStore {
|
|
|
676
685
|
* 真正需要它的读面是**已决行**的那一条:`findDecidedApprovalsForBinding`(decide 幂等回放),见那里。
|
|
677
686
|
*/
|
|
678
687
|
listPending(scope?: string): Promise<PendingCheckpoint[]>;
|
|
688
|
+
/**
|
|
689
|
+
* 🔴 [ref] —— 运维队列行的**技能卡三位**({@link SkillCardFacts})。值与收件箱行同源:core `summarizeCheckpoint` 对同一行投的摘要,经 {@link skillCardFacts} 这一只投影。
|
|
690
|
+
*
|
|
691
|
+
* 为什么不像 {@link parkRowFacts} 那几位一样直接读 `pendingAction` 子树原值:三位里的**清单** `boundGrants` 在寄存行上是未经哈希校验的原值,core 只在摘要里「哈希等于摘要」
|
|
692
|
+
* 才发布它(「never a list beside a digest that binds another」);校验读口与哈希函数都不在 core 的公开导出面上,本仓既算不出也不该再写一份 ——
|
|
693
|
+
* 于是要拿到校验过的清单,只有对这一行跑 core 的 `summarizeCheckpoint`,而它吃的是整只 {@link Checkpoint}(不是子树)。
|
|
694
|
+
*
|
|
695
|
+
* 读放大(如实):**只对行上真带三位之一的行**(摘要是串,或成因词键在场 —— {@link parkRowCarriesSkillCardFacts})多读该行一次整只快照;普通卡(绝大多数行)零额外读,
|
|
696
|
+
* 主 SELECT 仍只取 `pendingAction` 子树(NP-3 不退)。这是一个**只增不改的代价**:带三位之一的待决行数 × 读次数(`/stream` 每连接每 3s 一拍)。
|
|
697
|
+
* 🚧 清单校验读口成为 core 公开导出的那一版,本读口与门一并退役,三位改走 `parkRowFacts` 读原值经 core 的读口 —— 零额外读。
|
|
698
|
+
*
|
|
699
|
+
* 失败方向与 {@link readParkRowActionCell} 逐字同一条判据(展示/分诊材料、行级容忍 + 响亮、同一个 tag):整只快照在场却不是一只合法的检查点(读回的体坏了,或 core 的摘要读不动它)
|
|
700
|
+
* ⇒ 只丢**这一行**的三位;行已不在待决(两次读之间被决/过期)⇒ 三位缺席;`not-text`(读腿漏了投影)原样抛。
|
|
701
|
+
*/
|
|
702
|
+
private readParkedSkillCardFacts;
|
|
679
703
|
/**
|
|
680
704
|
* [ref] #2 (scheduler seam, core 1.105): a scope's PENDING checkpoints as lightweight
|
|
681
705
|
* {@link CheckpointSummary} rows in ONE query — the cross-task triage view a supervisor scheduler needs
|
|
@@ -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,
|
|
3
|
+
import { CHECKPOINT_FACET_SQL, CHECKPOINT_SWEEP_ORDER_SQL, CheckpointIndexRefusedError, judgeCheckpointIndexCells } from "./checkpoint-index-columns.js";
|
|
4
4
|
import { CheckpointError, isGateKind, MAX_RULE_TEXT_CHARS, validatePendingSteer, appendPendingSteer, winnerFromOutcome, summarizeCheckpoint, 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);
|
|
@@ -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 = [];
|
|
@@ -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. */
|
|
@@ -716,6 +716,29 @@ export class SqlFileHistoryStore {
|
|
|
716
716
|
conn.release();
|
|
717
717
|
}
|
|
718
718
|
}
|
|
719
|
+
async verifySourceBlobs(data, srcGetBlob, onVerified) {
|
|
720
|
+
const needed = new Set();
|
|
721
|
+
for (const t of data.tracked)
|
|
722
|
+
for (const v of t.versions)
|
|
723
|
+
if (v.blobHash !== null)
|
|
724
|
+
needed.add(v.blobHash);
|
|
725
|
+
for (const hash of needed) {
|
|
726
|
+
let bytes;
|
|
727
|
+
try {
|
|
728
|
+
bytes = await srcGetBlob(hash);
|
|
729
|
+
}
|
|
730
|
+
catch (err) {
|
|
731
|
+
return { ok: false, error: { code: "adopt_failed", message: `source blob ${hash} fetch failed: ${foldCaughtFailure(err).text}` } };
|
|
732
|
+
}
|
|
733
|
+
if (bytes === undefined)
|
|
734
|
+
return { ok: false, error: { code: "adopt_failed", message: `source blob ${hash} is missing — the envelope's graph cannot be completed, so nothing is published` } };
|
|
735
|
+
if (sha256hex(bytes) !== hash) {
|
|
736
|
+
return { ok: false, error: { code: "unsupported_format", message: `source blob ${hash} fails content-address integrity — refused, nothing published` } };
|
|
737
|
+
}
|
|
738
|
+
await onVerified?.(hash, bytes);
|
|
739
|
+
}
|
|
740
|
+
return { ok: true };
|
|
741
|
+
}
|
|
719
742
|
async importHistory(scope, data, srcGetBlob) {
|
|
720
743
|
try {
|
|
721
744
|
const shaped = validateFileHistoryExport(data);
|
|
@@ -733,31 +756,15 @@ export class SqlFileHistoryStore {
|
|
|
733
756
|
if (!exported.ok)
|
|
734
757
|
return exported;
|
|
735
758
|
const existing = exported.history;
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
return { ok: true };
|
|
759
|
+
const sameGraph = existing !== null && fileHistoryExportDigest(existing) === fileHistoryExportDigest(data);
|
|
760
|
+
if (existing !== null && !sameGraph) {
|
|
739
761
|
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
762
|
}
|
|
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
|
-
}
|
|
763
|
+
const verified = await this.verifySourceBlobs(data, srcGetBlob, sameGraph ? undefined : (hash, bytes) => this.blobs.putBlob(hash, bytes));
|
|
764
|
+
if (!verified.ok)
|
|
765
|
+
return verified;
|
|
766
|
+
if (sameGraph)
|
|
767
|
+
return { ok: true };
|
|
761
768
|
const conn = await this.db.connect();
|
|
762
769
|
try {
|
|
763
770
|
await conn.begin();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { denySweepOwns, parkedApprovalCard } from "../approval-content-kind.js";
|
|
3
|
-
import { parkRowFacts } from "../approval-card.js";
|
|
3
|
+
import { parkRowFacts, skillCardFacts } from "../approval-card.js";
|
|
4
4
|
import { durableRowIdentity } from "../checkpoint-identity.js";
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
6
|
import { existsSync, readFileSync, readdirSync, unlinkSync, mkdirSync } from "node:fs";
|
|
@@ -155,7 +155,7 @@ export class LocalCheckpointStore {
|
|
|
155
155
|
for (const summary of await this.inner.listByScope(s)) {
|
|
156
156
|
const cp = await this.inner.get(summary.token);
|
|
157
157
|
if (cp && cp.status === "pending")
|
|
158
|
-
out.push({ token: summary.token, cp });
|
|
158
|
+
out.push({ token: summary.token, cp, summary });
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
return out;
|
|
@@ -165,7 +165,7 @@ export class LocalCheckpointStore {
|
|
|
165
165
|
}
|
|
166
166
|
async listPending(scope) {
|
|
167
167
|
const rows = [];
|
|
168
|
-
for (const { token, cp } of await this.pendings(scope)) {
|
|
168
|
+
for (const { token, cp, summary } of await this.pendings(scope)) {
|
|
169
169
|
const pa = cp.pendingAction;
|
|
170
170
|
const gate = cp.gate;
|
|
171
171
|
const gateKind = queueRowGateKind(gate?.kind);
|
|
@@ -193,6 +193,7 @@ export class LocalCheckpointStore {
|
|
|
193
193
|
...(pa?.hasBidiControls === true ? { hasBidiControls: true } : {}),
|
|
194
194
|
...((card) => (card !== undefined ? { approvalCard: card } : {}))(parkedApprovalCard(cp.gate)),
|
|
195
195
|
...parkRowFacts(cp.pendingAction),
|
|
196
|
+
...skillCardFacts(summary),
|
|
196
197
|
});
|
|
197
198
|
}
|
|
198
199
|
return rows.sort((a, b) => (b.riskDescriptor?.severity ?? 0) - (a.riskDescriptor?.severity ?? 0) || a.createdAt - b.createdAt);
|
|
@@ -24,22 +24,20 @@
|
|
|
24
24
|
* ⇒ 换成本段:**升一次级把存量字节改完,兼容臂当场删干净**。代价是每一条在它的窗内每次启动都要跑一遍(第二次起全是
|
|
25
25
|
* 零命中的空转);收益是窗关之后可以把那一条连同它的 SQL 一起删,而读腿从引入那一版起就只有**一种**读法。
|
|
26
26
|
*
|
|
27
|
-
* ## 已退役的 M1–
|
|
27
|
+
* ## 已退役的 M1–M5 与 M14
|
|
28
28
|
* 退役的整树快照索引表(M1)、退役的单座 steering 列(M2)、被上游改了名的闭集词 `unresolvable` → `ancestor_marked`(M3)三条的改写、计数与读数
|
|
29
29
|
* 窗关后已全部删码,登记行一并删。跳过了它们直接升的库由起服对账一族的拒启臂接住(`schema-consistency.ts` 头注「第二道对账」一节:两件退役 schema
|
|
30
30
|
* 任一在场 ⇒ 拒启、点名出路;先于一切建表)—— 那一道没有窗,所以不在本文件里。本段入口({@link runSqlOneTimeMigrations})不问这一问。
|
|
31
31
|
* 审计明细里的 JSON 文本 `null`(M4:`device_audit.detail` 里「没有明细」的另一种写法)同样窗关后已删码,登记行一并删;它没有拒启臂 —— 跳过了它直接升的库,
|
|
32
32
|
* 含这种旧行的那一页审计由 `device-store-sql.ts` 的 `listAudit` 读形拒接住(一行读不出形 ⇒ 整页拒,拒句点名行 id;刻意不逐行隔离),出路是运维把那些行的 `detail` 置成 SQL NULL(MIGRATION 写了语句)。
|
|
33
|
+
* 会话化身列(M5:`session_meta.incarnation` 的补列与空化身行的逐行铸令牌)同样已删码(本版提前于窗关删),登记行一并删;它没有自己的拒启臂,跳过了它直接升的库由两道**既有、常设**的臂接住:
|
|
34
|
+
* ① 列不在 ⇒ 起服列册对账(`schema-consistency.ts`)缺列拒启 `config.schema_mismatch`,拒句里的 `ALTER TABLE … ADD COLUMN …` 由中央建表语句生成;
|
|
35
|
+
* ② 列在但行上仍是空化身 ⇒ 会话店头读投成活住客(`session-incarnation.ts` 的 `sessionFaceOfHead`)时对那一行响亮拒 `session.incarnation_missing`(parked 决定腿因此拒、记忆座退回按 id 绑定并 warn),
|
|
36
|
+
* 出路是运维给那些行各填一只互不相同的随机令牌,或删会话(MIGRATION 写了升级序)。
|
|
37
|
+
* LKG 的 formatVersion 1 迁移(M14:7.98.0 及以前写在默认路径的旧缓存文件迁进身份箱;非店迁移,不写库)同样已删码(本版提前于窗关删),登记行一并删;它没有自己的拒启臂 —— 那是缓存文件、不是数据:
|
|
38
|
+
* `CONFIG_LKG_PATH` 指到旧文件 ⇒ `loadLkg` 的 1 臂答 `config_lkg_unusable`(env 兜底),默认路径上的旧文件名不是身份箱的文件名形、不再被读,中心可达时第一份干净候选照常写出身份箱。
|
|
33
39
|
*
|
|
34
40
|
* ## 窗内的迁移(逐条的失败方向刻意不同)
|
|
35
|
-
* · **M5 会话化身列**([ref] / [ref],[ref] §2)—— `session_meta.incarnation` 是本版新加的列(新库由中央 DDL 建;
|
|
36
|
-
* 列定义唯一属主 = `src/session-incarnation.ts` 的 `SESSION_META_INCARNATION_COLUMN`,迁移内插同一串)。存量库:
|
|
37
|
-
* 列不在 ⇒ `ADD COLUMN`(加法 DDL,不丢任何字节),然后给每一行空化身(`''` = 没铸过)铸一只**互不相同**的令牌
|
|
38
|
-
* (应用侧 `randomUUID()`,不用库函数:MySQL / TiDB 的 `UUID()` 是 v1,带时间与机器信息)。回填之后读到空化身 ⇒ 读口响亮拒
|
|
39
|
-
* (`SessionIncarnationMissingError`),**不**当作通配 —— 硬 breaking 三句里持久数据那一句的形:一次性迁移 + 响亮拒,无双读。
|
|
40
|
-
* local 文件车道没有这张表:那一车道的存量会话在第一次被读时补铸并落盘(`LocalSessionStore.headOf`),本段答
|
|
41
|
-
* `"lazy-on-read"` / 0。
|
|
42
|
-
*
|
|
43
41
|
* · **M6 run 死亡判决码** —— 7.116.0 起 run 店的两只死亡判决(失联收割 / 重启孤儿回收)在行上同写机读码(`error_code` =
|
|
44
42
|
* `run.stalled` / `run.orphaned`,词表属主 `store-contracts.ts` 的 `RUN_HOLDER_DEATH_CODES`),读者(`isHolderDeathVerdict`)只认码。
|
|
45
43
|
* 存量行只有句子、码列空 ⇒ **写回半场**:起服把「`failed` ∧ 码列空 ∧ 句子是那两句(接管臂的括注形按前缀认)」的行补上码
|
|
@@ -256,7 +254,8 @@
|
|
|
256
254
|
* 与 M10 / M19 / M25 同为在本段之外跑的条目),编排在 `checkpoint-ctx-seal-m28.ts`(起服第 6 段,checkpoint 店建成之后、任何路由装配之前调一次),
|
|
257
255
|
* 店腿是两孪生各自的 `rewriteCtxRowsM28`(SQL:键集分页 + 按原文 CAS、`updated_at_ms` 不动;local:逐档原子重写、信封的 `updatedAt` 不动),
|
|
258
256
|
* 行改写是读写两侧同一只封口(`sealStoredCheckpointCtxM28`),封的是「读口会答 `plaintext` 的每一个值」(与开口同一只判词)—— 7.135.0 闭集加了一位
|
|
259
|
-
* (command 条目的 `plugin.options`)之后,7.134.0 写下的带记号行在那一位上的明文也落在这一条里、同窗补封(本条没有新窗)
|
|
257
|
+
* (command 条目的 `plugin.options`)之后,7.134.0 写下的带记号行在那一位上的明文也落在这一条里、同窗补封(本条没有新窗);7.136.0 再加四位(子进程的命令行:MCP stdio 传输与
|
|
258
|
+
* command 条目各自的 `command` / `args`,第 3 代)同理 —— 无记号行与 7.134.0 / 7.135.0 写下的第 1 / 2 代行上的明文命令行同窗补封。不删行、不改续跑语义;读不出的行计数不动;已封与不带凭据的行零写。读数不进
|
|
260
259
|
* {@link OneTimeMigrationReport}(`reportKeys` 空,与 M25 同形),由起服 info 一行 `checkpoint_ctx_credentials_sealed` 说(扫了几行、封了几行几个值)。
|
|
261
260
|
* 窗外不扫。窗 = 发布日 + 30 天(与 M21 / M23 同一条论证:窗关时缺省部署上升级前停驻的行已被终局兜底收走);窗关之后仍是明文的行由读口的 `plaintext` 拒接住
|
|
262
261
|
* (出路 = 重提)。**本条不覆盖混跑(如实登记)**:≤7.133.x 的续跑读者对凭据位上的封签值没有拒臂(读口、重放判据、店的形判都不看那一格,各消费者按「形不对」把它丢掉、
|
|
@@ -278,10 +277,10 @@
|
|
|
278
277
|
* 账就是读数,起服日志打一行 `one_time_migrations_applied` + 逐行的那几种;任何一步失败 ⇒ 账里有动过的格就随拒因交出({@link OneTimeMigrationInterruptedError}),起服日志在抛之前
|
|
279
278
|
* 用**同一只打印口**打一行 `one_time_migrations_interrupted`(读数键与读数行同一只投影)+ 逐行的那几种({@link runAndLogOneTimeMigrations})。理由:提交了的改写下一次起服再扫是
|
|
280
279
|
* 零命中 —— 被改写 / 被删的行、它们的计数只有「改写发生的那一拍」说得出。没有任何一格动过就失败 ⇒ 原错误原样抛。
|
|
281
|
-
* 如实的残余:提交与那一行日志之间进程被杀、或一条提交的确认在连接上丢了 —— 收据没有耐久载体(不为短命的迁移另建迁移账表:有 schema 形可当「跑过了」标记的几条用它自己的形 ——
|
|
280
|
+
* 如实的残余:提交与那一行日志之间进程被杀、或一条提交的确认在连接上丢了 —— 收据没有耐久载体(不为短命的迁移另建迁移账表:有 schema 形可当「跑过了」标记的几条用它自己的形 —— M9 的主键宽度、M17 的列名;改值类的几条没有这种标记,为它们造一张账表 / 一个旋钮,就是给一条短命的迁移留一份长命的状态)。
|
|
282
281
|
*
|
|
283
282
|
* ## 幂等
|
|
284
|
-
* 改写的几条都可重复跑(
|
|
283
|
+
* 改写的几条都可重复跑(M6:`WHERE error_code IS NULL`;
|
|
285
284
|
* M8 / M18:字样扫描 + CAS 回写 —— 写过的行不再命中;M17 的改写只在改名那一拍跑,第二跑列名已是新名 ⇒ 零读零写;续判时已带戳的行零动作;
|
|
286
285
|
* M24:正表 scope 列在场探针 —— 换名之后只补收尾(删待删旧表),换名之前被打断则暂存表删掉重建、回填只补缺的属主记录(`NOT EXISTS`);
|
|
287
286
|
* M27:只按判官挑 —— 删过的键不再被扫到)。并发起动由 {@link runSqlOneTimeMigrations} 的 advisory 锁 + 每步的后置条件判据兜住(见那里)。
|
|
@@ -292,13 +291,7 @@ import { type AllowEntryInertReason, type GateKind } from "@sema-agent/core";
|
|
|
292
291
|
import { type UndurableScopeRuleRow, type UnreadableRuleBucket } from "./permission-rule-store-sql.js";
|
|
293
292
|
/** 迁移读数(启动日志一行写全;测试按它断言,不靠日志文本)。 */
|
|
294
293
|
export interface OneTimeMigrationReport {
|
|
295
|
-
/**
|
|
296
|
-
* `no-table` = 这只库上没有 `session_meta`(中央 DDL 之后不可达,只为不编一个读数);`lazy-on-read` = local 文件车道
|
|
297
|
-
* (没有这张表,存量会话首读补铸)。 */
|
|
298
|
-
readonly sessionIncarnationColumn: "added" | "present" | "no-table" | "lazy-on-read";
|
|
299
|
-
/** M5:本次给空化身铸了令牌的行数。非零 = 这次迁移真写了存量行,必须让运维看见。 */
|
|
300
|
-
readonly sessionIncarnationsBackfilled: number;
|
|
301
|
-
/** M6:本次给「只有句子」的死亡判决行补上机读码的 run 行数(SQL `task_run` / local `run.json`)。非零 = 这次迁移真写了存量行(与 M5 同一条纪律)。 */
|
|
294
|
+
/** M6:本次给「只有句子」的死亡判决行补上机读码的 run 行数(SQL `task_run` / local `run.json`)。非零 = 这次迁移真写了存量行,必须让运维看见。 */
|
|
302
295
|
readonly holderDeathCodeRowsBackfilled: number;
|
|
303
296
|
/** M8:本次把 `checkpoint.risk_descriptor` 的旧披露键 `shadowedRule` 改写成 `allowRuleShadowed` 的行数(SQL 独有;local 恒 0)。非零 = 真写了存量行。 */
|
|
304
297
|
readonly allowRuleShadowRowsMigrated: number;
|
|
@@ -406,8 +399,8 @@ export interface ParkedApprovalCardRowUnreadable {
|
|
|
406
399
|
export interface ParkedApprovalCardRowTooLong extends ParkedApprovalCardRowUnreadable {
|
|
407
400
|
readonly bytes: number;
|
|
408
401
|
}
|
|
409
|
-
/** 三个形读数(
|
|
410
|
-
type ShapeReadingKey = "
|
|
402
|
+
/** 三个形读数(M9 主键 / M17 列名 / M24 scope 列)—— 那一步**落定**之前收据账里缺席(读数行要它们齐;被打断的那一行只带已落定的)。 */
|
|
403
|
+
type ShapeReadingKey = "workflowParkIndexKey" | "sessionRuleColumn" | "workflowJournalScopeColumn";
|
|
411
404
|
/**
|
|
412
405
|
* 读数的「已记下的那些」形:形读数可缺席(那一步还没落定),其余每一键与 {@link OneTimeMigrationReport} 同名同形。读数行({@link logOneTimeMigrations})与
|
|
413
406
|
* 被打断的那一行({@link logOneTimeMigrationsInterrupted})读同一只投影({@link readingFields}),拒因({@link OneTimeMigrationInterruptedError})带的也是这一形。
|
|
@@ -441,7 +434,7 @@ export declare class OneTimeMigrationInterruptedError extends Error {
|
|
|
441
434
|
* 规则(窗长 30 天、窗关后的第一个 minor 删码、门怎么判)在本文件头注「窗」一节,只写那一次。
|
|
442
435
|
*/
|
|
443
436
|
export interface OneTimeMigrationWindow {
|
|
444
|
-
/** 本文件里的条目名(头注「窗内的迁移」的
|
|
437
|
+
/** 本文件里的条目名(头注「窗内的迁移」的 M6…)。 */
|
|
445
438
|
readonly id: string;
|
|
446
439
|
/** 引入这条迁移的版本 = 第一只含其代码的发布 tag(取证:`git log -S '<该条的常量名>' -- src/plugins/one-time-migrations.ts`
|
|
447
440
|
* 找引入提交 → `git tag --contains <提交> --sort=creatordate` 取最早)。 */
|
|
@@ -470,14 +463,14 @@ export declare const ONE_TIME_MIGRATIONS: readonly OneTimeMigrationWindow[];
|
|
|
470
463
|
* 只给**窗内才生效的行为臂**用(M10 的认领、M19 / M20 的续跑重读):那种臂过了窗就停,不等删码车 —— 删码因此是零行为变化,而窗只写在本表这一处。
|
|
471
464
|
*/
|
|
472
465
|
export declare function migrationWindowOpen(window: Pick<OneTimeMigrationWindow, "removableAfter">, today: string): boolean;
|
|
473
|
-
/** local 车道上 SQL 独有的几条(
|
|
466
|
+
/** local 车道上 SQL 独有的几条(M8 / M9 / M11 / M17 / M18 / M21 / M23 / M24 / M26 / M27 / M29)的读数(M6 两车道都有,local 腿的读数由 `LocalBackend.runOneTimeMigrations` 现算):那几张表 / 列在文件底座上从来不存在 ⇒ 如实答 absent / 0。 */
|
|
474
467
|
export declare const NO_SQL_MIGRATIONS: Omit<OneTimeMigrationReport, "holderDeathCodeRowsBackfilled">;
|
|
475
468
|
/** 启动期的日志口(logger 的子集)。两级各有其用,见 {@link logOneTimeMigrations}。 */
|
|
476
469
|
export interface MigrationLogSink {
|
|
477
470
|
warn(msg: string, meta?: Record<string, unknown>): void;
|
|
478
471
|
info(msg: string, meta?: Record<string, unknown>): void;
|
|
479
472
|
}
|
|
480
|
-
/** 方言无关的最小查询口(两方言的返回形不同,由下面两只适配器收平)。`params` 只给带值的语句用(
|
|
473
|
+
/** 方言无关的最小查询口(两方言的返回形不同,由下面两只适配器收平)。`params` 只给带值的语句用(会话 id、行原文等
|
|
481
474
|
* 调用方自选的任意串,不许内插进 SQL 文本);占位符形由调用方按方言写(`?` / `$n`)。 */
|
|
482
475
|
export interface MigrationDb {
|
|
483
476
|
/** 取行(只读探针用)。 */
|
|
@@ -498,7 +491,7 @@ export interface MigrationDb {
|
|
|
498
491
|
* 每条改表 DDL 的成功判据仍是它的**后置条件**({@link execUntil}),「另一个副本抢先把同一张表换了形」不会把滚动升级变成启动失败循环。
|
|
499
492
|
*
|
|
500
493
|
* ## 次序(codex r1 [medium] 同轮)
|
|
501
|
-
* 非破坏改写(
|
|
494
|
+
* 非破坏改写(M6–M9、M18:改值不丢信息 / 复制 + 换名)在前,破坏性步骤(M11 的删行、M24 丢下跨 scope 行的换名)排在最后;
|
|
502
495
|
* M11 自己半途失败时,拒因自带已删的行(`purgeUndurableScopeRows`)—— 否则重试看不见已删的行,证据永久消失;M24 的丢行计数在换名提交之后进收据账。
|
|
503
496
|
*
|
|
504
497
|
* 本函数**不问**退役 schema:带着它的库在更早处就被 `ensureSchema()` 自身拒掉了(起服对账一族 `schema-consistency.ts` 的探针口,先于建表);走公开序列
|
|
@@ -618,10 +611,10 @@ export declare function parkedRowRepairOf(gateKindCell: unknown, gate: unknown,
|
|
|
618
611
|
export declare function repairParkedRows(db: MigrationDb, dialect: "tidb" | "pg", receipts: MigrationReceipts): Promise<void>;
|
|
619
612
|
/**
|
|
620
613
|
* M29 —— 存量停驻行的 `approval_card` 列回填(规则、为什么、够不到的一格见头注 M29 段):列不在 ⇒ 延后(零扫描);只扫 `status = 'pending'` ∧ 列为 NULL ∧ 审批门型的行,
|
|
621
|
-
* 按 `token`
|
|
614
|
+
* 按 `token` 键集分页,每行单独读整只文档、交给写口用的**同一只**判官({@link judgeCheckpointIndexCells} → `buildCheckpointIndexCells`,core `checkpointIndexFacts`):答出词 ⇒
|
|
622
615
|
* `UPDATE … SET approval_card = ? WHERE token = ? AND status = 'pending' AND approval_card IS NULL`(第二跑零写;两次读之间被决掉 / 收割 ⇒ 零命中);答 NULL(无卡、非串、
|
|
623
|
-
* 文档的门不是审批门)⇒ 不写;文档读不出、或派生读不了它(缺门 / 缺寄存行)⇒ 计数不写;答出的词超过列的容量({@link
|
|
624
|
-
* (同一个态度:一行手改数据不许挡整只部署起服)。每一次提交当场记进收据账。
|
|
616
|
+
* 文档的门不是审批门)⇒ 不写;文档读不出、或派生读不了它(缺门 / 缺寄存行)⇒ 计数不写;答出的词超过列的容量(判官 {@link judgeCheckpointIndexCells} 的 `card_too_long`,
|
|
617
|
+
* MySQL 协议 TEXT 65,535 UTF-8 字节)⇒ 计数 + 点名不写(同一个态度:一行手改数据不许挡整只部署起服)。每一次提交当场记进收据账。
|
|
625
618
|
*/
|
|
626
619
|
export declare function backfillParkedApprovalCards(db: MigrationDb, dialect: "tidb" | "pg", receipts: MigrationReceipts): Promise<void>;
|
|
627
620
|
/**
|