deepline 0.3.185 → 0.3.187

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.
@@ -88,7 +88,6 @@ import {
88
88
  activeRuntimeSheetAttemptFenceSql,
89
89
  newerTerminalRuntimeSheetRowSql,
90
90
  releasableRuntimeSheetAttemptFenceSql,
91
- sameOwnerTerminalAttemptEpochSql,
92
91
  } from './sheet-attempt-sql';
93
92
  import type { MapRowOutcome } from './durability-store';
94
93
  import { RUNTIME_CAPACITY_POLICY } from './runtime-capacity-policy';
@@ -7120,7 +7119,6 @@ export type RuntimeMapRowsWriteResult = {
7120
7119
  type RuntimeMapRowsMutationResult = {
7121
7120
  updated: number;
7122
7121
  fencedRows: RuntimeMapRowsStaleRow[];
7123
- conflictKeys: string[];
7124
7122
  };
7125
7123
 
7126
7124
  const RUNTIME_MAP_ROWS_STALE_REASONS: ReadonlySet<RuntimeMapRowsStaleReason> =
@@ -7310,7 +7308,6 @@ async function completeRuntimeMapRowChunks(
7310
7308
  ): Promise<RuntimeMapRowsMutationResult> {
7311
7309
  let updated = 0;
7312
7310
  const fencedRows: RuntimeMapRowsStaleRow[] = [];
7313
- const conflictKeys: string[] = [];
7314
7311
  for (const chunk of input.chunks) {
7315
7312
  const chunkKeys = chunk.map((row) => row.key);
7316
7313
  const chunkInputIndexes = chunk.map((row) => row.input_index);
@@ -7338,7 +7335,6 @@ async function completeRuntimeMapRowChunks(
7338
7335
  updated: number;
7339
7336
  matched_keys: string[];
7340
7337
  fenced_rows: unknown;
7341
- conflict_keys: string[];
7342
7338
  }>(
7343
7339
  `WITH updates AS (
7344
7340
  SELECT key_values._key,
@@ -7355,6 +7351,7 @@ async function completeRuntimeMapRowChunks(
7355
7351
  ),
7356
7352
  matched_updates AS (
7357
7353
  SELECT target._key AS matched_key,
7354
+ updates._key AS submitted_key,
7358
7355
  target._status AS prev_status,
7359
7356
  target._cell_meta AS prev_cell_meta,
7360
7357
  updates.input_index,
@@ -7364,40 +7361,6 @@ async function completeRuntimeMapRowChunks(
7364
7361
  JOIN ${sheetTable(session)} AS target
7365
7362
  ON target._key = updates._key
7366
7363
  ),
7367
- same_version_conflicts AS (
7368
- SELECT updates.matched_key AS _key
7369
- FROM matched_updates AS updates
7370
- JOIN ${sheetTable(session)} AS target
7371
- ON target._key = updates.matched_key
7372
- WHERE $13::bigint IS NOT NULL
7373
- AND target._write_version = $13::bigint
7374
- AND target._run_id = $5::text
7375
- AND target._status IN ('enriched', 'failed')
7376
- AND (
7377
- EXISTS (
7378
- SELECT 1
7379
- FROM unnest($8::text[]) AS field_values(field)
7380
- WHERE target._cell_meta -> field_values.field ->> 'runId' = $5::text
7381
- )
7382
- OR (
7383
- coalesce(target._attempt_owner_run_id, target._run_id) = $10::text
7384
- AND COALESCE(target._attempt_seq, 0) = $12::integer
7385
- )
7386
- )
7387
- AND (
7388
- target._status <> 'enriched'
7389
- OR (${targetChangedPatchedCellSql})
7390
- )
7391
- -- First terminal wins: suppress conflict for same owner+epoch already-terminal row.
7392
- -- Durable replay (omitted_terminal_replay) from same attempt epoch is idempotent.
7393
- AND NOT (
7394
- $10::text IS NOT NULL
7395
- AND $12::integer IS NOT NULL
7396
- AND target._status = 'enriched'
7397
- AND coalesce(target._attempt_owner_run_id, target._run_id) = $10::text
7398
- AND COALESCE(target._attempt_seq, 0) = $12::integer
7399
- )
7400
- ),
7401
7364
  applied_rows AS (
7402
7365
  UPDATE ${sheetTable(session)} AS target
7403
7366
  SET _status = 'enriched',
@@ -7414,7 +7377,6 @@ async function completeRuntimeMapRowChunks(
7414
7377
  _cell_meta = ${mergeRuntimeCellMetaPatchSql('target._cell_meta', 'updates.cell_meta_patch')}${input.physicalUpdateSetSql}
7415
7378
  FROM matched_updates AS updates
7416
7379
  WHERE target._key = updates.matched_key
7417
- AND NOT EXISTS (SELECT 1 FROM same_version_conflicts)
7418
7380
  AND ($13::bigint IS NULL OR target._write_version = $13::bigint)
7419
7381
  AND ($13::bigint IS NOT NULL OR target._status <> 'enriched')
7420
7382
  AND NOT (${newerTerminalRuntimeSheetRowSql('target', '$11::timestamptz', '$12::integer', '$9::text', '$10::text')})
@@ -7424,14 +7386,27 @@ async function completeRuntimeMapRowChunks(
7424
7386
  OR ($9::text IS NOT NULL AND ${activeRuntimeSheetAttemptFenceSql('target', '$9::text', '$10::text', '$11::timestamptz', '$12::integer')})
7425
7387
  OR ($9::text IS NOT NULL AND ${writableEnrichedAttemptSql})
7426
7388
  )
7427
- -- First terminal wins: skip UPDATE for already-terminal same-owner+epoch row.
7428
- -- Even though we excluded from conflicts, we still no-op here for idempotency.
7389
+ -- A same-version enriched row is already the durable result for this
7390
+ -- logical run. Provider retries may produce a different payload or
7391
+ -- timestamp; preserve the first terminal value. Input-index repair
7392
+ -- remains writable when the submitted key itself changed.
7429
7393
  AND NOT (
7430
- $10::text IS NOT NULL
7431
- AND $12::integer IS NOT NULL
7394
+ $13::bigint IS NOT NULL
7395
+ AND target._write_version = $13::bigint
7396
+ AND target._run_id = $5::text
7432
7397
  AND target._status = 'enriched'
7433
- AND coalesce(target._attempt_owner_run_id, target._run_id) = $10::text
7434
- AND COALESCE(target._attempt_seq, 0) = $12::integer
7398
+ AND updates.submitted_key = target._key
7399
+ AND (
7400
+ EXISTS (
7401
+ SELECT 1
7402
+ FROM unnest($8::text[]) AS field_values(field)
7403
+ WHERE target._cell_meta -> field_values.field ->> 'runId' = $5::text
7404
+ )
7405
+ OR (
7406
+ $10::text IS NOT NULL
7407
+ AND target._attempt_owner_run_id IS NOT DISTINCT FROM $10::text
7408
+ )
7409
+ )
7435
7410
  )
7436
7411
  AND (
7437
7412
  target._status <> 'enriched'
@@ -7564,7 +7539,6 @@ async function completeRuntimeMapRowChunks(
7564
7539
  (SELECT count(*)::int FROM applied_rows) AS updated,
7565
7540
  coalesce((SELECT array_agg(matched_key) FROM matched_updates), '{}'::text[]) AS matched_keys,
7566
7541
  coalesce((SELECT jsonb_agg(jsonb_build_object('key', _key, 'reason', reason, 'rowVersionStamp', row_write_version)) FROM fenced_rows), '[]'::jsonb) AS fenced_rows,
7567
- coalesce((SELECT array_agg(_key) FROM same_version_conflicts), '{}'::text[]) AS conflict_keys,
7568
7542
  (SELECT count(*)::int FROM summary_delta) AS summary_delta_count,
7569
7543
  (SELECT count(*)::int FROM column_delta) AS column_delta_count`,
7570
7544
  [
@@ -7585,19 +7559,12 @@ async function completeRuntimeMapRowChunks(
7585
7559
  );
7586
7560
  const matchedKeys = new Set(rows[0]?.matched_keys ?? []);
7587
7561
  const chunkFencedRows = parseRuntimeMapRowsFencedRows(rows[0]?.fenced_rows);
7588
- const chunkConflictKeys = rows[0]?.conflict_keys ?? [];
7589
7562
  // An exact-version replay that already produced the same terminal value is
7590
7563
  // accepted idempotently even when SQL has no physical mutation to apply.
7591
7564
  // Stale versions remain excluded through fenced_rows.
7592
7565
  const appliedCount = Number(rows[0]?.updated ?? 0);
7593
7566
  updated += appliedCount;
7594
7567
  fencedRows.push(...chunkFencedRows);
7595
- conflictKeys.push(...chunkConflictKeys);
7596
-
7597
- if (chunkConflictKeys.length > 0) {
7598
- continue;
7599
- }
7600
-
7601
7568
  if (matchedKeys.size === chunk.length) {
7602
7569
  continue;
7603
7570
  }
@@ -7615,9 +7582,8 @@ async function completeRuntimeMapRowChunks(
7615
7582
  );
7616
7583
  updated += repaired.updated;
7617
7584
  fencedRows.push(...repaired.fencedRows);
7618
- conflictKeys.push(...repaired.conflictKeys);
7619
7585
  }
7620
- return { updated, fencedRows, conflictKeys };
7586
+ return { updated, fencedRows };
7621
7587
  }
7622
7588
 
7623
7589
  async function completeRuntimeMapRowChunksWithInputIndexRepair(
@@ -7627,7 +7593,6 @@ async function completeRuntimeMapRowChunksWithInputIndexRepair(
7627
7593
  ): Promise<RuntimeMapRowsMutationResult> {
7628
7594
  let updated = 0;
7629
7595
  const fencedRows: RuntimeMapRowsStaleRow[] = [];
7630
- const conflictKeys: string[] = [];
7631
7596
  for (const chunk of input.chunks) {
7632
7597
  const chunkKeys = chunk.map((row) => row.key);
7633
7598
  const chunkInputIndexes = chunk.map((row) => row.input_index);
@@ -7647,7 +7612,6 @@ async function completeRuntimeMapRowChunksWithInputIndexRepair(
7647
7612
  const { rows } = await client.query<{
7648
7613
  _key?: string;
7649
7614
  fenced_rows?: unknown;
7650
- conflict_keys?: string[];
7651
7615
  updated?: number;
7652
7616
  }>(
7653
7617
  `WITH updates AS (
@@ -7679,40 +7643,6 @@ async function completeRuntimeMapRowChunksWithInputIndexRepair(
7679
7643
  )
7680
7644
  ORDER BY target._key, (target._key = updates._key) DESC
7681
7645
  ),
7682
- same_version_conflicts AS (
7683
- SELECT updates.submitted_key AS _key
7684
- FROM matched_updates AS updates
7685
- JOIN ${sheetTable(session)} AS target
7686
- ON target._key = updates.matched_key
7687
- WHERE $13::bigint IS NOT NULL
7688
- AND target._write_version = $13::bigint
7689
- AND target._run_id = $5::text
7690
- AND target._status IN ('enriched', 'failed')
7691
- AND (
7692
- EXISTS (
7693
- SELECT 1
7694
- FROM unnest($8::text[]) AS field_values(field)
7695
- WHERE target._cell_meta -> field_values.field ->> 'runId' = $5::text
7696
- )
7697
- OR (
7698
- coalesce(target._attempt_owner_run_id, target._run_id) = $10::text
7699
- AND COALESCE(target._attempt_seq, 0) = $12::integer
7700
- )
7701
- )
7702
- AND (
7703
- target._status <> 'enriched'
7704
- OR (${targetChangedPatchedCellSql})
7705
- )
7706
- -- First terminal wins: suppress conflict for same owner+epoch already-terminal row.
7707
- -- Durable replay (omitted_terminal_replay) from same attempt epoch is idempotent.
7708
- AND NOT (
7709
- $10::text IS NOT NULL
7710
- AND $12::integer IS NOT NULL
7711
- AND target._status = 'enriched'
7712
- AND coalesce(target._attempt_owner_run_id, target._run_id) = $10::text
7713
- AND COALESCE(target._attempt_seq, 0) = $12::integer
7714
- )
7715
- ),
7716
7646
  applied_rows AS (
7717
7647
  UPDATE ${sheetTable(session)} AS target
7718
7648
  SET _status = 'enriched',
@@ -7730,14 +7660,31 @@ async function completeRuntimeMapRowChunksWithInputIndexRepair(
7730
7660
  FROM matched_updates AS updates, ${sheetTable(session)} AS prev
7731
7661
  WHERE target._key = updates.matched_key
7732
7662
  AND prev._key = target._key
7733
- AND NOT EXISTS (SELECT 1 FROM same_version_conflicts)
7734
7663
  AND ($13::bigint IS NULL OR target._write_version = $13::bigint)
7735
7664
  AND ($13::bigint IS NOT NULL OR target._status <> 'enriched')
7736
7665
  AND NOT (${newerTerminalRuntimeSheetRowSql('target', '$11::timestamptz', '$12::integer', '$9::text', '$10::text')})
7737
7666
  AND (
7738
7667
  ($9::text IS NULL AND target._run_id = $5::text)
7739
7668
  OR ($9::text IS NOT NULL AND ${activeRuntimeSheetAttemptFenceSql('target', '$9::text', '$10::text', '$11::timestamptz', '$12::integer')})
7740
- OR ($9::text IS NOT NULL AND ${writableEnrichedAttemptSql})
7669
+ OR ($9::text IS NOT NULL AND ${writableEnrichedAttemptSql})
7670
+ )
7671
+ AND NOT (
7672
+ $13::bigint IS NOT NULL
7673
+ AND target._write_version = $13::bigint
7674
+ AND target._run_id = $5::text
7675
+ AND target._status = 'enriched'
7676
+ AND updates.submitted_key = target._key
7677
+ AND (
7678
+ EXISTS (
7679
+ SELECT 1
7680
+ FROM unnest($8::text[]) AS field_values(field)
7681
+ WHERE target._cell_meta -> field_values.field ->> 'runId' = $5::text
7682
+ )
7683
+ OR (
7684
+ $10::text IS NOT NULL
7685
+ AND target._attempt_owner_run_id IS NOT DISTINCT FROM $10::text
7686
+ )
7687
+ )
7741
7688
  )
7742
7689
  AND (
7743
7690
  target._status <> 'enriched'
@@ -7866,8 +7813,7 @@ async function completeRuntimeMapRowChunksWithInputIndexRepair(
7866
7813
  )
7867
7814
  SELECT
7868
7815
  (SELECT count(*)::int FROM applied_rows) AS updated,
7869
- coalesce((SELECT jsonb_agg(jsonb_build_object('key', _key, 'reason', reason, 'rowVersionStamp', row_write_version)) FROM fenced_rows), '[]'::jsonb) AS fenced_rows,
7870
- coalesce((SELECT array_agg(_key) FROM same_version_conflicts), '{}'::text[]) AS conflict_keys`,
7816
+ coalesce((SELECT jsonb_agg(jsonb_build_object('key', _key, 'reason', reason, 'rowVersionStamp', row_write_version)) FROM fenced_rows), '[]'::jsonb) AS fenced_rows`,
7871
7817
  [
7872
7818
  chunkKeys,
7873
7819
  chunkInputIndexes,
@@ -7886,9 +7832,8 @@ async function completeRuntimeMapRowChunksWithInputIndexRepair(
7886
7832
  );
7887
7833
  updated += Number(rows[0]?.updated ?? 0);
7888
7834
  fencedRows.push(...parseRuntimeMapRowsFencedRows(rows[0]?.fenced_rows));
7889
- conflictKeys.push(...(rows[0]?.conflict_keys ?? []));
7890
7835
  }
7891
- return { updated, fencedRows, conflictKeys };
7836
+ return { updated, fencedRows };
7892
7837
  }
7893
7838
 
7894
7839
  async function insertMissingCompletedMapRowChunks(
@@ -8084,7 +8029,6 @@ async function failRuntimeMapRowChunks(
8084
8029
  ): Promise<RuntimeMapRowsMutationResult> {
8085
8030
  let updated = 0;
8086
8031
  const fencedRows: RuntimeMapRowsStaleRow[] = [];
8087
- const conflictKeys: string[] = [];
8088
8032
  for (const chunk of input.chunks) {
8089
8033
  const chunkKeys = chunk.map((row) => row.key);
8090
8034
  const chunkInputIndexes = chunk.map((row) => row.input_index);
@@ -8117,7 +8061,6 @@ async function failRuntimeMapRowChunks(
8117
8061
  const { rows } = await client.query<{
8118
8062
  updated?: number;
8119
8063
  fenced_rows?: unknown;
8120
- conflict_keys?: string[];
8121
8064
  }>(
8122
8065
  `WITH updates AS (
8123
8066
  SELECT key_values._key,
@@ -8138,6 +8081,7 @@ async function failRuntimeMapRowChunks(
8138
8081
  matched_updates AS (
8139
8082
  SELECT DISTINCT ON (target._key)
8140
8083
  target._key AS matched_key,
8084
+ updates._key AS submitted_key,
8141
8085
  updates.data_patch,
8142
8086
  updates.cell_meta_patch,
8143
8087
  updates.error
@@ -8151,42 +8095,6 @@ async function failRuntimeMapRowChunks(
8151
8095
  )
8152
8096
  ORDER BY target._key, (target._key = updates._key) DESC
8153
8097
  ),
8154
- same_version_conflicts AS (
8155
- SELECT updates.matched_key AS _key
8156
- FROM matched_updates AS updates
8157
- JOIN ${sheetTable(session)} AS target
8158
- ON target._key = updates.matched_key
8159
- WHERE $16::bigint IS NOT NULL
8160
- AND target._write_version = $16::bigint
8161
- AND target._run_id = $6::text
8162
- AND target._status IN ('enriched', 'failed')
8163
- AND (
8164
- EXISTS (
8165
- SELECT 1
8166
- FROM unnest($9::text[]) AS field_values(field)
8167
- WHERE target._cell_meta -> field_values.field ->> 'runId' = $6::text
8168
- )
8169
- OR (
8170
- coalesce(target._attempt_owner_run_id, target._run_id) = $12::text
8171
- AND COALESCE(target._attempt_seq, 0) = $14::integer
8172
- )
8173
- )
8174
- AND (
8175
- target._status <> 'failed'
8176
- OR target._error IS DISTINCT FROM updates.error
8177
- OR (${targetChangedPatchedCellSql})
8178
- )
8179
- -- Same-owner terminal replay is not a conflict. Normal replay
8180
- -- preserves the first value below; explicit force-failure may update it.
8181
- -- Different owners and attempt epochs still conflict.
8182
- AND NOT (
8183
- $12::text IS NOT NULL
8184
- AND $14::integer IS NOT NULL
8185
- AND target._status IN ('failed', 'enriched')
8186
- AND coalesce(target._attempt_owner_run_id, target._run_id) = $12::text
8187
- AND COALESCE(target._attempt_seq, 0) = $14::integer
8188
- )
8189
- ),
8190
8098
  applied_rows AS (
8191
8099
  UPDATE ${sheetTable(session)} AS target
8192
8100
  SET _status = 'failed',
@@ -8203,7 +8111,6 @@ async function failRuntimeMapRowChunks(
8203
8111
  _cell_meta = ${mergeRuntimeCellMetaPatchSql('target._cell_meta', 'updates.cell_meta_patch')}${input.physicalUpdateSetSql}
8204
8112
  FROM matched_updates AS updates, ${sheetTable(session)} AS prev
8205
8113
  WHERE target._key = updates.matched_key
8206
- AND NOT EXISTS (SELECT 1 FROM same_version_conflicts)
8207
8114
  AND ($16::bigint IS NULL OR target._write_version = $16::bigint)
8208
8115
  AND prev._key = target._key
8209
8116
  AND ($15::boolean OR target._status <> 'enriched')
@@ -8212,15 +8119,16 @@ async function failRuntimeMapRowChunks(
8212
8119
  ($11::text IS NULL AND target._run_id = $6::text)
8213
8120
  OR ($11::text IS NOT NULL AND ${activeRuntimeSheetAttemptFenceSql('target', '$11::text', '$12::text', '$13::timestamptz', '$14::integer')})
8214
8121
  )
8215
- -- First terminal wins for failed rows too, BUT only when NOT force-failing.
8216
- -- Normal replay: no-op when already-failed OR already-enriched from same owner+epoch.
8122
+ -- Normal replay preserves the first terminal value for an exact
8123
+ -- same-version key. Force-failure is the explicit teardown escape
8124
+ -- hatch and may still overwrite it.
8217
8125
  AND NOT (
8218
8126
  $15::boolean = FALSE
8219
- AND $12::text IS NOT NULL
8220
- AND $14::integer IS NOT NULL
8127
+ AND $16::bigint IS NOT NULL
8128
+ AND target._write_version = $16::bigint
8129
+ AND target._run_id = $6::text
8221
8130
  AND target._status IN ('failed', 'enriched')
8222
- AND coalesce(target._attempt_owner_run_id, target._run_id) = $12::text
8223
- AND COALESCE(target._attempt_seq, 0) = $14::integer
8131
+ AND updates.submitted_key = target._key
8224
8132
  )
8225
8133
  RETURNING target._key, prev._status AS prev_status
8226
8134
  ),
@@ -8317,8 +8225,7 @@ async function failRuntimeMapRowChunks(
8317
8225
  )
8318
8226
  SELECT
8319
8227
  (SELECT count(*)::int FROM applied_rows) AS updated,
8320
- coalesce((SELECT jsonb_agg(jsonb_build_object('key', _key, 'reason', reason, 'rowVersionStamp', row_write_version)) FROM fenced_rows), '[]'::jsonb) AS fenced_rows,
8321
- coalesce((SELECT array_agg(_key) FROM same_version_conflicts), '{}'::text[]) AS conflict_keys`,
8228
+ coalesce((SELECT jsonb_agg(jsonb_build_object('key', _key, 'reason', reason, 'rowVersionStamp', row_write_version)) FROM fenced_rows), '[]'::jsonb) AS fenced_rows`,
8322
8229
  [
8323
8230
  chunkKeys,
8324
8231
  chunkInputIndexes,
@@ -8340,12 +8247,10 @@ async function failRuntimeMapRowChunks(
8340
8247
  );
8341
8248
  const appliedCount = Number(rows[0]?.updated ?? 0);
8342
8249
  const chunkFencedRows = parseRuntimeMapRowsFencedRows(rows[0]?.fenced_rows);
8343
- const chunkConflictKeys = rows[0]?.conflict_keys ?? [];
8344
8250
  updated += appliedCount;
8345
8251
  fencedRows.push(...chunkFencedRows);
8346
- conflictKeys.push(...chunkConflictKeys);
8347
8252
  }
8348
- return { updated, fencedRows, conflictKeys };
8253
+ return { updated, fencedRows };
8349
8254
  }
8350
8255
 
8351
8256
  /**
@@ -8556,10 +8461,9 @@ export async function completeRuntimeMapRows(
8556
8461
  return {
8557
8462
  updated: updated.updated + inserted.inserted,
8558
8463
  fencedRows: updated.fencedRows,
8559
- conflictKeys: updated.conflictKeys,
8560
8464
  };
8561
8465
  })()
8562
- : { updated: 0, fencedRows: [], conflictKeys: [] };
8466
+ : { updated: 0, fencedRows: [] };
8563
8467
  const failed =
8564
8468
  failedRows.length > 0
8565
8469
  ? await failRuntimeMapRowChunks(client, session, {
@@ -8578,17 +8482,7 @@ export async function completeRuntimeMapRows(
8578
8482
  input.tableNamespace,
8579
8483
  ),
8580
8484
  })
8581
- : { updated: 0, fencedRows: [], conflictKeys: [] };
8582
- const conflictKeys = [...completed.conflictKeys, ...failed.conflictKeys];
8583
- if (conflictKeys.length > 0) {
8584
- throw new Error(
8585
- `Runtime Sheet received the same write version with a different terminal payload for row(s): ${[
8586
- ...new Set(conflictKeys),
8587
- ]
8588
- .slice(0, 10)
8589
- .join(', ')}.`,
8590
- );
8591
- }
8485
+ : { updated: 0, fencedRows: [] };
8592
8486
  return classifyRuntimeMapRowsWrite({
8593
8487
  submittedKeys: uniqueRows.keys(),
8594
8488
  updated: completed.updated + failed.updated,
@@ -41,6 +41,12 @@ export type PlayRunLaunchConflictTelemetryInput = {
41
41
  artifactHash?: string | null;
42
42
  graphHash?: string | null;
43
43
  inputSha256?: string | null;
44
+ /** Named-intent digest of the incoming durable launch envelope. */
45
+ launchIdentityDigest?: string | null;
46
+ /** Admission-side conflict classification when available. */
47
+ conflictReason?: string | null;
48
+ /** Named launch-intent fields that differed, when the persisted envelope is available. */
49
+ conflictingFields?: readonly string[] | null;
44
50
  error?: unknown;
45
51
  };
46
52
 
@@ -113,6 +119,9 @@ export function buildPlayRunLaunchConflictTelemetry(
113
119
  revisionId: input.revisionId ?? null,
114
120
  artifactHash: input.artifactHash ?? null,
115
121
  graphHash: input.graphHash ?? null,
122
+ launchIdentityDigest: input.launchIdentityDigest ?? null,
123
+ conflictReason: input.conflictReason ?? null,
124
+ conflictingFields: input.conflictingFields ?? null,
116
125
  triage:
117
126
  level === 'error'
118
127
  ? {
@@ -216,6 +216,13 @@ export type PlaySchedulerSubmitInput = {
216
216
  runtimeTestFaultHeader?: string | null;
217
217
  /** Request-scoped, dev-only runtime policy overrides for black-box durability tests. */
218
218
  testPolicyOverrides?: RuntimeTestPolicyOverrides | null;
219
+ /**
220
+ * Digest of the named intent behind this launch. Runtime admission compares
221
+ * this optional field instead of treating transport and execution details as
222
+ * immutable identity. Older persisted rows omit it and keep the legacy
223
+ * comparison until they are gone.
224
+ */
225
+ launchIdentityDigest?: string | null;
219
226
  /** Millisecond epoch timestamp captured immediately before scheduler submit. */
220
227
  submittedAtMs?: number;
221
228
  /** Scheduler backend that owns orchestration for this run. */