deepline 0.1.200 → 0.1.201

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.
@@ -6173,7 +6173,20 @@ function createMinimalWorkerCtx(
6173
6173
  ? ` cells=${totalStepCells}/${totalStepCellsCompleted}/${totalStepCellsSkipped}`
6174
6174
  : '';
6175
6175
  await flushQueuedMapProgressUpdates();
6176
- callbacks?.onMapCompleted?.(mapNodeId, completedAt);
6176
+ // Ordering invariant (progress-before-terminal): the authoritative final
6177
+ // progress bump MUST land in the live progress map and reach the ledger
6178
+ // BEFORE `onMapCompleted` emits the map node's `step.completed`
6179
+ // transition. The reducer's `step.completed` event carries no progress of
6180
+ // its own — it inherits `current.progress` (run-ledger.ts step.completed
6181
+ // case). If we emitted `step.completed` first (as this used to), the step
6182
+ // could settle on the last mid-flight throttled bump (e.g. completed=2 of
6183
+ // a 10-row hint) while the reconciled terminal bump was still pending on
6184
+ // the throttled ledger channel, persisting the run forever as completed
6185
+ // with a stale "2/10" fraction. Draining the final bump first makes that
6186
+ // lost-update race structurally impossible: by the time `step.completed`
6187
+ // is enqueued, `stepProgressByNodeId[mapNodeId]` already holds
6188
+ // `completed=finalizedRowsWritten`, `total=completed+failed`, so the
6189
+ // settled step inherits truthful counts (`completed + failed === total`).
6177
6190
  await updateMapProgress({
6178
6191
  completed: finalizedRowsWritten,
6179
6192
  total: finalizedRowsWritten + totalRowsFailed,
@@ -6188,6 +6201,7 @@ function createMinimalWorkerCtx(
6188
6201
  finalizedRowsWritten,
6189
6202
  ),
6190
6203
  });
6204
+ callbacks?.onMapCompleted?.(mapNodeId, completedAt);
6191
6205
  emitEvent({
6192
6206
  type: 'log',
6193
6207
  level: totalRowsFailed > 0 ? 'warn' : 'info',
@@ -7011,9 +7025,10 @@ function createMinimalWorkerCtx(
7011
7025
  options?.timeoutMs == null && !childNeedsWorkflowScheduler,
7012
7026
  body: {
7013
7027
  name: resolvedName,
7014
- childIdempotencyKey: wasFailed && receiptContext.leaseId
7015
- ? `${receiptKey}:claim:${receiptContext.leaseId}`
7016
- : receiptKey,
7028
+ childIdempotencyKey:
7029
+ wasFailed && receiptContext.leaseId
7030
+ ? `${receiptKey}:claim:${receiptContext.leaseId}`
7031
+ : receiptKey,
7017
7032
  input: isRecord(input) ? input : {},
7018
7033
  orgId: req.orgId,
7019
7034
  callbackUrl: req.callbackUrl,
@@ -106,10 +106,10 @@ export const SDK_RELEASE = {
106
106
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
107
107
  // fields shipped in 0.1.153.
108
108
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
109
- version: '0.1.200',
109
+ version: '0.1.201',
110
110
  apiContract: '2026-06-dataset-handle-results-hard-cutover',
111
111
  supportPolicy: {
112
- latest: '0.1.200',
112
+ latest: '0.1.201',
113
113
  minimumSupported: '0.1.53',
114
114
  deprecatedBelow: '0.1.53',
115
115
  commandMinimumSupported: [
@@ -31,6 +31,32 @@ export type PlayRunLedgerEventSource =
31
31
  | 'coordinator'
32
32
  | 'system';
33
33
 
34
+ /**
35
+ * Live progress for a single step/node.
36
+ *
37
+ * Verified producer semantics (workers_edge map executor —
38
+ * `apps/play-runner-workers/src/entry.ts`):
39
+ *
40
+ * - `completed` counts SUCCESSFULLY written rows ONLY, never failures/skips.
41
+ * In-flight it is `min(total, totalRowsWritten + completedExecutedRows)`
42
+ * where `completedExecutedRows` increments on success only (entry.ts:5635);
43
+ * failures increment the separate `failedExecutedRows` (entry.ts:5684).
44
+ * At map finalize it is set to `finalizedRowsWritten` (successes).
45
+ * - `failed` counts settled-but-failed rows (row-failure isolation persists
46
+ * these; they re-execute on the next run).
47
+ * - `total` is the settled-row denominator. In-flight it is the row-count
48
+ * estimate (`rowCountHint`); at map finalize it is reconciled to
49
+ * `completed + failed` and CAN SHRINK from the initial estimate when rows are
50
+ * filtered/skipped. So `total` is monotonic-up during flight but is corrected
51
+ * down to the settled count at terminal.
52
+ *
53
+ * Terminal invariant: once the step is settled, `completed + failed === total`.
54
+ * Because `completed` is successes-only (NOT all-settled), readers must NOT
55
+ * assume `completed === total` at terminal — a truthful terminal render is
56
+ * `{total} rows` when `failed === 0`, else a `completed`/`failed`/`total`
57
+ * breakdown. See `settleRunningStepsOnTerminal` for why the reducer does not
58
+ * force `completed = total`.
59
+ */
34
60
  export type PlayRunLedgerStepProgress = {
35
61
  completed?: number;
36
62
  total?: number;
@@ -775,6 +801,23 @@ function retryablePlatformDeployFailureSnapshot(
775
801
  );
776
802
  }
777
803
 
804
+ // Force-settles steps still marked `running` when the RUN reaches a terminal
805
+ // status (the fallback for steps that never emitted their own step.completed,
806
+ // e.g. a crash between the last progress bump and the terminal event). This
807
+ // only flips status and stamps `completedAt`; it deliberately does NOT
808
+ // reconcile `progress.completed` to `progress.total`.
809
+ //
810
+ // Why not reconcile: per PlayRunLedgerStepProgress semantics, `completed`
811
+ // counts SUCCESSES ONLY, so `completed === total` is NOT a valid invariant at
812
+ // terminal — a run can legitimately settle with failed/skipped rows (row-
813
+ // failure isolation). Forcing `completed = total` here would fabricate
814
+ // successes that never happened and falsify the persisted counts. The truthful
815
+ // terminal counts are carried by the producer's final reconciled bump, which
816
+ // the workers_edge finalize path now drains BEFORE emitting step.completed
817
+ // (entry.ts finalize ordering invariant). This settle path is the last-resort
818
+ // heal for the missing-final-bump case, where the honest thing is "we know it
819
+ // stopped, we do not know it fully succeeded" — leave the last observed
820
+ // counts, just mark it settled so the UI stops rendering a live ticker.
778
821
  function settleRunningStepsOnTerminal(
779
822
  snapshot: PlayRunLedgerSnapshot,
780
823
  status: Extract<PlayRunLedgerStepStatus, 'completed' | 'failed'>,
@@ -726,6 +726,75 @@ async function isRuntimeSheetSchemaReady(
726
726
  }
727
727
  }
728
728
 
729
+ /**
730
+ * Report exactly what the scoped runtime role can see for the sheet table, from
731
+ * the same connection the readiness probe uses. When ensure_sheet reports the
732
+ * table as provisioned but the runner probe still returns not-ready, the cause
733
+ * is almost always that the runtime role lacks a privilege on the freshly
734
+ * created table (so information_schema.columns hides the attempt columns) or a
735
+ * schema/table-name mismatch. This turns the previously blind "still not ready"
736
+ * error into an actionable one that names the concrete missing piece.
737
+ */
738
+ async function readRuntimeSheetSchemaDiagnostic(
739
+ session: RuntimePostgresSession,
740
+ ): Promise<Record<string, unknown>> {
741
+ const query = async (
742
+ client: RuntimeQueryClient,
743
+ ): Promise<Record<string, unknown>> => {
744
+ const visibleColumns = await client.query<{ column_name: string }>(
745
+ `
746
+ SELECT column_name
747
+ FROM information_schema.columns
748
+ WHERE table_schema = $1
749
+ AND table_name = $2
750
+ ORDER BY ordinal_position
751
+ `,
752
+ [session.postgres.schema, session.postgres.sheetTable],
753
+ );
754
+ const privileges = await client.query<Record<string, unknown>>(
755
+ `
756
+ SELECT
757
+ current_user AS probe_role,
758
+ to_regclass($3) IS NOT NULL AS regclass_visible,
759
+ has_table_privilege(current_user, $3, 'SELECT') AS can_select,
760
+ has_table_privilege(current_user, $3, 'INSERT') AS can_insert,
761
+ has_table_privilege(current_user, $3, 'UPDATE') AS can_update
762
+ `,
763
+ [
764
+ session.postgres.schema,
765
+ session.postgres.sheetTable,
766
+ `${quoteIdentifier(session.postgres.schema)}.${quoteIdentifier(session.postgres.sheetTable)}`,
767
+ ],
768
+ );
769
+ const visible = new Set(visibleColumns.rows.map((row) => row.column_name));
770
+ return {
771
+ schema: session.postgres.schema,
772
+ table: session.postgres.sheetTable,
773
+ probeRole: privileges.rows[0]?.probe_role ?? null,
774
+ regclassVisible: privileges.rows[0]?.regclass_visible ?? null,
775
+ canSelect: privileges.rows[0]?.can_select ?? null,
776
+ canInsert: privileges.rows[0]?.can_insert ?? null,
777
+ canUpdate: privileges.rows[0]?.can_update ?? null,
778
+ visibleColumnCount: visible.size,
779
+ missingAttemptColumns: RUNTIME_SHEET_ATTEMPT_COLUMNS.filter(
780
+ (column) => !visible.has(column),
781
+ ),
782
+ };
783
+ };
784
+ try {
785
+ if (isRuntimeOneShotQueryFactoryRegistered()) {
786
+ return await withRuntimeOneShotPostgres(session, query);
787
+ }
788
+ return await withRuntimePostgres(session, query);
789
+ } catch (error) {
790
+ return {
791
+ schema: session.postgres.schema,
792
+ table: session.postgres.sheetTable,
793
+ diagnosticError: error instanceof Error ? error.message : String(error),
794
+ };
795
+ }
796
+ }
797
+
729
798
  async function waitForRuntimeSheetSchemaReadyAfterEnsure(
730
799
  session: RuntimePostgresSession,
731
800
  input: {
@@ -839,7 +908,7 @@ async function ensureRuntimeSheetForPreloadedSession(
839
908
  phase: 'ensure_sheet_for_preloaded_session',
840
909
  ms: Date.now() - ensureStartedAt,
841
910
  });
842
- const readyAfterEnsure = await waitForRuntimeSheetSchemaReadyAfterEnsure(
911
+ let readyAfterEnsure = await waitForRuntimeSheetSchemaReadyAfterEnsure(
843
912
  input.session,
844
913
  {
845
914
  sheetContract: input.sheetContract,
@@ -847,8 +916,39 @@ async function ensureRuntimeSheetForPreloadedSession(
847
916
  },
848
917
  );
849
918
  if (!readyAfterEnsure) {
919
+ // ensure_sheet provisions the sheet as the tenant admin role, but the
920
+ // preloaded session probes as the scoped runtime role. When the tenant
921
+ // role contract was only partially applied (e.g. role-membership grants
922
+ // skipped on a data plane where the owner cannot administer roles), the
923
+ // runtime role never inherits access to the freshly created table, so its
924
+ // information_schema view hides the attempt columns and the probe stays
925
+ // not-ready even though the table exists. Re-apply the storage grants
926
+ // through the same self-heal the pre-ensure probe uses, then re-check
927
+ // once. This is a grant repair, not a blind retry: if the runtime role
928
+ // still cannot see the sheet, we fail loudly below with the concrete
929
+ // runtime-role view.
930
+ const repairStartedAt = Date.now();
931
+ await repairRuntimeStorageGrants(context, {
932
+ playName: context.playName,
933
+ });
934
+ input.timings?.push({
935
+ phase: 'repair_storage_grants_after_preloaded_session_ensure',
936
+ ms: Date.now() - repairStartedAt,
937
+ retried: true,
938
+ });
939
+ readyAfterEnsure = await waitForRuntimeSheetSchemaReadyAfterEnsure(
940
+ input.session,
941
+ {
942
+ sheetContract: input.sheetContract,
943
+ timings: input.timings,
944
+ },
945
+ );
946
+ }
947
+ if (!readyAfterEnsure) {
948
+ const diagnostic = await readRuntimeSheetSchemaDiagnostic(input.session);
850
949
  throw new Error(
851
- `Runtime sheet schema for ctx.dataset("${input.tableNamespace}") is still not ready after ensure_sheet.`,
950
+ `Runtime sheet schema for ctx.dataset("${input.tableNamespace}") is still not ready after ensure_sheet. ` +
951
+ `Runtime-role view: ${JSON.stringify(diagnostic)}`,
852
952
  );
853
953
  }
854
954
  })();
package/dist/cli/index.js CHANGED
@@ -623,10 +623,10 @@ var SDK_RELEASE = {
623
623
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
624
624
  // fields shipped in 0.1.153.
625
625
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
626
- version: "0.1.200",
626
+ version: "0.1.201",
627
627
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
628
628
  supportPolicy: {
629
- latest: "0.1.200",
629
+ latest: "0.1.201",
630
630
  minimumSupported: "0.1.53",
631
631
  deprecatedBelow: "0.1.53",
632
632
  commandMinimumSupported: [
@@ -608,10 +608,10 @@ var SDK_RELEASE = {
608
608
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
609
609
  // fields shipped in 0.1.153.
610
610
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
611
- version: "0.1.200",
611
+ version: "0.1.201",
612
612
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
613
613
  supportPolicy: {
614
- latest: "0.1.200",
614
+ latest: "0.1.201",
615
615
  minimumSupported: "0.1.53",
616
616
  deprecatedBelow: "0.1.53",
617
617
  commandMinimumSupported: [
package/dist/index.js CHANGED
@@ -422,10 +422,10 @@ var SDK_RELEASE = {
422
422
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
423
423
  // fields shipped in 0.1.153.
424
424
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
425
- version: "0.1.200",
425
+ version: "0.1.201",
426
426
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
427
427
  supportPolicy: {
428
- latest: "0.1.200",
428
+ latest: "0.1.201",
429
429
  minimumSupported: "0.1.53",
430
430
  deprecatedBelow: "0.1.53",
431
431
  commandMinimumSupported: [
package/dist/index.mjs CHANGED
@@ -352,10 +352,10 @@ var SDK_RELEASE = {
352
352
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
353
353
  // fields shipped in 0.1.153.
354
354
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
355
- version: "0.1.200",
355
+ version: "0.1.201",
356
356
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
357
357
  supportPolicy: {
358
- latest: "0.1.200",
358
+ latest: "0.1.201",
359
359
  minimumSupported: "0.1.53",
360
360
  deprecatedBelow: "0.1.53",
361
361
  commandMinimumSupported: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.1.200",
3
+ "version": "0.1.201",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {