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/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?: RequestInit, options?: {
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?: RequestInit, options?: {
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.67",
244
+ version: "0.1.69",
245
245
  apiContract: "2026-05-play-bootstrap-dataset-summary",
246
246
  supportPolicy: {
247
- latest: "0.1.67",
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.67",
182
+ version: "0.1.69",
183
183
  apiContract: "2026-05-play-bootstrap-dataset-summary",
184
184
  supportPolicy: {
185
- latest: "0.1.67",
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
- tools: {
4188
- async execute(requestArg: unknown): Promise<unknown> {
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
- ): Promise<unknown> {
4261
- const normalizedKey = normalizeContextKey(key, 'runPlay');
4262
- const resolvedName = resolvePlayRefName(playRef);
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
- async fetch(
4502
- key: string,
4503
- input: string | URL,
4504
- init: RequestInit = {},
4505
- options?: { staleAfterSeconds?: number },
4506
- ): Promise<WorkerFetchResponse> {
4507
- assertNotAborted(abortSignal);
4508
- const normalizedKey = normalizeContextKey(key, 'fetch');
4509
- const url = input.toString();
4510
- const method = (init.method ?? 'GET').toUpperCase();
4511
- const safeHeaders = normalizeFetchHeaders(init.headers);
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
- return await executeWithRuntimeReceipt(receiptKey, async () => {
4528
- const response = await fetch(url, init);
4529
- assertNotAborted(abortSignal);
4530
- const bodyText = await response.text();
4531
- return {
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?: RequestInit,
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
- return bundlePlayFileCore(filePath, {
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.67',
53
+ version: '0.1.69',
54
54
  apiContract: '2026-05-play-bootstrap-dataset-summary',
55
55
  supportPolicy: {
56
- latest: '0.1.67',
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
+ }