deepline 0.3.273 → 0.3.275

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.
Files changed (29) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +143 -14
  2. package/dist/bundling-sources/sdk/src/index.ts +4 -1
  3. package/dist/bundling-sources/sdk/src/play.ts +5 -11
  4. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  5. package/dist/bundling-sources/sdk/src/runs/log-cursor.ts +35 -0
  6. package/dist/bundling-sources/sdk/src/types.ts +10 -3
  7. package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +12 -26
  8. package/dist/bundling-sources/shared_libs/observability/scheduled-work-manual.ts +7 -3
  9. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +15 -7
  10. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd.ts +18 -18
  11. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +1 -0
  12. package/dist/bundling-sources/shared_libs/plays/docflow-binding-owner.ts +7 -4
  13. package/dist/bundling-sources/shared_libs/plays/table-csv.ts +152 -0
  14. package/dist/cli/index.js +418 -103
  15. package/dist/cli/index.mjs +418 -103
  16. package/dist/{client-5xpuGpRn.d.ts → client-5zY7LsId.d.ts} +72 -7
  17. package/dist/{client-D9annc6K.d.mts → client-CbNvJLgV.d.mts} +72 -7
  18. package/dist/index.d.mts +6 -12
  19. package/dist/index.d.ts +6 -12
  20. package/dist/index.js +90 -12
  21. package/dist/index.mjs +90 -12
  22. package/dist/install-integrity.json +4 -2
  23. package/dist/plays/run-request.d.mts +1 -1
  24. package/dist/plays/run-request.d.ts +1 -1
  25. package/dist/release.d.mts +1 -1
  26. package/dist/release.d.ts +1 -1
  27. package/dist/release.js +1 -1
  28. package/dist/release.mjs +1 -1
  29. package/package.json +1 -1
@@ -74,6 +74,7 @@ import {
74
74
  type ProductNotificationGroupDraft,
75
75
  } from '../../shared_libs/product-notifications/configuration.js';
76
76
  import { watchRunEvents as pollRunSnapshotEvents } from './runs/watch-polling.js';
77
+ import { encodeRunLogsCursor, parseRunLogsCursor } from './runs/log-cursor.js';
77
78
  import type {
78
79
  DeeplineClientOptions,
79
80
  ResolvedConfig,
@@ -626,6 +627,49 @@ export type RunsListPage = {
626
627
  hasMore?: boolean;
627
628
  };
628
629
 
630
+ /** Cursor-paged workspace run history, including runs of removed Plays. */
631
+ export type RunsHistoryOptions = {
632
+ /** Case-insensitive Play/workflow name, run ID, or status substring. */
633
+ search?: string;
634
+ /** Search classification from recorded trigger metadata; requires search. */
635
+ type?: 'all' | 'plays' | 'monitors';
636
+ cursor?: string;
637
+ limit?: number;
638
+ /** Must match the workspace authorized by the credential. */
639
+ orgId?: string;
640
+ signal?: AbortSignal;
641
+ };
642
+
643
+ export type RunsHistoryItem = {
644
+ runId: string;
645
+ workflowId: string;
646
+ workflowName: string;
647
+ playScope: 'org' | 'system' | null;
648
+ status: string;
649
+ createdAt: number;
650
+ updatedAt: number;
651
+ callerName: string | null;
652
+ callerEmail: string | null;
653
+ authorName: string | null;
654
+ authorEmail: string | null;
655
+ callerAgent: string | null;
656
+ executionMode: string | null;
657
+ bindingId: string | null;
658
+ parentWorkflowName: string | null;
659
+ outputRowCount: number | null;
660
+ hasOutput: boolean;
661
+ billingTotalCredits?: number;
662
+ triggerSource?: string;
663
+ };
664
+
665
+ /** An empty search page is incomplete while hasMore is true. */
666
+ export type RunsHistoryPage = {
667
+ runs: RunsHistoryItem[];
668
+ nextCursor: string | null;
669
+ hasMore: boolean;
670
+ avatarsByName?: Record<string, string>;
671
+ };
672
+
629
673
  export type PlaysListOptions = {
630
674
  origin?: 'prebuilt' | 'owned';
631
675
  grep?: string;
@@ -657,6 +701,8 @@ export type PlaysListPage = {
657
701
  export type RunsGetOptions = {
658
702
  /** Return the raw status payload instead of the compact package. */
659
703
  full?: boolean;
704
+ /** Explicitly read this run's recorded graph and source, without full results. */
705
+ configuration?: boolean;
660
706
  /** Attach a bounded end-of-stream window for a failed run. */
661
707
  failedLogs?: boolean;
662
708
  /** Requested failed-log lines to attach (default 20, hard-capped at 20). */
@@ -748,6 +794,12 @@ export type RunsLogsOptions = {
748
794
  all?: boolean;
749
795
  /** Read a terminal-failed run's last 20 retained lines, or fewer with `limit`. Retention loss returns an explicit association and warning. */
750
796
  failed?: boolean;
797
+ /**
798
+ * Continue with the next OLDER page: pass a prior result's `nextCursor`.
799
+ * The page holds the last `limit` stored lines before that cursor (with
800
+ * `all`, every stored line before it).
801
+ */
802
+ cursor?: string;
751
803
  };
752
804
 
753
805
  /** Retained text for one run. Counts and retention flags describe the stored stream, not every original console call. */
@@ -764,8 +816,14 @@ export type RunsLogsResult = {
764
816
  lastSequence: number | null;
765
817
  /** Tail/all: returned count is below total count. Failed: the stream lost bodies to retention. Check `logsTruncated` to distinguish retention loss from a selected tail. */
766
818
  truncated: boolean;
767
- /** More retained lines exist after `lastSequence`; always false for the designed failure window. */
819
+ /** Older stored lines exist before this page; `nextCursor` reads them. */
768
820
  hasMore: boolean;
821
+ /**
822
+ * Opaque cursor for the next older page (`runs.logs(id, { cursor })`),
823
+ * null when this page reaches the oldest stored line. Absent for the failed
824
+ * view, which is a complete designed window rather than a pageable slice.
825
+ */
826
+ nextCursor?: string | null;
769
827
  /** Formatted text lines in stored sequence order; structured context is available through event search. */
770
828
  entries: string[];
771
829
  /** Selected public log view. */
@@ -965,6 +1023,8 @@ export type RunsNamespace = {
965
1023
  list: (options: RunsListOptions) => Promise<PlayRunListItem[]>;
966
1024
  /** Read one run page with total, offset, limit and completeness metadata. */
967
1025
  listPage: (options: RunsListOptions) => Promise<RunsListPage>;
1026
+ /** Search/page workspace run history newest first; follow empty continuing pages. */
1027
+ history: (options?: RunsHistoryOptions) => Promise<RunsHistoryPage>;
968
1028
  /** Poll run snapshots every two seconds until terminal status. */
969
1029
  watch: (runId: string, options?: RunsWatchOptions) => Promise<PlayStatus>;
970
1030
  /** @deprecated Use `watch`; retained as a source-compatible alias. */
@@ -2709,6 +2769,7 @@ export class DeeplineClient {
2709
2769
  status: (runId) => this.getRunLifecycleStatus(runId),
2710
2770
  list: (options) => this.listRuns(options),
2711
2771
  listPage: (options) => this.listRunsPage(options),
2772
+ history: (options) => this.getRunsHistory(options),
2712
2773
  watch: (runId, options) => this.watchRunToTerminal(runId, options),
2713
2774
  tail: (runId, options) => this.tailRun(runId, options),
2714
2775
  logs: (runId, options) => this.getRunLogs(runId, options),
@@ -2890,7 +2951,7 @@ export class DeeplineClient {
2890
2951
  play: Pick<PlayListItem, 'name' | 'reference' | 'inputSchema'>,
2891
2952
  ): string {
2892
2953
  const target = play.reference || play.name;
2893
- return `deepline plays run ${target}${playInputExampleArgs(play.inputSchema)} --watch`;
2954
+ return `deepline plays run ${target}${playInputExampleArgs(play.inputSchema)}`;
2894
2955
  }
2895
2956
 
2896
2957
  private starterPlayPath(
@@ -3689,7 +3750,7 @@ export class DeeplineClient {
3689
3750
  * Convenience wrapper around {@link registerPlayArtifact} plus
3690
3751
  * {@link startPlayRun}. This is the canonical file-backed path used by wrappers.
3691
3752
  * The returned id can be passed to {@link getPlayStatus} to retrieve the same
3692
- * durable `{ result }` object that the CLI prints after `--watch` completes.
3753
+ * durable `{ result }` object that the CLI prints after `plays run` completes.
3693
3754
  *
3694
3755
  * @param code - Source string fallback; the bundled artifact should be passed in `options.artifact`
3695
3756
  * @param csvPath - Path to input CSV file, or `null`
@@ -3871,7 +3932,7 @@ export class DeeplineClient {
3871
3932
  * Get the current status of a play execution.
3872
3933
  *
3873
3934
  * Internal/advanced primitive. Public callers should usually prefer
3874
- * {@link runPlay}, {@link PlayJob.get}, or `deepline plays run --watch`.
3935
+ * {@link runPlay}, {@link PlayJob.get}, or `deepline plays run`.
3875
3936
  *
3876
3937
  * @param workflowId - Play-run id from {@link startPlayRun}
3877
3938
  * @returns Current status with progress logs and partial results
@@ -4222,11 +4283,24 @@ export class DeeplineClient {
4222
4283
  if (options?.full === true) {
4223
4284
  params.set('full', 'true');
4224
4285
  }
4286
+ if (options?.configuration === true) {
4287
+ params.set('include', 'configuration');
4288
+ }
4225
4289
  const query = params.size > 0 ? `?${params.toString()}` : '';
4226
4290
  const response = await this.http.get<Record<string, unknown>>(
4227
4291
  `/api/v2/runs/${encodeURIComponent(runId)}${query}`,
4228
4292
  { headers: { 'x-deepline-run-status': '1' } },
4229
4293
  );
4294
+ if (
4295
+ options?.configuration === true &&
4296
+ !Object.hasOwn(response, 'contract')
4297
+ ) {
4298
+ throw new DeeplineError(
4299
+ 'This server does not support recorded configuration reads.',
4300
+ undefined,
4301
+ 'RUN_CONFIGURATION_UNAVAILABLE',
4302
+ );
4303
+ }
4230
4304
  const status = normalizePlayStatus(response);
4231
4305
  if (options?.failedLogs !== true || status.status !== 'failed') {
4232
4306
  return status;
@@ -4335,6 +4409,36 @@ export class DeeplineClient {
4335
4409
  return (await this.listRunsPage(options)).runs;
4336
4410
  }
4337
4411
 
4412
+ /** The same tenant-scoped run history searched by the dashboard and CLI. */
4413
+ async getRunsHistory(
4414
+ options: RunsHistoryOptions = {},
4415
+ ): Promise<RunsHistoryPage> {
4416
+ const params = new URLSearchParams();
4417
+ const search = options.search?.trim();
4418
+ if (search && search.length > 200) {
4419
+ throw new Error('Run search must be at most 200 characters.');
4420
+ }
4421
+ if (options.type && options.type !== 'all' && !search) {
4422
+ throw new Error('runs.history options.type requires options.search.');
4423
+ }
4424
+ if (search) params.set('search', search);
4425
+ if (search && options.type) params.set('type', options.type);
4426
+ if (options.cursor) params.set('cursor', options.cursor);
4427
+ if (options.orgId) params.set('orgId', options.orgId);
4428
+ if (options.limit !== undefined) {
4429
+ if (!Number.isSafeInteger(options.limit) || options.limit < 1) {
4430
+ throw new Error(
4431
+ 'runs.history options.limit must be a positive integer.',
4432
+ );
4433
+ }
4434
+ params.set('limit', String(options.limit));
4435
+ }
4436
+ return this.http.get<RunsHistoryPage>(
4437
+ `/api/v2/dashboard/runs-history?${params.toString()}`,
4438
+ { signal: options.signal },
4439
+ );
4440
+ }
4441
+
4338
4442
  /** Read one run page without discarding server pagination metadata. */
4339
4443
  async listRunsPage(options: RunsListOptions): Promise<RunsListPage> {
4340
4444
  const playName = options.play?.trim();
@@ -4477,6 +4581,13 @@ export class DeeplineClient {
4477
4581
  'INVALID_RUN_LOG_VIEW',
4478
4582
  );
4479
4583
  }
4584
+ if (options?.cursor !== undefined && options.failed === true) {
4585
+ throw new DeeplineError(
4586
+ 'runs.logs cannot combine failed with cursor.',
4587
+ undefined,
4588
+ 'INVALID_RUN_LOG_VIEW',
4589
+ );
4590
+ }
4480
4591
  if (options?.failed === true) {
4481
4592
  const requestedLimit =
4482
4593
  typeof options.limit === 'number' &&
@@ -4507,6 +4618,8 @@ export class DeeplineClient {
4507
4618
  ...(page.logsTruncated ? { logsTruncated: true } : {}),
4508
4619
  };
4509
4620
  }
4621
+ const beforeSequence =
4622
+ options?.cursor !== undefined ? parseRunLogsCursor(options.cursor) : null;
4510
4623
  const limit = options?.all
4511
4624
  ? Number.MAX_SAFE_INTEGER
4512
4625
  : typeof options?.limit === 'number' &&
@@ -4520,29 +4633,44 @@ export class DeeplineClient {
4520
4633
  { retryApiErrors: true },
4521
4634
  );
4522
4635
  // Probe for the run's stored extent, then read the LAST `limit` stored
4523
- // lines (matching the historical tail-slice semantics), paginating in
4524
- // server-capped pages until the window is exhausted.
4636
+ // lines (matching the historical tail-slice semantics) ending before the
4637
+ // cursor, paginating in server-capped pages until the window is exhausted.
4525
4638
  const probe = await fetchPage(0, 1);
4526
4639
  const lastStoredSeq = probe.lastStoredSeq;
4527
- let afterSeq = options?.all ? 0 : Math.max(0, lastStoredSeq - limit);
4640
+ const oldestStoredSeq = probe.entries[0]?.seq ?? null;
4641
+ const upperSeq =
4642
+ beforeSequence === null
4643
+ ? Number.POSITIVE_INFINITY
4644
+ : Math.min(beforeSequence - 1, lastStoredSeq);
4645
+ const windowEndSeq = Math.min(upperSeq, lastStoredSeq);
4646
+ let afterSeq = options?.all ? 0 : Math.max(0, windowEndSeq - limit);
4528
4647
  const entries: Array<{ seq: number; line: string }> = [];
4529
- while (entries.length < limit) {
4648
+ while (entries.length < limit && afterSeq < upperSeq) {
4530
4649
  const page = await fetchPage(
4531
4650
  afterSeq,
4532
- Math.min(RUN_LOGS_PAGE_LIMIT, limit - entries.length),
4651
+ Math.min(
4652
+ RUN_LOGS_PAGE_LIMIT,
4653
+ limit - entries.length,
4654
+ upperSeq - afterSeq,
4655
+ ),
4533
4656
  );
4534
- if (page.entries.length === 0) {
4657
+ const inWindow = page.entries.filter((entry) => entry.seq <= upperSeq);
4658
+ if (inWindow.length === 0) {
4535
4659
  break;
4536
4660
  }
4537
- entries.push(...page.entries);
4538
- afterSeq = page.entries[page.entries.length - 1]!.seq;
4539
- if (!page.hasMore) {
4661
+ entries.push(...inWindow);
4662
+ afterSeq = inWindow[inWindow.length - 1]!.seq;
4663
+ if (!page.hasMore || inWindow.length < page.entries.length) {
4540
4664
  break;
4541
4665
  }
4542
4666
  }
4543
4667
  const firstSequence = entries.length > 0 ? entries[0]!.seq : null;
4544
4668
  const lastSequence =
4545
4669
  entries.length > 0 ? entries[entries.length - 1]!.seq : null;
4670
+ const olderLinesExist =
4671
+ firstSequence !== null &&
4672
+ oldestStoredSeq !== null &&
4673
+ firstSequence > oldestStoredSeq;
4546
4674
  return {
4547
4675
  runId: probe.runId,
4548
4676
  totalCount: probe.totalLogCount,
@@ -4550,7 +4678,8 @@ export class DeeplineClient {
4550
4678
  firstSequence,
4551
4679
  lastSequence,
4552
4680
  truncated: entries.length < probe.totalLogCount,
4553
- hasMore: lastSequence !== null && lastSequence < lastStoredSeq,
4681
+ hasMore: olderLinesExist,
4682
+ nextCursor: olderLinesExist ? encodeRunLogsCursor(firstSequence) : null,
4554
4683
  entries: entries.map((entry) => entry.line),
4555
4684
  view: options?.all ? 'all' : 'tail',
4556
4685
  ...(probe.logsTruncated ? { logsTruncated: true } : {}),
@@ -50,7 +50,7 @@ export type {
50
50
  * });
51
51
  * ```
52
52
  *
53
- * Then run with the CLI: `deepline plays run --file my-play.play.ts --watch`
53
+ * Then run with the CLI: `deepline plays run --file my-play.play.ts`
54
54
  * Installed npm CLIs can also run saved or prebuilt plays by name.
55
55
  *
56
56
  * ## Architecture
@@ -139,6 +139,9 @@ export type {
139
139
  PlaySheetRowsResult,
140
140
  RunsListOptions,
141
141
  RunsListPage,
142
+ RunsHistoryOptions,
143
+ RunsHistoryItem,
144
+ RunsHistoryPage,
142
145
  RunLifecycleStatus,
143
146
  PlaysListOptions,
144
147
  PlaysListPage,
@@ -39,7 +39,7 @@
39
39
  * ```
40
40
  *
41
41
  * Then run via the CLI:
42
- * `deepline plays run --file my-play.play.ts --watch`.
42
+ * `deepline plays run --file my-play.play.ts`.
43
43
  * Installed npm CLIs can also run saved or prebuilt plays by name.
44
44
  *
45
45
  * ### Programmatic usage
@@ -481,16 +481,10 @@ export type CsvOptions = PlayAuthoringCsvOptions;
481
481
  export type DeeplinePlayRuntimeContext = PlayAuthoringRuntimeContext;
482
482
 
483
483
  /**
484
- * Handle to a running play execution.
485
- *
486
- * Provides methods to check status, stream logs, wait for completion,
487
- * or cancel the execution.
488
- *
489
- * This handle is the SDK-context equivalent of `deepline plays run --watch` and
490
- * `POST /api/v2/plays/run`: every surface returns a run id first, then exposes
491
- * the completed user output through `PlayJob.get()` or the status endpoint's
492
- * `result` field. Runtime logs are available from `status().progress.logs` and
493
- * are intentionally separate from the returned output object.
484
+ * Handle to a Play execution: inspect status, stream logs, await output, or cancel.
485
+ * Like `deepline plays run` and `POST /api/v2/plays/run`, it returns a run id first.
486
+ * Read completed output through `PlayJob.get()` or the status endpoint's `result` field;
487
+ * runtime logs in `status().progress.logs` are separate from that output.
494
488
  *
495
489
  * @typeParam TOutput - The play's return type
496
490
  *
@@ -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.273',
203
+ version: '0.3.275',
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: {
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Continuation cursor for the bounded run-log tail.
3
+ *
4
+ * `runs logs` returns the LAST `limit` stored lines, so the lines a caller has
5
+ * not seen yet are the OLDER ones. The cursor names the exclusive upper
6
+ * sequence bound of the next (older) page. Treat it as opaque; the `before:`
7
+ * prefix keeps it distinct from runtime event-search cursors, which share the
8
+ * `--cursor` flag on `deepline runs logs`.
9
+ */
10
+ const RUN_LOGS_CURSOR_PREFIX = 'before:';
11
+
12
+ export function encodeRunLogsCursor(beforeSequence: number): string {
13
+ return `${RUN_LOGS_CURSOR_PREFIX}${beforeSequence}`;
14
+ }
15
+
16
+ export function isRunLogsCursor(value: string): boolean {
17
+ return value.startsWith(RUN_LOGS_CURSOR_PREFIX);
18
+ }
19
+
20
+ /** Returns the exclusive upper sequence bound, or throws on a foreign token. */
21
+ export function parseRunLogsCursor(value: string): number {
22
+ const raw = isRunLogsCursor(value)
23
+ ? value.slice(RUN_LOGS_CURSOR_PREFIX.length)
24
+ : '';
25
+ const parsed = /^[1-9]\d*$/.test(raw) ? Number(raw) : NaN;
26
+ if (!Number.isSafeInteger(parsed)) {
27
+ throw Object.assign(
28
+ new Error(
29
+ `Invalid run log cursor ${JSON.stringify(value)}. Pass the nextCursor value from a previous deepline runs logs result.`,
30
+ ),
31
+ { exitCode: 2 },
32
+ );
33
+ }
34
+ return parsed;
35
+ }
@@ -1075,6 +1075,12 @@ export type PlayLiveEvent = LiveEventEnvelope<unknown> & {
1075
1075
  export interface StopPlayRunResult {
1076
1076
  /** Public play-run identifier the stop request targeted. */
1077
1077
  runId: string;
1078
+ /**
1079
+ * Typed stop outcome: `stopped` (terminal), `cancelling` (cancellation
1080
+ * durably queued; cleanup and the final run update are pending, not an
1081
+ * error), or `failed` (the stop was not confirmed). Absent on older servers.
1082
+ */
1083
+ state?: 'stopped' | 'cancelling' | 'failed';
1078
1084
  /** Whether the server confirmed the run was stopped. */
1079
1085
  stopped: boolean;
1080
1086
  /** True when the durable cancellation lane accepted the request and is draining. */
@@ -1084,11 +1090,12 @@ export interface StopPlayRunResult {
1084
1090
  /** True if HITL state or its same-message Slack card still needs retrying. */
1085
1091
  hitlCancellationPending?: boolean;
1086
1092
  /**
1087
- * True when the scheduler state for the run was stale and the stop could
1088
- * not be confirmed. Absent on older servers (treated as confirmed).
1093
+ * True only when the scheduler and the canonical run ledger disagree (for
1094
+ * example a terminal scheduler run whose ledger projection is pending), so
1095
+ * the stop could not be confirmed. Never set for a queued cancellation.
1089
1096
  */
1090
1097
  staleSchedulerState?: boolean;
1091
- /** Server-side error detail when the stop was not confirmed. */
1098
+ /** Server-side error detail when the stop failed (`state: 'failed'`). */
1092
1099
  error?: string;
1093
1100
  }
1094
1101
 
@@ -125,24 +125,6 @@
125
125
  "failureAlert": "github_workflow_run",
126
126
  "domainReportRequired": true
127
127
  },
128
- {
129
- "id": "github.convex-preview-usage-limit-reset",
130
- "name": "Reset Convex Preview usage limits",
131
- "scheduler": "github_actions",
132
- "cadence": "0 0 1 * *",
133
- "timezone": "UTC",
134
- "source": ".github/workflows/apply-preview-convex-usage-limits.yml",
135
- "locator": "schedule:",
136
- "owner": "platform",
137
- "criticality": "operational",
138
- "purpose": "Restore the strict per-deployment usage thresholds for current-project Convex Previews after their monthly UTC usage windows reset.",
139
- "successDefinition": "Every current-project Preview has all eight daily and monthly limits active at the strict baseline; only explicitly retired inventory entries are skipped.",
140
- "failureDefinition": "Preview inventory is unavailable, a deployment cannot be addressed, or any usage threshold cannot be set and verified.",
141
- "startGraceMs": 900000,
142
- "runTimeoutMs": 2700000,
143
- "failureAlert": "github_workflow_run",
144
- "domainReportRequired": true
145
- },
146
128
  {
147
129
  "id": "github.daily-slack-report",
148
130
  "name": "Daily Slack report",
@@ -325,15 +307,18 @@
325
307
  },
326
308
  {
327
309
  "id": "github.runtime-lifecycle",
328
- "name": "Scheduled Runtime Lifecycle Load Monitor",
310
+ "name": "On-Demand Runtime Lifecycle Load Check",
311
+ "legacyNames": [
312
+ "Scheduled Runtime Lifecycle Load Monitor"
313
+ ],
329
314
  "scheduler": "github_actions",
330
- "cadence": "10 1-23/2 * * *",
315
+ "cadence": "runAfter explicit workflow dispatch or PR label",
331
316
  "timezone": "UTC",
332
317
  "source": ".github/workflows/runtime-daytona-lifecycle-scheduled.yml",
333
- "locator": "schedule:",
318
+ "locator": "workflow_dispatch:",
334
319
  "owner": "runtime",
335
320
  "criticality": "operational",
336
- "purpose": "Exercise the canonical Daytona/Play runtime lifecycle against the exact healthy Preview target and retain load evidence.",
321
+ "purpose": "Exercise the canonical Daytona/Play runtime lifecycle against the exact healthy Preview target when explicitly requested and retain load evidence.",
337
322
  "successDefinition": "Target preparation, lifecycle bursts, correctness/latency checks, cleanup, and reporting complete.",
338
323
  "failureDefinition": "Target resolution, lifecycle execution, evaluation, cleanup, or reporting fails.",
339
324
  "startGraceMs": 600000,
@@ -361,19 +346,20 @@
361
346
  },
362
347
  {
363
348
  "id": "github.runtime-shape",
364
- "name": "Twice-Daily Play Runtime Dataset Matrix",
349
+ "name": "On-Demand Play Runtime Dataset Matrix",
365
350
  "legacyNames": [
351
+ "Twice-Daily Play Runtime Dataset Matrix",
366
352
  "Every-Six-Hours Play Runtime Dataset Matrix",
367
353
  "Every-Two-Hours Play Runtime Dataset Matrix"
368
354
  ],
369
355
  "scheduler": "github_actions",
370
- "cadence": "0 */12 * * *",
356
+ "cadence": "runAfter explicit workflow dispatch",
371
357
  "timezone": "UTC",
372
358
  "source": ".github/workflows/runtime-shape-hourly.yml",
373
- "locator": "schedule:",
359
+ "locator": "workflow_dispatch:",
374
360
  "owner": "runtime",
375
361
  "criticality": "operational",
376
- "purpose": "Run the exact-SHA Play runtime dataset-shape matrix across isolated Preview lanes.",
362
+ "purpose": "Run the exact-SHA Play runtime dataset-shape matrix across isolated Preview lanes when explicitly requested.",
377
363
  "successDefinition": "All required lanes pass and every lane is cleaned up with detailed results retained.",
378
364
  "failureDefinition": "A lane cannot prepare, violates a shape invariant, or cleanup/evidence is incomplete.",
379
365
  "startGraceMs": 600000,
@@ -44,10 +44,14 @@ export function isManuallyRunnableConvexCronJob(
44
44
  return manuallyRunnableConvexCronJobIds.has(jobId);
45
45
  }
46
46
 
47
- /** Preview cron roots run only when requested during a check. */
48
- export function isPreviewOnDemandScheduledWork(
47
+ /** Non-production cron roots run only when explicitly requested. */
48
+ export function isNonProductionOnDemandScheduledWork(
49
49
  jobId: string,
50
50
  environment: string,
51
51
  ): boolean {
52
- return environment === 'preview' && isManuallyRunnableConvexCronJob(jobId);
52
+ const normalizedEnvironment = environment.trim().toLowerCase();
53
+ return (
54
+ ['dev', 'development', 'preview'].includes(normalizedEnvironment) &&
55
+ isManuallyRunnableConvexCronJob(jobId)
56
+ );
53
57
  }
@@ -261,8 +261,11 @@ export type PlaySchedulerRunHandle = {
261
261
  observe(options?: {
262
262
  signal?: AbortSignal;
263
263
  }): AsyncIterable<PlaySchedulerProgressEvent>;
264
- /** Cooperatively cancel the run. */
265
- cancel(reason?: string): Promise<void>;
264
+ /**
265
+ * Cooperatively cancel the run. Resolves with the scheduler's typed
266
+ * outcome; a queued cancellation is an expected state, never an error.
267
+ */
268
+ cancel(reason?: string): Promise<PlaySchedulerCancelOutcome>;
266
269
  /** Inject an external event (HITL, webhook). */
267
270
  signal(payload: PlaySchedulerSignalPayload): Promise<void>;
268
271
  /** Block until terminal state and return final envelope. */
@@ -270,12 +273,17 @@ export type PlaySchedulerRunHandle = {
270
273
  };
271
274
 
272
275
  /**
273
- * The durable engine cancellation lane accepted the stop request, but the
274
- * scheduler run has not reached its authoritative terminal state yet.
275
- * Callers must surface this as an in-progress cancellation, never as success.
276
+ * Outcome of a scheduler cancel request.
277
+ *
278
+ * - `stopped`: the scheduler run is terminal (cancelled now, or it already
279
+ * reached a terminal state before the request).
280
+ * - `cancelling`: the durable engine cancellation lane accepted the request,
281
+ * but sandbox cleanup and the final run update are still pending. Callers
282
+ * surface this as an in-progress cancellation, never as success or failure.
276
283
  */
277
- export const RUNTIME_SCHEDULER_CANCELLATION_PENDING_CODE =
278
- 'RUNTIME_SCHEDULER_CANCELLATION_PENDING';
284
+ export type PlaySchedulerCancelOutcome =
285
+ | { state: 'stopped' }
286
+ | { state: 'cancelling' };
279
287
 
280
288
  export type PlaySchedulerSignalPayload = {
281
289
  kind: 'integration_event' | 'cancel' | 'custom';
@@ -25,6 +25,7 @@ import { PLAY_ADMISSION_MAX_ACTIVE_LIFECYCLES } from '../admission-transport-pol
25
25
  import {
26
26
  PLAY_SCHEDULER_BACKENDS,
27
27
  type PlaySchedulerBackend,
28
+ type PlaySchedulerCancelOutcome,
28
29
  type PlaySchedulerRunHandle,
29
30
  type PlaySchedulerSubmitInput,
30
31
  } from '../scheduler-backend';
@@ -46,7 +47,6 @@ import {
46
47
  withRuntimeSchedulerClient,
47
48
  withRuntimeSchedulerTransaction,
48
49
  } from './postgres-pool';
49
- import { RUNTIME_SCHEDULER_CANCELLATION_PENDING_CODE } from '../scheduler-backend';
50
50
  import { resolveRuntimeSchedulerSchemaEnv } from './runtime-scheduler-schema';
51
51
  import { withRuntimeSchedulerSubmitAdmission } from './submit-admission';
52
52
  import {
@@ -75,7 +75,10 @@ export type AbsurdSchedulerBackendOptions = AbsurdSchedulerOptions & {
75
75
  queueSuffix?: string | null;
76
76
  };
77
77
 
78
- const cancellationFlights = new Map<string, Promise<void>>();
78
+ const cancellationFlights = new Map<
79
+ string,
80
+ Promise<PlaySchedulerCancelOutcome>
81
+ >();
79
82
  const ABSURD_CANCEL_TASK_SAVEPOINT = 'deepline_cancel_absurd_task';
80
83
 
81
84
  function quotePostgresIdentifier(identifier: string): string {
@@ -127,8 +130,8 @@ async function cancelAbsurdTaskIfPresent(
127
130
 
128
131
  function runCancellationSingleFlight(
129
132
  key: string,
130
- operation: () => Promise<void>,
131
- ): Promise<void> {
133
+ operation: () => Promise<PlaySchedulerCancelOutcome>,
134
+ ): Promise<PlaySchedulerCancelOutcome> {
132
135
  const existing = cancellationFlights.get(key);
133
136
  if (existing) return existing;
134
137
  const flight = operation().finally(() => {
@@ -285,20 +288,17 @@ function absurdRunHandle(input: {
285
288
  { maxAttempts: 1 },
286
289
  );
287
290
 
288
- const requestCancellation = async (reason?: string) => {
289
- await runCancellationSingleFlight(cancellationKey, async () => {
290
- const queued = await queueCancellation(reason);
291
- if (queued) {
292
- const error = new Error(
293
- `Cancellation queued for ${input.runId}; sandbox cleanup and final run update are pending.`,
294
- );
295
- Object.assign(error, {
296
- code: RUNTIME_SCHEDULER_CANCELLATION_PENDING_CODE,
297
- });
298
- throw error;
299
- }
300
- });
301
- };
291
+ // A queued cancellation is the normal Absurd outcome: the worker owns
292
+ // sandbox cleanup and the final run update. Report it as a typed state, not
293
+ // an error; `stopped` means the scheduler result was already terminal.
294
+ const requestCancellation = (
295
+ reason?: string,
296
+ ): Promise<PlaySchedulerCancelOutcome> =>
297
+ runCancellationSingleFlight(cancellationKey, async () =>
298
+ (await queueCancellation(reason))
299
+ ? { state: 'cancelling' }
300
+ : { state: 'stopped' },
301
+ );
302
302
 
303
303
  return {
304
304
  ...delegate,
@@ -20098,6 +20098,7 @@ export function postgresSchedulerRunHandle(
20098
20098
  priority: 'critical',
20099
20099
  },
20100
20100
  );
20101
+ return { state: 'stopped' };
20101
20102
  },
20102
20103
  signal: async (payload) => {
20103
20104
  if (payload.kind === 'cancel') {
@@ -228,9 +228,7 @@ export function substepOwnSourceExtent(
228
228
  * another module, where the callee's own range is not even comparable to a line
229
229
  * number in this file.
230
230
  */
231
- function callSiteExtent(
232
- substep: PlayStaticSubstep,
233
- ): DocflowLineExtent | null {
231
+ function callSiteExtent(substep: PlayStaticSubstep): DocflowLineExtent | null {
234
232
  const range = substep.callSiteRange;
235
233
  return range ? { startLine: range.startLine, endLine: range.endLine } : null;
236
234
  }
@@ -350,7 +348,12 @@ export function resolveDocflowOwnerSubstep(
350
348
  binding: PlayDocflowBinding,
351
349
  substeps: readonly PlayStaticSubstep[],
352
350
  ): DocflowBindingJoin<DocflowOwnerSubstep> {
353
- const owners = substeps.filter(
351
+ // Docflow bindings are attached to diagram nodes, which may describe work
352
+ // inside a conditional arm, a row map, or a nested play. Resolve against the
353
+ // compiled tree rather than just its top-level entries; otherwise a branch
354
+ // dataset renders without its schema/row namespace and its cards can lose
355
+ // the exact run data needed by the inspector and row badges.
356
+ const owners = flattenSubsteps(substeps).filter(
354
357
  (substep): substep is DocflowOwnerSubstep =>
355
358
  substep.type === 'dataset' || substep.type === 'csv',
356
359
  );