@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,151 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
const
|
|
3
|
-
class PgWriteBehindCounter {
|
|
4
|
-
pool;
|
|
5
|
-
windowMs;
|
|
6
|
-
now;
|
|
7
|
-
onDegraded;
|
|
8
|
-
queryTimeoutMs;
|
|
9
|
-
pendingDelta = new Map();
|
|
10
|
-
fleetTotal = new Map();
|
|
11
|
-
timer;
|
|
12
|
-
flushing = false;
|
|
13
|
-
flushStartedAt = 0;
|
|
14
|
-
failStreak = new Map();
|
|
15
|
-
constructor(pool, windowMs, now = () => Date.now(), onDegraded, queryTimeoutMs = DEFAULT_COUNTER_QUERY_TIMEOUT_MS) {
|
|
16
|
-
this.pool = pool;
|
|
17
|
-
this.windowMs = windowMs;
|
|
18
|
-
this.now = now;
|
|
19
|
-
this.onDegraded = onDegraded;
|
|
20
|
-
this.queryTimeoutMs = queryTimeoutMs;
|
|
21
|
-
}
|
|
22
|
-
bump(kind, failed, error) {
|
|
23
|
-
if (!this.onDegraded)
|
|
24
|
-
return;
|
|
25
|
-
const prev = this.failStreak.get(kind) ?? 0;
|
|
26
|
-
if (!failed && prev === 0)
|
|
27
|
-
return;
|
|
28
|
-
const next = failed ? prev + 1 : 0;
|
|
29
|
-
this.failStreak.set(kind, next);
|
|
30
|
-
try {
|
|
31
|
-
this.onDegraded({ table: "cost_quota", kind, streak: next, prevStreak: prev, ...(failed && error !== undefined ? { error: error instanceof Error ? error.message : String(error) } : {}) });
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
bucket() {
|
|
37
|
-
return Math.floor(this.now() / this.windowMs);
|
|
38
|
-
}
|
|
39
|
-
rk(key) {
|
|
40
|
-
return `${key}${SEP}${this.bucket()}`;
|
|
41
|
-
}
|
|
42
|
-
used(key) {
|
|
43
|
-
const rk = this.rk(key);
|
|
44
|
-
return (this.fleetTotal.get(rk) ?? 0) + (this.pendingDelta.get(rk) ?? 0);
|
|
45
|
-
}
|
|
46
|
-
addLocal(key, n) {
|
|
47
|
-
if (n <= 0)
|
|
48
|
-
return;
|
|
49
|
-
const rk = this.rk(key);
|
|
50
|
-
this.pendingDelta.set(rk, (this.pendingDelta.get(rk) ?? 0) + n);
|
|
51
|
-
}
|
|
52
|
-
retryAfterSec() {
|
|
53
|
-
return Math.ceil(((this.bucket() + 1) * this.windowMs - this.now()) / 1000);
|
|
54
|
-
}
|
|
55
|
-
async flush() {
|
|
56
|
-
if (this.flushing) {
|
|
57
|
-
if (this.now() - this.flushStartedAt > 60_000)
|
|
58
|
-
this.bump("stalled", true, "flush round stalled >60s (hung query?)");
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
this.flushing = true;
|
|
62
|
-
this.flushStartedAt = this.now();
|
|
63
|
-
try {
|
|
64
|
-
await this.flushOnce();
|
|
65
|
-
}
|
|
66
|
-
finally {
|
|
67
|
-
this.flushing = false;
|
|
68
|
-
this.bump("stalled", false);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
async flushOnce() {
|
|
72
|
-
let attempted = false;
|
|
73
|
-
let failure;
|
|
74
|
-
let hadFailure = false;
|
|
75
|
-
for (const [rk, delta] of [...this.pendingDelta]) {
|
|
76
|
-
if (delta <= 0)
|
|
77
|
-
continue;
|
|
78
|
-
const sep = rk.lastIndexOf(SEP);
|
|
79
|
-
const key = rk.slice(0, sep);
|
|
80
|
-
const windowBucket = Number(rk.slice(sep + 1));
|
|
81
|
-
attempted = true;
|
|
82
|
-
try {
|
|
83
|
-
const q = {
|
|
84
|
-
text: "INSERT INTO cost_quota (scope_key, window_bucket, micro, updated_at) VALUES ($1,$2,$3,$4) " +
|
|
85
|
-
"ON CONFLICT (scope_key, window_bucket) DO UPDATE SET micro = cost_quota.micro + EXCLUDED.micro, updated_at = EXCLUDED.updated_at",
|
|
86
|
-
values: [key, windowBucket, delta, new Date()],
|
|
87
|
-
query_timeout: this.queryTimeoutMs,
|
|
88
|
-
};
|
|
89
|
-
await this.pool.query(q);
|
|
90
|
-
const remaining = (this.pendingDelta.get(rk) ?? 0) - delta;
|
|
91
|
-
if (remaining > 0)
|
|
92
|
-
this.pendingDelta.set(rk, remaining);
|
|
93
|
-
else
|
|
94
|
-
this.pendingDelta.delete(rk);
|
|
95
|
-
}
|
|
96
|
-
catch (err) {
|
|
97
|
-
hadFailure = true;
|
|
98
|
-
failure = err;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
if (attempted)
|
|
102
|
-
this.bump("flush", hadFailure, failure);
|
|
103
|
-
await this.refresh();
|
|
104
|
-
await this.reap();
|
|
105
|
-
}
|
|
106
|
-
async refresh() {
|
|
107
|
-
const b = this.bucket();
|
|
108
|
-
try {
|
|
109
|
-
const q = { text: "SELECT scope_key, micro FROM cost_quota WHERE window_bucket = $1", values: [b], query_timeout: this.queryTimeoutMs };
|
|
110
|
-
const res = await this.pool.query(q);
|
|
111
|
-
const next = new Map();
|
|
112
|
-
for (const r of res.rows) {
|
|
113
|
-
next.set(`${String(r.scope_key)}${SEP}${b}`, Number(r.micro));
|
|
114
|
-
}
|
|
115
|
-
this.fleetTotal = next;
|
|
116
|
-
this.bump("refresh", false);
|
|
117
|
-
}
|
|
118
|
-
catch (err) {
|
|
119
|
-
this.bump("refresh", true, err);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
async reap() {
|
|
123
|
-
try {
|
|
124
|
-
const q = { text: "DELETE FROM cost_quota WHERE window_bucket < $1", values: [this.bucket()], query_timeout: this.queryTimeoutMs };
|
|
125
|
-
const res = await this.pool.query(q);
|
|
126
|
-
return res.rowCount ?? 0;
|
|
127
|
-
}
|
|
128
|
-
catch {
|
|
129
|
-
return 0;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
startRefresh(intervalMs = 5_000) {
|
|
133
|
-
this.timer ??= setInterval(() => void this.flush(), intervalMs);
|
|
134
|
-
this.timer.unref?.();
|
|
135
|
-
return this;
|
|
136
|
-
}
|
|
137
|
-
stop() {
|
|
138
|
-
if (this.timer)
|
|
139
|
-
clearInterval(this.timer);
|
|
140
|
-
this.timer = undefined;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
1
|
+
import { PgWriteBehindCounter } from "./write-behind-counter.js";
|
|
2
|
+
const COST_QUOTA_TABLE = { table: "cost_quota", keyCol: "scope_key", valCol: "micro" };
|
|
143
3
|
export class PgCostQuota {
|
|
144
4
|
limitMicroUsd;
|
|
145
5
|
counter;
|
|
146
6
|
constructor(pool, limitMicroUsd, windowMs, now, onDegraded, queryTimeoutMs) {
|
|
147
7
|
this.limitMicroUsd = limitMicroUsd;
|
|
148
|
-
this.counter = new PgWriteBehindCounter(pool, windowMs, now, onDegraded, queryTimeoutMs);
|
|
8
|
+
this.counter = new PgWriteBehindCounter(pool, COST_QUOTA_TABLE, windowMs, now, onDegraded, queryTimeoutMs);
|
|
149
9
|
}
|
|
150
10
|
check(scopeKey) {
|
|
151
11
|
const used = this.counter.used(scopeKey);
|
|
@@ -1,28 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
import type { Pool } from "pg";
|
|
3
|
-
import { type BlobBackend } from "./blob-backend.js";
|
|
4
|
-
export declare class PgFileSnapshotStore implements FileSnapshotStore {
|
|
5
|
-
private readonly pool;
|
|
6
|
-
private readonly bounds;
|
|
7
|
-
private readonly inFlight;
|
|
8
|
-
private readonly blobs;
|
|
9
|
-
onSkippedVanished: ((count: number) => void | Promise<void>) | undefined;
|
|
10
|
-
constructor(pool: Pool, blobBackend?: BlobBackend, bounds?: FileSnapshotBounds);
|
|
11
|
-
private putBlobRow;
|
|
12
|
-
putBlob(hash: string, bytes: Uint8Array): Promise<FileSnapshotResult>;
|
|
13
|
-
snapshot(scope: string, key: string, env: ExecutionEnv, root: string, signal?: AbortSignal): Promise<FileSnapshotResult>;
|
|
14
|
-
importManifest(scope: string, key: string, manifest: Map<string, string>, srcGetBlob: (hash: string) => Promise<Uint8Array | undefined>): Promise<FileSnapshotResult>;
|
|
15
|
-
restore(scope: string, key: string, env: ExecutionEnv, root: string, signal?: AbortSignal): Promise<FileSnapshotResult>;
|
|
16
|
-
has(scope: string, key: string): Promise<boolean>;
|
|
17
|
-
listKeys(scope: string): Promise<string[]>;
|
|
18
|
-
exportManifest(scope: string, key: string): Promise<Map<string, string> | null>;
|
|
19
|
-
getBlob(hash: string): Promise<Uint8Array | undefined>;
|
|
20
|
-
hasBlobs(hashes: string[]): Promise<Set<string>>;
|
|
21
|
-
blobSizes(hashes: string[]): Promise<Map<string, number>>;
|
|
22
|
-
reap(scope: string, keepKeys: string[]): Promise<number>;
|
|
23
|
-
deleteBySession(scope: string): Promise<number>;
|
|
24
|
-
private gcOrphanBlobs;
|
|
25
|
-
sweepOrphanBlobs(): Promise<number>;
|
|
26
|
-
private tx;
|
|
27
|
-
}
|
|
1
|
+
export { PgFileSnapshotStore } from "./file-snapshot-store-sql.js";
|
|
28
2
|
//# sourceMappingURL=pg-file-snapshot-store.d.ts.map
|
|
@@ -1,191 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
3
|
-
import { SqlBlobBackend, sqlBlobSizes } from "./blob-backend.js";
|
|
4
|
-
export class PgFileSnapshotStore {
|
|
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("pg", 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} ${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 (c) => {
|
|
56
|
-
const exists = await c.query("SELECT 1 FROM snapshot_manifest WHERE scope = $1 AND snap_key = $2 LIMIT 1", [scope, key]);
|
|
57
|
-
if (exists.rows[0])
|
|
58
|
-
return;
|
|
59
|
-
for (const [relPath, hash] of cap.value) {
|
|
60
|
-
await c.query("INSERT INTO snapshot_manifest (scope, snap_key, rel_path, blob_hash) VALUES ($1,$2,$3,$4)", [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} ${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 (c) => {
|
|
96
|
-
const exists = await c.query("SELECT 1 FROM snapshot_manifest WHERE scope = $1 AND snap_key = $2 LIMIT 1", [scope, key]);
|
|
97
|
-
if (exists.rows[0])
|
|
98
|
-
return;
|
|
99
|
-
for (const [relPath, hash] of manifest) {
|
|
100
|
-
await c.query("INSERT INTO snapshot_manifest (scope, snap_key, rel_path, blob_hash) VALUES ($1,$2,$3,$4)", [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 r = await this.pool.query("SELECT rel_path, blob_hash FROM snapshot_manifest WHERE scope = $1 AND snap_key = $2", [scope, key]);
|
|
115
|
-
if (r.rows.length === 0)
|
|
116
|
-
return { ok: false, error: { code: "not_found", message: `no file snapshot for ${scope}/${key}` } };
|
|
117
|
-
const manifest = new Map(r.rows.map((row) => [row.rel_path, row.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 r = await this.pool.query("SELECT 1 FROM snapshot_manifest WHERE scope = $1 AND snap_key = $2 LIMIT 1", [scope, key]);
|
|
126
|
-
return Boolean(r.rows[0]);
|
|
127
|
-
}
|
|
128
|
-
async listKeys(scope) {
|
|
129
|
-
const r = await this.pool.query("SELECT DISTINCT snap_key FROM snapshot_manifest WHERE scope = $1", [scope]);
|
|
130
|
-
return r.rows.map((row) => row.snap_key);
|
|
131
|
-
}
|
|
132
|
-
async exportManifest(scope, key) {
|
|
133
|
-
const r = await this.pool.query("SELECT rel_path, blob_hash FROM snapshot_manifest WHERE scope = $1 AND snap_key = $2", [scope, key]);
|
|
134
|
-
if (r.rows.length === 0)
|
|
135
|
-
return null;
|
|
136
|
-
return new Map(r.rows.map((row) => [row.rel_path, row.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("pg", this.pool, hashes);
|
|
146
|
-
}
|
|
147
|
-
async reap(scope, keepKeys) {
|
|
148
|
-
return this.tx(async (c) => {
|
|
149
|
-
const keys = await c.query("SELECT DISTINCT snap_key FROM snapshot_manifest WHERE scope = $1", [scope]);
|
|
150
|
-
const drop = keys.rows.map((k) => k.snap_key).filter((k) => !keepKeys.includes(k));
|
|
151
|
-
for (const k of drop)
|
|
152
|
-
await c.query("DELETE FROM snapshot_manifest WHERE scope = $1 AND snap_key = $2", [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 = $1", [scope]);
|
|
158
|
-
return r.rowCount ?? 0;
|
|
159
|
-
}
|
|
160
|
-
async gcOrphanBlobs() {
|
|
161
|
-
const r = await this.pool.query("SELECT blob_hash FROM snapshot_blob WHERE blob_hash NOT IN (SELECT blob_hash FROM snapshot_manifest)").catch(() => undefined);
|
|
162
|
-
const orphans = (r?.rows ?? []).map((row) => row.blob_hash);
|
|
163
|
-
if (orphans.length === 0)
|
|
164
|
-
return 0;
|
|
165
|
-
return this.blobs.deleteBlobs(orphans);
|
|
166
|
-
}
|
|
167
|
-
async sweepOrphanBlobs() {
|
|
168
|
-
return this.gcOrphanBlobs();
|
|
169
|
-
}
|
|
170
|
-
async tx(fn) {
|
|
171
|
-
const c = await this.pool.connect();
|
|
172
|
-
try {
|
|
173
|
-
await c.query("BEGIN");
|
|
174
|
-
const out = await fn(c);
|
|
175
|
-
await c.query("COMMIT");
|
|
176
|
-
return out;
|
|
177
|
-
}
|
|
178
|
-
catch (err) {
|
|
179
|
-
try {
|
|
180
|
-
await c.query("ROLLBACK");
|
|
181
|
-
}
|
|
182
|
-
catch {
|
|
183
|
-
}
|
|
184
|
-
throw err;
|
|
185
|
-
}
|
|
186
|
-
finally {
|
|
187
|
-
c.release();
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
1
|
+
export { PgFileSnapshotStore } from "./file-snapshot-store-sql.js";
|
|
191
2
|
//# sourceMappingURL=pg-file-snapshot-store.js.map
|
|
@@ -1,42 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
import type { BakeStatus, BakeState, BakeRecord, BakeEvent, CreateBakeInput, BakeTerminal } from "./store-contracts.js";
|
|
1
|
+
export { PgImageBake, PG_IMAGE_BAKE_SCHEMA, ensurePgImageBakeSchema as ensureSchema } from "./image-bake-store-sql.js";
|
|
3
2
|
export type { BakeStatus, BakeState, BakeErrorCode, BakeRecord, BakeEvent, CreateBakeInput, BakeTerminal, } from "./store-contracts.js";
|
|
4
|
-
export declare const PG_IMAGE_BAKE_SCHEMA: string[];
|
|
5
|
-
export declare function ensureSchema(pool: Pool | PoolClient, poolName?: string): Promise<void>;
|
|
6
|
-
export declare class PgImageBake {
|
|
7
|
-
private readonly pool;
|
|
8
|
-
private readonly poolName;
|
|
9
|
-
constructor(pool: Pool, poolName?: string);
|
|
10
|
-
createBake(input: CreateBakeInput): Promise<{
|
|
11
|
-
created: boolean;
|
|
12
|
-
bake: BakeRecord;
|
|
13
|
-
}>;
|
|
14
|
-
createBakeIfIdle(input: CreateBakeInput): Promise<{
|
|
15
|
-
created: boolean;
|
|
16
|
-
bake: BakeRecord | null;
|
|
17
|
-
}>;
|
|
18
|
-
getBake(bakeId: string): Promise<BakeRecord | null>;
|
|
19
|
-
getBakeByIdem(idemKey: string): Promise<BakeRecord | null>;
|
|
20
|
-
findActiveByArgv(argv: string[]): Promise<BakeRecord | null>;
|
|
21
|
-
findActiveAny(): Promise<BakeRecord | null>;
|
|
22
|
-
findNextQueuedId(): Promise<string | null>;
|
|
23
|
-
claimBake(bakeId: string, runnerId: string, leaseMs: number): Promise<BakeRecord | null>;
|
|
24
|
-
appendEvent(bakeId: string, kind: string, data: unknown, opts?: {
|
|
25
|
-
lineOrd?: number | null;
|
|
26
|
-
level?: string | null;
|
|
27
|
-
}): Promise<number | null>;
|
|
28
|
-
private lineOrdExists;
|
|
29
|
-
maxSeq(bakeId: string): Promise<number>;
|
|
30
|
-
retainedFrom(bakeId: string): Promise<number>;
|
|
31
|
-
getEvents(bakeId: string, afterSeq: number): Promise<BakeEvent[]>;
|
|
32
|
-
setStatus(bakeId: string, status: BakeStatus): Promise<void>;
|
|
33
|
-
setState(bakeId: string, state: BakeState): Promise<void>;
|
|
34
|
-
setTerminal(bakeId: string, t: BakeTerminal): Promise<number>;
|
|
35
|
-
setIndexId(bakeId: string, indexId: string): Promise<boolean>;
|
|
36
|
-
heartbeatLease(bakeId: string, runnerId: string, leaseMs: number): Promise<boolean>;
|
|
37
|
-
requestCancel(bakeId: string): Promise<boolean>;
|
|
38
|
-
isCancelRequested(bakeId: string): Promise<boolean>;
|
|
39
|
-
reapStaleBakes(olderThanMs: number): Promise<number>;
|
|
40
|
-
private tx;
|
|
41
|
-
}
|
|
42
3
|
//# sourceMappingURL=pg-image-bake.d.ts.map
|