deepline 0.3.196 → 0.3.197

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.
@@ -200,7 +200,7 @@ export const SDK_RELEASE = {
200
200
  // getters keep their established compatibility behavior.
201
201
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
202
202
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
203
- version: '0.3.196',
203
+ version: '0.3.197',
204
204
  updateSummary:
205
205
  'Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.',
206
206
  packageCapabilities: {
@@ -62,6 +62,7 @@ import {
62
62
  type RuntimeReceiptValueReference,
63
63
  } from '../runtime-receipt-value';
64
64
  import { PLAY_RUNTIME_WORK_RECEIPT_LEASE_TTL_MS } from '../lease-policy';
65
+ import { RUNTIME_CAPACITY_POLICY } from '../runtime-capacity-policy';
65
66
  import { RUNTIME_RELIABILITY_POLICY } from '../runtime-reliability-policy';
66
67
  import { COMPLETED_RECEIPT_CACHE_INSERT_LEASE_ID } from '../durable-receipt-execution';
67
68
  import {
@@ -159,6 +160,7 @@ import { requireRuntimeAuthorityDescriptor } from '../execution-capabilities';
159
160
  import { stringifyPostgresJson } from '../postgres-json';
160
161
  import {
161
162
  buildPlayLaunchIdentity,
163
+ PLAY_LAUNCH_ENVELOPE_FIELD_ROLES,
162
164
  withPlayLaunchIdentityDigest,
163
165
  } from '../launch-identity';
164
166
  import { PlayRunLaunchConflictError } from '../play-runs/admission/contracts';
@@ -173,7 +175,10 @@ import {
173
175
  recordSchedulerLaunchRead,
174
176
  type SchedulerLaunchReadCaller,
175
177
  } from './scheduler-launch-read-observability';
176
- import { emitPlayTelemetry } from '../runtime-telemetry';
178
+ import {
179
+ emitPlayRunLaunchConflictTelemetry,
180
+ emitPlayTelemetry,
181
+ } from '../runtime-telemetry';
177
182
  import { buildAbsurdPlayRunTask } from './absurd-play-run-task';
178
183
  import {
179
184
  buildAbsurdCronFireTask,
@@ -4509,6 +4514,38 @@ export async function submitPostgresAbsurdPlayRun(
4509
4514
  };
4510
4515
  }
4511
4516
 
4517
+ function isJsonObject(value: unknown): value is Record<string, unknown> {
4518
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
4519
+ }
4520
+
4521
+ /**
4522
+ * Reasons this build cannot prove a stored launch has the incoming named
4523
+ * intent. Empty only when the stored envelope is readable, contains no field
4524
+ * this build does not know (a newer writer may have added identity), and
4525
+ * every named intent field matches.
4526
+ */
4527
+ function launchIntentRematchBlockers(
4528
+ existingLaunchJson: unknown,
4529
+ incomingLaunch: PlaySchedulerSubmitInput,
4530
+ ): string[] {
4531
+ if (!isJsonObject(existingLaunchJson)) return ['launch'];
4532
+ const unknownFields = Object.keys(existingLaunchJson).filter(
4533
+ (field) =>
4534
+ !Object.prototype.hasOwnProperty.call(
4535
+ PLAY_LAUNCH_ENVELOPE_FIELD_ROLES,
4536
+ field,
4537
+ ),
4538
+ );
4539
+ if (unknownFields.length > 0) return unknownFields.sort();
4540
+ return launchIdentityConflictFields(existingLaunchJson, incomingLaunch);
4541
+ }
4542
+
4543
+ function launchIdentityDigestOf(launchJson: unknown): string | null {
4544
+ if (!isJsonObject(launchJson)) return null;
4545
+ const digest = launchJson.launchIdentityDigest;
4546
+ return typeof digest === 'string' && digest.trim() ? digest.trim() : null;
4547
+ }
4548
+
4512
4549
  function launchIdentityConflictFields(
4513
4550
  existingLaunchJson: unknown,
4514
4551
  incomingLaunch: PlaySchedulerSubmitInput,
@@ -4559,12 +4596,18 @@ async function submitPostgresSchedulerRunTransaction(
4559
4596
  options: unknown;
4560
4597
  recoverExistingTask?: boolean;
4561
4598
  },
4599
+ /**
4600
+ * Set only on the single re-admission after the current build proved the
4601
+ * existing Run has the same named launch intent (see the conflict branch).
4602
+ */
4603
+ existingLaunchIntentMatches = false,
4562
4604
  ): Promise<{
4563
4605
  absurdTaskId: string | null;
4564
4606
  runCreated: boolean;
4565
4607
  taskCreated: boolean;
4566
4608
  taskRecoveryRequired: boolean;
4567
4609
  }> {
4610
+ const requestedInput = input;
4568
4611
  // Persist the ABI with every new launch. Null is reserved for genuinely
4569
4612
  // historic rows already in storage, where readers interpret it as legacy.
4570
4613
  input = {
@@ -4591,6 +4634,9 @@ async function submitPostgresSchedulerRunTransaction(
4591
4634
  // fields are removed so they can never change the durable launch identity.
4592
4635
  input = withPlayLaunchIdentityDigest(durableInput);
4593
4636
  const existingOnlySql = requestedExistingOnly === true ? 'true' : 'false';
4637
+ const existingLaunchIntentMatchesSql = existingLaunchIntentMatches
4638
+ ? 'true'
4639
+ : 'false';
4594
4640
  const releaseId = input.absurdReleaseId?.trim() || null;
4595
4641
  const legacyPreviewRelease =
4596
4642
  releaseId === 'dev' || releaseId?.startsWith('dev-') === true;
@@ -4717,9 +4763,12 @@ async function submitPostgresSchedulerRunTransaction(
4717
4763
  FOR UPDATE
4718
4764
  ), existing_run AS (
4719
4765
  SELECT
4720
- ${quoteIdent(schemaName(options))}.runtime_launch_admission_matches(
4721
- coalesce(runs.admission_json, runs.launch_json),
4722
- $8::jsonb
4766
+ (
4767
+ ${existingLaunchIntentMatchesSql}
4768
+ OR ${quoteIdent(schemaName(options))}.runtime_launch_admission_matches(
4769
+ coalesce(runs.admission_json, runs.launch_json),
4770
+ $8::jsonb
4771
+ )
4723
4772
  ) AS matches_launch,
4724
4773
  coalesce(runs.admission_json, runs.launch_json) AS launch_json,
4725
4774
  runs.absurd_task_id,
@@ -4985,13 +5034,42 @@ async function submitPostgresSchedulerRunTransaction(
4985
5034
  );
4986
5035
  }
4987
5036
  if (row?.accepted === false && row.existing_run && !row.matching_existing) {
4988
- throw new PlayRunLaunchConflictError(input.runId, {
4989
- conflictReason: 'launch_identity_mismatch',
4990
- conflictingFields: launchIdentityConflictFields(
4991
- row.existing_launch_json,
4992
- input,
4993
- ),
5037
+ const rematchBlockers = launchIntentRematchBlockers(
5038
+ row.existing_launch_json,
5039
+ input,
5040
+ );
5041
+ if (existingLaunchIntentMatches || rematchBlockers.length > 0) {
5042
+ throw new PlayRunLaunchConflictError(input.runId, {
5043
+ conflictReason: 'launch_identity_mismatch',
5044
+ conflictingFields: rematchBlockers,
5045
+ });
5046
+ }
5047
+ // The stored envelope was written by another build (no digest, or a
5048
+ // digest this build cannot reproduce), but every named intent field
5049
+ // matches. Re-admit once as a match so the Run's normal matched path,
5050
+ // including task recovery, runs instead of failing an at-least-once retry.
5051
+ emitPlayTelemetry({
5052
+ level: 'warn',
5053
+ event: 'play.run.admission_intent_rematch',
5054
+ tag: '[play.run.admission_intent_rematch]',
5055
+ fields: {
5056
+ runId: input.runId,
5057
+ orgId: input.orgId,
5058
+ playName: input.playName,
5059
+ triggerSource: input.triggerSource ?? null,
5060
+ existingLaunchIdentityDigest: launchIdentityDigestOf(
5061
+ row.existing_launch_json,
5062
+ ),
5063
+ launchIdentityDigest: input.launchIdentityDigest ?? null,
5064
+ },
4994
5065
  });
5066
+ return submitPostgresSchedulerRunTransaction(
5067
+ client,
5068
+ requestedInput,
5069
+ options,
5070
+ absurdTask,
5071
+ true,
5072
+ );
4995
5073
  }
4996
5074
  if (row?.accepted === false && !row?.existing_run && requestedExistingOnly) {
4997
5075
  throw new Error(
@@ -5683,10 +5761,11 @@ export type FirePostgresSchedulerCronScheduleInput = {
5683
5761
  * cursor and is a harmless `superseded` no-op.
5684
5762
  *
5685
5763
  * A re-fire of the same occurrence whose deterministic Run already exists is
5686
- * an idempotent re-fire, not a no-op. The cursor advances when the existing
5687
- * launch identity matches, even if this statement did not create the Run.
5688
- * A genuine identity mismatch is returned as `stalled` so it cannot silently
5689
- * freeze the schedule behind an indistinguishable replay.
5764
+ * an idempotent re-fire, not a no-op. The cursor advances over any existing
5765
+ * Run, even when a mixed-version or competing launch makes the named identity
5766
+ * comparator disagree. The persisted Run is authoritative for this
5767
+ * deterministic occurrence; a conflict is emitted for owner-visible
5768
+ * diagnosis, never converted into a permanent blocked cursor.
5690
5769
  */
5691
5770
  export async function firePostgresSchedulerCronSchedule(
5692
5771
  client: PostgresSchedulerQueryClient,
@@ -5698,6 +5777,7 @@ export async function firePostgresSchedulerCronSchedule(
5698
5777
  runId: string;
5699
5778
  successorDueAtMs: number;
5700
5779
  reused: boolean;
5780
+ conflictingFields?: string[];
5701
5781
  }
5702
5782
  | { status: 'superseded' }
5703
5783
  | { status: 'stalled'; runId: string; reason: string }
@@ -5772,6 +5852,7 @@ export async function firePostgresSchedulerCronSchedule(
5772
5852
  schedule_current: boolean;
5773
5853
  existing_run: boolean;
5774
5854
  matching_existing: boolean;
5855
+ existing_launch_json: unknown | null;
5775
5856
  release_accepting: boolean;
5776
5857
  }>(
5777
5858
  `
@@ -5797,6 +5878,7 @@ export async function firePostgresSchedulerCronSchedule(
5797
5878
  FOR UPDATE
5798
5879
  ), existing_run AS MATERIALIZED (
5799
5880
  SELECT runs.run_id,
5881
+ coalesce(runs.admission_json, runs.launch_json) AS launch_json,
5800
5882
  ${quoteIdent(schemaName(options))}.runtime_launch_admission_matches(
5801
5883
  coalesce(runs.admission_json, runs.launch_json),
5802
5884
  $6::jsonb
@@ -5864,7 +5946,6 @@ export async function firePostgresSchedulerCronSchedule(
5864
5946
  OR (
5865
5947
  occurrence.schedule_current
5866
5948
  AND occurrence.existing_run
5867
- AND occurrence.matching_existing
5868
5949
  )
5869
5950
  ), successor_task_request AS (
5870
5951
  SELECT $11::text AS queue_name,
@@ -5953,6 +6034,7 @@ export async function firePostgresSchedulerCronSchedule(
5953
6034
  (SELECT schedule_current FROM occurrence) AS schedule_current,
5954
6035
  (SELECT existing_run FROM occurrence) AS existing_run,
5955
6036
  (SELECT matching_existing FROM occurrence) AS matching_existing,
6037
+ (SELECT launch_json FROM existing_run) AS existing_launch_json,
5956
6038
  coalesce(
5957
6039
  (SELECT accepting
5958
6040
  FROM ${tableName('runtime_release_admission', options)}
@@ -5986,24 +6068,53 @@ export async function firePostgresSchedulerCronSchedule(
5986
6068
  );
5987
6069
  }
5988
6070
  if (fired.advanced === true) {
6071
+ const conflictingFields =
6072
+ fired.existing_run === true && fired.matching_existing !== true
6073
+ ? launchIntentRematchBlockers(fired.existing_launch_json, launch)
6074
+ : [];
6075
+ if (conflictingFields.length === 0 && fired.existing_run === true) {
6076
+ emitPlayTelemetry({
6077
+ level: 'warn',
6078
+ event: 'play.run.admission_intent_rematch',
6079
+ tag: '[play.run.admission_intent_rematch]',
6080
+ fields: {
6081
+ runId: launch.runId,
6082
+ orgId: launch.orgId,
6083
+ playName: launch.playName,
6084
+ triggerSource: 'cron',
6085
+ scheduleId: schedule.scheduleId,
6086
+ existingLaunchIdentityDigest: launchIdentityDigestOf(
6087
+ fired.existing_launch_json,
6088
+ ),
6089
+ launchIdentityDigest: launch.launchIdentityDigest ?? null,
6090
+ },
6091
+ });
6092
+ } else if (conflictingFields.length > 0) {
6093
+ const conflict = new Error(
6094
+ `Cron occurrence "${launch.runId}" already exists with a different launch identity: ${conflictingFields.join(', ')}`,
6095
+ );
6096
+ emitPlayRunLaunchConflictTelemetry({
6097
+ orgId: launch.orgId,
6098
+ runId: launch.runId,
6099
+ playName: launch.playName,
6100
+ triggerSource: 'cron',
6101
+ workflowFamilyKey: launch.workflowFamilyKey,
6102
+ revisionId: launch.revisionId,
6103
+ artifactHash: launch.artifactHash,
6104
+ graphHash: launch.graphHash,
6105
+ inputSha256: launch.inputSha256,
6106
+ launchIdentityDigest: launch.launchIdentityDigest,
6107
+ conflictReason: 'launch_identity_mismatch',
6108
+ conflictingFields,
6109
+ error: conflict,
6110
+ });
6111
+ }
5989
6112
  return {
5990
6113
  status: 'admitted',
5991
6114
  runId: launch.runId,
5992
6115
  successorDueAtMs,
5993
6116
  reused: fired.run_created !== true,
5994
- };
5995
- }
5996
- if (
5997
- fired.schedule_current === true &&
5998
- fired.existing_run === true &&
5999
- fired.matching_existing !== true
6000
- ) {
6001
- return {
6002
- status: 'stalled',
6003
- runId: launch.runId,
6004
- reason:
6005
- `Cron schedule "${schedule.scheduleId}" generation ${schedule.generation} cannot advance past ` +
6006
- `occurrence ${schedule.dueAtMs}: Run "${launch.runId}" already exists with a different launch identity.`,
6117
+ ...(conflictingFields.length > 0 ? { conflictingFields } : {}),
6007
6118
  };
6008
6119
  }
6009
6120
  return { status: 'superseded' };
@@ -16711,7 +16822,12 @@ export async function listPostgresSchedulerOutboxMaterializationPlans(
16711
16822
  */
16712
16823
  export type RunLedgerOutboxTokenAuthority = 'local_mint' | 'gateway';
16713
16824
 
16714
- const POSTGRES_SCHEDULER_CANCELLATION_OUTBOX_LEASE_SECONDS = 15;
16825
+ const POSTGRES_SCHEDULER_CANCELLATION_OUTBOX_LEASE_SECONDS =
16826
+ // The direct projection can run for the full app-runtime request deadline.
16827
+ // Keep another 30 seconds for dispatch setup and acknowledgement so a
16828
+ // background projector cannot reclaim an append that is still in flight.
16829
+ Math.ceil(RUNTIME_CAPACITY_POLICY.receiptGateway.requestTimeoutMs / 1_000) +
16830
+ 30;
16715
16831
 
16716
16832
  /**
16717
16833
  * Stable lease identity shared by the atomic scheduler cancellation and the
package/dist/cli/index.js CHANGED
@@ -3051,7 +3051,7 @@ var SDK_RELEASE = {
3051
3051
  // getters keep their established compatibility behavior.
3052
3052
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
3053
3053
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
3054
- version: "0.3.196",
3054
+ version: "0.3.197",
3055
3055
  updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
3056
3056
  packageCapabilities: {
3057
3057
  updatePreferences: 1
@@ -3046,7 +3046,7 @@ var SDK_RELEASE = {
3046
3046
  // getters keep their established compatibility behavior.
3047
3047
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
3048
3048
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
3049
- version: "0.3.196",
3049
+ version: "0.3.197",
3050
3050
  updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
3051
3051
  packageCapabilities: {
3052
3052
  updatePreferences: 1
package/dist/index.js CHANGED
@@ -864,7 +864,7 @@ var SDK_RELEASE = {
864
864
  // getters keep their established compatibility behavior.
865
865
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
866
866
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
867
- version: "0.3.196",
867
+ version: "0.3.197",
868
868
  updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
869
869
  packageCapabilities: {
870
870
  updatePreferences: 1
package/dist/index.mjs CHANGED
@@ -768,7 +768,7 @@ var SDK_RELEASE = {
768
768
  // getters keep their established compatibility behavior.
769
769
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
770
770
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
771
- version: "0.3.196",
771
+ version: "0.3.197",
772
772
  updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
773
773
  packageCapabilities: {
774
774
  updatePreferences: 1
@@ -149,7 +149,7 @@ type SdkRelease = {
149
149
  supportPolicy: SdkSupportPolicy;
150
150
  };
151
151
  declare const SDK_RELEASE: {
152
- readonly version: "0.3.196";
152
+ readonly version: "0.3.197";
153
153
  readonly updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.";
154
154
  readonly packageCapabilities: {
155
155
  readonly updatePreferences: 1;
package/dist/release.d.ts CHANGED
@@ -149,7 +149,7 @@ type SdkRelease = {
149
149
  supportPolicy: SdkSupportPolicy;
150
150
  };
151
151
  declare const SDK_RELEASE: {
152
- readonly version: "0.3.196";
152
+ readonly version: "0.3.197";
153
153
  readonly updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.";
154
154
  readonly packageCapabilities: {
155
155
  readonly updatePreferences: 1;
package/dist/release.js CHANGED
@@ -74,7 +74,7 @@ var SDK_RELEASE = {
74
74
  // getters keep their established compatibility behavior.
75
75
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
76
76
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
77
- version: "0.3.196",
77
+ version: "0.3.197",
78
78
  updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
79
79
  packageCapabilities: {
80
80
  updatePreferences: 1
package/dist/release.mjs CHANGED
@@ -48,7 +48,7 @@ var SDK_RELEASE = {
48
48
  // getters keep their established compatibility behavior.
49
49
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
50
50
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
51
- version: "0.3.196",
51
+ version: "0.3.197",
52
52
  updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
53
53
  packageCapabilities: {
54
54
  updatePreferences: 1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.3.196",
3
+ "version": "0.3.197",
4
4
  "description": "GTM data CLI and TypeScript SDK for coding agents",
5
5
  "license": "MIT",
6
6
  "homepage": "https://code.deepline.com",