deepline 0.1.67 → 0.1.69
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 +1027 -577
- package/dist/cli/index.mjs +1027 -577
- package/dist/index.d.mts +43 -1
- package/dist/index.d.ts +43 -1
- package/dist/index.js +18 -2
- package/dist/index.mjs +18 -2
- package/dist/repo/apps/play-runner-workers/src/entry.ts +122 -29
- package/dist/repo/sdk/src/client.ts +37 -0
- package/dist/repo/sdk/src/play.ts +33 -1
- package/dist/repo/sdk/src/plays/bundle-play-file.ts +4 -1
- package/dist/repo/sdk/src/release.ts +2 -2
- package/dist/repo/shared_libs/play-runtime/secret-capability.ts +103 -0
- package/dist/repo/shared_libs/play-runtime/secret-redaction.ts +90 -0
- package/dist/repo/shared_libs/plays/bundling/index.ts +10 -0
- package/dist/repo/shared_libs/plays/secret-guardrails.ts +57 -0
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1110,6 +1110,18 @@ type PlaySheetRowsResult = {
|
|
|
1110
1110
|
customerDbUrl?: string;
|
|
1111
1111
|
deltaCursor?: number;
|
|
1112
1112
|
};
|
|
1113
|
+
type PlaySecretMetadata = {
|
|
1114
|
+
_id: string;
|
|
1115
|
+
orgId: string;
|
|
1116
|
+
scope: 'org' | 'play';
|
|
1117
|
+
playName?: string;
|
|
1118
|
+
name: string;
|
|
1119
|
+
status: string;
|
|
1120
|
+
hasValue: boolean;
|
|
1121
|
+
createdAt: number;
|
|
1122
|
+
updatedAt: number;
|
|
1123
|
+
lastUsedAt?: number;
|
|
1124
|
+
};
|
|
1113
1125
|
type RunsNamespace = {
|
|
1114
1126
|
get: (runId: string, options?: {
|
|
1115
1127
|
full?: boolean;
|
|
@@ -1166,6 +1178,8 @@ declare class DeeplineClient {
|
|
|
1166
1178
|
private playCloneEditStarter;
|
|
1167
1179
|
private summarizePlayListItem;
|
|
1168
1180
|
private summarizePlayDetail;
|
|
1181
|
+
listSecrets(): Promise<PlaySecretMetadata[]>;
|
|
1182
|
+
checkSecret(name: string): Promise<PlaySecretMetadata | null>;
|
|
1169
1183
|
/**
|
|
1170
1184
|
* List all available tools.
|
|
1171
1185
|
*
|
|
@@ -2076,6 +2090,29 @@ type PlayBindings = {
|
|
|
2076
2090
|
/** IANA timezone (e.g. `'America/New_York'`). Defaults to UTC. */
|
|
2077
2091
|
timezone?: string;
|
|
2078
2092
|
};
|
|
2093
|
+
/**
|
|
2094
|
+
* Customer-authored play secrets this play is allowed to use at runtime.
|
|
2095
|
+
* Values are never bundled or exposed by the SDK; access them with
|
|
2096
|
+
* `ctx.secrets.get("NAME")` and approved helpers such as
|
|
2097
|
+
* `ctx.secrets.bearer(handle)`.
|
|
2098
|
+
*/
|
|
2099
|
+
secrets?: readonly string[];
|
|
2100
|
+
};
|
|
2101
|
+
declare const SECRET_HANDLE_BRAND: unique symbol;
|
|
2102
|
+
type SecretHandle = {
|
|
2103
|
+
readonly [SECRET_HANDLE_BRAND]: never;
|
|
2104
|
+
readonly name: string;
|
|
2105
|
+
toString(): string;
|
|
2106
|
+
toJSON(): never;
|
|
2107
|
+
};
|
|
2108
|
+
type SecretAuth = {
|
|
2109
|
+
readonly kind: 'bearer' | 'header';
|
|
2110
|
+
readonly secret: SecretHandle;
|
|
2111
|
+
readonly header?: string;
|
|
2112
|
+
};
|
|
2113
|
+
type SecretAwareRequestInit = Omit<RequestInit, 'headers'> & {
|
|
2114
|
+
headers?: HeadersInit;
|
|
2115
|
+
auth?: SecretAuth;
|
|
2079
2116
|
};
|
|
2080
2117
|
type LoosePlayObject = {
|
|
2081
2118
|
[key: string]: LoosePlayObject;
|
|
@@ -2377,7 +2414,7 @@ interface DeeplinePlayRuntimeContext {
|
|
|
2377
2414
|
* @param init - Fetch options.
|
|
2378
2415
|
* @returns Recorded response.
|
|
2379
2416
|
*/
|
|
2380
|
-
fetch(key: string, url: string | URL, init?:
|
|
2417
|
+
fetch(key: string, url: string | URL, init?: SecretAwareRequestInit, options?: {
|
|
2381
2418
|
staleAfterSeconds?: number;
|
|
2382
2419
|
}): Promise<{
|
|
2383
2420
|
ok: boolean;
|
|
@@ -2388,6 +2425,11 @@ interface DeeplinePlayRuntimeContext {
|
|
|
2388
2425
|
bodyText: string;
|
|
2389
2426
|
json: unknown | null;
|
|
2390
2427
|
}>;
|
|
2428
|
+
secrets: {
|
|
2429
|
+
get(name: string): SecretHandle;
|
|
2430
|
+
bearer(secret: SecretHandle): SecretAuth;
|
|
2431
|
+
header(header: string, secret: SecretHandle): SecretAuth;
|
|
2432
|
+
};
|
|
2391
2433
|
runPlay<TOutput = unknown>(key: string, playRef: string | PlayReferenceLike, input: Record<string, unknown>, options: {
|
|
2392
2434
|
description?: string;
|
|
2393
2435
|
staleAfterSeconds?: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -1110,6 +1110,18 @@ type PlaySheetRowsResult = {
|
|
|
1110
1110
|
customerDbUrl?: string;
|
|
1111
1111
|
deltaCursor?: number;
|
|
1112
1112
|
};
|
|
1113
|
+
type PlaySecretMetadata = {
|
|
1114
|
+
_id: string;
|
|
1115
|
+
orgId: string;
|
|
1116
|
+
scope: 'org' | 'play';
|
|
1117
|
+
playName?: string;
|
|
1118
|
+
name: string;
|
|
1119
|
+
status: string;
|
|
1120
|
+
hasValue: boolean;
|
|
1121
|
+
createdAt: number;
|
|
1122
|
+
updatedAt: number;
|
|
1123
|
+
lastUsedAt?: number;
|
|
1124
|
+
};
|
|
1113
1125
|
type RunsNamespace = {
|
|
1114
1126
|
get: (runId: string, options?: {
|
|
1115
1127
|
full?: boolean;
|
|
@@ -1166,6 +1178,8 @@ declare class DeeplineClient {
|
|
|
1166
1178
|
private playCloneEditStarter;
|
|
1167
1179
|
private summarizePlayListItem;
|
|
1168
1180
|
private summarizePlayDetail;
|
|
1181
|
+
listSecrets(): Promise<PlaySecretMetadata[]>;
|
|
1182
|
+
checkSecret(name: string): Promise<PlaySecretMetadata | null>;
|
|
1169
1183
|
/**
|
|
1170
1184
|
* List all available tools.
|
|
1171
1185
|
*
|
|
@@ -2076,6 +2090,29 @@ type PlayBindings = {
|
|
|
2076
2090
|
/** IANA timezone (e.g. `'America/New_York'`). Defaults to UTC. */
|
|
2077
2091
|
timezone?: string;
|
|
2078
2092
|
};
|
|
2093
|
+
/**
|
|
2094
|
+
* Customer-authored play secrets this play is allowed to use at runtime.
|
|
2095
|
+
* Values are never bundled or exposed by the SDK; access them with
|
|
2096
|
+
* `ctx.secrets.get("NAME")` and approved helpers such as
|
|
2097
|
+
* `ctx.secrets.bearer(handle)`.
|
|
2098
|
+
*/
|
|
2099
|
+
secrets?: readonly string[];
|
|
2100
|
+
};
|
|
2101
|
+
declare const SECRET_HANDLE_BRAND: unique symbol;
|
|
2102
|
+
type SecretHandle = {
|
|
2103
|
+
readonly [SECRET_HANDLE_BRAND]: never;
|
|
2104
|
+
readonly name: string;
|
|
2105
|
+
toString(): string;
|
|
2106
|
+
toJSON(): never;
|
|
2107
|
+
};
|
|
2108
|
+
type SecretAuth = {
|
|
2109
|
+
readonly kind: 'bearer' | 'header';
|
|
2110
|
+
readonly secret: SecretHandle;
|
|
2111
|
+
readonly header?: string;
|
|
2112
|
+
};
|
|
2113
|
+
type SecretAwareRequestInit = Omit<RequestInit, 'headers'> & {
|
|
2114
|
+
headers?: HeadersInit;
|
|
2115
|
+
auth?: SecretAuth;
|
|
2079
2116
|
};
|
|
2080
2117
|
type LoosePlayObject = {
|
|
2081
2118
|
[key: string]: LoosePlayObject;
|
|
@@ -2377,7 +2414,7 @@ interface DeeplinePlayRuntimeContext {
|
|
|
2377
2414
|
* @param init - Fetch options.
|
|
2378
2415
|
* @returns Recorded response.
|
|
2379
2416
|
*/
|
|
2380
|
-
fetch(key: string, url: string | URL, init?:
|
|
2417
|
+
fetch(key: string, url: string | URL, init?: SecretAwareRequestInit, options?: {
|
|
2381
2418
|
staleAfterSeconds?: number;
|
|
2382
2419
|
}): Promise<{
|
|
2383
2420
|
ok: boolean;
|
|
@@ -2388,6 +2425,11 @@ interface DeeplinePlayRuntimeContext {
|
|
|
2388
2425
|
bodyText: string;
|
|
2389
2426
|
json: unknown | null;
|
|
2390
2427
|
}>;
|
|
2428
|
+
secrets: {
|
|
2429
|
+
get(name: string): SecretHandle;
|
|
2430
|
+
bearer(secret: SecretHandle): SecretAuth;
|
|
2431
|
+
header(header: string, secret: SecretHandle): SecretAuth;
|
|
2432
|
+
};
|
|
2391
2433
|
runPlay<TOutput = unknown>(key: string, playRef: string | PlayReferenceLike, input: Record<string, unknown>, options: {
|
|
2392
2434
|
description?: string;
|
|
2393
2435
|
staleAfterSeconds?: number;
|
package/dist/index.js
CHANGED
|
@@ -241,10 +241,10 @@ var import_node_path2 = require("path");
|
|
|
241
241
|
|
|
242
242
|
// src/release.ts
|
|
243
243
|
var SDK_RELEASE = {
|
|
244
|
-
version: "0.1.
|
|
244
|
+
version: "0.1.69",
|
|
245
245
|
apiContract: "2026-05-play-bootstrap-dataset-summary",
|
|
246
246
|
supportPolicy: {
|
|
247
|
-
latest: "0.1.
|
|
247
|
+
latest: "0.1.69",
|
|
248
248
|
minimumSupported: "0.1.53",
|
|
249
249
|
deprecatedBelow: "0.1.53"
|
|
250
250
|
}
|
|
@@ -838,6 +838,22 @@ var DeeplineClient = class {
|
|
|
838
838
|
};
|
|
839
839
|
}
|
|
840
840
|
// ——————————————————————————————————————————————————————————
|
|
841
|
+
// Secrets
|
|
842
|
+
// ——————————————————————————————————————————————————————————
|
|
843
|
+
async listSecrets() {
|
|
844
|
+
const response = await this.http.get(
|
|
845
|
+
"/api/v2/secrets"
|
|
846
|
+
);
|
|
847
|
+
return Array.isArray(response.secrets) ? response.secrets : [];
|
|
848
|
+
}
|
|
849
|
+
async checkSecret(name) {
|
|
850
|
+
const normalized = name.trim().toUpperCase();
|
|
851
|
+
const secrets = await this.listSecrets();
|
|
852
|
+
return secrets.find(
|
|
853
|
+
(secret) => secret.name === normalized && secret.status === "active" && secret.hasValue
|
|
854
|
+
) ?? null;
|
|
855
|
+
}
|
|
856
|
+
// ——————————————————————————————————————————————————————————
|
|
841
857
|
// Tools
|
|
842
858
|
// ——————————————————————————————————————————————————————————
|
|
843
859
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -179,10 +179,10 @@ import { join as join2 } from "path";
|
|
|
179
179
|
|
|
180
180
|
// src/release.ts
|
|
181
181
|
var SDK_RELEASE = {
|
|
182
|
-
version: "0.1.
|
|
182
|
+
version: "0.1.69",
|
|
183
183
|
apiContract: "2026-05-play-bootstrap-dataset-summary",
|
|
184
184
|
supportPolicy: {
|
|
185
|
-
latest: "0.1.
|
|
185
|
+
latest: "0.1.69",
|
|
186
186
|
minimumSupported: "0.1.53",
|
|
187
187
|
deprecatedBelow: "0.1.53"
|
|
188
188
|
}
|
|
@@ -776,6 +776,22 @@ var DeeplineClient = class {
|
|
|
776
776
|
};
|
|
777
777
|
}
|
|
778
778
|
// ——————————————————————————————————————————————————————————
|
|
779
|
+
// Secrets
|
|
780
|
+
// ——————————————————————————————————————————————————————————
|
|
781
|
+
async listSecrets() {
|
|
782
|
+
const response = await this.http.get(
|
|
783
|
+
"/api/v2/secrets"
|
|
784
|
+
);
|
|
785
|
+
return Array.isArray(response.secrets) ? response.secrets : [];
|
|
786
|
+
}
|
|
787
|
+
async checkSecret(name) {
|
|
788
|
+
const normalized = name.trim().toUpperCase();
|
|
789
|
+
const secrets = await this.listSecrets();
|
|
790
|
+
return secrets.find(
|
|
791
|
+
(secret) => secret.name === normalized && secret.status === "active" && secret.hasValue
|
|
792
|
+
) ?? null;
|
|
793
|
+
}
|
|
794
|
+
// ——————————————————————————————————————————————————————————
|
|
779
795
|
// Tools
|
|
780
796
|
// ——————————————————————————————————————————————————————————
|
|
781
797
|
/**
|
|
@@ -128,6 +128,19 @@ import {
|
|
|
128
128
|
} from '../../../shared_libs/play-runtime/csv-rename';
|
|
129
129
|
import { coordinatorRequestHeaders } from '../../../shared_libs/play-runtime/coordinator-headers';
|
|
130
130
|
import { normalizePlayRunFailure } from '../../../shared_libs/play-runtime/run-failure';
|
|
131
|
+
import { createSecretRedactionContext } from '../../../shared_libs/play-runtime/secret-redaction';
|
|
132
|
+
import {
|
|
133
|
+
assertNoSecretTaint,
|
|
134
|
+
createBearerSecretAuth,
|
|
135
|
+
createHeaderSecretAuth,
|
|
136
|
+
createSecretHandle,
|
|
137
|
+
isSecretAuth,
|
|
138
|
+
secretAuthHeaderMarkers,
|
|
139
|
+
valueContainsSecret,
|
|
140
|
+
type SecretAuth,
|
|
141
|
+
type SecretAwareRequestInit,
|
|
142
|
+
type SecretHandle,
|
|
143
|
+
} from '../../../shared_libs/play-runtime/secret-capability';
|
|
131
144
|
import type {
|
|
132
145
|
LiveNodeProgressMap,
|
|
133
146
|
LiveNodeProgressSnapshot,
|
|
@@ -3183,6 +3196,39 @@ function createMinimalWorkerCtx(
|
|
|
3183
3196
|
const inFlightChildCallsByPlayName: Record<string, number> = {};
|
|
3184
3197
|
let inFlightChildPlayCalls = 0;
|
|
3185
3198
|
const childPlaySlotWaiters: Array<() => void> = [];
|
|
3199
|
+
const secretRedactor = createSecretRedactionContext();
|
|
3200
|
+
|
|
3201
|
+
const resolveSecretAuth = async (auth?: SecretAuth) => {
|
|
3202
|
+
if (!auth) return {};
|
|
3203
|
+
const response = await fetchRuntimeApi(
|
|
3204
|
+
req.baseUrl,
|
|
3205
|
+
'/api/v2/plays/internal/runtime',
|
|
3206
|
+
{
|
|
3207
|
+
method: 'POST',
|
|
3208
|
+
headers: {
|
|
3209
|
+
authorization: `Bearer ${req.executorToken}`,
|
|
3210
|
+
'content-type': 'application/json',
|
|
3211
|
+
},
|
|
3212
|
+
body: JSON.stringify({
|
|
3213
|
+
action: 'resolve_secret',
|
|
3214
|
+
name: auth.secret.name,
|
|
3215
|
+
playName: req.playName,
|
|
3216
|
+
}),
|
|
3217
|
+
},
|
|
3218
|
+
);
|
|
3219
|
+
if (!response.ok) {
|
|
3220
|
+
throw new Error(`Secret ${auth.secret.name} is not available to this run.`);
|
|
3221
|
+
}
|
|
3222
|
+
const payload = (await response.json()) as { value?: unknown };
|
|
3223
|
+
const value = typeof payload.value === 'string' ? payload.value : null;
|
|
3224
|
+
if (!value) {
|
|
3225
|
+
throw new Error(`Secret ${auth.secret.name} is not available to this run.`);
|
|
3226
|
+
}
|
|
3227
|
+
secretRedactor.register(value);
|
|
3228
|
+
return auth.kind === 'bearer'
|
|
3229
|
+
? { authorization: `Bearer ${value}` }
|
|
3230
|
+
: { [auth.header.toLowerCase()]: value };
|
|
3231
|
+
};
|
|
3186
3232
|
|
|
3187
3233
|
const acquireChildPlaySlot = async (): Promise<() => void> => {
|
|
3188
3234
|
while (
|
|
@@ -4007,6 +4053,7 @@ function createMinimalWorkerCtx(
|
|
|
4007
4053
|
*/
|
|
4008
4054
|
signal: abortSignal ?? new AbortController().signal,
|
|
4009
4055
|
log(message: string) {
|
|
4056
|
+
assertNoSecretTaint(message, 'ctx.log');
|
|
4010
4057
|
emitEvent({
|
|
4011
4058
|
type: 'log',
|
|
4012
4059
|
level: 'info',
|
|
@@ -4184,10 +4231,11 @@ function createMinimalWorkerCtx(
|
|
|
4184
4231
|
'ctx.map(key, rows, fields, options) was removed. Use ctx.map(key, rows).step(...).run(options).',
|
|
4185
4232
|
);
|
|
4186
4233
|
},
|
|
4187
|
-
|
|
4188
|
-
|
|
4234
|
+
tools: {
|
|
4235
|
+
async execute(requestArg: unknown): Promise<unknown> {
|
|
4189
4236
|
assertNotAborted(abortSignal);
|
|
4190
4237
|
const request = normalizeToolExecuteArgs(requestArg);
|
|
4238
|
+
assertNoSecretTaint(request.input, 'ctx.tools.execute input');
|
|
4191
4239
|
return await executeWithRuntimeReceipt(
|
|
4192
4240
|
`tool:${request.id}:${deriveToolRequestIdentity({
|
|
4193
4241
|
toolId: request.toolId,
|
|
@@ -4257,9 +4305,10 @@ function createMinimalWorkerCtx(
|
|
|
4257
4305
|
timeoutMs?: number;
|
|
4258
4306
|
staleAfterSeconds?: number;
|
|
4259
4307
|
},
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4308
|
+
): Promise<unknown> {
|
|
4309
|
+
const normalizedKey = normalizeContextKey(key, 'runPlay');
|
|
4310
|
+
const resolvedName = resolvePlayRefName(playRef);
|
|
4311
|
+
assertNoSecretTaint(input, 'ctx.runPlay input');
|
|
4263
4312
|
if (!resolvedName) {
|
|
4264
4313
|
throw new Error('ctx.runPlay(...) requires a resolvable play name.');
|
|
4265
4314
|
}
|
|
@@ -4498,17 +4547,37 @@ function createMinimalWorkerCtx(
|
|
|
4498
4547
|
}
|
|
4499
4548
|
});
|
|
4500
4549
|
},
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4550
|
+
async fetch(
|
|
4551
|
+
key: string,
|
|
4552
|
+
input: string | URL,
|
|
4553
|
+
init: SecretAwareRequestInit = {},
|
|
4554
|
+
options?: { staleAfterSeconds?: number },
|
|
4555
|
+
): Promise<WorkerFetchResponse> {
|
|
4556
|
+
assertNotAborted(abortSignal);
|
|
4557
|
+
const normalizedKey = normalizeContextKey(key, 'fetch');
|
|
4558
|
+
if (
|
|
4559
|
+
valueContainsSecret(input) ||
|
|
4560
|
+
valueContainsSecret(init.body)
|
|
4561
|
+
) {
|
|
4562
|
+
throw new Error(
|
|
4563
|
+
'ctx.fetch does not allow secrets in the URL or body. Use an approved secret auth helper.',
|
|
4564
|
+
);
|
|
4565
|
+
}
|
|
4566
|
+
if (valueContainsSecret(init.headers)) {
|
|
4567
|
+
throw new Error(
|
|
4568
|
+
'ctx.fetch does not allow raw secret headers. Use ctx.secrets.bearer(...) or ctx.secrets.header(...).',
|
|
4569
|
+
);
|
|
4570
|
+
}
|
|
4571
|
+
if (init.auth !== undefined && !isSecretAuth(init.auth)) {
|
|
4572
|
+
throw new Error('ctx.fetch auth must come from ctx.secrets.');
|
|
4573
|
+
}
|
|
4574
|
+
const url = input.toString();
|
|
4575
|
+
const method = (init.method ?? 'GET').toUpperCase();
|
|
4576
|
+
const secretHeaderMarkers = secretAuthHeaderMarkers(init.auth);
|
|
4577
|
+
const safeHeaders = {
|
|
4578
|
+
...normalizeFetchHeaders(init.headers),
|
|
4579
|
+
...secretHeaderMarkers,
|
|
4580
|
+
};
|
|
4512
4581
|
const body = fetchBodyIdentity(init.body);
|
|
4513
4582
|
const hasIdempotencyKey =
|
|
4514
4583
|
safeHeaders['idempotency-key'] !== undefined ||
|
|
@@ -4524,20 +4593,44 @@ function createMinimalWorkerCtx(
|
|
|
4524
4593
|
safeHeaders,
|
|
4525
4594
|
url,
|
|
4526
4595
|
})}${staleRuntimeSuffix(options?.staleAfterSeconds)}`;
|
|
4527
|
-
|
|
4528
|
-
const
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
ok: response.ok,
|
|
4533
|
-
status: response.status,
|
|
4534
|
-
statusText: response.statusText,
|
|
4535
|
-
url: response.url,
|
|
4536
|
-
headers: Object.fromEntries(response.headers.entries()),
|
|
4537
|
-
bodyText,
|
|
4538
|
-
json: parseFetchJsonOrNull(bodyText),
|
|
4596
|
+
return await executeWithRuntimeReceipt(receiptKey, async () => {
|
|
4597
|
+
const secretHeaders = await resolveSecretAuth(init.auth);
|
|
4598
|
+
const headers = {
|
|
4599
|
+
...normalizeFetchHeaders(init.headers),
|
|
4600
|
+
...secretHeaders,
|
|
4539
4601
|
};
|
|
4540
|
-
|
|
4602
|
+
const fetchInit = { ...init, headers };
|
|
4603
|
+
delete fetchInit.auth;
|
|
4604
|
+
const response = await fetch(url, fetchInit);
|
|
4605
|
+
assertNotAborted(abortSignal);
|
|
4606
|
+
const bodyText = await response.text();
|
|
4607
|
+
const redactedBodyText = secretRedactor.redactString(bodyText);
|
|
4608
|
+
return {
|
|
4609
|
+
ok: response.ok,
|
|
4610
|
+
status: response.status,
|
|
4611
|
+
statusText: response.statusText,
|
|
4612
|
+
url: response.url,
|
|
4613
|
+
headers: secretRedactor.redact(
|
|
4614
|
+
Object.fromEntries(response.headers.entries()),
|
|
4615
|
+
) as Record<string, string>,
|
|
4616
|
+
bodyText: redactedBodyText,
|
|
4617
|
+
json: secretRedactor.redact(parseFetchJsonOrNull(bodyText)),
|
|
4618
|
+
};
|
|
4619
|
+
});
|
|
4620
|
+
},
|
|
4621
|
+
secrets: {
|
|
4622
|
+
get(name: string): SecretHandle {
|
|
4623
|
+
if (typeof name !== 'string' || !name.trim()) {
|
|
4624
|
+
throw new Error('ctx.secrets.get(name) requires a non-empty name.');
|
|
4625
|
+
}
|
|
4626
|
+
return createSecretHandle(name.trim());
|
|
4627
|
+
},
|
|
4628
|
+
bearer(secret: SecretHandle): SecretAuth {
|
|
4629
|
+
return createBearerSecretAuth(secret);
|
|
4630
|
+
},
|
|
4631
|
+
header(header: string, secret: SecretHandle): SecretAuth {
|
|
4632
|
+
return createHeaderSecretAuth(header, secret);
|
|
4633
|
+
},
|
|
4541
4634
|
},
|
|
4542
4635
|
async waitForEvent(
|
|
4543
4636
|
eventType: string,
|
|
@@ -161,6 +161,19 @@ export type PlaySheetRowsResult = {
|
|
|
161
161
|
deltaCursor?: number;
|
|
162
162
|
};
|
|
163
163
|
|
|
164
|
+
export type PlaySecretMetadata = {
|
|
165
|
+
_id: string;
|
|
166
|
+
orgId: string;
|
|
167
|
+
scope: 'org' | 'play';
|
|
168
|
+
playName?: string;
|
|
169
|
+
name: string;
|
|
170
|
+
status: string;
|
|
171
|
+
hasValue: boolean;
|
|
172
|
+
createdAt: number;
|
|
173
|
+
updatedAt: number;
|
|
174
|
+
lastUsedAt?: number;
|
|
175
|
+
};
|
|
176
|
+
|
|
164
177
|
export type RunsNamespace = {
|
|
165
178
|
get: (runId: string, options?: { full?: boolean }) => Promise<PlayStatus>;
|
|
166
179
|
list: (options: RunsListOptions) => Promise<PlayRunListItem[]>;
|
|
@@ -574,6 +587,30 @@ export class DeeplineClient {
|
|
|
574
587
|
};
|
|
575
588
|
}
|
|
576
589
|
|
|
590
|
+
// ——————————————————————————————————————————————————————————
|
|
591
|
+
// Secrets
|
|
592
|
+
// ——————————————————————————————————————————————————————————
|
|
593
|
+
|
|
594
|
+
async listSecrets(): Promise<PlaySecretMetadata[]> {
|
|
595
|
+
const response = await this.http.get<{ secrets?: PlaySecretMetadata[] }>(
|
|
596
|
+
'/api/v2/secrets',
|
|
597
|
+
);
|
|
598
|
+
return Array.isArray(response.secrets) ? response.secrets : [];
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
async checkSecret(name: string): Promise<PlaySecretMetadata | null> {
|
|
602
|
+
const normalized = name.trim().toUpperCase();
|
|
603
|
+
const secrets = await this.listSecrets();
|
|
604
|
+
return (
|
|
605
|
+
secrets.find(
|
|
606
|
+
(secret) =>
|
|
607
|
+
secret.name === normalized &&
|
|
608
|
+
secret.status === 'active' &&
|
|
609
|
+
secret.hasValue,
|
|
610
|
+
) ?? null
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
|
|
577
614
|
// ——————————————————————————————————————————————————————————
|
|
578
615
|
// Tools
|
|
579
616
|
// ——————————————————————————————————————————————————————————
|
|
@@ -146,6 +146,33 @@ export type PlayBindings = {
|
|
|
146
146
|
/** IANA timezone (e.g. `'America/New_York'`). Defaults to UTC. */
|
|
147
147
|
timezone?: string;
|
|
148
148
|
};
|
|
149
|
+
/**
|
|
150
|
+
* Customer-authored play secrets this play is allowed to use at runtime.
|
|
151
|
+
* Values are never bundled or exposed by the SDK; access them with
|
|
152
|
+
* `ctx.secrets.get("NAME")` and approved helpers such as
|
|
153
|
+
* `ctx.secrets.bearer(handle)`.
|
|
154
|
+
*/
|
|
155
|
+
secrets?: readonly string[];
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
declare const SECRET_HANDLE_BRAND: unique symbol;
|
|
159
|
+
|
|
160
|
+
export type SecretHandle = {
|
|
161
|
+
readonly [SECRET_HANDLE_BRAND]: never;
|
|
162
|
+
readonly name: string;
|
|
163
|
+
toString(): string;
|
|
164
|
+
toJSON(): never;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export type SecretAuth = {
|
|
168
|
+
readonly kind: 'bearer' | 'header';
|
|
169
|
+
readonly secret: SecretHandle;
|
|
170
|
+
readonly header?: string;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export type SecretAwareRequestInit = Omit<RequestInit, 'headers'> & {
|
|
174
|
+
headers?: HeadersInit;
|
|
175
|
+
auth?: SecretAuth;
|
|
149
176
|
};
|
|
150
177
|
|
|
151
178
|
export type LoosePlayObject = {
|
|
@@ -523,7 +550,7 @@ export interface DeeplinePlayRuntimeContext {
|
|
|
523
550
|
fetch(
|
|
524
551
|
key: string,
|
|
525
552
|
url: string | URL,
|
|
526
|
-
init?:
|
|
553
|
+
init?: SecretAwareRequestInit,
|
|
527
554
|
options?: { staleAfterSeconds?: number },
|
|
528
555
|
): Promise<{
|
|
529
556
|
ok: boolean;
|
|
@@ -534,6 +561,11 @@ export interface DeeplinePlayRuntimeContext {
|
|
|
534
561
|
bodyText: string;
|
|
535
562
|
json: unknown | null;
|
|
536
563
|
}>;
|
|
564
|
+
secrets: {
|
|
565
|
+
get(name: string): SecretHandle;
|
|
566
|
+
bearer(secret: SecretHandle): SecretAuth;
|
|
567
|
+
header(header: string, secret: SecretHandle): SecretAuth;
|
|
568
|
+
};
|
|
537
569
|
runPlay<TOutput = unknown>(
|
|
538
570
|
key: string,
|
|
539
571
|
playRef: string | PlayReferenceLike,
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
type PlayArtifactKind,
|
|
18
18
|
} from '../../../shared_libs/play-runtime/backend.js';
|
|
19
19
|
import { resolveExecutionProfile } from '../../../shared_libs/play-runtime/profiles.js';
|
|
20
|
+
import { validatePlaySourceFilesHaveNoInlineSecrets } from '../../../shared_libs/plays/secret-guardrails.js';
|
|
20
21
|
import { discoverPackagedLocalFiles } from './local-file-discovery.js';
|
|
21
22
|
|
|
22
23
|
export type {
|
|
@@ -149,11 +150,13 @@ export async function bundlePlayFile(
|
|
|
149
150
|
filePath: string,
|
|
150
151
|
options: BundlePlayFileOptions = {},
|
|
151
152
|
): Promise<BundledPlayFileResult> {
|
|
152
|
-
|
|
153
|
+
const result = await bundlePlayFileCore(filePath, {
|
|
153
154
|
target: options.target ?? defaultPlayBundleTarget(),
|
|
154
155
|
exportName: options.exportName,
|
|
155
156
|
adapter: createSdkPlayBundlingAdapter(),
|
|
156
157
|
});
|
|
158
|
+
if (result.success) validatePlaySourceFilesHaveNoInlineSecrets(result.sourceFiles);
|
|
159
|
+
return result;
|
|
157
160
|
}
|
|
158
161
|
|
|
159
162
|
export { PLAY_ARTIFACT_KINDS };
|
|
@@ -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.69',
|
|
54
54
|
apiContract: '2026-05-play-bootstrap-dataset-summary',
|
|
55
55
|
supportPolicy: {
|
|
56
|
-
latest: '0.1.
|
|
56
|
+
latest: '0.1.69',
|
|
57
57
|
minimumSupported: '0.1.53',
|
|
58
58
|
deprecatedBelow: '0.1.53',
|
|
59
59
|
},
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const SECRET_HANDLE_BRAND = Symbol.for('deepline.secret.handle');
|
|
2
|
+
const SECRET_AUTH_BRAND = Symbol.for('deepline.secret.auth');
|
|
3
|
+
const SECRET_HANDLE_MARKER_RE = /\[secret:[A-Z0-9_ -]+\]/i;
|
|
4
|
+
|
|
5
|
+
export type SecretHandle = {
|
|
6
|
+
readonly [SECRET_HANDLE_BRAND]: true;
|
|
7
|
+
readonly name: string;
|
|
8
|
+
toString(): string;
|
|
9
|
+
toJSON(): never;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type SecretAuth =
|
|
13
|
+
| {
|
|
14
|
+
readonly [SECRET_AUTH_BRAND]: true;
|
|
15
|
+
readonly kind: 'bearer';
|
|
16
|
+
readonly secret: SecretHandle;
|
|
17
|
+
}
|
|
18
|
+
| {
|
|
19
|
+
readonly [SECRET_AUTH_BRAND]: true;
|
|
20
|
+
readonly kind: 'header';
|
|
21
|
+
readonly header: string;
|
|
22
|
+
readonly secret: SecretHandle;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type SecretAwareRequestInit = RequestInit & {
|
|
26
|
+
auth?: SecretAuth;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function isRecord(value: unknown): value is Record<string | symbol, unknown> {
|
|
30
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function isSecretHandle(value: unknown): value is SecretHandle {
|
|
34
|
+
return isRecord(value) && value[SECRET_HANDLE_BRAND] === true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function isSecretAuth(value: unknown): value is SecretAuth {
|
|
38
|
+
return isRecord(value) && value[SECRET_AUTH_BRAND] === true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function valueContainsSecret(value: unknown): boolean {
|
|
42
|
+
if (isSecretHandle(value) || isSecretAuth(value)) return true;
|
|
43
|
+
if (typeof value === 'string') return SECRET_HANDLE_MARKER_RE.test(value);
|
|
44
|
+
if (Array.isArray(value)) return value.some(valueContainsSecret);
|
|
45
|
+
if (isRecord(value)) return Object.values(value).some(valueContainsSecret);
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function createSecretHandle(name: string): SecretHandle {
|
|
50
|
+
return {
|
|
51
|
+
[SECRET_HANDLE_BRAND]: true,
|
|
52
|
+
name,
|
|
53
|
+
toString: () => `[secret:${name}]`,
|
|
54
|
+
toJSON: () => {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Secret ${name} cannot be serialized. Use an approved ctx.secrets helper.`,
|
|
57
|
+
);
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function createBearerSecretAuth(secret: SecretHandle): SecretAuth {
|
|
63
|
+
if (!isSecretHandle(secret)) {
|
|
64
|
+
throw new Error('ctx.secrets.bearer(...) requires a SecretHandle.');
|
|
65
|
+
}
|
|
66
|
+
return { [SECRET_AUTH_BRAND]: true, kind: 'bearer', secret };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function createHeaderSecretAuth(
|
|
70
|
+
header: string,
|
|
71
|
+
secret: SecretHandle,
|
|
72
|
+
): SecretAuth {
|
|
73
|
+
if (!isSecretHandle(secret)) {
|
|
74
|
+
throw new Error('ctx.secrets.header(...) requires a SecretHandle.');
|
|
75
|
+
}
|
|
76
|
+
if (typeof header !== 'string' || !header.trim()) {
|
|
77
|
+
throw new Error('ctx.secrets.header(...) requires a header name.');
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
[SECRET_AUTH_BRAND]: true,
|
|
81
|
+
kind: 'header',
|
|
82
|
+
header: header.trim(),
|
|
83
|
+
secret,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function secretAuthHeaderMarkers(
|
|
88
|
+
auth: SecretAuth | undefined,
|
|
89
|
+
): Record<string, string> {
|
|
90
|
+
if (!auth) return {};
|
|
91
|
+
if (auth.kind === 'bearer') {
|
|
92
|
+
return { authorization: `[secret:${auth.secret.name}]` };
|
|
93
|
+
}
|
|
94
|
+
return { [auth.header.toLowerCase()]: `[secret:${auth.secret.name}]` };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function assertNoSecretTaint(value: unknown, sink: string): void {
|
|
98
|
+
if (valueContainsSecret(value)) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`${sink} cannot receive secret handles or secret-tainted values. Use an approved ctx.secrets helper.`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|