deepline 0.1.295 → 0.1.297

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.
@@ -30,10 +30,7 @@ export type RuntimeResourceSnapshot = {
30
30
  };
31
31
  sheetFlush: {
32
32
  acquired: number;
33
- active: number;
34
- waiting: number;
35
33
  bytes: number;
36
- admissionWaitMsEwma: number | null;
37
34
  latencyMsEwma: number | null;
38
35
  };
39
36
  providers: Record<
@@ -62,11 +59,6 @@ export interface RuntimeResourceGovernor {
62
59
  toolId: string;
63
60
  signal?: AbortSignal;
64
61
  }): Promise<RuntimeResourceLease>;
65
- acquireSheetFlush(input?: {
66
- estimatedBytes?: number | null;
67
- rowCount?: number | null;
68
- signal?: AbortSignal;
69
- }): Promise<RuntimeResourceLease>;
70
62
  suggestedToolParallelism(toolId: string, fallback: number): Promise<number>;
71
63
  resolveRowConcurrency(requested?: number): number;
72
64
  reportProviderBackpressure(input: {
@@ -98,76 +90,6 @@ function createEwma(alpha = 0.2): Ewma {
98
90
  };
99
91
  }
100
92
 
101
- // Sheet persistence has its own local admission lane. Receipt lifecycle calls
102
- // are admitted by the receipt sink/gateway and must not queue behind a large
103
- // Runtime Sheet flush in the runner. Two flushes keep the database busy while
104
- // bounding resident payloads and gateway/pool pressure (ADR 0012).
105
- const MAX_CONCURRENT_SHEET_FLUSHES = 2;
106
-
107
- type SheetFlushWaiter = {
108
- resolve: (lease: RuntimeResourceLease) => void;
109
- reject: (error: unknown) => void;
110
- signal?: AbortSignal;
111
- onAbort?: () => void;
112
- };
113
-
114
- class SheetFlushAdmission {
115
- #active = 0;
116
- #waiters: SheetFlushWaiter[] = [];
117
-
118
- get active(): number {
119
- return this.#active;
120
- }
121
-
122
- get waiting(): number {
123
- return this.#waiters.length;
124
- }
125
-
126
- acquire(signal?: AbortSignal): Promise<RuntimeResourceLease> {
127
- if (signal?.aborted) {
128
- return Promise.reject(signal.reason ?? new Error('Sheet flush aborted'));
129
- }
130
- if (this.#active < MAX_CONCURRENT_SHEET_FLUSHES) {
131
- this.#active += 1;
132
- return Promise.resolve(this.#lease());
133
- }
134
- return new Promise((resolve, reject) => {
135
- const waiter: SheetFlushWaiter = {
136
- resolve,
137
- reject,
138
- ...(signal ? { signal } : {}),
139
- };
140
- if (signal) {
141
- waiter.onAbort = () => {
142
- const index = this.#waiters.indexOf(waiter);
143
- if (index >= 0) this.#waiters.splice(index, 1);
144
- reject(signal.reason ?? new Error('Sheet flush aborted'));
145
- };
146
- signal.addEventListener('abort', waiter.onAbort, { once: true });
147
- }
148
- this.#waiters.push(waiter);
149
- });
150
- }
151
-
152
- #lease(): RuntimeResourceLease {
153
- let released = false;
154
- return {
155
- release: () => {
156
- if (released) return;
157
- released = true;
158
- while (this.#waiters.length > 0) {
159
- const waiter = this.#waiters.shift()!;
160
- waiter.signal?.removeEventListener('abort', waiter.onAbort!);
161
- if (waiter.signal?.aborted) continue;
162
- waiter.resolve(this.#lease());
163
- return;
164
- }
165
- this.#active = Math.max(0, this.#active - 1);
166
- },
167
- };
168
- }
169
- }
170
-
171
93
  function elapsedSince(startedAt: number): number {
172
94
  return Math.max(0, Date.now() - startedAt);
173
95
  }
@@ -183,8 +105,6 @@ export function createRuntimeResourceGovernor(input: {
183
105
  const rowAdmissionWait = createEwma();
184
106
  const toolAdmissionWait = createEwma();
185
107
  const sheetFlushLatency = createEwma();
186
- const sheetFlushAdmissionWait = createEwma();
187
- const sheetFlushAdmission = new SheetFlushAdmission();
188
108
  const providers = new Map<
189
109
  string,
190
110
  {
@@ -223,6 +143,9 @@ export function createRuntimeResourceGovernor(input: {
223
143
  rowAdmissionWait.observe(observation.rowAdmissionWaitMs);
224
144
  toolAdmissionWait.observe(observation.toolAdmissionWaitMs);
225
145
  sheetFlushLatency.observe(observation.sheetFlushLatencyMs);
146
+ if (observation.sheetFlushLatencyMs != null) {
147
+ sheetFlushCounters.acquired += 1;
148
+ }
226
149
  if (observation.rowEstimatedBytes != null) {
227
150
  rowCounters.estimatedBytes += Math.max(
228
151
  0,
@@ -301,17 +224,6 @@ export function createRuntimeResourceGovernor(input: {
301
224
  );
302
225
  },
303
226
 
304
- async acquireSheetFlush(sheetInput) {
305
- const startedAt = Date.now();
306
- const lease = await sheetFlushAdmission.acquire(sheetInput?.signal);
307
- sheetFlushCounters.acquired += 1;
308
- sheetFlushAdmissionWait.observe(elapsedSince(startedAt));
309
- observe({
310
- sheetFlushBytes: sheetInput?.estimatedBytes ?? null,
311
- });
312
- return lease;
313
- },
314
-
315
227
  suggestedToolParallelism(toolId, fallback) {
316
228
  return input.executionGovernor.suggestedParallelism(toolId, fallback);
317
229
  },
@@ -344,10 +256,7 @@ export function createRuntimeResourceGovernor(input: {
344
256
  },
345
257
  sheetFlush: {
346
258
  acquired: sheetFlushCounters.acquired,
347
- active: sheetFlushAdmission.active,
348
- waiting: sheetFlushAdmission.waiting,
349
259
  bytes: sheetFlushCounters.bytes,
350
- admissionWaitMsEwma: sheetFlushAdmissionWait.value,
351
260
  latencyMsEwma: sheetFlushLatency.value,
352
261
  },
353
262
  providers: Object.fromEntries(
@@ -6456,12 +6456,44 @@ type CompleteRuntimeMapRowChunksInput = {
6456
6456
  };
6457
6457
 
6458
6458
  export type RuntimeMapRowsWriteResult = {
6459
+ /** Submitted row identities durably accepted, including idempotent replays. */
6460
+ committedKeys: string[];
6461
+ /** Submitted row identities rejected because a newer write owns the row. */
6462
+ staleKeys: string[];
6463
+ /** Physical row mutations. Idempotent commits do not increment this count. */
6459
6464
  updated: number;
6465
+ /** Compatibility alias retained for callers that still call stale rows fenced. */
6460
6466
  fencedKeys: string[];
6461
6467
  staleDropped?: number;
6462
6468
  staleDroppedKeys?: string[];
6463
6469
  };
6464
6470
 
6471
+ type RuntimeMapRowsMutationResult = {
6472
+ updated: number;
6473
+ fencedKeys: string[];
6474
+ conflictKeys: string[];
6475
+ };
6476
+
6477
+ function classifyRuntimeMapRowsWrite(input: {
6478
+ submittedKeys: Iterable<string>;
6479
+ updated: number;
6480
+ fencedKeys: Iterable<string>;
6481
+ }): RuntimeMapRowsWriteResult {
6482
+ const staleKeys = [...new Set(input.fencedKeys)];
6483
+ const staleSet = new Set(staleKeys);
6484
+ const committedKeys = [...new Set(input.submittedKeys)].filter(
6485
+ (key) => !staleSet.has(key),
6486
+ );
6487
+ return {
6488
+ committedKeys,
6489
+ staleKeys,
6490
+ updated: input.updated,
6491
+ fencedKeys: staleKeys,
6492
+ staleDropped: staleKeys.length,
6493
+ staleDroppedKeys: staleKeys,
6494
+ };
6495
+ }
6496
+
6465
6497
  function writableEnrichedRuntimeSheetAttemptSql(
6466
6498
  tableAlias: string,
6467
6499
  attemptOwnerRunIdExpression: string,
@@ -6480,9 +6512,10 @@ async function completeRuntimeMapRowChunks(
6480
6512
  client: RuntimeQueryClient,
6481
6513
  session: RuntimePostgresSession,
6482
6514
  input: CompleteRuntimeMapRowChunksInput,
6483
- ): Promise<RuntimeMapRowsWriteResult> {
6515
+ ): Promise<RuntimeMapRowsMutationResult> {
6484
6516
  let updated = 0;
6485
6517
  const fencedKeys: string[] = [];
6518
+ const conflictKeys: string[] = [];
6486
6519
  for (const chunk of input.chunks) {
6487
6520
  const chunkKeys = chunk.map((row) => row.key);
6488
6521
  const chunkInputIndexes = chunk.map((row) => row.input_index);
@@ -6515,6 +6548,7 @@ async function completeRuntimeMapRowChunks(
6515
6548
  updated: number;
6516
6549
  matched_keys: string[];
6517
6550
  fenced_keys: string[];
6551
+ conflict_keys: string[];
6518
6552
  }>(
6519
6553
  `WITH updates AS (
6520
6554
  SELECT key_values._key,
@@ -6540,6 +6574,31 @@ async function completeRuntimeMapRowChunks(
6540
6574
  JOIN ${sheetTable(session)} AS target
6541
6575
  ON target._key = updates._key
6542
6576
  ),
6577
+ same_version_conflicts AS (
6578
+ SELECT updates.matched_key AS _key
6579
+ FROM matched_updates AS updates
6580
+ JOIN ${sheetTable(session)} AS target
6581
+ ON target._key = updates.matched_key
6582
+ WHERE $13::bigint IS NOT NULL
6583
+ AND target._write_version = $13::bigint
6584
+ AND target._run_id = $5::text
6585
+ AND target._status IN ('enriched', 'failed')
6586
+ AND (
6587
+ EXISTS (
6588
+ SELECT 1
6589
+ FROM unnest($8::text[]) AS field_values(field)
6590
+ WHERE target._cell_meta -> field_values.field ->> 'runId' = $5::text
6591
+ )
6592
+ OR (
6593
+ coalesce(target._attempt_owner_run_id, target._run_id) = $10::text
6594
+ AND COALESCE(target._attempt_seq, 0) = $12::integer
6595
+ )
6596
+ )
6597
+ AND (
6598
+ target._status <> 'enriched'
6599
+ OR (${targetChangedPatchedCellSql})
6600
+ )
6601
+ ),
6543
6602
  applied_rows AS (
6544
6603
  UPDATE ${sheetTable(session)} AS target
6545
6604
  SET _status = 'enriched',
@@ -6556,6 +6615,7 @@ async function completeRuntimeMapRowChunks(
6556
6615
  _cell_meta = ${mergeRuntimeCellMetaPatchSql('target._cell_meta', 'updates.cell_meta_patch')}${input.physicalUpdateSetSql}
6557
6616
  FROM matched_updates AS updates
6558
6617
  WHERE target._key = updates.matched_key
6618
+ AND NOT EXISTS (SELECT 1 FROM same_version_conflicts)
6559
6619
  AND ($13::bigint IS NULL OR target._write_version = $13::bigint)
6560
6620
  AND ($13::bigint IS NOT NULL OR target._status <> 'enriched')
6561
6621
  AND NOT (${newerTerminalRuntimeSheetRowSql('target', '$11::timestamptz', '$12::integer', '$9::text', '$10::text')})
@@ -6691,6 +6751,7 @@ async function completeRuntimeMapRowChunks(
6691
6751
  (SELECT count(*)::int FROM applied_rows) AS updated,
6692
6752
  coalesce((SELECT array_agg(matched_key) FROM matched_updates), '{}'::text[]) AS matched_keys,
6693
6753
  coalesce((SELECT array_agg(_key) FROM fenced_rows), '{}'::text[]) AS fenced_keys,
6754
+ coalesce((SELECT array_agg(_key) FROM same_version_conflicts), '{}'::text[]) AS conflict_keys,
6694
6755
  (SELECT count(*)::int FROM summary_delta) AS summary_delta_count,
6695
6756
  (SELECT count(*)::int FROM column_delta) AS column_delta_count`,
6696
6757
  [
@@ -6711,12 +6772,18 @@ async function completeRuntimeMapRowChunks(
6711
6772
  );
6712
6773
  const matchedKeys = new Set(rows[0]?.matched_keys ?? []);
6713
6774
  const chunkFencedKeys = rows[0]?.fenced_keys ?? [];
6775
+ const chunkConflictKeys = rows[0]?.conflict_keys ?? [];
6714
6776
  // An exact-version replay that already produced the same terminal value is
6715
6777
  // accepted idempotently even when SQL has no physical mutation to apply.
6716
6778
  // Stale versions remain excluded through fenced_keys.
6717
6779
  const appliedCount = Number(rows[0]?.updated ?? 0);
6718
6780
  updated += appliedCount;
6719
6781
  fencedKeys.push(...chunkFencedKeys);
6782
+ conflictKeys.push(...chunkConflictKeys);
6783
+
6784
+ if (chunkConflictKeys.length > 0) {
6785
+ continue;
6786
+ }
6720
6787
 
6721
6788
  if (matchedKeys.size === chunk.length) {
6722
6789
  continue;
@@ -6735,17 +6802,19 @@ async function completeRuntimeMapRowChunks(
6735
6802
  );
6736
6803
  updated += repaired.updated;
6737
6804
  fencedKeys.push(...repaired.fencedKeys);
6805
+ conflictKeys.push(...repaired.conflictKeys);
6738
6806
  }
6739
- return { updated, fencedKeys };
6807
+ return { updated, fencedKeys, conflictKeys };
6740
6808
  }
6741
6809
 
6742
6810
  async function completeRuntimeMapRowChunksWithInputIndexRepair(
6743
6811
  client: RuntimeQueryClient,
6744
6812
  session: RuntimePostgresSession,
6745
6813
  input: CompleteRuntimeMapRowChunksInput,
6746
- ): Promise<RuntimeMapRowsWriteResult> {
6814
+ ): Promise<RuntimeMapRowsMutationResult> {
6747
6815
  let updated = 0;
6748
6816
  const fencedKeys: string[] = [];
6817
+ const conflictKeys: string[] = [];
6749
6818
  for (const chunk of input.chunks) {
6750
6819
  const chunkKeys = chunk.map((row) => row.key);
6751
6820
  const chunkInputIndexes = chunk.map((row) => row.input_index);
@@ -6770,6 +6839,7 @@ async function completeRuntimeMapRowChunksWithInputIndexRepair(
6770
6839
  const { rows } = await client.query<{
6771
6840
  _key?: string;
6772
6841
  fenced_keys?: string[];
6842
+ conflict_keys?: string[];
6773
6843
  updated?: number;
6774
6844
  }>(
6775
6845
  `WITH updates AS (
@@ -6788,6 +6858,7 @@ async function completeRuntimeMapRowChunksWithInputIndexRepair(
6788
6858
  matched_updates AS (
6789
6859
  SELECT DISTINCT ON (target._key)
6790
6860
  target._key AS matched_key,
6861
+ updates._key AS submitted_key,
6791
6862
  updates.data_patch,
6792
6863
  updates.cell_meta_patch
6793
6864
  FROM updates
@@ -6800,10 +6871,37 @@ async function completeRuntimeMapRowChunksWithInputIndexRepair(
6800
6871
  )
6801
6872
  ORDER BY target._key, (target._key = updates._key) DESC
6802
6873
  ),
6874
+ same_version_conflicts AS (
6875
+ SELECT updates.submitted_key AS _key
6876
+ FROM matched_updates AS updates
6877
+ JOIN ${sheetTable(session)} AS target
6878
+ ON target._key = updates.matched_key
6879
+ WHERE $13::bigint IS NOT NULL
6880
+ AND target._write_version = $13::bigint
6881
+ AND target._run_id = $5::text
6882
+ AND target._status IN ('enriched', 'failed')
6883
+ AND (
6884
+ EXISTS (
6885
+ SELECT 1
6886
+ FROM unnest($8::text[]) AS field_values(field)
6887
+ WHERE target._cell_meta -> field_values.field ->> 'runId' = $5::text
6888
+ )
6889
+ OR (
6890
+ coalesce(target._attempt_owner_run_id, target._run_id) = $10::text
6891
+ AND COALESCE(target._attempt_seq, 0) = $12::integer
6892
+ )
6893
+ )
6894
+ AND (
6895
+ target._status <> 'enriched'
6896
+ OR (${targetChangedPatchedCellSql})
6897
+ )
6898
+ ),
6803
6899
  applied_rows AS (
6804
6900
  UPDATE ${sheetTable(session)} AS target
6805
6901
  SET _status = 'enriched',
6806
6902
  _run_id = $5::text,
6903
+ _writer_run_id = $5::text,
6904
+ _write_version = COALESCE($13::bigint, target._write_version),
6807
6905
  _error = NULL,
6808
6906
  _attempt_id = $9::text,
6809
6907
  _attempt_owner_run_id = $10::text,
@@ -6815,6 +6913,9 @@ async function completeRuntimeMapRowChunksWithInputIndexRepair(
6815
6913
  FROM matched_updates AS updates, ${sheetTable(session)} AS prev
6816
6914
  WHERE target._key = updates.matched_key
6817
6915
  AND prev._key = target._key
6916
+ AND NOT EXISTS (SELECT 1 FROM same_version_conflicts)
6917
+ AND ($13::bigint IS NULL OR target._write_version = $13::bigint)
6918
+ AND ($13::bigint IS NOT NULL OR target._status <> 'enriched')
6818
6919
  AND NOT (${newerTerminalRuntimeSheetRowSql('target', '$11::timestamptz', '$12::integer', '$9::text', '$10::text')})
6819
6920
  AND NOT (${sameOwnerTerminalEpochSql})
6820
6921
  AND (
@@ -6842,15 +6943,17 @@ async function completeRuntimeMapRowChunksWithInputIndexRepair(
6842
6943
  FROM applied_rows
6843
6944
  ),
6844
6945
  fenced_rows AS (
6845
- SELECT updates.matched_key AS _key
6946
+ SELECT updates.submitted_key AS _key
6846
6947
  FROM matched_updates AS updates
6847
6948
  JOIN ${sheetTable(session)} AS target
6848
6949
  ON target._key = updates.matched_key
6849
6950
  WHERE NOT EXISTS (
6850
6951
  SELECT 1 FROM applied_rows WHERE applied_rows._key = updates.matched_key
6851
- )
6952
+ )
6852
6953
  AND (
6853
- (${newerTerminalRuntimeSheetRowSql('target', '$11::timestamptz', '$12::integer', '$9::text', '$10::text')})
6954
+ ($13::bigint IS NOT NULL AND target._write_version <> $13::bigint)
6955
+ OR ($13::bigint IS NULL AND target._status = 'enriched')
6956
+ OR (${newerTerminalRuntimeSheetRowSql('target', '$11::timestamptz', '$12::integer', '$9::text', '$10::text')})
6854
6957
  OR NOT (
6855
6958
  ($9::text IS NULL AND target._run_id = $5::text)
6856
6959
  OR ($9::text IS NOT NULL AND ${activeRuntimeSheetAttemptFenceSql('target', '$9::text', '$10::text', '$11::timestamptz', '$12::integer')})
@@ -6941,7 +7044,8 @@ async function completeRuntimeMapRowChunksWithInputIndexRepair(
6941
7044
  )
6942
7045
  SELECT
6943
7046
  (SELECT count(*)::int FROM applied_rows) AS updated,
6944
- coalesce((SELECT array_agg(_key) FROM fenced_rows), '{}'::text[]) AS fenced_keys`,
7047
+ coalesce((SELECT array_agg(_key) FROM fenced_rows), '{}'::text[]) AS fenced_keys,
7048
+ coalesce((SELECT array_agg(_key) FROM same_version_conflicts), '{}'::text[]) AS conflict_keys`,
6945
7049
  [
6946
7050
  chunkKeys,
6947
7051
  chunkInputIndexes,
@@ -6955,12 +7059,14 @@ async function completeRuntimeMapRowChunksWithInputIndexRepair(
6955
7059
  input.attemptOwnerRunId,
6956
7060
  input.attemptExpiresAt,
6957
7061
  input.attemptSeq,
7062
+ input.writeVersion,
6958
7063
  ],
6959
7064
  );
6960
7065
  updated += Number(rows[0]?.updated ?? 0);
6961
7066
  fencedKeys.push(...(rows[0]?.fenced_keys ?? []));
7067
+ conflictKeys.push(...(rows[0]?.conflict_keys ?? []));
6962
7068
  }
6963
- return { updated, fencedKeys };
7069
+ return { updated, fencedKeys, conflictKeys };
6964
7070
  }
6965
7071
 
6966
7072
  async function insertMissingCompletedMapRowChunks(
@@ -7140,6 +7246,7 @@ async function failRuntimeMapRowChunks(
7140
7246
  input: {
7141
7247
  chunks: RuntimePreparedFailedRow[][];
7142
7248
  physicalUpdateSetSql: string;
7249
+ physicalColumnProjections: PhysicalSheetColumnProjection[];
7143
7250
  forceTerminal?: boolean;
7144
7251
  runId: string;
7145
7252
  attemptId: string | null;
@@ -7150,15 +7257,21 @@ async function failRuntimeMapRowChunks(
7150
7257
  normalizedPlayName: string;
7151
7258
  normalizedTableNamespace: string;
7152
7259
  },
7153
- ): Promise<RuntimeMapRowsWriteResult> {
7260
+ ): Promise<RuntimeMapRowsMutationResult> {
7154
7261
  let updated = 0;
7155
7262
  const fencedKeys: string[] = [];
7263
+ const conflictKeys: string[] = [];
7156
7264
  for (const chunk of input.chunks) {
7157
7265
  const chunkKeys = chunk.map((row) => row.key);
7158
7266
  const chunkInputIndexes = chunk.map((row) => row.input_index);
7159
7267
  const chunkDataPatches = chunk.map((row) => row.data_patch_json);
7160
7268
  const chunkCellMetaPatches = chunk.map((row) => row.cell_meta_patch_json);
7161
7269
  const chunkErrors = chunk.map((row) => row.error);
7270
+ const targetChangedPatchedCellSql = changedPatchedCellSql(
7271
+ 'target',
7272
+ 'updates.data_patch',
7273
+ input.physicalColumnProjections,
7274
+ );
7162
7275
  // Per-field failed-cell counts for the column summary, computed from the
7163
7276
  // cell meta patches (a failed row records exactly which cell failed).
7164
7277
  const failedCellCounts = new Map<string, number>();
@@ -7180,6 +7293,7 @@ async function failRuntimeMapRowChunks(
7180
7293
  const { rows } = await client.query<{
7181
7294
  updated?: number;
7182
7295
  fenced_keys?: string[];
7296
+ conflict_keys?: string[];
7183
7297
  }>(
7184
7298
  `WITH updates AS (
7185
7299
  SELECT key_values._key,
@@ -7213,6 +7327,32 @@ async function failRuntimeMapRowChunks(
7213
7327
  )
7214
7328
  ORDER BY target._key, (target._key = updates._key) DESC
7215
7329
  ),
7330
+ same_version_conflicts AS (
7331
+ SELECT updates.matched_key AS _key
7332
+ FROM matched_updates AS updates
7333
+ JOIN ${sheetTable(session)} AS target
7334
+ ON target._key = updates.matched_key
7335
+ WHERE $16::bigint IS NOT NULL
7336
+ AND target._write_version = $16::bigint
7337
+ AND target._run_id = $6::text
7338
+ AND target._status IN ('enriched', 'failed')
7339
+ AND (
7340
+ EXISTS (
7341
+ SELECT 1
7342
+ FROM unnest($9::text[]) AS field_values(field)
7343
+ WHERE target._cell_meta -> field_values.field ->> 'runId' = $6::text
7344
+ )
7345
+ OR (
7346
+ coalesce(target._attempt_owner_run_id, target._run_id) = $12::text
7347
+ AND COALESCE(target._attempt_seq, 0) = $14::integer
7348
+ )
7349
+ )
7350
+ AND (
7351
+ target._status <> 'failed'
7352
+ OR target._error IS DISTINCT FROM updates.error
7353
+ OR (${targetChangedPatchedCellSql})
7354
+ )
7355
+ ),
7216
7356
  applied_rows AS (
7217
7357
  UPDATE ${sheetTable(session)} AS target
7218
7358
  SET _status = 'failed',
@@ -7229,6 +7369,7 @@ async function failRuntimeMapRowChunks(
7229
7369
  _cell_meta = ${mergeRuntimeCellMetaPatchSql('target._cell_meta', 'updates.cell_meta_patch')}${input.physicalUpdateSetSql}
7230
7370
  FROM matched_updates AS updates, ${sheetTable(session)} AS prev
7231
7371
  WHERE target._key = updates.matched_key
7372
+ AND NOT EXISTS (SELECT 1 FROM same_version_conflicts)
7232
7373
  AND ($16::bigint IS NULL OR target._write_version = $16::bigint)
7233
7374
  AND prev._key = target._key
7234
7375
  AND ($15::boolean OR target._status <> 'enriched')
@@ -7237,6 +7378,14 @@ async function failRuntimeMapRowChunks(
7237
7378
  ($11::text IS NULL AND target._run_id = $6::text)
7238
7379
  OR ($11::text IS NOT NULL AND ${activeRuntimeSheetAttemptFenceSql('target', '$11::text', '$12::text', '$13::timestamptz', '$14::integer')})
7239
7380
  )
7381
+ AND NOT (
7382
+ $16::bigint IS NOT NULL
7383
+ AND target._write_version = $16::bigint
7384
+ AND target._run_id = $6::text
7385
+ AND target._status = 'failed'
7386
+ AND target._error IS NOT DISTINCT FROM updates.error
7387
+ AND NOT (${targetChangedPatchedCellSql})
7388
+ )
7240
7389
  RETURNING target._key, prev._status AS prev_status
7241
7390
  ),
7242
7391
  applied_count AS (
@@ -7326,7 +7475,8 @@ async function failRuntimeMapRowChunks(
7326
7475
  )
7327
7476
  SELECT
7328
7477
  (SELECT count(*)::int FROM applied_rows) AS updated,
7329
- coalesce((SELECT array_agg(_key) FROM fenced_rows), '{}'::text[]) AS fenced_keys`,
7478
+ coalesce((SELECT array_agg(_key) FROM fenced_rows), '{}'::text[]) AS fenced_keys,
7479
+ coalesce((SELECT array_agg(_key) FROM same_version_conflicts), '{}'::text[]) AS conflict_keys`,
7330
7480
  [
7331
7481
  chunkKeys,
7332
7482
  chunkInputIndexes,
@@ -7348,8 +7498,9 @@ async function failRuntimeMapRowChunks(
7348
7498
  );
7349
7499
  updated += Number(rows[0]?.updated ?? 0);
7350
7500
  fencedKeys.push(...(rows[0]?.fenced_keys ?? []));
7501
+ conflictKeys.push(...(rows[0]?.conflict_keys ?? []));
7351
7502
  }
7352
- return { updated, fencedKeys };
7503
+ return { updated, fencedKeys, conflictKeys };
7353
7504
  }
7354
7505
 
7355
7506
  /**
@@ -7382,6 +7533,13 @@ export async function completeRuntimeMapRows(
7382
7533
  forceFailedRows?: boolean;
7383
7534
  },
7384
7535
  ): Promise<RuntimeMapRowsWriteResult> {
7536
+ if (input.rows.length === 0) {
7537
+ return classifyRuntimeMapRowsWrite({
7538
+ submittedKeys: [],
7539
+ updated: 0,
7540
+ fencedKeys: [],
7541
+ });
7542
+ }
7385
7543
  if (context.dbSessionStrategy === 'gateway_only') {
7386
7544
  const keyedRows = input.rows.map((row) => {
7387
7545
  if (row.key) return row;
@@ -7395,13 +7553,27 @@ export async function completeRuntimeMapRows(
7395
7553
  runId: input.runId,
7396
7554
  outputFields: input.outputFields ?? [],
7397
7555
  });
7398
- return await postRuntimeApi<RuntimeMapRowsWriteResult>(context, {
7556
+ const result = await postRuntimeApi<
7557
+ Partial<RuntimeMapRowsWriteResult> &
7558
+ Pick<RuntimeMapRowsWriteResult, 'updated' | 'fencedKeys'>
7559
+ >(context, {
7399
7560
  action: 'runtime_sheet_complete_map_rows',
7400
7561
  input: { ...input, rows },
7401
7562
  });
7402
- }
7403
- if (input.rows.length === 0) {
7404
- return { updated: 0, fencedKeys: [] };
7563
+ const submittedKeys = keyedRows
7564
+ .map((row) => row.key)
7565
+ .filter((key): key is string => Boolean(key));
7566
+ const staleKeys =
7567
+ result.staleKeys ?? result.staleDroppedKeys ?? result.fencedKeys;
7568
+ return {
7569
+ ...classifyRuntimeMapRowsWrite({
7570
+ submittedKeys,
7571
+ updated: result.updated,
7572
+ fencedKeys: staleKeys,
7573
+ }),
7574
+ // Preserve the gateway's compatibility classification during rollout.
7575
+ fencedKeys: result.fencedKeys,
7576
+ };
7405
7577
  }
7406
7578
  const sheetContract = augmentSheetContractWithDatasetFields({
7407
7579
  contract: input.sheetContract,
@@ -7441,7 +7613,11 @@ export async function completeRuntimeMapRows(
7441
7613
  uniqueRows.set(resolvedKey, { ...row, key: resolvedKey });
7442
7614
  }
7443
7615
  if (uniqueRows.size === 0) {
7444
- return { updated: 0, fencedKeys: [] };
7616
+ return classifyRuntimeMapRowsWrite({
7617
+ submittedKeys: [],
7618
+ updated: 0,
7619
+ fencedKeys: [],
7620
+ });
7445
7621
  }
7446
7622
 
7447
7623
  const projections = physicalSheetColumnProjections(sheetContract);
@@ -7526,14 +7702,16 @@ export async function completeRuntimeMapRows(
7526
7702
  return {
7527
7703
  updated: updated.updated + inserted.inserted,
7528
7704
  fencedKeys: updated.fencedKeys,
7705
+ conflictKeys: updated.conflictKeys,
7529
7706
  };
7530
7707
  })()
7531
- : { updated: 0, fencedKeys: [] };
7708
+ : { updated: 0, fencedKeys: [], conflictKeys: [] };
7532
7709
  const failed =
7533
7710
  failedRows.length > 0
7534
7711
  ? await failRuntimeMapRowChunks(client, session, {
7535
7712
  chunks: failedChunks,
7536
7713
  physicalUpdateSetSql,
7714
+ physicalColumnProjections: projections,
7537
7715
  forceTerminal: input.forceFailedRows === true,
7538
7716
  runId: input.runId,
7539
7717
  attemptId,
@@ -7546,13 +7724,22 @@ export async function completeRuntimeMapRows(
7546
7724
  input.tableNamespace,
7547
7725
  ),
7548
7726
  })
7549
- : { updated: 0, fencedKeys: [] };
7550
- return {
7727
+ : { updated: 0, fencedKeys: [], conflictKeys: [] };
7728
+ const conflictKeys = [...completed.conflictKeys, ...failed.conflictKeys];
7729
+ if (conflictKeys.length > 0) {
7730
+ throw new Error(
7731
+ `Runtime Sheet received the same write version with a different terminal payload for row(s): ${[
7732
+ ...new Set(conflictKeys),
7733
+ ]
7734
+ .slice(0, 10)
7735
+ .join(', ')}.`,
7736
+ );
7737
+ }
7738
+ return classifyRuntimeMapRowsWrite({
7739
+ submittedKeys: uniqueRows.keys(),
7551
7740
  updated: completed.updated + failed.updated,
7552
7741
  fencedKeys: [...completed.fencedKeys, ...failed.fencedKeys],
7553
- staleDropped: completed.fencedKeys.length + failed.fencedKeys.length,
7554
- staleDroppedKeys: [...completed.fencedKeys, ...failed.fencedKeys],
7555
- };
7742
+ });
7556
7743
  },
7557
7744
  );
7558
7745
  }