deepline 0.1.110 → 0.1.111

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.
@@ -720,6 +720,11 @@ export function reducePlayRunLedgerEvent(
720
720
  );
721
721
  case 'step.started': {
722
722
  const current = base.stepsById[event.stepId];
723
+ const shouldRefineSyntheticStart =
724
+ current?.startedAt != null &&
725
+ current.completedAt != null &&
726
+ current.startedAt === current.completedAt &&
727
+ occurredAt < current.startedAt;
723
728
  const nextStep: PlayRunLedgerStepSnapshot = {
724
729
  ...(current ?? { stepId: event.stepId, status: 'running' as const }),
725
730
  stepId: event.stepId,
@@ -733,8 +738,10 @@ export function reducePlayRunLedgerEvent(
733
738
  event.artifactTableNamespace ??
734
739
  current?.artifactTableNamespace ??
735
740
  null,
736
- startedAt: current?.startedAt ?? occurredAt,
737
- updatedAt: occurredAt,
741
+ startedAt: shouldRefineSyntheticStart
742
+ ? occurredAt
743
+ : (current?.startedAt ?? occurredAt),
744
+ updatedAt: Math.max(current?.updatedAt ?? 0, occurredAt),
738
745
  };
739
746
  return withTiming({
740
747
  ...base,
@@ -775,6 +782,25 @@ export function reducePlayRunLedgerEvent(
775
782
  : event.status === 'running' && inferredStatus === 'completed'
776
783
  ? 'completed'
777
784
  : (event.status ?? current?.status ?? inferredStatus);
785
+ const completedAt =
786
+ current?.completedAt ??
787
+ event.progress.completedAt ??
788
+ (status === 'completed'
789
+ ? (event.progress.updatedAt ?? occurredAt)
790
+ : null);
791
+ const shouldRefineSyntheticStart =
792
+ current?.startedAt != null &&
793
+ current.completedAt != null &&
794
+ current.startedAt === current.completedAt &&
795
+ event.progress.startedAt != null &&
796
+ event.progress.startedAt < current.startedAt;
797
+ const startedAt =
798
+ (shouldRefineSyntheticStart ? event.progress.startedAt : null) ??
799
+ current?.startedAt ??
800
+ event.progress.startedAt ??
801
+ (status === 'completed' || status === 'failed'
802
+ ? (completedAt ?? event.progress.updatedAt ?? occurredAt)
803
+ : null);
778
804
  const nextStep: PlayRunLedgerStepSnapshot = {
779
805
  ...(current ?? { stepId: event.stepId, status }),
780
806
  stepId: event.stepId,
@@ -785,13 +811,8 @@ export function reducePlayRunLedgerEvent(
785
811
  progress.artifactTableNamespace ??
786
812
  current?.artifactTableNamespace ??
787
813
  null,
788
- startedAt: current?.startedAt ?? event.progress.startedAt ?? null,
789
- completedAt:
790
- current?.completedAt ??
791
- event.progress.completedAt ??
792
- (status === 'completed'
793
- ? (event.progress.updatedAt ?? occurredAt)
794
- : null),
814
+ startedAt,
815
+ completedAt,
795
816
  updatedAt: occurredAt,
796
817
  progress,
797
818
  };
@@ -826,6 +847,10 @@ export function reducePlayRunLedgerEvent(
826
847
  : event.type === 'step.failed'
827
848
  ? 'failed'
828
849
  : 'skipped';
850
+ const completedAt =
851
+ status === 'skipped' || preserveFailedStatus
852
+ ? (current?.completedAt ?? null)
853
+ : occurredAt;
829
854
  const nextStep: PlayRunLedgerStepSnapshot = {
830
855
  ...(current ?? { stepId: event.stepId, status }),
831
856
  stepId: event.stepId,
@@ -836,11 +861,12 @@ export function reducePlayRunLedgerEvent(
836
861
  event.artifactTableNamespace ??
837
862
  current?.artifactTableNamespace ??
838
863
  null,
839
- startedAt: current?.startedAt ?? null,
840
- completedAt:
841
- status === 'skipped' || preserveFailedStatus
842
- ? (current?.completedAt ?? null)
843
- : occurredAt,
864
+ startedAt:
865
+ current?.startedAt ??
866
+ (status === 'completed' || status === 'failed'
867
+ ? (completedAt ?? occurredAt)
868
+ : null),
869
+ completedAt,
844
870
  updatedAt: occurredAt,
845
871
  progress: current?.progress ?? null,
846
872
  };
@@ -129,6 +129,8 @@ export type PlaySchedulerSubmitInput = {
129
129
  userId?: string | null;
130
130
  source?: 'published' | 'ad_hoc' | 'draft';
131
131
  executionProfile?: string | null;
132
+ /** Durable per-workspace active run cap enforced when the run row is projected. */
133
+ activeRunLimit?: number | null;
132
134
  /** runner backend to use for executing attempts */
133
135
  runtimeBackend: string;
134
136
  /** dedup backend for cross-attempt cross-process idempotency */
@@ -8,6 +8,7 @@ import {
8
8
  extname,
9
9
  isAbsolute,
10
10
  join,
11
+ relative,
11
12
  resolve,
12
13
  } from 'node:path';
13
14
  import { builtinModules } from 'node:module';
@@ -416,6 +417,106 @@ export function extractDefinedPlayName(sourceCode: string): string | null {
416
417
  return null;
417
418
  }
418
419
 
420
+ function canonicalizeRootPlayNameForWorkersRuntimeHash(
421
+ sourceCode: string,
422
+ ): string {
423
+ const source = stripCommentsToSpaces(sourceCode);
424
+ const callPattern =
425
+ /(?:\b[A-Za-z_$][\w$]*\s*\.\s*)?\b(?:definePlay|defineWorkflow)\s*\(/g;
426
+ const match = callPattern.exec(source);
427
+ if (!match) return sourceCode;
428
+
429
+ const openParen = match.index + match[0].length - 1;
430
+ const firstArgStart = openParen + 1;
431
+ const firstNonSpace = source.slice(firstArgStart).search(/\S/);
432
+ if (firstNonSpace < 0) return sourceCode;
433
+
434
+ const argIndex = firstArgStart + firstNonSpace;
435
+ const quote = source[argIndex];
436
+ if (quote === '"' || quote === "'") {
437
+ const literalMatch = source
438
+ .slice(argIndex)
439
+ .match(/^(['"])(?:\\.|(?!\1)[\s\S])*\1/);
440
+ if (!literalMatch) return sourceCode;
441
+ const replacement = `${quote}__deepline_runtime_play_name__${quote}`;
442
+ return `${sourceCode.slice(0, argIndex)}${replacement}${sourceCode.slice(argIndex + literalMatch[0].length)}`;
443
+ }
444
+
445
+ if (quote !== '{') return sourceCode;
446
+ const closeBrace = findMatchingBrace(source, argIndex);
447
+ if (closeBrace < 0) return sourceCode;
448
+ const objectSource = source.slice(argIndex + 1, closeBrace);
449
+ const idMatch = objectSource.match(
450
+ /(^|[,{\s])((?:id|['"]id['"])\s*:\s*)(['"])([\s\S]*?)\3/,
451
+ );
452
+ if (!idMatch || idMatch.index === undefined) return sourceCode;
453
+ const idValueStart =
454
+ argIndex +
455
+ 1 +
456
+ idMatch.index +
457
+ idMatch[1]!.length +
458
+ idMatch[2]!.length +
459
+ idMatch[3]!.length;
460
+ return `${sourceCode.slice(0, idValueStart)}__deepline_runtime_play_name__${sourceCode.slice(idValueStart + idMatch[4]!.length)}`;
461
+ }
462
+
463
+ function workersRuntimeGraphFilePath(input: {
464
+ entryFile: string;
465
+ filePath: string;
466
+ adapter: PlayBundlingAdapter;
467
+ }): string {
468
+ if (input.filePath === input.entryFile) return '<entry>';
469
+ const entryRelative = relative(dirname(input.entryFile), input.filePath);
470
+ if (entryRelative && !entryRelative.startsWith('..')) {
471
+ return entryRelative.replaceAll('\\', '/');
472
+ }
473
+ return `<project>/${relative(input.adapter.projectRoot, input.filePath).replaceAll('\\', '/')}`;
474
+ }
475
+
476
+ function buildWorkersRuntimeGraphHash(input: {
477
+ analysis: SourceGraphAnalysis;
478
+ entryFile: string;
479
+ adapter: PlayBundlingAdapter;
480
+ exportName: string;
481
+ }): string {
482
+ const sourceFiles = Object.entries(input.analysis.sourceFiles)
483
+ .map(([filePath, contents]) => ({
484
+ filePath: workersRuntimeGraphFilePath({
485
+ entryFile: input.entryFile,
486
+ filePath,
487
+ adapter: input.adapter,
488
+ }),
489
+ hash: sha256(
490
+ filePath === input.entryFile
491
+ ? canonicalizeRootPlayNameForWorkersRuntimeHash(contents)
492
+ : contents,
493
+ ),
494
+ }))
495
+ .sort((left, right) => left.filePath.localeCompare(right.filePath));
496
+
497
+ return sha256(
498
+ JSON.stringify({
499
+ entryFile: '<entry>',
500
+ entryExport: input.exportName,
501
+ localFiles: sourceFiles,
502
+ nodeBuiltins: [...input.analysis.importPolicy.nodeBuiltins].sort(),
503
+ packages: input.analysis.importPolicy.packages
504
+ .map(({ name, version }) => ({ name, version }))
505
+ .sort((left, right) => left.name.localeCompare(right.name)),
506
+ importedPlayDependencies: input.analysis.importedPlayDependencies
507
+ .map((dependency) => ({
508
+ filePath: workersRuntimeGraphFilePath({
509
+ entryFile: input.entryFile,
510
+ filePath: dependency.filePath,
511
+ adapter: input.adapter,
512
+ }),
513
+ playName: dependency.playName,
514
+ }))
515
+ .sort((left, right) => left.filePath.localeCompare(right.filePath)),
516
+ }),
517
+ );
518
+ }
519
+
419
520
  function readPackageVersionFromPackageJson(
420
521
  packageJsonPath: string,
421
522
  packageName: string,
@@ -1488,9 +1589,15 @@ export async function bundlePlayFile(
1488
1589
 
1489
1590
  try {
1490
1591
  const analysis = await analyzeSourceGraph(absolutePath, adapter);
1491
- analysis.graphHash = sha256(
1492
- `${analysis.graphHash}\nentry-export:${exportName}`,
1493
- );
1592
+ analysis.graphHash =
1593
+ target === PLAY_ARTIFACT_KINDS.esmWorkers
1594
+ ? buildWorkersRuntimeGraphHash({
1595
+ analysis,
1596
+ entryFile: absolutePath,
1597
+ adapter,
1598
+ exportName,
1599
+ })
1600
+ : sha256(`${analysis.graphHash}\nentry-export:${exportName}`);
1494
1601
  // For esm_workers builds, the harness source files (entry.ts +
1495
1602
  // peer DO/coordinator types it imports) are bundled INTO every play
1496
1603
  // artifact. So any harness edit must produce a different graphHash so
@@ -1537,11 +1644,10 @@ export async function bundlePlayFile(
1537
1644
  // Cache lookup happens after validation because a bundle cache hit is keyed
1538
1645
  // by source and target, while cloud descriptor typecheck results also depend
1539
1646
  // on generated tool metadata.
1540
- const cachedArtifact = await readArtifactCache(
1541
- analysis.graphHash,
1542
- target,
1543
- adapter,
1544
- );
1647
+ const canUseArtifactCache = target !== PLAY_ARTIFACT_KINDS.esmWorkers;
1648
+ const cachedArtifact = canUseArtifactCache
1649
+ ? await readArtifactCache(analysis.graphHash, target, adapter)
1650
+ : null;
1545
1651
  const discoveredFiles =
1546
1652
  await adapter.discoverPackagedLocalFiles(absolutePath);
1547
1653
  if (cachedArtifact) {
@@ -1560,7 +1666,13 @@ export async function bundlePlayFile(
1560
1666
 
1561
1667
  return {
1562
1668
  success: true,
1563
- artifact: { ...cachedArtifact, cacheHit: true },
1669
+ artifact: {
1670
+ ...cachedArtifact,
1671
+ entryFile: absolutePath,
1672
+ sourceHash: analysis.sourceHash,
1673
+ importPolicy: analysis.importPolicy,
1674
+ cacheHit: true,
1675
+ },
1564
1676
  sourceCode: analysis.sourceCode,
1565
1677
  sourceFiles: analysis.sourceFiles,
1566
1678
  filePath: absolutePath,
@@ -1626,7 +1738,9 @@ export async function bundlePlayFile(
1626
1738
  cacheHit: false,
1627
1739
  };
1628
1740
 
1629
- await writeArtifactCache(artifact, adapter);
1741
+ if (canUseArtifactCache) {
1742
+ await writeArtifactCache(artifact, adapter);
1743
+ }
1630
1744
 
1631
1745
  return {
1632
1746
  success: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.1.110",
3
+ "version": "0.1.111",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {