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.
@@ -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.185',
203
+ version: '0.3.187',
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: {
@@ -1331,6 +1331,8 @@ export interface PlayDefinitionDetail {
1331
1331
  cronTimezone?: string | null;
1332
1332
  nextScheduledAt?: number | null;
1333
1333
  lastScheduledAt?: number | null;
1334
+ blockedAt?: number | null;
1335
+ blockedReasonCode?: string | null;
1334
1336
  blockedReason?: string | null;
1335
1337
  [key: string]: unknown;
1336
1338
  } | null;
@@ -1403,6 +1405,8 @@ export interface PlayTriggerStatus {
1403
1405
  cron: string | null;
1404
1406
  webhook: string | null;
1405
1407
  blockedReason: string | null;
1408
+ blockedReasonCode?: string;
1409
+ blockedAt?: number;
1406
1410
  }
1407
1411
 
1408
1412
  export interface ProductNotificationEventDefinition {
@@ -0,0 +1,229 @@
1
+ import { sha256Hex, stableStringify } from '../plays/row-identity';
2
+ import { LEGACY_MAX_CONCURRENT_EXTERNAL_CALLS } from './governor/policy';
3
+ import type { PlaySchedulerSubmitInput } from './scheduler-backend';
4
+
5
+ /** Optional launch-envelope field used by the mixed-release admission fence. */
6
+ export const PLAY_LAUNCH_IDENTITY_DIGEST_FIELD = 'launchIdentityDigest';
7
+ const PLAY_LAUNCH_IDENTITY_DIGEST_NAMESPACE = 'deepline:play-launch-identity';
8
+
9
+ export type PlayLaunchFieldRole = 'identity' | 'detail';
10
+
11
+ /**
12
+ * Keep this list exhaustive over the launch envelope. A newly added field must
13
+ * be classified before it can participate in launch admission.
14
+ */
15
+ export const PLAY_LAUNCH_ENVELOPE_FIELD_ROLES: {
16
+ readonly [K in keyof PlaySchedulerSubmitInput]-?: PlayLaunchFieldRole;
17
+ } = {
18
+ orgId: 'identity',
19
+ playName: 'identity',
20
+ playReference: 'identity',
21
+ definitionScope: 'identity',
22
+ revisionId: 'identity',
23
+ artifactStorageKey: 'identity',
24
+ artifactHash: 'identity',
25
+ graphHash: 'identity',
26
+ workflowFamilyKey: 'identity',
27
+ inputSha256: 'identity',
28
+ source: 'identity',
29
+ triggerSource: 'identity',
30
+ parentRunId: 'identity',
31
+ rootRunId: 'identity',
32
+ asyncLaunchKey: 'identity',
33
+ asyncInvocationKey: 'identity',
34
+ asyncRequestFingerprint: 'identity',
35
+ force: 'identity',
36
+ forceToolRefresh: 'identity',
37
+ maxConcurrentExternalCalls: 'identity',
38
+ maxConcurrentRows: 'identity',
39
+ integrationMode: 'identity',
40
+ runtimeSandboxPlacementPolicyId: 'identity',
41
+ input: 'identity',
42
+ inputFile: 'identity',
43
+ inlineInputFile: 'identity',
44
+ inlineCsv: 'identity',
45
+ packagedFiles: 'identity',
46
+ runId: 'detail',
47
+ existingOnly: 'detail',
48
+ asyncParentRunAttempt: 'detail',
49
+ asyncParentRunLeaseToken: 'detail',
50
+ asyncParentAttemptRequired: 'detail',
51
+ asyncAncestryPlayIds: 'detail',
52
+ playId: 'detail',
53
+ schedulerSchema: 'detail',
54
+ requiresRunCreatedProjection: 'detail',
55
+ runtimeArtifact: 'detail',
56
+ runtimeProtocolVersion: 'detail',
57
+ staticPipeline: 'detail',
58
+ inputSummary: 'detail',
59
+ inputBytes: 'detail',
60
+ inputFileId: 'detail',
61
+ replayedFromRunId: 'detail',
62
+ totalRows: 'detail',
63
+ contractSnapshot: 'detail',
64
+ executionPlan: 'detail',
65
+ preloadedDbSessions: 'detail',
66
+ preloadedDbSessionUnwrapKey: 'detail',
67
+ executorToken: 'detail',
68
+ runtimeAuthority: 'detail',
69
+ runtimeApiBaseUrl: 'detail',
70
+ runtimeTokenOrigin: 'detail',
71
+ baseUrl: 'detail',
72
+ coordinatorUrl: 'detail',
73
+ coordinatorInternalToken: 'detail',
74
+ runtimeDeployVersion: 'detail',
75
+ absurdReleaseId: 'detail',
76
+ absurdReleaseEnvironment: 'detail',
77
+ vercelProtectionBypassToken: 'detail',
78
+ preferredReceiptGatewayMachineId: 'detail',
79
+ runtimeTestFaultHeader: 'detail',
80
+ testPolicyOverrides: 'detail',
81
+ submittedAtMs: 'detail',
82
+ schedulerBackend: 'detail',
83
+ executionProfile: 'detail',
84
+ runtimeBackend: 'detail',
85
+ queueMaxDelaySeconds: 'detail',
86
+ queuePriority: 'detail',
87
+ userEmail: 'detail',
88
+ userId: 'detail',
89
+ launchIdentityDigest: 'detail',
90
+ };
91
+
92
+ export const PLAY_LAUNCH_IDENTITY_FIELDS_COMPARED_OUTSIDE_DIGEST: readonly string[] =
93
+ Object.freeze(['runtimeSandboxPlacementPolicyId']);
94
+
95
+ export const PLAY_LAUNCH_IDENTITY_FIELDS: readonly string[] = Object.freeze(
96
+ Object.entries(PLAY_LAUNCH_ENVELOPE_FIELD_ROLES)
97
+ .filter(([, role]) => role === 'identity')
98
+ .map(([field]) => field)
99
+ .sort(),
100
+ );
101
+
102
+ export const PLAY_LAUNCH_DETAIL_FIELDS: readonly string[] = Object.freeze(
103
+ Object.entries(PLAY_LAUNCH_ENVELOPE_FIELD_ROLES)
104
+ .filter(([, role]) => role === 'detail')
105
+ .map(([field]) => field)
106
+ .sort(),
107
+ );
108
+
109
+ export type PlayLaunchIdentitySource = Readonly<
110
+ Partial<Record<keyof PlaySchedulerSubmitInput, unknown>>
111
+ >;
112
+
113
+ function text(value: unknown): string | null {
114
+ return typeof value === 'string' && value.trim() ? value.trim() : null;
115
+ }
116
+
117
+ function flag(value: unknown): boolean {
118
+ return value === true;
119
+ }
120
+
121
+ function count(value: unknown): number | null {
122
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
123
+ }
124
+
125
+ function record(value: unknown): Record<string, unknown> | null {
126
+ return value && typeof value === 'object' && !Array.isArray(value)
127
+ ? (value as Record<string, unknown>)
128
+ : null;
129
+ }
130
+
131
+ function stagedFileIdentity(value: unknown): Record<string, unknown> | null {
132
+ const file = record(value);
133
+ if (!file) return null;
134
+ return {
135
+ logicalPath:
136
+ text(file.logicalPath) ?? text(file.path) ?? text(file.playPath),
137
+ fileName: text(file.fileName) ?? text(file.name),
138
+ contentType: text(file.contentType),
139
+ bytes: count(file.bytes),
140
+ contentDigest:
141
+ typeof file.contentBase64 === 'string'
142
+ ? sha256Hex(file.contentBase64)
143
+ : typeof file.inlineText === 'string'
144
+ ? sha256Hex(file.inlineText)
145
+ : null,
146
+ };
147
+ }
148
+
149
+ function stagedFileListIdentity(
150
+ value: unknown,
151
+ ): Array<Record<string, unknown>> {
152
+ if (!Array.isArray(value)) return [];
153
+ return value
154
+ .map((entry) => stagedFileIdentity(entry))
155
+ .filter((entry): entry is Record<string, unknown> => entry !== null)
156
+ .sort((left, right) =>
157
+ stableStringify(left).localeCompare(stableStringify(right)),
158
+ );
159
+ }
160
+
161
+ /** Return only customer intent; transport, authority, and release details are excluded. */
162
+ export function buildPlayLaunchIdentity(
163
+ launch: PlayLaunchIdentitySource,
164
+ ): Record<string, unknown> {
165
+ const inlineCsv = record(launch.inlineCsv);
166
+ return {
167
+ orgId: text(launch.orgId),
168
+ playName: text(launch.playName),
169
+ playReference: text(launch.playReference) ?? text(launch.playName),
170
+ definitionScope: text(launch.definitionScope),
171
+ revisionId: text(launch.revisionId),
172
+ artifactStorageKey: text(launch.artifactStorageKey),
173
+ artifactHash: text(launch.artifactHash),
174
+ graphHash: text(launch.graphHash),
175
+ workflowFamilyKey: text(launch.workflowFamilyKey),
176
+ inputSha256: text(launch.inputSha256),
177
+ source: text(launch.source),
178
+ triggerSource: text(launch.triggerSource),
179
+ parentRunId: text(launch.parentRunId),
180
+ rootRunId: text(launch.rootRunId),
181
+ asyncLaunchKey: text(launch.asyncLaunchKey),
182
+ asyncInvocationKey: text(launch.asyncInvocationKey),
183
+ asyncRequestFingerprint: text(launch.asyncRequestFingerprint),
184
+ force: flag(launch.force),
185
+ forceToolRefresh: flag(launch.forceToolRefresh),
186
+ integrationMode: text(launch.integrationMode),
187
+ maxConcurrentExternalCalls:
188
+ count(launch.maxConcurrentExternalCalls) ??
189
+ LEGACY_MAX_CONCURRENT_EXTERNAL_CALLS,
190
+ maxConcurrentRows: count(launch.maxConcurrentRows),
191
+ inputDigest: sha256Hex(stableStringify(launch.input ?? {})),
192
+ inputFile: stagedFileIdentity(launch.inputFile),
193
+ inlineInputFile: stagedFileIdentity(launch.inlineInputFile),
194
+ inlineCsv: inlineCsv
195
+ ? {
196
+ name: text(inlineCsv.name),
197
+ rowsDigest: sha256Hex(stableStringify(inlineCsv.rows ?? [])),
198
+ }
199
+ : null,
200
+ packagedFiles: stagedFileListIdentity(launch.packagedFiles),
201
+ };
202
+ }
203
+
204
+ export const PLAY_LAUNCH_DIGESTED_IDENTITY_FIELDS: readonly string[] =
205
+ Object.freeze(
206
+ PLAY_LAUNCH_IDENTITY_FIELDS.filter(
207
+ (field) =>
208
+ !PLAY_LAUNCH_IDENTITY_FIELDS_COMPARED_OUTSIDE_DIGEST.includes(field),
209
+ ),
210
+ );
211
+
212
+ export function buildPlayLaunchIdentityDigest(
213
+ launch: PlayLaunchIdentitySource,
214
+ ): string {
215
+ return sha256Hex(
216
+ `${PLAY_LAUNCH_IDENTITY_DIGEST_NAMESPACE}\0${stableStringify(
217
+ buildPlayLaunchIdentity(launch),
218
+ )}`,
219
+ );
220
+ }
221
+
222
+ export function withPlayLaunchIdentityDigest<
223
+ T extends PlaySchedulerSubmitInput,
224
+ >(launch: T): T {
225
+ return {
226
+ ...launch,
227
+ launchIdentityDigest: buildPlayLaunchIdentityDigest(launch),
228
+ };
229
+ }
@@ -42,16 +42,31 @@ export type PlayRunAdmissionJob = {
42
42
  admit(input: PlayRunAdmissionInput): Promise<PlayRunAdmissionOutcome>;
43
43
  };
44
44
 
45
+ export type PlayRunLaunchConflictReason = 'launch_identity_mismatch';
46
+
45
47
  /** A stable, machine-readable rejection for a reused run id with new intent. */
46
48
  export class PlayRunLaunchConflictError extends Error {
47
49
  readonly code = 'PLAY_RUN_LAUNCH_CONFLICT';
50
+ readonly conflictReason: PlayRunLaunchConflictReason | undefined;
51
+ readonly conflictingFields: string[] | undefined;
48
52
 
49
- constructor(runId: string, options?: { cause?: unknown }) {
53
+ constructor(
54
+ runId: string,
55
+ options?: {
56
+ cause?: unknown;
57
+ conflictReason?: PlayRunLaunchConflictReason;
58
+ conflictingFields?: readonly string[];
59
+ },
60
+ ) {
50
61
  super(
51
62
  `Play run "${runId}" already exists with a different launch.`,
52
63
  options,
53
64
  );
54
65
  this.name = 'PlayRunLaunchConflictError';
66
+ this.conflictReason = options?.conflictReason;
67
+ this.conflictingFields = options?.conflictingFields
68
+ ? [...options.conflictingFields]
69
+ : undefined;
55
70
  }
56
71
  }
57
72
 
@@ -54,6 +54,9 @@ export function createPostgresPlayRunAdmissionPorts(input: {
54
54
  artifactHash: admission.launch.artifactHash,
55
55
  graphHash: admission.launch.graphHash,
56
56
  inputSha256: admission.launch.inputSha256,
57
+ launchIdentityDigest: admission.launch.launchIdentityDigest,
58
+ conflictReason: conflict.conflictReason ?? null,
59
+ conflictingFields: conflict.conflictingFields ?? null,
57
60
  error: conflict,
58
61
  });
59
62
  throw conflict;