deepline 0.3.65 → 0.3.67

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.
@@ -748,6 +748,8 @@ export type MonitorListEntry = {
748
748
  monitor_key?: string;
749
749
  status?: string;
750
750
  tool?: string;
751
+ /** Generic provider-authored event category for this deployed monitor. */
752
+ type?: string;
751
753
  name?: string;
752
754
  configured?: boolean;
753
755
  active?: boolean;
@@ -199,7 +199,7 @@ export const SDK_RELEASE = {
199
199
  // available at toolResponse.rawV2 while toolResponse.raw and all declared
200
200
  // getters keep their established compatibility behavior.
201
201
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
202
- version: '0.3.65',
202
+ version: '0.3.67',
203
203
  updateSummary:
204
204
  'Automatic CLI updates are now enabled by default. To opt out, run `deepline settings autoupdate off`; use `deepline settings autoupdate on` to re-enable updates or `deepline settings autoupdate pin <version>` to hold an exact release. This release also adds raw-v2 tool responses at toolResponse.rawV2 while preserving existing toolResponse.raw and declared getters.',
205
205
  packageCapabilities: {
@@ -915,6 +915,8 @@ export interface PlayRunListItem {
915
915
  startTime?: string | null;
916
916
  /** Unix epoch milliseconds when the run started, returned by normalized V2 run summaries. */
917
917
  startedAt?: number | string | null;
918
+ /** Unix epoch milliseconds when the run was created. */
919
+ createdAt?: number | string | null;
918
920
  /** ISO 8601 timestamp when the run finished. */
919
921
  closeTime?: string | null;
920
922
  /** Unix epoch milliseconds when the run finished, returned by normalized V2 run summaries. */
@@ -194,6 +194,10 @@ import {
194
194
  type PlayAuthoringRunScope,
195
195
  type PlayAuthoringRuntimeContext,
196
196
  } from '../plays/authoring-contract';
197
+ import {
198
+ formatCtxFetchHttpFailureDiagnostic,
199
+ tagLogProvenance,
200
+ } from './log-provenance';
197
201
  import {
198
202
  DURABLE_RECEIPT_WAIT_DELAY_MS,
199
203
  DURABLE_RECEIPT_WAIT_MAX_ATTEMPTS,
@@ -451,6 +455,10 @@ const DEFAULT_TOOL_RUNTIME_TIMEOUT_MS = 15 * 60 * 1000 + 30_000;
451
455
  const FETCH_TRANSPORT_MAX_ATTEMPTS =
452
456
  RUNTIME_RELIABILITY_POLICY.egress.fetchMaxAttempts;
453
457
  const FETCH_TRANSPORT_RETRY_DELAY_MS = 100;
458
+ // Diagnostic logs are retained in the context until terminalization. Keep a
459
+ // representative, deduplicated set so row-heavy continued failures cannot turn
460
+ // customer-safe observability into unbounded runner memory or log traffic.
461
+ const MAX_CTX_FETCH_HTTP_FAILURE_DIAGNOSTICS = 16;
454
462
  const CTX_FETCH_HEADERS_TIMEOUT_MS =
455
463
  RUNTIME_RELIABILITY_POLICY.egress.fetchHeadersTimeoutMs;
456
464
  const CTX_FETCH_BODY_TIMEOUT_MS =
@@ -2087,6 +2095,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
2087
2095
  #options: ContextOptions;
2088
2096
  private readonly executionScope: RunExecutionScope;
2089
2097
  private logBuffer: string[] = [];
2098
+ private readonly ctxFetchHttpFailureDiagnosticIdentities = new Set<string>();
2090
2099
  private checkpoint: PlayCheckpoint;
2091
2100
  private readonly durableCallCacheEpochMs: number;
2092
2101
  /**
@@ -10160,6 +10169,58 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10160
10169
  if (this.#options.verbose) console.log(line);
10161
10170
  }
10162
10171
 
10172
+ /**
10173
+ * Emit a runtime-authored diagnostic through the same durable log path as
10174
+ * ctx.log without changing the customer-authored log wire format.
10175
+ */
10176
+ private runtimeDiagnosticLog(message: string): void {
10177
+ assertNoSecretTaint(message, 'runtime diagnostic log');
10178
+ const line = tagLogProvenance(
10179
+ 'diagnostic',
10180
+ `[${new Date().toISOString()}] ${this.secretRedactor.redactRegisteredSecrets(message)}`,
10181
+ );
10182
+ this.logBuffer.push(line);
10183
+ this.#options.onLog?.(line);
10184
+ if (this.#options.verbose) console.log(line);
10185
+ }
10186
+
10187
+ /** A URL safe to persist in a customer-visible run log (origin only). */
10188
+ private ctxFetchDiagnosticUrl(url: string): string {
10189
+ try {
10190
+ const parsed = new URL(url);
10191
+ return parsed.origin;
10192
+ } catch {
10193
+ // ctx.fetch already parses its URL before this helper is reachable.
10194
+ return '[invalid-url]';
10195
+ }
10196
+ }
10197
+
10198
+ private logCtxFetchHttpFailure(input: {
10199
+ key: string;
10200
+ method: string;
10201
+ url: string;
10202
+ httpStatus: number;
10203
+ }): void {
10204
+ const url = this.ctxFetchDiagnosticUrl(input.url);
10205
+ const identity = `${input.key}\u0000${input.method}\u0000${url}\u0000${input.httpStatus}`;
10206
+ if (
10207
+ this.ctxFetchHttpFailureDiagnosticIdentities.has(identity) ||
10208
+ this.ctxFetchHttpFailureDiagnosticIdentities.size >=
10209
+ MAX_CTX_FETCH_HTTP_FAILURE_DIAGNOSTICS
10210
+ ) {
10211
+ return;
10212
+ }
10213
+ this.ctxFetchHttpFailureDiagnosticIdentities.add(identity);
10214
+ this.runtimeDiagnosticLog(
10215
+ formatCtxFetchHttpFailureDiagnostic({
10216
+ key: input.key,
10217
+ method: input.method,
10218
+ url,
10219
+ http_status: input.httpStatus,
10220
+ }),
10221
+ );
10222
+ }
10223
+
10163
10224
  async sleep(ms: number): Promise<void> {
10164
10225
  this.assertInlineChildContract('suspending_child');
10165
10226
  const delayMs =
@@ -10212,6 +10273,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10212
10273
 
10213
10274
  const url = input.toString();
10214
10275
  const parsedUrl = new URL(url);
10276
+ const method = (init.method ?? 'GET').toUpperCase();
10215
10277
  const urlContainsResolvedSecret =
10216
10278
  this.secretRedactor.containsRegisteredSecret(url, {
10217
10279
  includeEncoded: true,
@@ -10303,6 +10365,14 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10303
10365
  staleAfterSeconds: options?.staleAfterSeconds,
10304
10366
  transient: options?.transient === true,
10305
10367
  onRecovered: (output) => {
10368
+ if (!output.ok) {
10369
+ this.logCtxFetchHttpFailure({
10370
+ key: normalizedKey,
10371
+ method,
10372
+ url: output.url || url,
10373
+ httpStatus: output.status,
10374
+ });
10375
+ }
10306
10376
  if (!output.ok && this.currentAuthoringContractEdition >= 5) {
10307
10377
  throw new CtxFetchHttpError(output);
10308
10378
  }
@@ -10314,7 +10384,6 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10314
10384
  this.currentAuthoringContractEdition >= 5
10315
10385
  ),
10316
10386
  execute: async ({ retainExternalCallSlot }) => {
10317
- const method = (init.method ?? 'GET').toUpperCase();
10318
10387
  const secretHeaders = await this.resolveSecretAuth(secretAuth);
10319
10388
  const headers: Record<string, string> = {
10320
10389
  ...normalizeFetchHeaders(init.headers),
@@ -10349,6 +10418,15 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10349
10418
  'output' in existing
10350
10419
  ) {
10351
10420
  this.log(`ctx.fetch(${url}): recovered response from checkpoint`);
10421
+ const checkpointOutput = existing.output as PlayFetchResponse;
10422
+ if (!checkpointOutput.ok) {
10423
+ this.logCtxFetchHttpFailure({
10424
+ key: normalizedKey,
10425
+ method,
10426
+ url: checkpointOutput.url || url,
10427
+ httpStatus: checkpointOutput.status,
10428
+ });
10429
+ }
10352
10430
  if (this.durableDirectToolResultsBackedByReceipts) {
10353
10431
  // The outer durable receipt is the replay authority in hosted
10354
10432
  // runtimes. A legacy checkpoint fetch may seed that receipt
@@ -10356,7 +10434,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10356
10434
  // cache for the lifetime of a large map.
10357
10435
  delete this.checkpoint.resolvedBoundaries?.[boundaryId];
10358
10436
  }
10359
- return existing.output as PlayFetchResponse;
10437
+ return checkpointOutput;
10360
10438
  }
10361
10439
 
10362
10440
  if (!['GET', 'HEAD', 'OPTIONS'].includes(method)) {
@@ -10496,6 +10574,15 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10496
10574
  json: this.secretRedactor.redactKnownSecrets(rawJson),
10497
10575
  };
10498
10576
 
10577
+ if (!output.ok) {
10578
+ this.logCtxFetchHttpFailure({
10579
+ key: normalizedKey,
10580
+ method,
10581
+ url: output.url || url,
10582
+ httpStatus: output.status,
10583
+ });
10584
+ }
10585
+
10499
10586
  // Edition 5 adopts normal fetch semantics: a non-2xx response is
10500
10587
  // a failed durable operation. Throw before checkpoint/receipt
10501
10588
  // completion so the failure is never cached. Editions 1–4 retain
@@ -125,6 +125,85 @@ export function tagLogProvenance(
125
125
  return `${PROVENANCE_PREFIX}${provenance}${PROVENANCE_SENTINEL}${line}`;
126
126
  }
127
127
 
128
+ /**
129
+ * A customer-safe record of a `ctx.fetch` response that reached the server but
130
+ * was not successful. Its URL is destination-origin-only; it deliberately
131
+ * omits paths, query strings, request/response bodies, headers, receipt ids,
132
+ * and row identity: all of those can contain customer data or credentials.
133
+ * This one stable shape is shared by the
134
+ * runtime (emission), finalization (terminal warning), and CLI/tests
135
+ * (inspection), rather than each layer trying to infer an HTTP failure from
136
+ * arbitrary user logs.
137
+ */
138
+ export type CtxFetchHttpFailureDiagnostic = {
139
+ key: string;
140
+ method: string;
141
+ url: string;
142
+ http_status: number;
143
+ };
144
+
145
+ /** Guard terminal-transport diagnostics before they reach customer surfaces. */
146
+ export function isCtxFetchHttpFailureDiagnostic(
147
+ value: unknown,
148
+ ): value is CtxFetchHttpFailureDiagnostic {
149
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
150
+ return false;
151
+ }
152
+ const record = value as Record<string, unknown>;
153
+ return (
154
+ typeof record.key === 'string' &&
155
+ typeof record.method === 'string' &&
156
+ typeof record.url === 'string' &&
157
+ typeof record.http_status === 'number' &&
158
+ Number.isInteger(record.http_status)
159
+ );
160
+ }
161
+
162
+ export const CTX_FETCH_HTTP_FAILURE_LOG_PREFIX =
163
+ '[runtime.ctx_fetch_http_failure]';
164
+
165
+ /** Format the canonical durable log line for an observed non-2xx ctx.fetch. */
166
+ export function formatCtxFetchHttpFailureDiagnostic(
167
+ diagnostic: CtxFetchHttpFailureDiagnostic,
168
+ ): string {
169
+ return `${CTX_FETCH_HTTP_FAILURE_LOG_PREFIX} ${JSON.stringify(diagnostic)}`;
170
+ }
171
+
172
+ /**
173
+ * Parse only runtime-authored, canonical ctx.fetch diagnostics. Untagged
174
+ * legacy/user lines are never interpreted as evidence, even if they happen to
175
+ * contain the same words.
176
+ */
177
+ export function parseCtxFetchHttpFailureDiagnostic(
178
+ rawLine: string,
179
+ ): CtxFetchHttpFailureDiagnostic | null {
180
+ const tagged = readProvenanceTag(rawLine);
181
+ if (tagged.provenance !== 'diagnostic') return null;
182
+ const index = tagged.line.indexOf(CTX_FETCH_HTTP_FAILURE_LOG_PREFIX);
183
+ if (index === -1) return null;
184
+ const json = tagged.line
185
+ .slice(index + CTX_FETCH_HTTP_FAILURE_LOG_PREFIX.length)
186
+ .trim();
187
+ try {
188
+ const parsed: unknown = JSON.parse(json);
189
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
190
+ return null;
191
+ }
192
+ const record = parsed as Record<string, unknown>;
193
+ if (!isCtxFetchHttpFailureDiagnostic(record)) {
194
+ return null;
195
+ }
196
+ return {
197
+ key: record.key,
198
+ method: record.method,
199
+ url: record.url,
200
+ http_status: record.http_status,
201
+ };
202
+ } catch {
203
+ return null;
204
+ }
205
+ }
206
+
128
207
  /**
129
208
  * Read a structural provenance tag off a line, if present, and return the tag
130
209
  * plus the original untagged line. Returns `null` provenance when untagged.
@@ -141,9 +220,7 @@ export function readProvenanceTag(line: string): {
141
220
  return { provenance: null, line };
142
221
  }
143
222
  const candidate = line.slice(PROVENANCE_PREFIX.length, end);
144
- const provenance = LOG_PROVENANCE_CLASSES.includes(
145
- candidate as LogProvenance,
146
- )
223
+ const provenance = LOG_PROVENANCE_CLASSES.includes(candidate as LogProvenance)
147
224
  ? (candidate as LogProvenance)
148
225
  : null;
149
226
  return { provenance, line: line.slice(end + 1) };
@@ -17,6 +17,7 @@ import type { PlayRunFailureDetails } from './run-failure';
17
17
  import type { ToolExecutionErrorSchemaVersion } from '../plays/tool-execution-error';
18
18
  import type { ToolResponseContract } from '../plays/tool-response-contract';
19
19
  import type { FixtureBehavior } from './fixture-behavior';
20
+ import type { CtxFetchHttpFailureDiagnostic } from './log-provenance';
20
21
 
21
22
  export type PlayRunnerRateStateBackendConfig =
22
23
  | {
@@ -307,6 +308,8 @@ export type PlayRunnerResult =
307
308
  status: 'completed';
308
309
  output: unknown;
309
310
  outputWarnings?: PlayRunOutputWarning[];
311
+ /** Customer-safe runtime observations that must survive bounded log tails. */
312
+ runtimeDiagnostics?: CtxFetchHttpFailureDiagnostic[];
310
313
  outputRowCount?: number;
311
314
  logs: string[];
312
315
  stats: Record<string, unknown>;