deepline 0.1.64 → 0.1.66
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/cli/index.js +236 -199
- package/dist/cli/index.mjs +120 -83
- package/dist/index.js +48 -11
- package/dist/index.mjs +44 -7
- package/dist/repo/apps/play-runner-workers/src/coordinator-entry.ts +475 -69
- package/dist/repo/apps/play-runner-workers/src/dedup-do.ts +134 -0
- package/dist/repo/apps/play-runner-workers/src/entry.ts +37 -49
- package/dist/repo/apps/play-runner-workers/src/runtime/harness-receipt-store.ts +13 -0
- package/dist/repo/apps/play-runner-workers/src/runtime/receipts.ts +10 -136
- package/dist/repo/sdk/src/http.ts +44 -0
- package/dist/repo/sdk/src/plays/harness-stub.ts +4 -0
- package/dist/repo/sdk/src/release.ts +2 -2
- package/dist/repo/shared_libs/play-runtime/db-session-crypto.ts +312 -0
- package/dist/repo/shared_libs/play-runtime/db-session-plan.ts +69 -0
- package/dist/repo/shared_libs/play-runtime/db-session.ts +439 -0
- package/dist/repo/shared_libs/play-runtime/work-receipts.ts +92 -0
- package/package.json +1 -1
|
@@ -168,6 +168,8 @@ export async function harnessReadSheetDatasetRows(
|
|
|
168
168
|
*/
|
|
169
169
|
export async function harnessPrewarmPostgresSessions(input: {
|
|
170
170
|
executorToken: string;
|
|
171
|
+
orgId: string;
|
|
172
|
+
playName: string;
|
|
171
173
|
sessions: PreloadedRuntimeDbSessionInput[];
|
|
172
174
|
}): Promise<{ ok: true; sessions: number }> {
|
|
173
175
|
return requireBinding().prewarmPostgresSessions(input);
|
|
@@ -180,6 +182,7 @@ export async function harnessPrewarmPostgresSessions(input: {
|
|
|
180
182
|
export async function harnessStartSheetDataset(input: {
|
|
181
183
|
baseUrl: string;
|
|
182
184
|
executorToken: string;
|
|
185
|
+
orgId: string;
|
|
183
186
|
preloadedDbSessions?: PreloadedRuntimeDbSessionInput[] | null;
|
|
184
187
|
playName: string;
|
|
185
188
|
tableNamespace: string;
|
|
@@ -206,6 +209,7 @@ export async function harnessStartSheetDataset(input: {
|
|
|
206
209
|
export async function harnessPersistCompletedSheetRows(input: {
|
|
207
210
|
baseUrl: string;
|
|
208
211
|
executorToken: string;
|
|
212
|
+
orgId: string;
|
|
209
213
|
preloadedDbSessions?: PreloadedRuntimeDbSessionInput[] | null;
|
|
210
214
|
playName: string;
|
|
211
215
|
tableNamespace: string;
|
|
@@ -50,10 +50,10 @@ export type SdkRelease = {
|
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
export const SDK_RELEASE = {
|
|
53
|
-
version: '0.1.
|
|
53
|
+
version: '0.1.66',
|
|
54
54
|
apiContract: '2026-05-play-bootstrap-dataset-summary',
|
|
55
55
|
supportPolicy: {
|
|
56
|
-
latest: '0.1.
|
|
56
|
+
latest: '0.1.66',
|
|
57
57
|
minimumSupported: '0.1.53',
|
|
58
58
|
deprecatedBelow: '0.1.53',
|
|
59
59
|
},
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import type { CreateDbSessionResponse } from './db-session';
|
|
2
|
+
|
|
3
|
+
const POSTGRES_URL_ENCRYPTION_ALGORITHM = 'AES-GCM' as const;
|
|
4
|
+
const POSTGRES_URL_ENCRYPTION_KEY_ID =
|
|
5
|
+
'deepline-runtime-db-session-url:v1' as const;
|
|
6
|
+
const POSTGRES_URL_PUBLIC_KEY_ENCRYPTION_KEY_ID =
|
|
7
|
+
'deepline-runtime-db-session-url:v2' as const;
|
|
8
|
+
const POSTGRES_URL_ENCRYPTION_LABEL =
|
|
9
|
+
'deepline:runtime-db-session-postgres-url:v1';
|
|
10
|
+
const POSTGRES_URL_PUBLIC_KEY_ENCRYPTION_ALGORITHM =
|
|
11
|
+
'RSA-OAEP-256+A256GCM' as const;
|
|
12
|
+
const IV_LENGTH_BYTES = 12;
|
|
13
|
+
const AUTH_TAG_LENGTH_BYTES = 16;
|
|
14
|
+
|
|
15
|
+
export type SharedSecretEncryptedPostgresUrl = {
|
|
16
|
+
alg: 'A256GCM';
|
|
17
|
+
kid: typeof POSTGRES_URL_ENCRYPTION_KEY_ID;
|
|
18
|
+
iv: string;
|
|
19
|
+
ciphertext: string;
|
|
20
|
+
tag: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type PublicKeyEncryptedPostgresUrl = {
|
|
24
|
+
alg: typeof POSTGRES_URL_PUBLIC_KEY_ENCRYPTION_ALGORITHM;
|
|
25
|
+
kid: typeof POSTGRES_URL_PUBLIC_KEY_ENCRYPTION_KEY_ID;
|
|
26
|
+
wrappedKey: string;
|
|
27
|
+
iv: string;
|
|
28
|
+
ciphertext: string;
|
|
29
|
+
tag: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type EncryptedPostgresUrl =
|
|
33
|
+
| SharedSecretEncryptedPostgresUrl
|
|
34
|
+
| PublicKeyEncryptedPostgresUrl;
|
|
35
|
+
|
|
36
|
+
export type PostgresUrlEncryptionRequest = {
|
|
37
|
+
alg: typeof POSTGRES_URL_PUBLIC_KEY_ENCRYPTION_ALGORITHM;
|
|
38
|
+
publicKeyJwk: JsonWebKey;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type PostgresUrlDecryptionKey = {
|
|
42
|
+
request: PostgresUrlEncryptionRequest;
|
|
43
|
+
privateKey: CryptoKey;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
function encodeBase64Url(bytes: Uint8Array): string {
|
|
47
|
+
const base64 =
|
|
48
|
+
typeof Buffer !== 'undefined'
|
|
49
|
+
? Buffer.from(bytes).toString('base64')
|
|
50
|
+
: btoa(String.fromCharCode(...bytes));
|
|
51
|
+
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function decodeBase64Url(value: string): Uint8Array {
|
|
55
|
+
const padding =
|
|
56
|
+
value.length % 4 === 0 ? '' : '='.repeat(4 - (value.length % 4));
|
|
57
|
+
const normalized = value.replace(/-/g, '+').replace(/_/g, '/') + padding;
|
|
58
|
+
if (typeof Buffer !== 'undefined') {
|
|
59
|
+
return new Uint8Array(Buffer.from(normalized, 'base64'));
|
|
60
|
+
}
|
|
61
|
+
return Uint8Array.from(atob(normalized), (char) => char.charCodeAt(0));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function toArrayBuffer(bytes: Uint8Array): ArrayBuffer {
|
|
65
|
+
return bytes.buffer.slice(
|
|
66
|
+
bytes.byteOffset,
|
|
67
|
+
bytes.byteOffset + bytes.byteLength,
|
|
68
|
+
) as ArrayBuffer;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
type RsaOaepKeyGenAlgorithm = {
|
|
72
|
+
name: 'RSA-OAEP';
|
|
73
|
+
modulusLength: number;
|
|
74
|
+
publicExponent: Uint8Array;
|
|
75
|
+
hash: string;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
type RsaOaepImportAlgorithm = {
|
|
79
|
+
name: 'RSA-OAEP';
|
|
80
|
+
hash: string;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
function rsaOaepAlgorithm(): RsaOaepKeyGenAlgorithm {
|
|
84
|
+
return {
|
|
85
|
+
name: 'RSA-OAEP',
|
|
86
|
+
modulusLength: 2048,
|
|
87
|
+
publicExponent: new Uint8Array([1, 0, 1]),
|
|
88
|
+
hash: 'SHA-256',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function rsaOaepImportAlgorithm(): RsaOaepImportAlgorithm {
|
|
93
|
+
return {
|
|
94
|
+
name: 'RSA-OAEP',
|
|
95
|
+
hash: 'SHA-256',
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function derivePostgresUrlEncryptionKey(
|
|
100
|
+
secret: string,
|
|
101
|
+
): Promise<CryptoKey> {
|
|
102
|
+
const normalizedSecret = secret.trim();
|
|
103
|
+
if (!normalizedSecret) {
|
|
104
|
+
throw new Error('Runtime DB session encryption secret is empty.');
|
|
105
|
+
}
|
|
106
|
+
const keyBytes = new Uint8Array(
|
|
107
|
+
await crypto.subtle.digest(
|
|
108
|
+
'SHA-256',
|
|
109
|
+
new TextEncoder().encode(
|
|
110
|
+
`${POSTGRES_URL_ENCRYPTION_LABEL}:${normalizedSecret}`,
|
|
111
|
+
),
|
|
112
|
+
),
|
|
113
|
+
);
|
|
114
|
+
return await crypto.subtle.importKey(
|
|
115
|
+
'raw',
|
|
116
|
+
toArrayBuffer(keyBytes),
|
|
117
|
+
{ name: POSTGRES_URL_ENCRYPTION_ALGORITHM },
|
|
118
|
+
false,
|
|
119
|
+
['encrypt', 'decrypt'],
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function dbSessionPostgresUrlAad(
|
|
124
|
+
session: Omit<
|
|
125
|
+
CreateDbSessionResponse,
|
|
126
|
+
'postgresUrl' | 'encryptedPostgresUrl'
|
|
127
|
+
>,
|
|
128
|
+
): string {
|
|
129
|
+
return JSON.stringify({
|
|
130
|
+
sessionId: session.sessionId,
|
|
131
|
+
expiresAt: session.expiresAt,
|
|
132
|
+
playName: session.playName,
|
|
133
|
+
target: session.target,
|
|
134
|
+
operations: [...session.operations].sort(),
|
|
135
|
+
postgres: session.postgres ?? null,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export async function generateDbSessionPostgresUrlDecryptionKey(): Promise<PostgresUrlDecryptionKey> {
|
|
140
|
+
const keyPair = (await crypto.subtle.generateKey(rsaOaepAlgorithm(), true, [
|
|
141
|
+
'encrypt',
|
|
142
|
+
'decrypt',
|
|
143
|
+
])) as CryptoKeyPair;
|
|
144
|
+
const publicKeyJwk = (await crypto.subtle.exportKey(
|
|
145
|
+
'jwk',
|
|
146
|
+
keyPair.publicKey,
|
|
147
|
+
)) as JsonWebKey;
|
|
148
|
+
const serializablePublicKeyJwk: JsonWebKey = {
|
|
149
|
+
kty: publicKeyJwk.kty,
|
|
150
|
+
n: publicKeyJwk.n,
|
|
151
|
+
e: publicKeyJwk.e,
|
|
152
|
+
alg: publicKeyJwk.alg,
|
|
153
|
+
ext: publicKeyJwk.ext,
|
|
154
|
+
key_ops: publicKeyJwk.key_ops ? [...publicKeyJwk.key_ops] : undefined,
|
|
155
|
+
};
|
|
156
|
+
return {
|
|
157
|
+
request: {
|
|
158
|
+
alg: POSTGRES_URL_PUBLIC_KEY_ENCRYPTION_ALGORITHM,
|
|
159
|
+
publicKeyJwk: serializablePublicKeyJwk,
|
|
160
|
+
},
|
|
161
|
+
privateKey: keyPair.privateKey,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export async function encryptDbSessionPostgresUrl(input: {
|
|
166
|
+
postgresUrl: string;
|
|
167
|
+
secret: string;
|
|
168
|
+
aad: string;
|
|
169
|
+
}): Promise<SharedSecretEncryptedPostgresUrl> {
|
|
170
|
+
const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH_BYTES));
|
|
171
|
+
const key = await derivePostgresUrlEncryptionKey(input.secret);
|
|
172
|
+
const encrypted = new Uint8Array(
|
|
173
|
+
await crypto.subtle.encrypt(
|
|
174
|
+
{
|
|
175
|
+
name: POSTGRES_URL_ENCRYPTION_ALGORITHM,
|
|
176
|
+
iv: toArrayBuffer(iv),
|
|
177
|
+
additionalData: new TextEncoder().encode(input.aad),
|
|
178
|
+
tagLength: AUTH_TAG_LENGTH_BYTES * 8,
|
|
179
|
+
},
|
|
180
|
+
key,
|
|
181
|
+
new TextEncoder().encode(input.postgresUrl),
|
|
182
|
+
),
|
|
183
|
+
);
|
|
184
|
+
return {
|
|
185
|
+
alg: 'A256GCM',
|
|
186
|
+
kid: POSTGRES_URL_ENCRYPTION_KEY_ID,
|
|
187
|
+
iv: encodeBase64Url(iv),
|
|
188
|
+
ciphertext: encodeBase64Url(encrypted.slice(0, -AUTH_TAG_LENGTH_BYTES)),
|
|
189
|
+
tag: encodeBase64Url(encrypted.slice(-AUTH_TAG_LENGTH_BYTES)),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export async function encryptDbSessionPostgresUrlWithPublicKey(input: {
|
|
194
|
+
postgresUrl: string;
|
|
195
|
+
request: PostgresUrlEncryptionRequest;
|
|
196
|
+
aad: string;
|
|
197
|
+
}): Promise<PublicKeyEncryptedPostgresUrl> {
|
|
198
|
+
if (input.request.alg !== POSTGRES_URL_PUBLIC_KEY_ENCRYPTION_ALGORITHM) {
|
|
199
|
+
throw new Error('Unsupported runtime DB session public-key envelope.');
|
|
200
|
+
}
|
|
201
|
+
const publicKey = (await crypto.subtle.importKey(
|
|
202
|
+
'jwk',
|
|
203
|
+
input.request.publicKeyJwk,
|
|
204
|
+
rsaOaepImportAlgorithm(),
|
|
205
|
+
false,
|
|
206
|
+
['encrypt'],
|
|
207
|
+
)) as CryptoKey;
|
|
208
|
+
const contentKey = (await crypto.subtle.generateKey(
|
|
209
|
+
{ name: POSTGRES_URL_ENCRYPTION_ALGORITHM, length: 256 },
|
|
210
|
+
true,
|
|
211
|
+
['encrypt', 'decrypt'],
|
|
212
|
+
)) as CryptoKey;
|
|
213
|
+
const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH_BYTES));
|
|
214
|
+
const encrypted = new Uint8Array(
|
|
215
|
+
await crypto.subtle.encrypt(
|
|
216
|
+
{
|
|
217
|
+
name: POSTGRES_URL_ENCRYPTION_ALGORITHM,
|
|
218
|
+
iv: toArrayBuffer(iv),
|
|
219
|
+
additionalData: new TextEncoder().encode(input.aad),
|
|
220
|
+
tagLength: AUTH_TAG_LENGTH_BYTES * 8,
|
|
221
|
+
},
|
|
222
|
+
contentKey,
|
|
223
|
+
new TextEncoder().encode(input.postgresUrl),
|
|
224
|
+
),
|
|
225
|
+
);
|
|
226
|
+
const rawContentKey = (await crypto.subtle.exportKey(
|
|
227
|
+
'raw',
|
|
228
|
+
contentKey,
|
|
229
|
+
)) as ArrayBuffer;
|
|
230
|
+
const wrappedKey = new Uint8Array(
|
|
231
|
+
await crypto.subtle.encrypt({ name: 'RSA-OAEP' }, publicKey, rawContentKey),
|
|
232
|
+
);
|
|
233
|
+
return {
|
|
234
|
+
alg: POSTGRES_URL_PUBLIC_KEY_ENCRYPTION_ALGORITHM,
|
|
235
|
+
kid: POSTGRES_URL_PUBLIC_KEY_ENCRYPTION_KEY_ID,
|
|
236
|
+
wrappedKey: encodeBase64Url(wrappedKey),
|
|
237
|
+
iv: encodeBase64Url(iv),
|
|
238
|
+
ciphertext: encodeBase64Url(encrypted.slice(0, -AUTH_TAG_LENGTH_BYTES)),
|
|
239
|
+
tag: encodeBase64Url(encrypted.slice(-AUTH_TAG_LENGTH_BYTES)),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export async function decryptDbSessionPostgresUrl(input: {
|
|
244
|
+
encrypted: EncryptedPostgresUrl;
|
|
245
|
+
secret: string;
|
|
246
|
+
aad: string;
|
|
247
|
+
}): Promise<string> {
|
|
248
|
+
if (
|
|
249
|
+
input.encrypted.alg !== 'A256GCM' ||
|
|
250
|
+
input.encrypted.kid !== POSTGRES_URL_ENCRYPTION_KEY_ID
|
|
251
|
+
) {
|
|
252
|
+
throw new Error('Unsupported runtime DB session URL encryption envelope.');
|
|
253
|
+
}
|
|
254
|
+
const ciphertext = decodeBase64Url(input.encrypted.ciphertext);
|
|
255
|
+
const tag = decodeBase64Url(input.encrypted.tag);
|
|
256
|
+
const combined = new Uint8Array(ciphertext.byteLength + tag.byteLength);
|
|
257
|
+
combined.set(ciphertext, 0);
|
|
258
|
+
combined.set(tag, ciphertext.byteLength);
|
|
259
|
+
const key = await derivePostgresUrlEncryptionKey(input.secret);
|
|
260
|
+
const plaintext = await crypto.subtle.decrypt(
|
|
261
|
+
{
|
|
262
|
+
name: POSTGRES_URL_ENCRYPTION_ALGORITHM,
|
|
263
|
+
iv: toArrayBuffer(decodeBase64Url(input.encrypted.iv)),
|
|
264
|
+
additionalData: new TextEncoder().encode(input.aad),
|
|
265
|
+
tagLength: AUTH_TAG_LENGTH_BYTES * 8,
|
|
266
|
+
},
|
|
267
|
+
key,
|
|
268
|
+
toArrayBuffer(combined),
|
|
269
|
+
);
|
|
270
|
+
return new TextDecoder().decode(plaintext);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export async function decryptDbSessionPostgresUrlWithPrivateKey(input: {
|
|
274
|
+
encrypted: EncryptedPostgresUrl;
|
|
275
|
+
privateKey: CryptoKey;
|
|
276
|
+
aad: string;
|
|
277
|
+
}): Promise<string> {
|
|
278
|
+
if (
|
|
279
|
+
input.encrypted.alg !== POSTGRES_URL_PUBLIC_KEY_ENCRYPTION_ALGORITHM ||
|
|
280
|
+
input.encrypted.kid !== POSTGRES_URL_PUBLIC_KEY_ENCRYPTION_KEY_ID
|
|
281
|
+
) {
|
|
282
|
+
throw new Error('Unsupported runtime DB session URL public-key envelope.');
|
|
283
|
+
}
|
|
284
|
+
const rawContentKey = await crypto.subtle.decrypt(
|
|
285
|
+
{ name: 'RSA-OAEP' },
|
|
286
|
+
input.privateKey,
|
|
287
|
+
toArrayBuffer(decodeBase64Url(input.encrypted.wrappedKey)),
|
|
288
|
+
);
|
|
289
|
+
const contentKey = await crypto.subtle.importKey(
|
|
290
|
+
'raw',
|
|
291
|
+
rawContentKey,
|
|
292
|
+
{ name: POSTGRES_URL_ENCRYPTION_ALGORITHM },
|
|
293
|
+
false,
|
|
294
|
+
['decrypt'],
|
|
295
|
+
);
|
|
296
|
+
const ciphertext = decodeBase64Url(input.encrypted.ciphertext);
|
|
297
|
+
const tag = decodeBase64Url(input.encrypted.tag);
|
|
298
|
+
const combined = new Uint8Array(ciphertext.byteLength + tag.byteLength);
|
|
299
|
+
combined.set(ciphertext, 0);
|
|
300
|
+
combined.set(tag, ciphertext.byteLength);
|
|
301
|
+
const plaintext = await crypto.subtle.decrypt(
|
|
302
|
+
{
|
|
303
|
+
name: POSTGRES_URL_ENCRYPTION_ALGORITHM,
|
|
304
|
+
iv: toArrayBuffer(decodeBase64Url(input.encrypted.iv)),
|
|
305
|
+
additionalData: new TextEncoder().encode(input.aad),
|
|
306
|
+
tagLength: AUTH_TAG_LENGTH_BYTES * 8,
|
|
307
|
+
},
|
|
308
|
+
contentKey,
|
|
309
|
+
toArrayBuffer(combined),
|
|
310
|
+
);
|
|
311
|
+
return new TextDecoder().decode(plaintext);
|
|
312
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {
|
|
2
|
+
flattenStaticPipeline,
|
|
3
|
+
resolveSheetContractForTableNamespace,
|
|
4
|
+
type PlaySheetContract,
|
|
5
|
+
type PlayStaticPipeline,
|
|
6
|
+
} from '../plays/static-pipeline';
|
|
7
|
+
import {
|
|
8
|
+
RUNTIME_SHEET_ROWS_LOGICAL_TABLE,
|
|
9
|
+
RUNTIME_WORK_RECEIPT_LOGICAL_TABLE,
|
|
10
|
+
RUNTIME_WORK_RECEIPT_TABLE_NAMESPACE,
|
|
11
|
+
type DbLogicalTable,
|
|
12
|
+
type DbSessionLimits,
|
|
13
|
+
type DbSessionOperation,
|
|
14
|
+
} from './db-session';
|
|
15
|
+
|
|
16
|
+
export type RuntimeDbSessionRequirement = {
|
|
17
|
+
tableNamespace: string;
|
|
18
|
+
logicalTable: DbLogicalTable;
|
|
19
|
+
operations: DbSessionOperation[];
|
|
20
|
+
limits?: DbSessionLimits;
|
|
21
|
+
sheetContract?: PlaySheetContract | null;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function planRuntimeSheetDbSessionRequirements(
|
|
25
|
+
pipeline: PlayStaticPipeline | null,
|
|
26
|
+
): RuntimeDbSessionRequirement[] {
|
|
27
|
+
if (!pipeline) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
const byNamespace = new Map<string, PlaySheetContract>();
|
|
31
|
+
for (const substep of flattenStaticPipeline(pipeline)) {
|
|
32
|
+
if (substep.type !== 'map') continue;
|
|
33
|
+
const tableNamespace = (
|
|
34
|
+
substep.tableNamespace ??
|
|
35
|
+
substep.field ??
|
|
36
|
+
''
|
|
37
|
+
).trim();
|
|
38
|
+
if (!tableNamespace) continue;
|
|
39
|
+
const sheetContract =
|
|
40
|
+
resolveSheetContractForTableNamespace(pipeline, tableNamespace) ??
|
|
41
|
+
substep.sheetContract ??
|
|
42
|
+
null;
|
|
43
|
+
if (!sheetContract) continue;
|
|
44
|
+
byNamespace.set(tableNamespace, sheetContract);
|
|
45
|
+
}
|
|
46
|
+
return [...byNamespace.entries()].map(([tableNamespace, sheetContract]) => ({
|
|
47
|
+
tableNamespace,
|
|
48
|
+
logicalTable: RUNTIME_SHEET_ROWS_LOGICAL_TABLE,
|
|
49
|
+
operations: ['rows.read', 'rows.upsert'],
|
|
50
|
+
sheetContract,
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function runtimeWorkReceiptDbSessionRequirement(): RuntimeDbSessionRequirement {
|
|
55
|
+
return {
|
|
56
|
+
tableNamespace: RUNTIME_WORK_RECEIPT_TABLE_NAMESPACE,
|
|
57
|
+
logicalTable: RUNTIME_WORK_RECEIPT_LOGICAL_TABLE,
|
|
58
|
+
operations: ['rows.read', 'rows.upsert'],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function planRuntimeDbSessionRequirements(
|
|
63
|
+
pipeline: PlayStaticPipeline | null,
|
|
64
|
+
): RuntimeDbSessionRequirement[] {
|
|
65
|
+
return [
|
|
66
|
+
...planRuntimeSheetDbSessionRequirements(pipeline),
|
|
67
|
+
runtimeWorkReceiptDbSessionRequirement(),
|
|
68
|
+
];
|
|
69
|
+
}
|