@sema-agent/server 1.314.0 → 1.316.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/dist/approval-hmac.d.ts +17 -0
- package/dist/approval-hmac.js +27 -0
- package/dist/config-center/apply-effective.d.ts +22 -0
- package/dist/config-center/apply-effective.js +283 -0
- package/dist/config-center/http-client.d.ts +23 -0
- package/dist/config-center/http-client.js +109 -0
- package/dist/config-center/restart-signal.d.ts +12 -0
- package/dist/config-center/restart-signal.js +70 -0
- package/dist/config-center/skills-mcp.d.ts +13 -0
- package/dist/config-center/skills-mcp.js +113 -0
- package/dist/config-center/types.d.ts +143 -0
- package/dist/config-center/types.js +2 -0
- package/dist/fleet/fleet-bus.js +92 -83
- package/dist/hooks/hook-runner.js +18 -9
- package/dist/http/idempotency.d.ts +1 -0
- package/dist/http/idempotency.js +3 -0
- package/dist/http/principal-gate.d.ts +12 -0
- package/dist/http/principal-gate.js +52 -0
- package/dist/http/route-ctx.d.ts +67 -0
- package/dist/http/route-ctx.js +2 -0
- package/dist/http/routes/attachments.d.ts +4 -0
- package/dist/http/routes/attachments.js +106 -0
- package/dist/http/routes/capabilities.d.ts +4 -0
- package/dist/http/routes/capabilities.js +111 -0
- package/dist/http/routes/images.d.ts +20 -0
- package/dist/http/routes/images.js +572 -0
- package/dist/http/routes/memory-policy.d.ts +4 -0
- package/dist/http/routes/memory-policy.js +105 -0
- package/dist/http/routes/observability.d.ts +4 -0
- package/dist/http/routes/observability.js +63 -0
- package/dist/http/routes/session-sync.d.ts +36 -0
- package/dist/http/routes/session-sync.js +513 -0
- package/dist/http/routes/sessions-list.d.ts +4 -0
- package/dist/http/routes/sessions-list.js +117 -0
- package/dist/http/routes/sessions.d.ts +17 -0
- package/dist/http/routes/sessions.js +746 -0
- package/dist/http/send.d.ts +6 -0
- package/dist/http/send.js +19 -0
- package/dist/http/server.d.ts +101 -76
- package/dist/http/server.js +129 -2410
- package/dist/http/sse-log.d.ts +25 -0
- package/dist/http/sse-log.js +59 -0
- package/dist/http/workspace-content.d.ts +3 -0
- package/dist/http/workspace-content.js +20 -0
- package/dist/index.d.ts +1 -1
- package/dist/leader/leader.js +5 -2
- package/dist/leader/wire.js +169 -165
- package/dist/main.js +462 -451
- package/dist/plugins/approval-store-sql.d.ts +52 -0
- package/dist/plugins/approval-store-sql.js +119 -0
- package/dist/plugins/breaker-state-sql.d.ts +29 -0
- package/dist/plugins/breaker-state-sql.js +104 -0
- package/dist/plugins/checkpoint-store-sql.d.ts +67 -0
- package/dist/plugins/checkpoint-store-sql.js +224 -0
- package/dist/plugins/file-snapshot-store-sql.d.ts +38 -0
- package/dist/plugins/file-snapshot-store-sql.js +210 -0
- package/dist/plugins/image-bake-store-sql.d.ts +53 -0
- package/dist/plugins/image-bake-store-sql.js +463 -0
- package/dist/plugins/image-index-sql.d.ts +33 -0
- package/dist/plugins/image-index-sql.js +328 -0
- package/dist/plugins/k8s-bg-scripts.d.ts +19 -0
- package/dist/plugins/k8s-bg-scripts.js +129 -0
- package/dist/plugins/k8s-exec-protocol.d.ts +20 -0
- package/dist/plugins/k8s-exec-protocol.js +87 -0
- package/dist/plugins/outcome-ledger-sql.d.ts +70 -0
- package/dist/plugins/outcome-ledger-sql.js +158 -0
- package/dist/plugins/pg-approval-store.d.ts +1 -27
- package/dist/plugins/pg-approval-store.js +1 -83
- package/dist/plugins/pg-breaker-state.d.ts +1 -19
- package/dist/plugins/pg-breaker-state.js +1 -88
- package/dist/plugins/pg-checkpoint-store.d.ts +1 -33
- package/dist/plugins/pg-checkpoint-store.js +1 -189
- package/dist/plugins/pg-cost-quota.js +3 -143
- package/dist/plugins/pg-file-snapshot-store.d.ts +1 -27
- package/dist/plugins/pg-file-snapshot-store.js +1 -190
- package/dist/plugins/pg-image-bake.d.ts +1 -40
- package/dist/plugins/pg-image-bake.js +1 -420
- package/dist/plugins/pg-image-index.d.ts +1 -21
- package/dist/plugins/pg-image-index.js +1 -217
- package/dist/plugins/pg-outcome-ledger.d.ts +2 -22
- package/dist/plugins/pg-outcome-ledger.js +1 -104
- package/dist/plugins/pg-rate-limiter.d.ts +2 -32
- package/dist/plugins/pg-rate-limiter.js +4 -147
- package/dist/plugins/pg-resume-anchor-store.d.ts +1 -13
- package/dist/plugins/pg-resume-anchor-store.js +1 -22
- package/dist/plugins/pg-run-store.d.ts +1 -75
- package/dist/plugins/pg-run-store.js +1 -287
- package/dist/plugins/pg-session-policy-store.d.ts +1 -11
- package/dist/plugins/pg-session-policy-store.js +1 -90
- package/dist/plugins/pg-tool-result-store.d.ts +1 -15
- package/dist/plugins/pg-tool-result-store.js +1 -52
- package/dist/plugins/pg-workflow-journal-store.d.ts +1 -20
- package/dist/plugins/pg-workflow-journal-store.js +1 -34
- package/dist/plugins/pg-workflow-run-store.d.ts +1 -55
- package/dist/plugins/pg-workflow-run-store.js +1 -220
- package/dist/plugins/remote-env-k8s.d.ts +5 -38
- package/dist/plugins/remote-env-k8s.js +7 -213
- package/dist/plugins/resume-anchor-store-sql.d.ts +23 -0
- package/dist/plugins/resume-anchor-store-sql.js +38 -0
- package/dist/plugins/run-store-sql.d.ts +89 -0
- package/dist/plugins/run-store-sql.js +397 -0
- package/dist/plugins/session-policy-store-sql.d.ts +22 -0
- package/dist/plugins/session-policy-store-sql.js +109 -0
- package/dist/plugins/sql-driver.d.ts +25 -0
- package/dist/plugins/sql-driver.js +59 -0
- package/dist/plugins/tidb-approval-store.d.ts +1 -39
- package/dist/plugins/tidb-approval-store.js +1 -71
- package/dist/plugins/tidb-breaker-state.d.ts +1 -17
- package/dist/plugins/tidb-breaker-state.js +1 -74
- package/dist/plugins/tidb-checkpoint-store.d.ts +1 -49
- package/dist/plugins/tidb-checkpoint-store.js +1 -191
- package/dist/plugins/tidb-file-snapshot-store.d.ts +1 -27
- package/dist/plugins/tidb-file-snapshot-store.js +1 -192
- package/dist/plugins/tidb-image-bake.d.ts +1 -38
- package/dist/plugins/tidb-image-bake.js +1 -348
- package/dist/plugins/tidb-image-index.d.ts +1 -19
- package/dist/plugins/tidb-image-index.js +1 -149
- package/dist/plugins/tidb-outcome-ledger.d.ts +1 -57
- package/dist/plugins/tidb-outcome-ledger.js +1 -97
- package/dist/plugins/tidb-resume-anchor-store.d.ts +1 -13
- package/dist/plugins/tidb-resume-anchor-store.js +1 -22
- package/dist/plugins/tidb-run-store.d.ts +1 -75
- package/dist/plugins/tidb-run-store.js +1 -306
- package/dist/plugins/tidb-session-policy-store.d.ts +1 -11
- package/dist/plugins/tidb-session-policy-store.js +1 -86
- package/dist/plugins/tidb-tool-result-store.d.ts +1 -13
- package/dist/plugins/tidb-tool-result-store.js +1 -38
- package/dist/plugins/tidb-workflow-journal-store.d.ts +1 -20
- package/dist/plugins/tidb-workflow-journal-store.js +1 -34
- package/dist/plugins/tidb-workflow-run-store.d.ts +1 -61
- package/dist/plugins/tidb-workflow-run-store.js +1 -231
- package/dist/plugins/tool-result-store-sql.d.ts +25 -0
- package/dist/plugins/tool-result-store-sql.js +69 -0
- package/dist/plugins/workflow-journal-store-sql.d.ts +30 -0
- package/dist/plugins/workflow-journal-store-sql.js +53 -0
- package/dist/plugins/workflow-run-store-sql.d.ts +85 -0
- package/dist/plugins/workflow-run-store-sql.js +299 -0
- package/dist/plugins/write-behind-counter.d.ts +14 -3
- package/dist/plugins/write-behind-counter.js +39 -12
- package/dist/principal-jwt.d.ts +44 -0
- package/dist/principal-jwt.js +95 -0
- package/dist/security.d.ts +2 -58
- package/dist/security.js +3 -118
- package/dist/sema-registry.d.ts +5 -200
- package/dist/sema-registry.js +4 -567
- package/package.json +1 -1
|
@@ -1,193 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
3
|
-
import { SqlBlobBackend, sqlBlobSizes } from "./blob-backend.js";
|
|
4
|
-
export class TiDBFileSnapshotStore {
|
|
5
|
-
pool;
|
|
6
|
-
bounds;
|
|
7
|
-
inFlight = new Set();
|
|
8
|
-
blobs;
|
|
9
|
-
onSkippedVanished = undefined;
|
|
10
|
-
constructor(pool, blobBackend, bounds = DEFAULT_SNAPSHOT_BOUNDS) {
|
|
11
|
-
this.pool = pool;
|
|
12
|
-
this.bounds = bounds;
|
|
13
|
-
this.blobs = blobBackend ?? new SqlBlobBackend("tidb", pool);
|
|
14
|
-
}
|
|
15
|
-
async putBlobRow(hash, bytes) {
|
|
16
|
-
await this.blobs.putBlob(hash, bytes);
|
|
17
|
-
}
|
|
18
|
-
async putBlob(hash, bytes) {
|
|
19
|
-
if (createHash("sha256").update(bytes).digest("hex") !== hash) {
|
|
20
|
-
return { ok: false, error: { code: "read_failed", message: `putBlob ${hash} fails content-address integrity — nothing stored` } };
|
|
21
|
-
}
|
|
22
|
-
try {
|
|
23
|
-
await this.putBlobRow(hash, bytes);
|
|
24
|
-
return { ok: true };
|
|
25
|
-
}
|
|
26
|
-
catch (err) {
|
|
27
|
-
return { ok: false, error: { code: "restore_failed", message: `putBlob ${hash} write/upload failed: ${err.message}` } };
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
async snapshot(scope, key, env, root, signal) {
|
|
31
|
-
const flightKey = `${scope}\x00${key}`;
|
|
32
|
-
if (this.inFlight.has(flightKey))
|
|
33
|
-
return { ok: true };
|
|
34
|
-
this.inFlight.add(flightKey);
|
|
35
|
-
try {
|
|
36
|
-
if (await this.has(scope, key))
|
|
37
|
-
return { ok: true };
|
|
38
|
-
let cap;
|
|
39
|
-
try {
|
|
40
|
-
cap = await captureManifest(env, root, this.bounds, signal, async (hash, bytes) => {
|
|
41
|
-
await this.putBlobRow(hash, bytes);
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
catch (err) {
|
|
45
|
-
return { ok: false, error: { code: "restore_failed", message: `snapshot blob write failed: ${err.message}` } };
|
|
46
|
-
}
|
|
47
|
-
if (!cap.ok)
|
|
48
|
-
return { ok: false, error: cap.error };
|
|
49
|
-
if (cap.skippedVanished > 0 && this.onSkippedVanished) {
|
|
50
|
-
try {
|
|
51
|
-
void Promise.resolve(this.onSkippedVanished(cap.skippedVanished)).catch(() => { });
|
|
52
|
-
}
|
|
53
|
-
catch { }
|
|
54
|
-
}
|
|
55
|
-
await this.tx(async (conn) => {
|
|
56
|
-
const [exists] = await conn.query("SELECT 1 FROM snapshot_manifest WHERE scope = ? AND snap_key = ? LIMIT 1", [scope, key]);
|
|
57
|
-
if (exists[0])
|
|
58
|
-
return;
|
|
59
|
-
for (const [relPath, hash] of cap.value) {
|
|
60
|
-
await conn.query("INSERT INTO snapshot_manifest (scope, snap_key, rel_path, blob_hash) VALUES (?,?,?,?)", [scope, key, relPath, hash]);
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
return { ok: true };
|
|
64
|
-
}
|
|
65
|
-
catch (err) {
|
|
66
|
-
return { ok: false, error: { code: "restore_failed", message: `snapshot persist failed: ${err.message}` } };
|
|
67
|
-
}
|
|
68
|
-
finally {
|
|
69
|
-
this.inFlight.delete(flightKey);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
async importManifest(scope, key, manifest, srcGetBlob) {
|
|
73
|
-
const flightKey = `${scope}\x00${key}`;
|
|
74
|
-
if (this.inFlight.has(flightKey))
|
|
75
|
-
return { ok: true };
|
|
76
|
-
this.inFlight.add(flightKey);
|
|
77
|
-
try {
|
|
78
|
-
if (await this.has(scope, key))
|
|
79
|
-
return { ok: true };
|
|
80
|
-
for (const hash of new Set(manifest.values())) {
|
|
81
|
-
let bytes;
|
|
82
|
-
try {
|
|
83
|
-
bytes = await srcGetBlob(hash);
|
|
84
|
-
}
|
|
85
|
-
catch (err) {
|
|
86
|
-
return { ok: false, error: { code: "read_failed", message: `source blob ${hash} fetch failed: ${err.message}` } };
|
|
87
|
-
}
|
|
88
|
-
if (!bytes)
|
|
89
|
-
return { ok: false, error: { code: "read_failed", message: `missing source blob ${hash}` } };
|
|
90
|
-
if (createHash("sha256").update(bytes).digest("hex") !== hash) {
|
|
91
|
-
return { ok: false, error: { code: "read_failed", message: `source blob ${hash} fails content-address integrity` } };
|
|
92
|
-
}
|
|
93
|
-
await this.putBlobRow(hash, bytes);
|
|
94
|
-
}
|
|
95
|
-
await this.tx(async (conn) => {
|
|
96
|
-
const [exists] = await conn.query("SELECT 1 FROM snapshot_manifest WHERE scope = ? AND snap_key = ? LIMIT 1", [scope, key]);
|
|
97
|
-
if (exists[0])
|
|
98
|
-
return;
|
|
99
|
-
for (const [relPath, hash] of manifest) {
|
|
100
|
-
await conn.query("INSERT INTO snapshot_manifest (scope, snap_key, rel_path, blob_hash) VALUES (?,?,?,?)", [scope, key, relPath, hash]);
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
return { ok: true };
|
|
104
|
-
}
|
|
105
|
-
catch (err) {
|
|
106
|
-
return { ok: false, error: { code: "restore_failed", message: `import persist failed: ${err.message}` } };
|
|
107
|
-
}
|
|
108
|
-
finally {
|
|
109
|
-
this.inFlight.delete(flightKey);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
async restore(scope, key, env, root, signal) {
|
|
113
|
-
try {
|
|
114
|
-
const [rows] = await this.pool.query("SELECT rel_path, blob_hash FROM snapshot_manifest WHERE scope = ? AND snap_key = ?", [scope, key]);
|
|
115
|
-
if (rows.length === 0)
|
|
116
|
-
return { ok: false, error: { code: "not_found", message: `no file snapshot for ${scope}/${key}` } };
|
|
117
|
-
const manifest = new Map(rows.map((r) => [String(r.rel_path), String(r.blob_hash)]));
|
|
118
|
-
return await applyManifest(env, root, this.bounds, signal, manifest, (hash) => this.blobs.getBlob(hash));
|
|
119
|
-
}
|
|
120
|
-
catch (err) {
|
|
121
|
-
return { ok: false, error: { code: "restore_failed", message: `restore blob read failed: ${err.message}` } };
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
async has(scope, key) {
|
|
125
|
-
const [rows] = await this.pool.query("SELECT 1 FROM snapshot_manifest WHERE scope = ? AND snap_key = ? LIMIT 1", [scope, key]);
|
|
126
|
-
return Boolean(rows[0]);
|
|
127
|
-
}
|
|
128
|
-
async listKeys(scope) {
|
|
129
|
-
const [rows] = await this.pool.query("SELECT DISTINCT snap_key FROM snapshot_manifest WHERE scope = ?", [scope]);
|
|
130
|
-
return rows.map((r) => String(r.snap_key));
|
|
131
|
-
}
|
|
132
|
-
async exportManifest(scope, key) {
|
|
133
|
-
const [rows] = await this.pool.query("SELECT rel_path, blob_hash FROM snapshot_manifest WHERE scope = ? AND snap_key = ?", [scope, key]);
|
|
134
|
-
if (rows.length === 0)
|
|
135
|
-
return null;
|
|
136
|
-
return new Map(rows.map((r) => [String(r.rel_path), String(r.blob_hash)]));
|
|
137
|
-
}
|
|
138
|
-
async getBlob(hash) {
|
|
139
|
-
return this.blobs.getBlob(hash);
|
|
140
|
-
}
|
|
141
|
-
async hasBlobs(hashes) {
|
|
142
|
-
return this.blobs.hasBlobs(hashes);
|
|
143
|
-
}
|
|
144
|
-
async blobSizes(hashes) {
|
|
145
|
-
return sqlBlobSizes("tidb", this.pool, hashes);
|
|
146
|
-
}
|
|
147
|
-
async reap(scope, keepKeys) {
|
|
148
|
-
return this.tx(async (conn) => {
|
|
149
|
-
const [keys] = await conn.query("SELECT DISTINCT snap_key FROM snapshot_manifest WHERE scope = ?", [scope]);
|
|
150
|
-
const drop = keys.map((k) => String(k.snap_key)).filter((k) => !keepKeys.includes(k));
|
|
151
|
-
for (const k of drop)
|
|
152
|
-
await conn.query("DELETE FROM snapshot_manifest WHERE scope = ? AND snap_key = ?", [scope, k]);
|
|
153
|
-
return drop.length;
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
async deleteBySession(scope) {
|
|
157
|
-
const [r] = await this.pool.query("DELETE FROM snapshot_manifest WHERE scope = ?", [scope]);
|
|
158
|
-
return r.affectedRows;
|
|
159
|
-
}
|
|
160
|
-
async gcOrphanBlobs() {
|
|
161
|
-
const [rows] = await this.pool
|
|
162
|
-
.query("SELECT blob_hash FROM snapshot_blob WHERE blob_hash NOT IN (SELECT blob_hash FROM snapshot_manifest)")
|
|
163
|
-
.catch(() => [[]]);
|
|
164
|
-
const orphans = rows.map((r) => String(r.blob_hash));
|
|
165
|
-
if (orphans.length === 0)
|
|
166
|
-
return 0;
|
|
167
|
-
return this.blobs.deleteBlobs(orphans);
|
|
168
|
-
}
|
|
169
|
-
async sweepOrphanBlobs() {
|
|
170
|
-
return this.gcOrphanBlobs();
|
|
171
|
-
}
|
|
172
|
-
async tx(fn) {
|
|
173
|
-
const conn = await this.pool.getConnection();
|
|
174
|
-
try {
|
|
175
|
-
await conn.beginTransaction();
|
|
176
|
-
const out = await fn(conn);
|
|
177
|
-
await conn.commit();
|
|
178
|
-
return out;
|
|
179
|
-
}
|
|
180
|
-
catch (err) {
|
|
181
|
-
try {
|
|
182
|
-
await conn.rollback();
|
|
183
|
-
}
|
|
184
|
-
catch {
|
|
185
|
-
}
|
|
186
|
-
throw err;
|
|
187
|
-
}
|
|
188
|
-
finally {
|
|
189
|
-
conn.release();
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
1
|
+
export { TiDBFileSnapshotStore } from "./file-snapshot-store-sql.js";
|
|
193
2
|
//# sourceMappingURL=tidb-file-snapshot-store.js.map
|
|
@@ -1,41 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { type BakeStatus, type BakeState, type BakeRecord, type BakeEvent, type CreateBakeInput, type BakeTerminal } from "./store-contracts.js";
|
|
1
|
+
export { TiDBImageBake, SqlImageBake } from "./image-bake-store-sql.js";
|
|
3
2
|
export type { BakeStatus, BakeState, BakeErrorCode, BakeRecord, BakeEvent, CreateBakeInput, BakeTerminal, BakeRow, } from "./store-contracts.js";
|
|
4
3
|
export { mapBakeRow as mapRow, BAKE_SELECT_COLS as SELECT_COLS } from "./store-contracts.js";
|
|
5
|
-
export declare class TiDBImageBake {
|
|
6
|
-
private readonly pool;
|
|
7
|
-
private readonly poolName;
|
|
8
|
-
constructor(pool: Pool, poolName?: string);
|
|
9
|
-
createBake(input: CreateBakeInput): Promise<{
|
|
10
|
-
created: boolean;
|
|
11
|
-
bake: BakeRecord;
|
|
12
|
-
}>;
|
|
13
|
-
createBakeIfIdle(input: CreateBakeInput): Promise<{
|
|
14
|
-
created: boolean;
|
|
15
|
-
bake: BakeRecord | null;
|
|
16
|
-
}>;
|
|
17
|
-
getBake(bakeId: string): Promise<BakeRecord | null>;
|
|
18
|
-
getBakeByIdem(idemKey: string): Promise<BakeRecord | null>;
|
|
19
|
-
findActiveByArgv(argv: string[]): Promise<BakeRecord | null>;
|
|
20
|
-
findActiveAny(): Promise<BakeRecord | null>;
|
|
21
|
-
findNextQueuedId(): Promise<string | null>;
|
|
22
|
-
claimBake(bakeId: string, runnerId: string, leaseMs: number): Promise<BakeRecord | null>;
|
|
23
|
-
appendEvent(bakeId: string, kind: string, data: unknown, opts?: {
|
|
24
|
-
lineOrd?: number | null;
|
|
25
|
-
level?: string | null;
|
|
26
|
-
}): Promise<number | null>;
|
|
27
|
-
private lineOrdExists;
|
|
28
|
-
maxSeq(bakeId: string): Promise<number>;
|
|
29
|
-
retainedFrom(bakeId: string): Promise<number>;
|
|
30
|
-
getEvents(bakeId: string, afterSeq: number): Promise<BakeEvent[]>;
|
|
31
|
-
setStatus(bakeId: string, status: BakeStatus): Promise<void>;
|
|
32
|
-
setState(bakeId: string, state: BakeState): Promise<void>;
|
|
33
|
-
setTerminal(bakeId: string, t: BakeTerminal): Promise<number>;
|
|
34
|
-
setIndexId(bakeId: string, indexId: string): Promise<boolean>;
|
|
35
|
-
heartbeatLease(bakeId: string, runnerId: string, leaseMs: number): Promise<boolean>;
|
|
36
|
-
requestCancel(bakeId: string): Promise<boolean>;
|
|
37
|
-
isCancelRequested(bakeId: string): Promise<boolean>;
|
|
38
|
-
reapStaleBakes(olderThanMs: number): Promise<number>;
|
|
39
|
-
private tx;
|
|
40
|
-
}
|
|
41
4
|
//# sourceMappingURL=tidb-image-bake.d.ts.map
|
|
@@ -1,350 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
import { randomBytes } from "node:crypto";
|
|
3
|
-
import { parseJsonOr as parseJson, toIso as iso } from "./sql-row-helpers.js";
|
|
4
|
-
import { mapBakeRow as mapRow, BAKE_SELECT_COLS as SELECT_COLS, } from "./store-contracts.js";
|
|
1
|
+
export { TiDBImageBake, SqlImageBake } from "./image-bake-store-sql.js";
|
|
5
2
|
export { mapBakeRow as mapRow, BAKE_SELECT_COLS as SELECT_COLS } from "./store-contracts.js";
|
|
6
|
-
function isDupKey(err) {
|
|
7
|
-
return Boolean(err) && err.code === "ER_DUP_ENTRY";
|
|
8
|
-
}
|
|
9
|
-
function dupKeyName(err) {
|
|
10
|
-
const msg = String(err?.sqlMessage ?? err?.message ?? "");
|
|
11
|
-
if (/uq_line/i.test(msg))
|
|
12
|
-
return "uq_line";
|
|
13
|
-
if (/PRIMARY/i.test(msg))
|
|
14
|
-
return "primary";
|
|
15
|
-
return "other";
|
|
16
|
-
}
|
|
17
|
-
export class TiDBImageBake {
|
|
18
|
-
pool;
|
|
19
|
-
poolName;
|
|
20
|
-
constructor(pool, poolName = "standard") {
|
|
21
|
-
this.pool = pool;
|
|
22
|
-
this.poolName = poolName;
|
|
23
|
-
}
|
|
24
|
-
async createBake(input) {
|
|
25
|
-
const bakeId = uuidv7();
|
|
26
|
-
const now = new Date();
|
|
27
|
-
try {
|
|
28
|
-
await this.pool.query("INSERT INTO image_bake (bake_id, profile, bands, base_ref, push, dry_run, logs, argv, status, " +
|
|
29
|
-
"idem_key, cancel_requested, requested_by, created_at, updated_at) " +
|
|
30
|
-
"VALUES (?,?,?,?,?,?,?,?,'queued',?,0,?,?,?)", [
|
|
31
|
-
bakeId,
|
|
32
|
-
input.profile,
|
|
33
|
-
input.bands == null ? null : JSON.stringify(input.bands),
|
|
34
|
-
input.baseRef,
|
|
35
|
-
input.push ? 1 : 0,
|
|
36
|
-
input.dryRun ? 1 : 0,
|
|
37
|
-
input.logs ? 1 : 0,
|
|
38
|
-
JSON.stringify(input.argv),
|
|
39
|
-
input.idemKey,
|
|
40
|
-
input.requestedBy,
|
|
41
|
-
now,
|
|
42
|
-
now,
|
|
43
|
-
]);
|
|
44
|
-
}
|
|
45
|
-
catch (err) {
|
|
46
|
-
if (isDupKey(err) && input.idemKey) {
|
|
47
|
-
const existing = await this.getBakeByIdem(input.idemKey);
|
|
48
|
-
if (existing)
|
|
49
|
-
return { created: false, bake: existing };
|
|
50
|
-
}
|
|
51
|
-
throw err;
|
|
52
|
-
}
|
|
53
|
-
const bake = await this.getBake(bakeId);
|
|
54
|
-
if (!bake)
|
|
55
|
-
throw new Error("createBake: row vanished after insert");
|
|
56
|
-
return { created: true, bake };
|
|
57
|
-
}
|
|
58
|
-
async createBakeIfIdle(input) {
|
|
59
|
-
const bakeId = uuidv7();
|
|
60
|
-
const now = new Date();
|
|
61
|
-
const conn = await this.pool.getConnection();
|
|
62
|
-
let inserted = false;
|
|
63
|
-
try {
|
|
64
|
-
await conn.query("BEGIN PESSIMISTIC");
|
|
65
|
-
await conn.query("INSERT IGNORE INTO image_bake_admit (pool) VALUES (?)", [this.poolName]);
|
|
66
|
-
await conn.query("SELECT pool FROM image_bake_admit WHERE pool = ? FOR UPDATE", [this.poolName]);
|
|
67
|
-
const [busyRows] = await conn.query("SELECT 1 FROM image_bake WHERE status IN ('queued','running') AND dry_run = 0 LIMIT 1 FOR UPDATE");
|
|
68
|
-
if (busyRows.length === 0) {
|
|
69
|
-
await conn.query("INSERT INTO image_bake (bake_id, profile, bands, base_ref, push, dry_run, logs, argv, status, " +
|
|
70
|
-
"idem_key, cancel_requested, requested_by, created_at, updated_at) " +
|
|
71
|
-
"VALUES (?,?,?,?,?,?,?,?,'queued',?,0,?,?,?)", [
|
|
72
|
-
bakeId,
|
|
73
|
-
input.profile,
|
|
74
|
-
input.bands == null ? null : JSON.stringify(input.bands),
|
|
75
|
-
input.baseRef,
|
|
76
|
-
input.push ? 1 : 0,
|
|
77
|
-
input.dryRun ? 1 : 0,
|
|
78
|
-
input.logs ? 1 : 0,
|
|
79
|
-
JSON.stringify(input.argv),
|
|
80
|
-
input.idemKey,
|
|
81
|
-
input.requestedBy,
|
|
82
|
-
now,
|
|
83
|
-
now,
|
|
84
|
-
]);
|
|
85
|
-
inserted = true;
|
|
86
|
-
}
|
|
87
|
-
await conn.commit();
|
|
88
|
-
}
|
|
89
|
-
catch (err) {
|
|
90
|
-
try {
|
|
91
|
-
await conn.rollback();
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
}
|
|
95
|
-
throw err;
|
|
96
|
-
}
|
|
97
|
-
finally {
|
|
98
|
-
conn.release();
|
|
99
|
-
}
|
|
100
|
-
if (!inserted)
|
|
101
|
-
return { created: false, bake: null };
|
|
102
|
-
const bake = await this.getBake(bakeId);
|
|
103
|
-
if (!bake)
|
|
104
|
-
throw new Error("createBakeIfIdle: row vanished after insert");
|
|
105
|
-
return { created: true, bake };
|
|
106
|
-
}
|
|
107
|
-
async getBake(bakeId) {
|
|
108
|
-
const [rows] = await this.pool.query(`SELECT ${SELECT_COLS} FROM image_bake WHERE bake_id = ? LIMIT 1`, [bakeId]);
|
|
109
|
-
return rows[0] ? mapRow(rows[0]) : null;
|
|
110
|
-
}
|
|
111
|
-
async getBakeByIdem(idemKey) {
|
|
112
|
-
const [rows] = await this.pool.query(`SELECT ${SELECT_COLS} FROM image_bake WHERE idem_key = ? LIMIT 1`, [idemKey]);
|
|
113
|
-
return rows[0] ? mapRow(rows[0]) : null;
|
|
114
|
-
}
|
|
115
|
-
async findActiveByArgv(argv) {
|
|
116
|
-
const [rows] = await this.pool.query(`SELECT ${SELECT_COLS} FROM image_bake WHERE status IN ('queued','running') AND argv = CAST(? AS JSON) ` +
|
|
117
|
-
"ORDER BY created_at DESC LIMIT 1", [JSON.stringify(argv)]);
|
|
118
|
-
return rows[0] ? mapRow(rows[0]) : null;
|
|
119
|
-
}
|
|
120
|
-
async findActiveAny() {
|
|
121
|
-
const [rows] = await this.pool.query(`SELECT ${SELECT_COLS} FROM image_bake WHERE status IN ('queued','running') AND dry_run = 0 ` +
|
|
122
|
-
"ORDER BY created_at ASC LIMIT 1", []);
|
|
123
|
-
return rows[0] ? mapRow(rows[0]) : null;
|
|
124
|
-
}
|
|
125
|
-
async findNextQueuedId() {
|
|
126
|
-
const [rows] = await this.pool.query("SELECT bake_id FROM image_bake WHERE status = 'queued' ORDER BY created_at ASC LIMIT 1", []);
|
|
127
|
-
return rows[0] ? String(rows[0].bake_id) : null;
|
|
128
|
-
}
|
|
129
|
-
async claimBake(bakeId, runnerId, leaseMs) {
|
|
130
|
-
const conn = await this.pool.getConnection();
|
|
131
|
-
try {
|
|
132
|
-
await conn.beginTransaction();
|
|
133
|
-
const now = new Date();
|
|
134
|
-
const leaseUntil = new Date(now.getTime() + leaseMs);
|
|
135
|
-
const ingestSecret = randomBytes(24).toString("hex");
|
|
136
|
-
const [bakeRes] = await conn.query("UPDATE image_bake SET status='running', state='PENDING', runner_id=?, ingest_secret=?, " +
|
|
137
|
-
"lease_until=?, updated_at=? WHERE bake_id=? AND status='queued'", [runnerId, ingestSecret, leaseUntil, now, bakeId]);
|
|
138
|
-
if (bakeRes.affectedRows !== 1) {
|
|
139
|
-
await conn.rollback();
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
const [dryRows] = await conn.query("SELECT dry_run FROM image_bake WHERE bake_id=?", [bakeId]);
|
|
143
|
-
if (Boolean(dryRows[0]?.dry_run)) {
|
|
144
|
-
await conn.commit();
|
|
145
|
-
return (await this.getBake(bakeId)) ?? null;
|
|
146
|
-
}
|
|
147
|
-
const [leaseRows] = await conn.query("SELECT bake_id, expires_at FROM image_bake_lease WHERE pool=?", [this.poolName]);
|
|
148
|
-
const cur = leaseRows[0];
|
|
149
|
-
if (!cur) {
|
|
150
|
-
try {
|
|
151
|
-
await conn.query("INSERT INTO image_bake_lease (pool, bake_id, acquired_at, expires_at) VALUES (?,?,?,?)", [this.poolName, bakeId, now, leaseUntil]);
|
|
152
|
-
}
|
|
153
|
-
catch (err) {
|
|
154
|
-
if (isDupKey(err)) {
|
|
155
|
-
await conn.rollback();
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
158
|
-
throw err;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
if (new Date(cur.expires_at).getTime() >= now.getTime()) {
|
|
163
|
-
await conn.rollback();
|
|
164
|
-
return null;
|
|
165
|
-
}
|
|
166
|
-
const holderId = String(cur.bake_id ?? "");
|
|
167
|
-
const [holderRows] = await conn.query("SELECT status FROM image_bake WHERE bake_id=?", [holderId]);
|
|
168
|
-
const holderStatus = String(holderRows[0]?.status ?? "");
|
|
169
|
-
if (holderStatus === "queued" || holderStatus === "running") {
|
|
170
|
-
await conn.rollback();
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
const [stealRes] = await conn.query("UPDATE image_bake_lease SET bake_id=?, acquired_at=?, expires_at=? WHERE pool=? AND bake_id=? AND expires_at=?", [bakeId, now, leaseUntil, this.poolName, holderId, cur.expires_at]);
|
|
174
|
-
if (stealRes.affectedRows === 0) {
|
|
175
|
-
await conn.rollback();
|
|
176
|
-
return null;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
const [check] = await conn.query("SELECT bake_id FROM image_bake_lease WHERE pool=?", [this.poolName]);
|
|
180
|
-
if (String(check[0]?.bake_id ?? "") !== bakeId) {
|
|
181
|
-
await conn.rollback();
|
|
182
|
-
return null;
|
|
183
|
-
}
|
|
184
|
-
await conn.commit();
|
|
185
|
-
return (await this.getBake(bakeId)) ?? null;
|
|
186
|
-
}
|
|
187
|
-
catch (err) {
|
|
188
|
-
try {
|
|
189
|
-
await conn.rollback();
|
|
190
|
-
}
|
|
191
|
-
catch {
|
|
192
|
-
}
|
|
193
|
-
throw err;
|
|
194
|
-
}
|
|
195
|
-
finally {
|
|
196
|
-
conn.release();
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
async appendEvent(bakeId, kind, data, opts = {}) {
|
|
200
|
-
const lineOrd = opts.lineOrd ?? null;
|
|
201
|
-
if (lineOrd !== null && (await this.lineOrdExists(bakeId, lineOrd)))
|
|
202
|
-
return null;
|
|
203
|
-
const payload = data == null ? null : JSON.stringify(data);
|
|
204
|
-
for (let attempt = 0; attempt < 50; attempt += 1) {
|
|
205
|
-
const seq = (await this.maxSeq(bakeId)) + 1;
|
|
206
|
-
try {
|
|
207
|
-
await this.pool.query("INSERT INTO image_bake_event (bake_id, seq, line_ord, kind, level, data, ts) VALUES (?,?,?,?,?,?,?)", [bakeId, seq, lineOrd, kind, opts.level ?? null, payload, new Date()]);
|
|
208
|
-
return seq;
|
|
209
|
-
}
|
|
210
|
-
catch (err) {
|
|
211
|
-
if (!isDupKey(err))
|
|
212
|
-
throw err;
|
|
213
|
-
const which = dupKeyName(err);
|
|
214
|
-
if (which === "uq_line" && lineOrd !== null)
|
|
215
|
-
return null;
|
|
216
|
-
await new Promise((r) => setTimeout(r, Math.floor(Math.random() * 5)));
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
throw new Error(`appendEvent: seq allocation lost the (bake_id,seq) race after retries (bakeId=${bakeId})`);
|
|
220
|
-
}
|
|
221
|
-
async lineOrdExists(bakeId, lineOrd) {
|
|
222
|
-
const [rows] = await this.pool.query("SELECT 1 FROM image_bake_event WHERE bake_id = ? AND line_ord = ? LIMIT 1", [bakeId, lineOrd]);
|
|
223
|
-
return rows.length > 0;
|
|
224
|
-
}
|
|
225
|
-
async maxSeq(bakeId) {
|
|
226
|
-
const [rows] = await this.pool.query("SELECT MAX(seq) AS m FROM image_bake_event WHERE bake_id = ?", [bakeId]);
|
|
227
|
-
const m = rows[0]?.m;
|
|
228
|
-
return m == null ? 0 : Number(m);
|
|
229
|
-
}
|
|
230
|
-
async retainedFrom(bakeId) {
|
|
231
|
-
const [rows] = await this.pool.query("SELECT MIN(seq) AS m FROM image_bake_event WHERE bake_id = ?", [bakeId]);
|
|
232
|
-
const m = rows[0]?.m;
|
|
233
|
-
return m == null ? 0 : Number(m);
|
|
234
|
-
}
|
|
235
|
-
async getEvents(bakeId, afterSeq) {
|
|
236
|
-
const [rows] = await this.pool.query("SELECT seq, line_ord, kind, level, data, ts FROM image_bake_event WHERE bake_id = ? AND seq > ? ORDER BY seq ASC", [bakeId, afterSeq]);
|
|
237
|
-
return rows.map((r) => ({
|
|
238
|
-
seq: Number(r.seq),
|
|
239
|
-
lineOrd: r.line_ord === null ? null : Number(r.line_ord),
|
|
240
|
-
kind: String(r.kind),
|
|
241
|
-
level: r.level ?? null,
|
|
242
|
-
data: parseJson(r.data, null),
|
|
243
|
-
ts: iso(r.ts),
|
|
244
|
-
}));
|
|
245
|
-
}
|
|
246
|
-
async setStatus(bakeId, status) {
|
|
247
|
-
await this.pool.query("UPDATE image_bake SET status = ?, updated_at = ? WHERE bake_id = ?", [status, new Date(), bakeId]);
|
|
248
|
-
}
|
|
249
|
-
async setState(bakeId, state) {
|
|
250
|
-
await this.pool.query("UPDATE image_bake SET state = ?, updated_at = ? WHERE bake_id = ?", [state, new Date(), bakeId]);
|
|
251
|
-
}
|
|
252
|
-
async setTerminal(bakeId, t) {
|
|
253
|
-
let affectedRows = 0;
|
|
254
|
-
await this.tx(async (conn) => {
|
|
255
|
-
const [res] = await conn.query("UPDATE image_bake SET status=?, state=?, digest=?, repo=?, ref=?, index_id=?, exit_code=?, error=?, " +
|
|
256
|
-
"error_code=?, manifest_sha=?, tag=?, ingest_secret=NULL, updated_at=? WHERE bake_id=? AND status IN ('queued','running')", [
|
|
257
|
-
t.status,
|
|
258
|
-
t.state,
|
|
259
|
-
t.digest ?? null,
|
|
260
|
-
t.repo ?? null,
|
|
261
|
-
t.ref ?? null,
|
|
262
|
-
t.indexId ?? null,
|
|
263
|
-
t.exitCode ?? null,
|
|
264
|
-
t.error ?? null,
|
|
265
|
-
t.errorCode ?? null,
|
|
266
|
-
t.manifestSha ?? null,
|
|
267
|
-
t.tag ?? null,
|
|
268
|
-
new Date(),
|
|
269
|
-
bakeId,
|
|
270
|
-
]);
|
|
271
|
-
affectedRows = res.affectedRows;
|
|
272
|
-
await conn.query("DELETE FROM image_bake_lease WHERE pool = ? AND bake_id = ?", [this.poolName, bakeId]);
|
|
273
|
-
});
|
|
274
|
-
return affectedRows;
|
|
275
|
-
}
|
|
276
|
-
async setIndexId(bakeId, indexId) {
|
|
277
|
-
const [res] = await this.pool.query("UPDATE image_bake SET index_id = ?, updated_at = ? WHERE bake_id = ? AND status IN ('queued','running')", [indexId, new Date(), bakeId]);
|
|
278
|
-
return res.affectedRows >= 1;
|
|
279
|
-
}
|
|
280
|
-
async heartbeatLease(bakeId, runnerId, leaseMs) {
|
|
281
|
-
const now = new Date();
|
|
282
|
-
const leaseUntil = new Date(now.getTime() + leaseMs);
|
|
283
|
-
const [res] = await this.pool.query("UPDATE image_bake SET lease_until = ?, updated_at = ? WHERE bake_id = ? AND runner_id = ? AND status = 'running'", [leaseUntil, now, bakeId, runnerId]);
|
|
284
|
-
if (res.affectedRows < 1)
|
|
285
|
-
return false;
|
|
286
|
-
const [dryRows] = await this.pool.query("SELECT dry_run FROM image_bake WHERE bake_id = ?", [bakeId]);
|
|
287
|
-
if (Boolean(dryRows[0]?.dry_run))
|
|
288
|
-
return true;
|
|
289
|
-
const [leaseRes] = await this.pool.query("UPDATE image_bake_lease SET expires_at = ? WHERE pool = ? AND bake_id = ?", [leaseUntil, this.poolName, bakeId]);
|
|
290
|
-
return leaseRes.affectedRows >= 1;
|
|
291
|
-
}
|
|
292
|
-
async requestCancel(bakeId) {
|
|
293
|
-
const [r] = await this.pool.query("UPDATE image_bake SET cancel_requested = 1, updated_at = ? WHERE bake_id = ? AND status IN ('queued','running')", [new Date(), bakeId]);
|
|
294
|
-
return r.affectedRows > 0;
|
|
295
|
-
}
|
|
296
|
-
async isCancelRequested(bakeId) {
|
|
297
|
-
const [rows] = await this.pool.query("SELECT cancel_requested FROM image_bake WHERE bake_id = ?", [bakeId]);
|
|
298
|
-
return Boolean(rows[0]?.cancel_requested);
|
|
299
|
-
}
|
|
300
|
-
async reapStaleBakes(olderThanMs) {
|
|
301
|
-
const cutoff = new Date(Date.now() - olderThanMs);
|
|
302
|
-
const [victims] = await this.pool.query("SELECT bake_id FROM image_bake WHERE status = 'running' AND " +
|
|
303
|
-
"(lease_until IS NULL OR lease_until < ?) AND updated_at < ?", [cutoff, cutoff]);
|
|
304
|
-
let reaped = 0;
|
|
305
|
-
for (const v of victims) {
|
|
306
|
-
const bakeId = String(v.bake_id);
|
|
307
|
-
const [res] = await this.pool.query("UPDATE image_bake SET status='failed', state='FAILED', error='bake-runner lost (lease expired)', " +
|
|
308
|
-
"ingest_secret=NULL, index_id=NULL, updated_at=? WHERE bake_id=? AND status='running'", [new Date(), bakeId]);
|
|
309
|
-
if (res.affectedRows === 1) {
|
|
310
|
-
reaped += 1;
|
|
311
|
-
try {
|
|
312
|
-
await this.appendEvent(bakeId, "done", {
|
|
313
|
-
status: "FAILED",
|
|
314
|
-
digest: null,
|
|
315
|
-
repo: null,
|
|
316
|
-
ref: null,
|
|
317
|
-
indexId: null,
|
|
318
|
-
exitCode: null,
|
|
319
|
-
error: "bake-runner lost (lease expired)",
|
|
320
|
-
errorCode: null,
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
catch {
|
|
324
|
-
}
|
|
325
|
-
await this.pool.query("DELETE FROM image_bake_lease WHERE pool = ? AND bake_id = ?", [this.poolName, bakeId]);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
return reaped;
|
|
329
|
-
}
|
|
330
|
-
async tx(fn) {
|
|
331
|
-
const conn = await this.pool.getConnection();
|
|
332
|
-
try {
|
|
333
|
-
await conn.beginTransaction();
|
|
334
|
-
await fn(conn);
|
|
335
|
-
await conn.commit();
|
|
336
|
-
}
|
|
337
|
-
catch (err) {
|
|
338
|
-
try {
|
|
339
|
-
await conn.rollback();
|
|
340
|
-
}
|
|
341
|
-
catch {
|
|
342
|
-
}
|
|
343
|
-
throw err;
|
|
344
|
-
}
|
|
345
|
-
finally {
|
|
346
|
-
conn.release();
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
3
|
//# sourceMappingURL=tidb-image-bake.js.map
|
|
@@ -1,22 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { type ImageStatus, type ImageIndexEntry, type ImageIndexUpsert, type ImageViewer, type ImageListFilter } from "./store-contracts.js";
|
|
1
|
+
export { TiDBImageIndex } from "./image-index-sql.js";
|
|
3
2
|
export type { ImageNestedBuildMode, ImageCapabilities, ImagePodContract, ImageStatus, ImageVisibility, ImageIndexEntry, ImageIndexUpsert, ImageViewer, ImageListFilter, ImageRow, } from "./store-contracts.js";
|
|
4
3
|
export { mapImageRow as mapRow } from "./store-contracts.js";
|
|
5
|
-
export declare class TiDBImageIndex {
|
|
6
|
-
private readonly pool;
|
|
7
|
-
constructor(pool: Pool);
|
|
8
|
-
static idFor(repo: string, digest: string): string;
|
|
9
|
-
upsert(e: ImageIndexUpsert): Promise<string>;
|
|
10
|
-
registerBuilding(e: ImageIndexUpsert): Promise<string>;
|
|
11
|
-
getById(id: string): Promise<ImageIndexEntry | null>;
|
|
12
|
-
getByDigest(digest: string): Promise<ImageIndexEntry | null>;
|
|
13
|
-
latestPublished(profile: string, viewer: ImageViewer): Promise<ImageIndexEntry | null>;
|
|
14
|
-
list(filter: ImageListFilter): Promise<{
|
|
15
|
-
entries: ImageIndexEntry[];
|
|
16
|
-
nextCursor: string | null;
|
|
17
|
-
}>;
|
|
18
|
-
setStatus(id: string, status: ImageStatus, opts?: {
|
|
19
|
-
supersedes?: string | null;
|
|
20
|
-
}): Promise<void>;
|
|
21
|
-
}
|
|
22
4
|
//# sourceMappingURL=tidb-image-index.d.ts.map
|