deepline 0.1.239 → 0.1.241

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.
@@ -110,10 +110,13 @@ export const SDK_RELEASE = {
110
110
  // silently materializing them as unmatched results.
111
111
  // 0.1.220 deprecates SDK CLI versions below 0.1.219 and updates them
112
112
  // automatically without blocking their current command.
113
- version: '0.1.239',
114
- apiContract: '2026-06-dataset-handle-results-hard-cutover',
113
+ // 0.1.241 hard-cuts published and launched plays to immutable cjs_node20
114
+ // artifacts. Older clients can submit esm_workers artifacts that the Absurd
115
+ // runtime intentionally no longer compiles at publish or launch time.
116
+ version: '0.1.241',
117
+ apiContract: '2026-07-immutable-cjs-play-artifacts-hard-cutover',
115
118
  supportPolicy: {
116
- latest: '0.1.239',
119
+ latest: '0.1.241',
117
120
  minimumSupported: '0.1.53',
118
121
  deprecatedBelow: '0.1.219',
119
122
  commandMinimumSupported: [
@@ -5852,6 +5852,10 @@ export class PlayContextImpl {
5852
5852
  for (const wake of waiters) wake();
5853
5853
  }
5854
5854
 
5855
+ private toolBatchDispatcherNowMs(): number {
5856
+ return this.#options.toolBatchDispatcherClock?.nowMs() ?? Date.now();
5857
+ }
5858
+
5855
5859
  private enqueueToolCall(request: ToolCallRequest): void {
5856
5860
  if (this.toolDispatcherFailure != null) {
5857
5861
  const resolver = this.toolCallResolvers.get(request.callId);
@@ -5864,7 +5868,10 @@ export class PlayContextImpl {
5864
5868
  this.toolCallQueue.push(request);
5865
5869
  const batchKey = this.toolBatchCoalesceKey(request);
5866
5870
  if (batchKey && !this.toolBatchQueuedAtByKey.has(batchKey)) {
5867
- this.toolBatchQueuedAtByKey.set(batchKey, Date.now());
5871
+ this.toolBatchQueuedAtByKey.set(
5872
+ batchKey,
5873
+ this.toolBatchDispatcherNowMs(),
5874
+ );
5868
5875
  }
5869
5876
  this.wakeToolDispatcher();
5870
5877
  }
@@ -5941,15 +5948,26 @@ export class PlayContextImpl {
5941
5948
 
5942
5949
  private waitForToolDispatcherWake(deadlineMs: number | null): Promise<void> {
5943
5950
  return new Promise((resolve) => {
5944
- let timer: ReturnType<typeof setTimeout> | null = null;
5951
+ let cancelDeadline: (() => void) | null = null;
5945
5952
  const wake = () => {
5946
- if (timer) clearTimeout(timer);
5953
+ cancelDeadline?.();
5954
+ cancelDeadline = null;
5947
5955
  this.toolDispatcherWakeWaiters.delete(wake);
5948
5956
  resolve();
5949
5957
  };
5950
5958
  this.toolDispatcherWakeWaiters.add(wake);
5951
5959
  if (deadlineMs != null) {
5952
- timer = setTimeout(wake, Math.max(0, deadlineMs - Date.now()));
5960
+ const delayMs = Math.max(
5961
+ 0,
5962
+ deadlineMs - this.toolBatchDispatcherNowMs(),
5963
+ );
5964
+ const clock = this.#options.toolBatchDispatcherClock;
5965
+ if (clock) {
5966
+ cancelDeadline = clock.schedule(delayMs, wake);
5967
+ } else {
5968
+ const timer = setTimeout(wake, delayMs);
5969
+ cancelDeadline = () => clearTimeout(timer);
5970
+ }
5953
5971
  }
5954
5972
  });
5955
5973
  }
@@ -5977,7 +5995,9 @@ export class PlayContextImpl {
5977
5995
  throw this.toolDispatcherFailure;
5978
5996
  }
5979
5997
 
5980
- const dispatchable = this.takeDispatchableToolCalls(Date.now());
5998
+ const dispatchable = this.takeDispatchableToolCalls(
5999
+ this.toolBatchDispatcherNowMs(),
6000
+ );
5981
6001
  if (dispatchable.requests.length > 0) {
5982
6002
  pass += 1;
5983
6003
  this.log(` Batch pass ${pass}`);
@@ -6688,8 +6708,8 @@ export class PlayContextImpl {
6688
6708
  rowScopeKey ? `@${rowScopeKey}` : ''
6689
6709
  }`;
6690
6710
  if (rowScope) {
6691
- const namespaces =
6692
- rowScope.inlineChildInvocationNamespaces ??= new Set<string>();
6711
+ const namespaces = (rowScope.inlineChildInvocationNamespaces ??=
6712
+ new Set<string>());
6693
6713
  namespaces.add(namespace);
6694
6714
  if (namespaces.size > MAX_INLINE_CHILD_INVOCATIONS_PER_ROW) {
6695
6715
  throw new Error(
@@ -680,6 +680,14 @@ export interface ContextOptions {
680
680
  getBatchOperationStrategy?: (
681
681
  operation: string,
682
682
  ) => AnyBatchOperationStrategy | null;
683
+ /**
684
+ * Internal deterministic clock for fixed-window dispatcher tests. Runtime
685
+ * adapters omit this and use the system clock.
686
+ */
687
+ toolBatchDispatcherClock?: {
688
+ nowMs: () => number;
689
+ schedule: (delayMs: number, wake: () => void) => () => void;
690
+ };
683
691
  // How long batchable calls wait for same-key row continuations before
684
692
  // dispatching, measured from the first queued item. Defaults to 5ms in
685
693
  // production; tests override it to a large value to make coalescing
@@ -1055,7 +1055,12 @@ export function reducePlayRunLedgerEvent(
1055
1055
  if (ignoreNonTerminalAfterStickyTerminal) {
1056
1056
  return withTiming(base);
1057
1057
  }
1058
- const createdStatus = event.status ?? base.status;
1058
+ // Scheduler wait/resume events own the active lifecycle once a run is
1059
+ // parked. Worker registration can arrive later through an independent
1060
+ // ledger append lane; replaying that older `run.created` fact must not
1061
+ // make an externally-waiting run look active again.
1062
+ const createdStatus =
1063
+ base.status === 'waiting' ? 'waiting' : (event.status ?? base.status);
1059
1064
  return withTiming({
1060
1065
  ...base,
1061
1066
  playName: event.playName ?? base.playName ?? null,
@@ -1075,7 +1080,9 @@ export function reducePlayRunLedgerEvent(
1075
1080
  playName: event.playName ?? base.playName ?? null,
1076
1081
  status: isTerminalPlayRunLedgerStatus(base.status)
1077
1082
  ? base.status
1078
- : 'running',
1083
+ : base.status === 'waiting'
1084
+ ? 'waiting'
1085
+ : 'running',
1079
1086
  startedAt: base.startedAt ?? occurredAt,
1080
1087
  });
1081
1088
  case 'run.waiting':
@@ -1200,7 +1207,9 @@ export function reducePlayRunLedgerEvent(
1200
1207
  ...base,
1201
1208
  status: isTerminalPlayRunLedgerStatus(base.status)
1202
1209
  ? base.status
1203
- : 'running',
1210
+ : base.status === 'waiting'
1211
+ ? 'waiting'
1212
+ : 'running',
1204
1213
  startedAt: base.startedAt ?? occurredAt,
1205
1214
  orderedStepIds: appendOrderedStepId(base, event.stepId),
1206
1215
  stepsById: { ...base.stepsById, [event.stepId]: nextStep },
@@ -32,6 +32,7 @@ export type StagedDaytonaPayload = {
32
32
  workDir: string;
33
33
  command: string;
34
34
  readyPath: string;
35
+ startAckPath: string;
35
36
  outputPath: string;
36
37
  exitCodePath: string;
37
38
  progressEventPath: string;
@@ -389,6 +390,7 @@ export async function stageDaytonaRunnerPayload(input: {
389
390
  const outputPath = `${workDir}/deepline-play-output-${randomUUID()}.log`;
390
391
  const exitCodePath = `${workDir}/deepline-play-exit-${randomUUID()}.txt`;
391
392
  const readyPath = `${workDir}/deepline-play-ready-${randomUUID()}.json`;
393
+ const startAckPath = `${workDir}/deepline-play-start-ack-${randomUUID()}.json`;
392
394
  const progressEventPath = `${workDir}/deepline-play-progress-${randomUUID()}.jsonl`;
393
395
  const runnerTraceEnv =
394
396
  process.env.DEEPLINE_RUNTIME_RECEIPT_TRACE === '1'
@@ -401,18 +403,19 @@ export async function stageDaytonaRunnerPayload(input: {
401
403
  artifactCodePath,
402
404
  artifactSourceMapPath,
403
405
  crashPusherPath,
404
- })} && DEEPLINE_PLAY_RUNNER_READY_PATH=${shellQuote(readyPath)} DEEPLINE_PLAY_RUNNER_PROGRESS_EVENT_PATH=${shellQuote(progressEventPath)} DEEPLINE_PLAY_RUNNER_STDOUT_EVENT_MODE=compact ${runnerTraceEnv}node ${shellQuote(runnerPath)} ${shellQuote(configPath)}`;
406
+ })} && DEEPLINE_PLAY_RUNNER_READY_PATH=${shellQuote(readyPath)} DEEPLINE_PLAY_RUNNER_START_ACK_PATH=${shellQuote(startAckPath)} DEEPLINE_PLAY_RUNNER_PROGRESS_EVENT_PATH=${shellQuote(progressEventPath)} DEEPLINE_PLAY_RUNNER_STDOUT_EVENT_MODE=compact ${runnerTraceEnv}node ${shellQuote(runnerPath)} ${shellQuote(configPath)}`;
405
407
  // Crash-containment epilogue: runs UNCONDITIONALLY after the runner exits and
406
408
  // pushes the parsed (or synthesized) terminal to the gateway so the parked
407
409
  // worker wakes within seconds of ANY runner death — process.exit abuse, OOM
408
410
  // SIGKILL, or a crash before the in-process push. Idempotent against a
409
411
  // successful in-runner push (terminals are first-write-wins).
410
- const command = `rm -f ${shellQuote(outputPath)} ${shellQuote(exitCodePath)} ${shellQuote(readyPath)} ${shellQuote(progressEventPath)} ${shellQuote(`${progressEventPath}.*`)}; ( ${runnerCommand} ) > ${shellQuote(outputPath)} 2>&1; code=$?; printf '%s' "$code" > ${shellQuote(exitCodePath)}; node ${shellQuote(crashPusherPath)} ${shellQuote(configPath)} "$code" ${shellQuote(outputPath)} >> ${shellQuote(outputPath)} 2>&1 || true; printf 'deepline runner output captured: %s\\n' ${shellQuote(outputPath)}; exit "$code"`;
412
+ const command = `rm -f ${shellQuote(outputPath)} ${shellQuote(exitCodePath)} ${shellQuote(readyPath)} ${shellQuote(startAckPath)} ${shellQuote(progressEventPath)} ${shellQuote(`${progressEventPath}.*`)}; ( ${runnerCommand} ) > ${shellQuote(outputPath)} 2>&1; code=$?; printf '%s' "$code" > ${shellQuote(exitCodePath)}; node ${shellQuote(crashPusherPath)} ${shellQuote(configPath)} "$code" ${shellQuote(outputPath)} >> ${shellQuote(outputPath)} 2>&1 || true; printf 'deepline runner output captured: %s\\n' ${shellQuote(outputPath)}; exit "$code"`;
411
413
 
412
414
  return {
413
415
  workDir: input.workDir,
414
416
  command,
415
417
  readyPath,
418
+ startAckPath,
416
419
  outputPath,
417
420
  exitCodePath,
418
421
  progressEventPath,
@@ -1,6 +1,8 @@
1
1
  import type { DaytonaSandbox } from './daytona-lifecycle';
2
2
 
3
3
  export type DetachedDaytonaStartupDiagnostic = {
4
+ readyMarkerObserved: boolean;
5
+ startAcknowledgementAttempted: boolean;
4
6
  commandFound: boolean;
5
7
  exitCode: number | null;
6
8
  sessionFound: boolean;
@@ -51,6 +53,8 @@ export async function inspectDetachedDaytonaStartup(input: {
51
53
  }): Promise<DetachedDaytonaStartupDiagnostic> {
52
54
  const errors: string[] = [];
53
55
  const diagnostic: DetachedDaytonaStartupDiagnostic = {
56
+ readyMarkerObserved: false,
57
+ startAcknowledgementAttempted: false,
54
58
  commandFound: false,
55
59
  exitCode: null,
56
60
  sessionFound: false,
@@ -119,7 +123,10 @@ export const DAYTONA_RUNNER_READY_TIMEOUT_MS = 60_000;
119
123
  const DAYTONA_RUNNER_READY_POLL_MS = 250;
120
124
 
121
125
  export class DaytonaRunnerInitializationError extends Error {
122
- constructor(message: string) {
126
+ constructor(
127
+ message: string,
128
+ readonly startupDiagnostic: DetachedDaytonaStartupDiagnostic,
129
+ ) {
123
130
  super(message);
124
131
  this.name = 'DaytonaRunnerInitializationError';
125
132
  }
@@ -163,6 +170,7 @@ export async function confirmDetachedDaytonaRunnerReady(input: {
163
170
  sessionId: string;
164
171
  cmdId: string;
165
172
  readyPath: string;
173
+ startAckPath: string;
166
174
  exitCodePath: string;
167
175
  timeoutMs?: number;
168
176
  pollMs?: number;
@@ -180,6 +188,8 @@ export async function confirmDetachedDaytonaRunnerReady(input: {
180
188
  const pollMs = Math.max(1, input.pollMs ?? DAYTONA_RUNNER_READY_POLL_MS);
181
189
  const deadline = now() + timeoutMs;
182
190
  let markerDiagnosis = 'ready_marker_unavailable';
191
+ let readyMarkerObserved = false;
192
+ let startAcknowledgementAttempted = false;
183
193
 
184
194
  while (now() < deadline) {
185
195
  try {
@@ -187,8 +197,21 @@ export async function confirmDetachedDaytonaRunnerReady(input: {
187
197
  const parsed = JSON.parse(marker.toString('utf-8')) as {
188
198
  ready?: unknown;
189
199
  };
190
- if (parsed.ready === true) return;
191
- markerDiagnosis = 'ready_marker_invalid';
200
+ if (parsed.ready === true) {
201
+ readyMarkerObserved = true;
202
+ startAcknowledgementAttempted = true;
203
+ try {
204
+ await input.sandbox.fs.uploadFile(
205
+ Buffer.from('{"start":true}', 'utf-8'),
206
+ input.startAckPath,
207
+ );
208
+ return;
209
+ } catch {
210
+ markerDiagnosis = 'start_ack_unavailable';
211
+ }
212
+ } else {
213
+ markerDiagnosis = 'ready_marker_invalid';
214
+ }
192
215
  } catch {
193
216
  markerDiagnosis = 'ready_marker_unavailable';
194
217
  }
@@ -201,9 +224,12 @@ export async function confirmDetachedDaytonaRunnerReady(input: {
201
224
  cmdId: input.cmdId,
202
225
  exitCodePath: input.exitCodePath,
203
226
  });
227
+ diagnostic.readyMarkerObserved = readyMarkerObserved;
228
+ diagnostic.startAcknowledgementAttempted = startAcknowledgementAttempted;
204
229
  const oomIndicated =
205
230
  diagnostic.exitCode === 137 || diagnostic.exitCodeFile === 137;
206
231
  throw new DaytonaRunnerInitializationError(
207
- `RUNTIME_SANDBOX_START_FAILED: Daytona accepted detached command ${input.cmdId} in sandbox ${input.sandbox.id}, but the play runner did not materialize and establish receipt-gateway liveness within ${timeoutMs}ms. Marker diagnosis: ${markerDiagnosis}. Daytona diagnosis: ${JSON.stringify(diagnostic)}. OOM is ${oomIndicated ? 'indicated by exit code 137' : 'not confirmed by the available Daytona evidence'}. The play was stopped and was not retried.`,
232
+ `RUNTIME_SANDBOX_START_FAILED: Daytona accepted detached command ${input.cmdId} in sandbox ${input.sandbox.id}, but the play runner did not materialize and establish receipt-gateway liveness within ${timeoutMs}ms. Marker diagnosis: ${markerDiagnosis}. Daytona diagnosis: ${JSON.stringify(diagnostic)}. OOM is ${oomIndicated ? 'indicated by exit code 137' : 'not confirmed by the available Daytona evidence'}. The worker will not park this runner; the failed runner was stopped and was not retried in place.`,
233
+ diagnostic,
208
234
  );
209
235
  }
@@ -474,6 +474,27 @@ function isRetryableDaytonaInfrastructureFailure(error: unknown): boolean {
474
474
  return DAYTONA_INFRASTRUCTURE_RETRY_PATTERN.test(message);
475
475
  }
476
476
 
477
+ function isRetryableUnstartedDaytonaRunner(
478
+ error: unknown,
479
+ ): error is DaytonaRunnerInitializationError {
480
+ if (!(error instanceof DaytonaRunnerInitializationError)) return false;
481
+ const diagnostic = error.startupDiagnostic;
482
+ // The ready fence runs before any customer play code. If Daytona accepted
483
+ // the command but the worker never observed its ready marker, the runner
484
+ // cannot pass the reverse start-ack fence and customer code cannot begin.
485
+ // Terminate that sandbox and retry once on a fresh one even when its Node
486
+ // process is still alive. Do not retry after a start acknowledgement was
487
+ // attempted (its write outcome may be ambiguous), or after a runner exit.
488
+ return (
489
+ diagnostic.commandFound &&
490
+ diagnostic.sessionFound &&
491
+ diagnostic.exitCode === null &&
492
+ diagnostic.exitCodeFile === null &&
493
+ !diagnostic.readyMarkerObserved &&
494
+ !diagnostic.startAcknowledgementAttempted
495
+ );
496
+ }
497
+
477
498
  function prepareDaytonaExecution(
478
499
  input: PlayRunnerPrepareInput,
479
500
  callbacks: Parameters<PlayRunnerBackend['execute']>[1],
@@ -819,6 +840,7 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
819
840
  sessionId: start.sessionId,
820
841
  cmdId: start.cmdId,
821
842
  readyPath: stagedPayload.readyPath,
843
+ startAckPath: stagedPayload.startAckPath,
822
844
  exitCodePath: stagedPayload.exitCodePath,
823
845
  }),
824
846
  cancellationPromise,
@@ -868,6 +890,25 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
868
890
  onCancel();
869
891
  throw new Error(DAYTONA_CANCELLED_ERROR);
870
892
  }
893
+ if (
894
+ executionAttempt < DAYTONA_RUNTIME_INITIALIZATION_MAX_ATTEMPTS &&
895
+ isRetryableUnstartedDaytonaRunner(error)
896
+ ) {
897
+ emitDaytonaStage(callbacks, config.context, 'execute:retry', {
898
+ sandboxId: sandboxForAttempt?.id ?? null,
899
+ attempt: executionAttempt + 1,
900
+ reason: 'runner_startup_not_live',
901
+ error: error.message,
902
+ elapsedMs: Date.now() - startedAt,
903
+ });
904
+ if (sandboxForAttempt) {
905
+ activeAcquiredResource!.billingEndedAt = Date.now();
906
+ await reportRetiringRuntimeResource(activeAcquiredResource!);
907
+ sandboxCleanup.stashActiveSandboxForRetry();
908
+ }
909
+ acquiredSandboxPromise = sandboxLifecycle.createFreshSandbox();
910
+ continue;
911
+ }
871
912
  if (
872
913
  executionAttempt < DAYTONA_INFRASTRUCTURE_MAX_ATTEMPTS &&
873
914
  isRetryableDaytonaInfrastructureFailure(error)
@@ -2,7 +2,11 @@ export const SECRET_REDACTION_PLACEHOLDER = '[REDACTED_SECRET]';
2
2
 
3
3
  const BEARER_TOKEN_RE = /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}\b/g;
4
4
  const BASIC_CREDENTIAL_RE = /\bBasic\s+[A-Za-z0-9+/]{4,}={0,2}\b/g;
5
- const JWT_RE = /\b[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{1,}\.[A-Za-z0-9_-]{8,}\b/g;
5
+ // Bound each segment so V8 cannot recursively backtrack across a multi-megabyte
6
+ // opaque receipt value that contains no dots. Real executor/provider JWT
7
+ // segments are far smaller; 16 KiB per segment still leaves ample headroom.
8
+ const JWT_RE =
9
+ /\b[A-Za-z0-9_-]{8,16384}\.[A-Za-z0-9_-]{1,16384}\.[A-Za-z0-9_-]{8,16384}\b/g;
6
10
  const PRIVATE_KEY_RE =
7
11
  /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g;
8
12
  const SENSITIVE_FIELD_NAME =
@@ -42,12 +42,18 @@ export function bundledCodeHasCloudflareSchemeImport(
42
42
  export class PlayArtifactKindMismatchError extends Error {
43
43
  readonly expectedKind: PlayArtifactKind;
44
44
  readonly actualKind: PlayArtifactKind;
45
- readonly reason: 'kind_mismatch' | 'cloudflare_scheme_import';
45
+ readonly reason:
46
+ | 'kind_mismatch'
47
+ | 'code_format_mismatch'
48
+ | 'cloudflare_scheme_import';
46
49
 
47
50
  constructor(input: {
48
51
  expectedKind: PlayArtifactKind;
49
52
  actualKind: PlayArtifactKind;
50
- reason: 'kind_mismatch' | 'cloudflare_scheme_import';
53
+ reason:
54
+ | 'kind_mismatch'
55
+ | 'code_format_mismatch'
56
+ | 'cloudflare_scheme_import';
51
57
  message: string;
52
58
  }) {
53
59
  super(input.message);
@@ -96,6 +102,19 @@ export function assertNodeRunnableArtifact(input: {
96
102
  });
97
103
  }
98
104
 
105
+ if (artifact.codeFormat !== 'cjs_module') {
106
+ throw new PlayArtifactKindMismatchError({
107
+ expectedKind,
108
+ actualKind,
109
+ reason: 'code_format_mismatch',
110
+ message:
111
+ `Node runner (${context}) received a "${actualKind}" play artifact ` +
112
+ `with codeFormat "${artifact.codeFormat}" but requires "cjs_module". ` +
113
+ `The artifact metadata is inconsistent; rebuild it for the node runner ` +
114
+ `before launch.`,
115
+ });
116
+ }
117
+
99
118
  if (bundledCodeHasCloudflareSchemeImport(artifact.bundledCode)) {
100
119
  throw new PlayArtifactKindMismatchError({
101
120
  expectedKind,
@@ -440,6 +440,17 @@ function findSourceImportReferences(
440
440
  );
441
441
  }
442
442
 
443
+ /**
444
+ * Reports local runtime edges using the same parser that drives graph analysis.
445
+ * Migration code uses this to distinguish truly single-file source from stored
446
+ * graph metadata without maintaining a second import parser.
447
+ */
448
+ export function countLocalRuntimeImportReferences(sourceCode: string): number {
449
+ return findSourceImportReferences(sourceCode).filter((reference) =>
450
+ isLocalSpecifier(reference.specifier),
451
+ ).length;
452
+ }
453
+
443
454
  function findMatchingBrace(source: string, openIndex: number): number {
444
455
  const structuralSource = maskSourceForStructure(source);
445
456
  let depth = 0;
@@ -6,6 +6,10 @@ const BEARER_LITERAL_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/=-]{16,}/i;
6
6
  const ASSIGNMENT_SECRET_LITERAL_PATTERN =
7
7
  /\b(?:api[_-]?key|token|secret|password)\b\s*[:=]\s*['"][^'"]{12,}['"]/i;
8
8
  const HIGH_ENTROPY_LITERAL_PATTERN = /['"]([A-Za-z0-9+/=_-]{32,})['"]/g;
9
+ const UUID_IDENTIFIER_PATTERN =
10
+ /^((?:[A-Za-z0-9]+[-_])*)?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
11
+ const SECRET_LABEL_PATTERN =
12
+ /(?:^|[-_])(?:api|auth|access|secret|token|key|password|credential|bearer|sk|pk|live)(?:[-_]|$)/i;
9
13
 
10
14
  function shannonEntropy(value: string): number {
11
15
  const counts = new Map<string, number>();
@@ -16,6 +20,13 @@ function shannonEntropy(value: string): number {
16
20
  }, 0);
17
21
  }
18
22
 
23
+ function isNonSecretUuidIdentifier(value: string): boolean {
24
+ const match = UUID_IDENTIFIER_PATTERN.exec(value);
25
+ if (!match) return false;
26
+ const label = match[1] ?? '';
27
+ return !SECRET_LABEL_PATTERN.test(label);
28
+ }
29
+
19
30
  /**
20
31
  * Returns the inline-secret findings in a string (empty if none). The throwing
21
32
  * validator below and the workflows→plays migration validator both call this so
@@ -35,6 +46,9 @@ export function collectInlineSecretFindings(sourceCode: string): string[] {
35
46
  }
36
47
  for (const match of sourceCode.matchAll(HIGH_ENTROPY_LITERAL_PATTERN)) {
37
48
  const literal = match[1] ?? '';
49
+ // UUID-bearing resource names are structured identifiers, not opaque
50
+ // credentials. Keep secret-looking labels on the conservative path.
51
+ if (isNonSecretUuidIdentifier(literal)) continue;
38
52
  if (literal.length >= 40 && shannonEntropy(literal) >= 4.2) {
39
53
  findings.push('high-entropy string literal');
40
54
  break;
@@ -0,0 +1,239 @@
1
+ import ts from 'typescript';
2
+
3
+ type SourceMetadata = {
4
+ name: string | null;
5
+ description: string | null;
6
+ };
7
+
8
+ type SourceContext = {
9
+ declarations: Map<string, ts.Expression>;
10
+ namedExports: Map<string, string>;
11
+ defaultExport: ts.Expression | null;
12
+ };
13
+
14
+ function unwrap(expression: ts.Expression | null): ts.Expression | null {
15
+ let current = expression;
16
+ while (current) {
17
+ if (
18
+ ts.isAsExpression(current) ||
19
+ ts.isSatisfiesExpression(current) ||
20
+ ts.isTypeAssertionExpression(current) ||
21
+ ts.isNonNullExpression(current) ||
22
+ ts.isParenthesizedExpression(current)
23
+ ) {
24
+ current = current.expression;
25
+ continue;
26
+ }
27
+ break;
28
+ }
29
+ return current;
30
+ }
31
+
32
+ function buildContext(sourceFile: ts.SourceFile): SourceContext {
33
+ const declarations = new Map<string, ts.Expression>();
34
+ const namedExports = new Map<string, string>();
35
+ let defaultExport: ts.Expression | null = null;
36
+
37
+ for (const statement of sourceFile.statements) {
38
+ if (ts.isVariableStatement(statement)) {
39
+ const exported = statement.modifiers?.some(
40
+ (modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword,
41
+ );
42
+ const immutable = Boolean(
43
+ statement.declarationList.flags & ts.NodeFlags.Const,
44
+ );
45
+ for (const declaration of statement.declarationList.declarations) {
46
+ if (!ts.isIdentifier(declaration.name) || !declaration.initializer)
47
+ continue;
48
+ if (immutable) {
49
+ declarations.set(declaration.name.text, declaration.initializer);
50
+ }
51
+ if (exported) {
52
+ namedExports.set(declaration.name.text, declaration.name.text);
53
+ }
54
+ }
55
+ continue;
56
+ }
57
+ if (ts.isExportAssignment(statement) && !statement.isExportEquals) {
58
+ defaultExport = statement.expression;
59
+ continue;
60
+ }
61
+ if (!ts.isExportDeclaration(statement) || !statement.exportClause) {
62
+ continue;
63
+ }
64
+ if (ts.isNamedExports(statement.exportClause)) {
65
+ for (const element of statement.exportClause.elements) {
66
+ namedExports.set(
67
+ element.name.text,
68
+ element.propertyName?.text ?? element.name.text,
69
+ );
70
+ }
71
+ }
72
+ }
73
+ return { declarations, namedExports, defaultExport };
74
+ }
75
+
76
+ function resolveExpression(
77
+ expression: ts.Expression | null,
78
+ context: SourceContext,
79
+ seen = new Set<string>(),
80
+ ): ts.Expression | null {
81
+ const unwrapped = unwrap(expression);
82
+ if (!unwrapped || !ts.isIdentifier(unwrapped)) return unwrapped;
83
+ if (seen.has(unwrapped.text)) return null;
84
+ seen.add(unwrapped.text);
85
+ return resolveExpression(
86
+ context.declarations.get(unwrapped.text) ?? null,
87
+ context,
88
+ seen,
89
+ );
90
+ }
91
+
92
+ function staticString(
93
+ expression: ts.Expression | undefined,
94
+ context: SourceContext,
95
+ seen = new Set<string>(),
96
+ trimResult = true,
97
+ ): string | null {
98
+ const resolved = unwrap(expression ?? null);
99
+ if (
100
+ resolved &&
101
+ (ts.isStringLiteral(resolved) ||
102
+ ts.isNoSubstitutionTemplateLiteral(resolved))
103
+ ) {
104
+ if (!resolved.text.trim()) return null;
105
+ return trimResult ? resolved.text.trim() : resolved.text;
106
+ }
107
+ if (
108
+ resolved &&
109
+ ts.isBinaryExpression(resolved) &&
110
+ resolved.operatorToken.kind === ts.SyntaxKind.PlusToken
111
+ ) {
112
+ const left = staticString(resolved.left, context, new Set(seen), false);
113
+ const right = staticString(resolved.right, context, new Set(seen), false);
114
+ const value = left !== null && right !== null ? `${left}${right}` : null;
115
+ if (!value?.trim()) return null;
116
+ return trimResult ? value.trim() : value;
117
+ }
118
+ if (!resolved || !ts.isIdentifier(resolved) || seen.has(resolved.text)) {
119
+ return null;
120
+ }
121
+ seen.add(resolved.text);
122
+ return staticString(
123
+ context.declarations.get(resolved.text),
124
+ context,
125
+ seen,
126
+ trimResult,
127
+ );
128
+ }
129
+
130
+ function propertyName(name: ts.PropertyName): string | null {
131
+ return ts.isIdentifier(name) ||
132
+ ts.isStringLiteral(name) ||
133
+ ts.isNumericLiteral(name)
134
+ ? name.text
135
+ : null;
136
+ }
137
+
138
+ function objectStringProperty(
139
+ expression: ts.Expression | undefined,
140
+ key: string,
141
+ context: SourceContext,
142
+ ): string | null {
143
+ const resolved = resolveExpression(expression ?? null, context);
144
+ if (!resolved || !ts.isObjectLiteralExpression(resolved)) return null;
145
+ for (const property of resolved.properties) {
146
+ if (
147
+ ts.isPropertyAssignment(property) &&
148
+ propertyName(property.name) === key
149
+ ) {
150
+ return staticString(property.initializer, context);
151
+ }
152
+ if (
153
+ ts.isShorthandPropertyAssignment(property) &&
154
+ property.name.text === key
155
+ ) {
156
+ return staticString(property.name, context);
157
+ }
158
+ }
159
+ return null;
160
+ }
161
+
162
+ function isDefinePlayCall(call: ts.CallExpression): boolean {
163
+ const callee = unwrap(call.expression);
164
+ if (callee && ts.isIdentifier(callee)) {
165
+ return callee.text === 'definePlay' || callee.text === 'defineWorkflow';
166
+ }
167
+ return Boolean(
168
+ callee &&
169
+ ts.isPropertyAccessExpression(callee) &&
170
+ (callee.name.text === 'definePlay' ||
171
+ callee.name.text === 'defineWorkflow'),
172
+ );
173
+ }
174
+
175
+ function metadataFromExpression(
176
+ expression: ts.Expression | null,
177
+ context: SourceContext,
178
+ ): SourceMetadata | null {
179
+ const resolved = resolveExpression(expression, context);
180
+ if (
181
+ !resolved ||
182
+ !ts.isCallExpression(resolved) ||
183
+ !isDefinePlayCall(resolved)
184
+ ) {
185
+ return null;
186
+ }
187
+ const [first] = resolved.arguments;
188
+ const name =
189
+ objectStringProperty(first, 'id', context) ?? staticString(first, context);
190
+ let description = objectStringProperty(first, 'description', context);
191
+ for (
192
+ let index = resolved.arguments.length - 1;
193
+ !description && index >= 2;
194
+ index -= 1
195
+ ) {
196
+ description = objectStringProperty(
197
+ resolved.arguments[index],
198
+ 'description',
199
+ context,
200
+ );
201
+ }
202
+ return name || description ? { name, description } : null;
203
+ }
204
+
205
+ function extractMetadata(sourceCode: string, exportName: string | null) {
206
+ const sourceFile = ts.createSourceFile(
207
+ 'play.ts',
208
+ sourceCode,
209
+ ts.ScriptTarget.Latest,
210
+ true,
211
+ ts.ScriptKind.TS,
212
+ );
213
+ const context = buildContext(sourceFile);
214
+ const exportedExpression = exportName
215
+ ? exportName === 'default'
216
+ ? context.defaultExport
217
+ : (context.declarations.get(
218
+ context.namedExports.get(exportName) ?? exportName,
219
+ ) ?? null)
220
+ : context.defaultExport;
221
+ const exported = metadataFromExpression(exportedExpression, context);
222
+ if (exported || exportName) return exported;
223
+ for (const expression of context.declarations.values()) {
224
+ const metadata = metadataFromExpression(expression, context);
225
+ if (metadata) return metadata;
226
+ }
227
+ return null;
228
+ }
229
+
230
+ export function extractDefinedPlayName(sourceCode: string): string | null {
231
+ return extractMetadata(sourceCode, null)?.name ?? null;
232
+ }
233
+
234
+ export function extractDefinedPlayDescriptionForExport(
235
+ sourceCode: string,
236
+ exportName: string,
237
+ ): string | null {
238
+ return extractMetadata(sourceCode, exportName)?.description ?? null;
239
+ }
package/dist/cli/index.js CHANGED
@@ -627,10 +627,13 @@ var SDK_RELEASE = {
627
627
  // silently materializing them as unmatched results.
628
628
  // 0.1.220 deprecates SDK CLI versions below 0.1.219 and updates them
629
629
  // automatically without blocking their current command.
630
- version: "0.1.239",
631
- apiContract: "2026-06-dataset-handle-results-hard-cutover",
630
+ // 0.1.241 hard-cuts published and launched plays to immutable cjs_node20
631
+ // artifacts. Older clients can submit esm_workers artifacts that the Absurd
632
+ // runtime intentionally no longer compiles at publish or launch time.
633
+ version: "0.1.241",
634
+ apiContract: "2026-07-immutable-cjs-play-artifacts-hard-cutover",
632
635
  supportPolicy: {
633
- latest: "0.1.239",
636
+ latest: "0.1.241",
634
637
  minimumSupported: "0.1.53",
635
638
  deprecatedBelow: "0.1.219",
636
639
  commandMinimumSupported: [
@@ -1371,7 +1374,7 @@ function normalizePlayRunLedgerTerminalSource(value) {
1371
1374
  var SECRET_REDACTION_PLACEHOLDER = "[REDACTED_SECRET]";
1372
1375
  var BEARER_TOKEN_RE = /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}\b/g;
1373
1376
  var BASIC_CREDENTIAL_RE = /\bBasic\s+[A-Za-z0-9+/]{4,}={0,2}\b/g;
1374
- var JWT_RE = /\b[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{1,}\.[A-Za-z0-9_-]{8,}\b/g;
1377
+ var JWT_RE = /\b[A-Za-z0-9_-]{8,16384}\.[A-Za-z0-9_-]{1,16384}\.[A-Za-z0-9_-]{8,16384}\b/g;
1375
1378
  var PRIVATE_KEY_RE = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g;
1376
1379
  var SENSITIVE_FIELD_NAME = "authorization|cookie|password|(?:client[_-]?)?secret|(?:access|refresh)[_-]?token|(?:x[_-]?)?api[_-]?key|private[_-]?key|token|credentials";
1377
1380
  var COMMON_SECRET_RE = /\b(?:[prs]k_(?:live|test)_|(?:pat|ghp|github_pat)_|xox[baprs]-)[A-Za-z0-9_./+=:-]{12,}\b/gi;
@@ -28040,6 +28043,8 @@ var PRIVATE_KEY_PATTERN = /-----BEGIN (?:RSA |EC |OPENSSH |PGP )?PRIVATE KEY----
28040
28043
  var BEARER_LITERAL_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/=-]{16,}/i;
28041
28044
  var ASSIGNMENT_SECRET_LITERAL_PATTERN = /\b(?:api[_-]?key|token|secret|password)\b\s*[:=]\s*['"][^'"]{12,}['"]/i;
28042
28045
  var HIGH_ENTROPY_LITERAL_PATTERN = /['"]([A-Za-z0-9+/=_-]{32,})['"]/g;
28046
+ var UUID_IDENTIFIER_PATTERN = /^((?:[A-Za-z0-9]+[-_])*)?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
28047
+ var SECRET_LABEL_PATTERN = /(?:^|[-_])(?:api|auth|access|secret|token|key|password|credential|bearer|sk|pk|live)(?:[-_]|$)/i;
28043
28048
  function shannonEntropy(value) {
28044
28049
  const counts = /* @__PURE__ */ new Map();
28045
28050
  for (const char of value) counts.set(char, (counts.get(char) ?? 0) + 1);
@@ -28048,6 +28053,12 @@ function shannonEntropy(value) {
28048
28053
  return entropy - p * Math.log2(p);
28049
28054
  }, 0);
28050
28055
  }
28056
+ function isNonSecretUuidIdentifier(value) {
28057
+ const match = UUID_IDENTIFIER_PATTERN.exec(value);
28058
+ if (!match) return false;
28059
+ const label = match[1] ?? "";
28060
+ return !SECRET_LABEL_PATTERN.test(label);
28061
+ }
28051
28062
  function collectInlineSecretFindings(sourceCode) {
28052
28063
  const findings = [];
28053
28064
  for (const match of sourceCode.matchAll(SECRET_ENV_PATTERN)) {
@@ -28061,6 +28072,7 @@ function collectInlineSecretFindings(sourceCode) {
28061
28072
  }
28062
28073
  for (const match of sourceCode.matchAll(HIGH_ENTROPY_LITERAL_PATTERN)) {
28063
28074
  const literal = match[1] ?? "";
28075
+ if (isNonSecretUuidIdentifier(literal)) continue;
28064
28076
  if (literal.length >= 40 && shannonEntropy(literal) >= 4.2) {
28065
28077
  findings.push("high-entropy string literal");
28066
28078
  break;
@@ -612,10 +612,13 @@ var SDK_RELEASE = {
612
612
  // silently materializing them as unmatched results.
613
613
  // 0.1.220 deprecates SDK CLI versions below 0.1.219 and updates them
614
614
  // automatically without blocking their current command.
615
- version: "0.1.239",
616
- apiContract: "2026-06-dataset-handle-results-hard-cutover",
615
+ // 0.1.241 hard-cuts published and launched plays to immutable cjs_node20
616
+ // artifacts. Older clients can submit esm_workers artifacts that the Absurd
617
+ // runtime intentionally no longer compiles at publish or launch time.
618
+ version: "0.1.241",
619
+ apiContract: "2026-07-immutable-cjs-play-artifacts-hard-cutover",
617
620
  supportPolicy: {
618
- latest: "0.1.239",
621
+ latest: "0.1.241",
619
622
  minimumSupported: "0.1.53",
620
623
  deprecatedBelow: "0.1.219",
621
624
  commandMinimumSupported: [
@@ -1356,7 +1359,7 @@ function normalizePlayRunLedgerTerminalSource(value) {
1356
1359
  var SECRET_REDACTION_PLACEHOLDER = "[REDACTED_SECRET]";
1357
1360
  var BEARER_TOKEN_RE = /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}\b/g;
1358
1361
  var BASIC_CREDENTIAL_RE = /\bBasic\s+[A-Za-z0-9+/]{4,}={0,2}\b/g;
1359
- var JWT_RE = /\b[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{1,}\.[A-Za-z0-9_-]{8,}\b/g;
1362
+ var JWT_RE = /\b[A-Za-z0-9_-]{8,16384}\.[A-Za-z0-9_-]{1,16384}\.[A-Za-z0-9_-]{8,16384}\b/g;
1360
1363
  var PRIVATE_KEY_RE = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g;
1361
1364
  var SENSITIVE_FIELD_NAME = "authorization|cookie|password|(?:client[_-]?)?secret|(?:access|refresh)[_-]?token|(?:x[_-]?)?api[_-]?key|private[_-]?key|token|credentials";
1362
1365
  var COMMON_SECRET_RE = /\b(?:[prs]k_(?:live|test)_|(?:pat|ghp|github_pat)_|xox[baprs]-)[A-Za-z0-9_./+=:-]{12,}\b/gi;
@@ -28088,6 +28091,8 @@ var PRIVATE_KEY_PATTERN = /-----BEGIN (?:RSA |EC |OPENSSH |PGP )?PRIVATE KEY----
28088
28091
  var BEARER_LITERAL_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/=-]{16,}/i;
28089
28092
  var ASSIGNMENT_SECRET_LITERAL_PATTERN = /\b(?:api[_-]?key|token|secret|password)\b\s*[:=]\s*['"][^'"]{12,}['"]/i;
28090
28093
  var HIGH_ENTROPY_LITERAL_PATTERN = /['"]([A-Za-z0-9+/=_-]{32,})['"]/g;
28094
+ var UUID_IDENTIFIER_PATTERN = /^((?:[A-Za-z0-9]+[-_])*)?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
28095
+ var SECRET_LABEL_PATTERN = /(?:^|[-_])(?:api|auth|access|secret|token|key|password|credential|bearer|sk|pk|live)(?:[-_]|$)/i;
28091
28096
  function shannonEntropy(value) {
28092
28097
  const counts = /* @__PURE__ */ new Map();
28093
28098
  for (const char of value) counts.set(char, (counts.get(char) ?? 0) + 1);
@@ -28096,6 +28101,12 @@ function shannonEntropy(value) {
28096
28101
  return entropy - p * Math.log2(p);
28097
28102
  }, 0);
28098
28103
  }
28104
+ function isNonSecretUuidIdentifier(value) {
28105
+ const match = UUID_IDENTIFIER_PATTERN.exec(value);
28106
+ if (!match) return false;
28107
+ const label = match[1] ?? "";
28108
+ return !SECRET_LABEL_PATTERN.test(label);
28109
+ }
28099
28110
  function collectInlineSecretFindings(sourceCode) {
28100
28111
  const findings = [];
28101
28112
  for (const match of sourceCode.matchAll(SECRET_ENV_PATTERN)) {
@@ -28109,6 +28120,7 @@ function collectInlineSecretFindings(sourceCode) {
28109
28120
  }
28110
28121
  for (const match of sourceCode.matchAll(HIGH_ENTROPY_LITERAL_PATTERN)) {
28111
28122
  const literal = match[1] ?? "";
28123
+ if (isNonSecretUuidIdentifier(literal)) continue;
28112
28124
  if (literal.length >= 40 && shannonEntropy(literal) >= 4.2) {
28113
28125
  findings.push("high-entropy string literal");
28114
28126
  break;
package/dist/index.js CHANGED
@@ -426,10 +426,13 @@ var SDK_RELEASE = {
426
426
  // silently materializing them as unmatched results.
427
427
  // 0.1.220 deprecates SDK CLI versions below 0.1.219 and updates them
428
428
  // automatically without blocking their current command.
429
- version: "0.1.239",
430
- apiContract: "2026-06-dataset-handle-results-hard-cutover",
429
+ // 0.1.241 hard-cuts published and launched plays to immutable cjs_node20
430
+ // artifacts. Older clients can submit esm_workers artifacts that the Absurd
431
+ // runtime intentionally no longer compiles at publish or launch time.
432
+ version: "0.1.241",
433
+ apiContract: "2026-07-immutable-cjs-play-artifacts-hard-cutover",
431
434
  supportPolicy: {
432
- latest: "0.1.239",
435
+ latest: "0.1.241",
433
436
  minimumSupported: "0.1.53",
434
437
  deprecatedBelow: "0.1.219",
435
438
  commandMinimumSupported: [
@@ -1170,7 +1173,7 @@ function normalizePlayRunLedgerTerminalSource(value) {
1170
1173
  var SECRET_REDACTION_PLACEHOLDER = "[REDACTED_SECRET]";
1171
1174
  var BEARER_TOKEN_RE = /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}\b/g;
1172
1175
  var BASIC_CREDENTIAL_RE = /\bBasic\s+[A-Za-z0-9+/]{4,}={0,2}\b/g;
1173
- var JWT_RE = /\b[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{1,}\.[A-Za-z0-9_-]{8,}\b/g;
1176
+ var JWT_RE = /\b[A-Za-z0-9_-]{8,16384}\.[A-Za-z0-9_-]{1,16384}\.[A-Za-z0-9_-]{8,16384}\b/g;
1174
1177
  var PRIVATE_KEY_RE = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g;
1175
1178
  var SENSITIVE_FIELD_NAME = "authorization|cookie|password|(?:client[_-]?)?secret|(?:access|refresh)[_-]?token|(?:x[_-]?)?api[_-]?key|private[_-]?key|token|credentials";
1176
1179
  var COMMON_SECRET_RE = /\b(?:[prs]k_(?:live|test)_|(?:pat|ghp|github_pat)_|xox[baprs]-)[A-Za-z0-9_./+=:-]{12,}\b/gi;
package/dist/index.mjs CHANGED
@@ -356,10 +356,13 @@ var SDK_RELEASE = {
356
356
  // silently materializing them as unmatched results.
357
357
  // 0.1.220 deprecates SDK CLI versions below 0.1.219 and updates them
358
358
  // automatically without blocking their current command.
359
- version: "0.1.239",
360
- apiContract: "2026-06-dataset-handle-results-hard-cutover",
359
+ // 0.1.241 hard-cuts published and launched plays to immutable cjs_node20
360
+ // artifacts. Older clients can submit esm_workers artifacts that the Absurd
361
+ // runtime intentionally no longer compiles at publish or launch time.
362
+ version: "0.1.241",
363
+ apiContract: "2026-07-immutable-cjs-play-artifacts-hard-cutover",
361
364
  supportPolicy: {
362
- latest: "0.1.239",
365
+ latest: "0.1.241",
363
366
  minimumSupported: "0.1.53",
364
367
  deprecatedBelow: "0.1.219",
365
368
  commandMinimumSupported: [
@@ -1100,7 +1103,7 @@ function normalizePlayRunLedgerTerminalSource(value) {
1100
1103
  var SECRET_REDACTION_PLACEHOLDER = "[REDACTED_SECRET]";
1101
1104
  var BEARER_TOKEN_RE = /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}\b/g;
1102
1105
  var BASIC_CREDENTIAL_RE = /\bBasic\s+[A-Za-z0-9+/]{4,}={0,2}\b/g;
1103
- var JWT_RE = /\b[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{1,}\.[A-Za-z0-9_-]{8,}\b/g;
1106
+ var JWT_RE = /\b[A-Za-z0-9_-]{8,16384}\.[A-Za-z0-9_-]{1,16384}\.[A-Za-z0-9_-]{8,16384}\b/g;
1104
1107
  var PRIVATE_KEY_RE = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g;
1105
1108
  var SENSITIVE_FIELD_NAME = "authorization|cookie|password|(?:client[_-]?)?secret|(?:access|refresh)[_-]?token|(?:x[_-]?)?api[_-]?key|private[_-]?key|token|credentials";
1106
1109
  var COMMON_SECRET_RE = /\b(?:[prs]k_(?:live|test)_|(?:pat|ghp|github_pat)_|xox[baprs]-)[A-Za-z0-9_./+=:-]{12,}\b/gi;
@@ -80,6 +80,8 @@ var PRIVATE_KEY_PATTERN = /-----BEGIN (?:RSA |EC |OPENSSH |PGP )?PRIVATE KEY----
80
80
  var BEARER_LITERAL_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/=-]{16,}/i;
81
81
  var ASSIGNMENT_SECRET_LITERAL_PATTERN = /\b(?:api[_-]?key|token|secret|password)\b\s*[:=]\s*['"][^'"]{12,}['"]/i;
82
82
  var HIGH_ENTROPY_LITERAL_PATTERN = /['"]([A-Za-z0-9+/=_-]{32,})['"]/g;
83
+ var UUID_IDENTIFIER_PATTERN = /^((?:[A-Za-z0-9]+[-_])*)?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
84
+ var SECRET_LABEL_PATTERN = /(?:^|[-_])(?:api|auth|access|secret|token|key|password|credential|bearer|sk|pk|live)(?:[-_]|$)/i;
83
85
  function shannonEntropy(value) {
84
86
  const counts = /* @__PURE__ */ new Map();
85
87
  for (const char of value) counts.set(char, (counts.get(char) ?? 0) + 1);
@@ -88,6 +90,12 @@ function shannonEntropy(value) {
88
90
  return entropy - p * Math.log2(p);
89
91
  }, 0);
90
92
  }
93
+ function isNonSecretUuidIdentifier(value) {
94
+ const match = UUID_IDENTIFIER_PATTERN.exec(value);
95
+ if (!match) return false;
96
+ const label = match[1] ?? "";
97
+ return !SECRET_LABEL_PATTERN.test(label);
98
+ }
91
99
  function collectInlineSecretFindings(sourceCode) {
92
100
  const findings = [];
93
101
  for (const match of sourceCode.matchAll(SECRET_ENV_PATTERN)) {
@@ -101,6 +109,7 @@ function collectInlineSecretFindings(sourceCode) {
101
109
  }
102
110
  for (const match of sourceCode.matchAll(HIGH_ENTROPY_LITERAL_PATTERN)) {
103
111
  const literal = match[1] ?? "";
112
+ if (isNonSecretUuidIdentifier(literal)) continue;
104
113
  if (literal.length >= 40 && shannonEntropy(literal) >= 4.2) {
105
114
  findings.push("high-entropy string literal");
106
115
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.1.239",
3
+ "version": "0.1.241",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -62,6 +62,6 @@
62
62
  "typescript": "^5.9.3"
63
63
  },
64
64
  "deepline": {
65
- "apiContract": "2026-06-dataset-handle-results-hard-cutover"
65
+ "apiContract": "2026-07-immutable-cjs-play-artifacts-hard-cutover"
66
66
  }
67
67
  }