deepline 0.3.297 → 0.3.299

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 (91) hide show
  1. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  2. package/dist/bundling-sources/shared_libs/monitors/quickstart-prompts.ts +15 -0
  3. package/dist/bundling-sources/shared_libs/monitors/starter-templates.ts +9 -98
  4. package/dist/bundling-sources/shared_libs/play-runtime/async-operation.ts +15 -2
  5. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +3 -1
  6. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +80 -33
  7. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-outbox-recovery.ts +115 -59
  8. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +15 -3
  9. package/dist/cli/auth-4WFBFN3L.js +31 -0
  10. package/dist/cli/{auth-FOHTQKQV.mjs → auth-GWIEEZWL.mjs} +4 -4
  11. package/dist/cli/{billing-NRKIBTKJ.js → billing-K5EVUVEH.js} +70 -70
  12. package/dist/cli/{billing-ZY3KZNZD.mjs → billing-O5RAKWXK.mjs} +4 -4
  13. package/dist/cli/{chunk-VIOM5OEQ.js → chunk-2BISKKLW.js} +58 -58
  14. package/dist/cli/{chunk-YH2XATMF.js → chunk-3M3LQJUV.js} +26 -26
  15. package/dist/cli/{chunk-FSPE246W.js → chunk-4B7VWFHW.js} +15 -15
  16. package/dist/cli/{chunk-YCCDPYIV.mjs → chunk-5465KL4F.mjs} +4 -4
  17. package/dist/cli/{chunk-QPBTAYNK.mjs → chunk-6ECTEH6V.mjs} +2 -2
  18. package/dist/cli/{chunk-4NYMO67X.js → chunk-ACSNZYU3.js} +2 -2
  19. package/dist/cli/{chunk-ZXJWXNGA.mjs → chunk-BQ63LCEL.mjs} +3 -3
  20. package/dist/cli/{chunk-PYXPP7NX.mjs → chunk-CJBMCJW7.mjs} +1 -1
  21. package/dist/cli/{chunk-B52G7BYN.js → chunk-CL4YLYGW.js} +6 -6
  22. package/dist/cli/{chunk-FHO5PO6Q.js → chunk-D2P4AM5M.js} +6 -6
  23. package/dist/cli/{chunk-OSUS6K5I.js → chunk-DMTWRFPD.js} +4 -4
  24. package/dist/cli/{chunk-PLKA4WFH.mjs → chunk-FKKCGFQ2.mjs} +3 -3
  25. package/dist/cli/{chunk-DBZX2IWV.mjs → chunk-FT2TIKOX.mjs} +2 -2
  26. package/dist/cli/{chunk-R2COLUYE.mjs → chunk-QAEIUQPS.mjs} +1 -1
  27. package/dist/cli/{chunk-IJGJKU4L.js → chunk-REFGDIMC.js} +1 -1
  28. package/dist/cli/{chunk-YFPOHUWQ.mjs → chunk-S34ZVHGJ.mjs} +1 -1
  29. package/dist/cli/{chunk-QIJ32UEW.js → chunk-SJK2LSDQ.js} +6 -6
  30. package/dist/cli/{chunk-SZJM7D7W.mjs → chunk-TRSF2X3K.mjs} +1 -1
  31. package/dist/cli/{chunk-AC3SC3AC.mjs → chunk-W2N5FICT.mjs} +1 -1
  32. package/dist/cli/{chunk-34A5RSXF.js → chunk-WF7XMZAC.js} +6 -6
  33. package/dist/cli/{chunk-V24XJVLX.js → chunk-XUMCCIOY.js} +48 -48
  34. package/dist/cli/{chunk-R5BISWXF.mjs → chunk-ZWUOF7WR.mjs} +1 -1
  35. package/dist/cli/{client-65MCTEMR.mjs → client-OXUEAZD2.mjs} +3 -3
  36. package/dist/cli/client-ZBLK3VOY.js +20 -0
  37. package/dist/cli/{csv-SHCW3IJL.js → csv-J24BXLDP.js} +12 -12
  38. package/dist/cli/{csv-TJWBSMR2.mjs → csv-NV6QBRGT.mjs} +3 -3
  39. package/dist/cli/{db-DN6YHQTC.js → db-FL4N5LXG.js} +20 -20
  40. package/dist/cli/{db-K7QXXM5C.mjs → db-IY4LHKLR.mjs} +4 -4
  41. package/dist/cli/{executions-FRQO6SJF.mjs → executions-72PNXJWI.mjs} +4 -4
  42. package/dist/cli/{executions-2POJWTCH.js → executions-OYGLF6K2.js} +6 -6
  43. package/dist/cli/{feedback-A6232HB5.mjs → feedback-NGPJBTUS.mjs} +3 -3
  44. package/dist/cli/{feedback-EEYZOH3S.js → feedback-WHOLOKBU.js} +6 -6
  45. package/dist/cli/git-GD5I7YOP.js +23 -0
  46. package/dist/cli/{git-FRVNZNIA.mjs → git-HQKCORLT.mjs} +4 -4
  47. package/dist/cli/index.js +73 -73
  48. package/dist/cli/index.mjs +30 -30
  49. package/dist/cli/{legacy-noops-NREP3YMQ.js → legacy-noops-5RHXWONK.js} +7 -7
  50. package/dist/cli/{legacy-noops-WEN2VQKS.mjs → legacy-noops-MCAHTGPU.mjs} +4 -4
  51. package/dist/cli/{monitors-EHHO4IGT.js → monitors-MC4QEQ7N.js} +85 -85
  52. package/dist/cli/{monitors-2F7GLG24.mjs → monitors-X2KE6CL6.mjs} +4 -4
  53. package/dist/cli/{org-TTFZKSMD.js → org-75TWPW5D.js} +60 -60
  54. package/dist/cli/{org-JONFO5PV.mjs → org-YW6EKMHL.mjs} +4 -4
  55. package/dist/cli/{play-N3JMRLKG.js → play-CAMPEI5I.js} +200 -200
  56. package/dist/cli/{play-Y7U2XU2K.mjs → play-DEBY5SCV.mjs} +6 -6
  57. package/dist/cli/{providers-CWPT5L3U.mjs → providers-IA77P6YJ.mjs} +4 -4
  58. package/dist/cli/{providers-I4V4QCOM.js → providers-VNOHQ2AS.js} +7 -7
  59. package/dist/cli/{quickstart-UBONJ6EY.mjs → quickstart-K7HQS543.mjs} +3 -3
  60. package/dist/cli/{quickstart-2DDUIWC5.js → quickstart-LDTV2HGI.js} +5 -5
  61. package/dist/cli/{search-BHCCYLF2.mjs → search-536X3UCU.mjs} +5 -5
  62. package/dist/cli/{search-Z72FUFXL.js → search-SN5OPRXZ.js} +6 -6
  63. package/dist/cli/{secrets-AE22MX42.js → secrets-2EW56WXT.js} +13 -13
  64. package/dist/cli/{secrets-BGD4PNTG.mjs → secrets-B3AJVZEX.mjs} +4 -4
  65. package/dist/cli/{sessions-7SIBBFHF.js → sessions-3VDMBHNS.js} +5 -5
  66. package/dist/cli/{sessions-JYSG6SUN.mjs → sessions-WBJUUGWS.mjs} +4 -4
  67. package/dist/cli/{settings-LCA42CL3.js → settings-KABYSG3X.js} +74 -74
  68. package/dist/cli/{settings-4XWN4JFT.mjs → settings-RLDDNKLV.mjs} +7 -7
  69. package/dist/cli/{setup-NID2M4GQ.js → setup-CSOSGT4H.js} +41 -41
  70. package/dist/cli/{setup-WMOKDWHV.mjs → setup-LXS6GANX.mjs} +6 -6
  71. package/dist/cli/{skills-6IL3LQUJ.mjs → skills-63SF5XTZ.mjs} +4 -4
  72. package/dist/cli/skills-C7MLZJEK.js +28 -0
  73. package/dist/cli/{tools-BOIRKQA4.mjs → tools-HNP35FVR.mjs} +5 -5
  74. package/dist/cli/{tools-TZLX4WAL.js → tools-RHL5K6UA.js} +50 -50
  75. package/dist/cli/{update-VF3BB3R7.mjs → update-BIUXK2GK.mjs} +6 -6
  76. package/dist/cli/update-NFJGKKZ6.js +28 -0
  77. package/dist/cli/{workflow-RUTOLKOS.js → workflow-45YNQNPE.js} +15 -15
  78. package/dist/cli/{workflow-UZKD5KU2.mjs → workflow-EKLCRCUD.mjs} +5 -5
  79. package/dist/index.js +6 -6
  80. package/dist/index.mjs +3 -3
  81. package/dist/install-integrity.json +68 -68
  82. package/dist/release.d.mts +1 -1
  83. package/dist/release.d.ts +1 -1
  84. package/dist/release.js +2 -2
  85. package/dist/release.mjs +1 -1
  86. package/package.json +1 -1
  87. package/dist/cli/auth-DLGUNY3B.js +0 -31
  88. package/dist/cli/client-QLERARAF.js +0 -20
  89. package/dist/cli/git-SZHVHZWU.js +0 -23
  90. package/dist/cli/skills-AYCZ4UXS.js +0 -28
  91. package/dist/cli/update-EEFUC76J.js +0 -28
@@ -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.297',
203
+ version: '0.3.299',
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: {
@@ -1,20 +1,35 @@
1
1
  export type MonitorQuickstartPrompt = {
2
+ id: string;
3
+ title: string;
4
+ description: string;
2
5
  prompt: string;
3
6
  icon: 'job-change' | 'new-hires' | 'hiring-signal';
4
7
  };
5
8
 
9
+ /** Shared by the first-run card, template gallery, and template discovery API. */
6
10
  export const MONITOR_QUICKSTART_PROMPTS: MonitorQuickstartPrompt[] = [
7
11
  {
12
+ id: 'new-leadership-hires',
13
+ title: 'New leadership hires',
14
+ description:
15
+ 'Watch target accounts for new VP Sales, Marketing, and Revenue hires.',
8
16
  prompt:
9
17
  '/deepline-gtm Monitor my target accounts for new VP Sales, Marketing, and Revenue hires.',
10
18
  icon: 'new-hires',
11
19
  },
12
20
  {
21
+ id: 'champion-job-change',
22
+ title: 'Job changes',
23
+ description: 'Find when people at your target accounts change jobs.',
13
24
  prompt:
14
25
  '/deepline-gtm Monitor when people at my target accounts change jobs.',
15
26
  icon: 'job-change',
16
27
  },
17
28
  {
29
+ id: 'job-post-keywords',
30
+ title: 'Job openings',
31
+ description:
32
+ 'Watch target accounts for hiring signals in the roles you sell to.',
18
33
  prompt:
19
34
  '/deepline-gtm Monitor my target accounts for job openings in the roles I sell to.',
20
35
  icon: 'hiring-signal',
@@ -1,3 +1,5 @@
1
+ import { MONITOR_QUICKSTART_PROMPTS } from './quickstart-prompts';
2
+
1
3
  export type MonitorStarterTemplate = {
2
4
  id: string;
3
5
  title: string;
@@ -5,101 +7,10 @@ export type MonitorStarterTemplate = {
5
7
  prompt: string;
6
8
  };
7
9
 
8
- export const MONITOR_STARTER_TEMPLATES: MonitorStarterTemplate[] = [
9
- {
10
- id: 'job-post-keywords',
11
- title: 'Job post keyword monitor',
12
- description:
13
- 'Watch target accounts for hiring signals that imply new budget, new projects, or urgency.',
14
- prompt: `Create a Deepline definePlay() workflow for a job post keyword monitor.
15
-
16
- Input: a list of target accounts, job title or description keywords, optional regions, and optional owner routing.
17
- Behavior: check each account for recent open roles that match the keywords, dedupe against prior matches, enrich each match with company context and job URL, and rank the signal by relevance.
18
- Output: one concise rep-ready message per owner with account name, matched role, why it matters, job URL, and recommended next step. Include Slack Block Kit formatting and an email fallback.
19
- Verification: add a small dry-run input, run deepline plays check, run one sample execution against a scratch output destination, and report exactly what changed and what was not published.`,
20
- },
21
- {
22
- id: 'champion-job-change',
23
- title: 'Champion job change monitor',
24
- description:
25
- 'Find when a known buyer, user, or champion joins a new account and route a warm-path action.',
26
- prompt: `Create a Deepline definePlay() workflow for a champion job change monitor.
27
-
28
- Input: known champions or contacts from CRM, their prior companies, owner routing rules, and optional target-account filters.
29
- Behavior: detect likely role or company changes, validate the new company and title, suppress weak matches, and connect the signal back to the owning rep or CSM.
30
- Output: one routed message per owner with champion name, old company, new company, new title, relationship context, source URL, and suggested outreach angle. Include Slack Block Kit formatting and an email fallback.
31
- Verification: add a small dry-run input, run deepline plays check, run one sample execution against a scratch output destination, and report exactly what changed and what was not published.`,
32
- },
33
- {
34
- id: 'website-page-change',
35
- title: 'Website page change monitor',
36
- description:
37
- 'Track pricing, docs, security, integrations, or product pages for GTM-relevant changes.',
38
- prompt: `Create a Deepline definePlay() workflow for a website page change monitor.
39
-
40
- Input: a list of account URLs, page categories to watch, a minimum change threshold, and owner routing rules.
41
- Behavior: fetch current page content, compare it with the prior snapshot, classify meaningful GTM changes, ignore noisy cosmetic edits, and store the new snapshot only after a successful run.
42
- Output: one routed message per owner with account name, changed URL, before/after summary, why the change matters, confidence, and suggested next step. Include Slack Block Kit formatting and an email fallback.
43
- Verification: add a small dry-run input, run deepline plays check, run one sample execution against a scratch output destination, and report exactly what changed and what was not published.`,
44
- },
45
- {
46
- id: 'website-visitor-intent',
47
- title: 'Website visitor intent monitor',
48
- description:
49
- 'Turn repeat high-value website visits into account-specific follow-up for the right owner.',
50
- prompt: `Create a Deepline definePlay() workflow for a website visitor intent monitor.
51
-
52
- Input: visitor or account intent events, high-intent page definitions, account matching rules, suppression rules, and owner routing.
53
- Behavior: group events by account, score intent from page depth and recency, suppress existing active opportunities when configured, and explain why the account is worth actioning.
54
- Output: one routed message per owner with account name, intent score, pages visited, timing, CRM context, and recommended next step. Include Slack Block Kit formatting and an email fallback.
55
- Verification: add a small dry-run input, run deepline plays check, run one sample execution against a scratch output destination, and report exactly what changed and what was not published.`,
56
- },
57
- {
58
- id: 'tech-stack-change',
59
- title: 'Tech stack change monitor',
60
- description:
61
- 'Detect install, removal, or competitor stack changes that create a concrete sales motion.',
62
- prompt: `Create a Deepline definePlay() workflow for a tech stack change monitor.
63
-
64
- Input: target accounts, technologies to watch, competitor or complementary-product tags, owner routing, and optional exclusion rules.
65
- Behavior: detect meaningful install or removal events, confirm with available evidence, classify the motion as displacement, expansion, integration, or renewal risk, and dedupe prior alerts.
66
- Output: one routed message per owner with account name, detected technology change, evidence, GTM interpretation, and suggested next step. Include Slack Block Kit formatting and an email fallback.
67
- Verification: add a small dry-run input, run deepline plays check, run one sample execution against a scratch output destination, and report exactly what changed and what was not published.`,
68
- },
69
- {
70
- id: 'social-community-mention',
71
- title: 'Social and community mention monitor',
72
- description:
73
- 'Catch relevant public mentions, buying questions, complaints, or competitor comparisons.',
74
- prompt: `Create a Deepline definePlay() workflow for a social and community mention monitor.
75
-
76
- Input: keywords, target accounts or personas, source allowlist, sentiment or category filters, and owner routing rules.
77
- Behavior: find recent relevant mentions, classify each mention as buying intent, competitor comparison, complaint, hiring, integration request, or general awareness, and suppress duplicates or low-quality chatter.
78
- Output: one routed message per owner with mention summary, source link, account/person context, signal category, confidence, and suggested next step. Include Slack Block Kit formatting and an email fallback.
79
- Verification: add a small dry-run input, run deepline plays check, run one sample execution against a scratch output destination, and report exactly what changed and what was not published.`,
80
- },
81
- {
82
- id: 'competitor-churn',
83
- title: 'Competitor complaint monitor',
84
- description:
85
- 'Surface competitor dissatisfaction or sunset risk while the account is likely open to change.',
86
- prompt: `Create a Deepline definePlay() workflow for a competitor complaint monitor.
87
-
88
- Input: competitor names, complaint or migration keywords, source allowlist, target-account matching rules, and owner routing.
89
- Behavior: find recent competitor dissatisfaction or sunset-risk mentions, match them to companies where possible, classify the pain, and suppress vague or stale mentions.
90
- Output: one routed message per owner with account or person, competitor, pain summary, source URL, suggested displacement angle, and confidence. Include Slack Block Kit formatting and an email fallback.
91
- Verification: add a small dry-run input, run deepline plays check, run one sample execution against a scratch output destination, and report exactly what changed and what was not published.`,
92
- },
93
- {
94
- id: 'customer-usage-renewal',
95
- title: 'Customer usage and renewal monitor',
96
- description:
97
- 'Route expansion, risk, and renewal signals from product behavior to the account team.',
98
- prompt: `Create a Deepline definePlay() workflow for a customer usage and renewal monitor.
99
-
100
- Input: product usage events, entitlement limits, renewal dates, account health fields, and owner routing.
101
- Behavior: detect expansion signals, underuse risk, overage pressure, renewal timing, or feature adoption changes; explain the evidence; and suppress noisy repeat alerts.
102
- Output: one routed message per owner with account name, signal type, usage evidence, renewal context, business impact, and suggested next step. Include Slack Block Kit formatting and an email fallback.
103
- Verification: add a small dry-run input, run deepline plays check, run one sample execution against a scratch output destination, and report exactly what changed and what was not published.`,
104
- },
105
- ];
10
+ export const MONITOR_STARTER_TEMPLATES: MonitorStarterTemplate[] =
11
+ MONITOR_QUICKSTART_PROMPTS.map(({ id, title, description, prompt }) => ({
12
+ id,
13
+ title,
14
+ description,
15
+ prompt,
16
+ }));
@@ -149,6 +149,13 @@ export type AsyncOperationContractWire = {
149
149
  launchValue?: boolean;
150
150
  }
151
151
  | { kind: 'caller_managed' };
152
+ /** Opt-in completion wait for keyed API calls; ordinary Play waiting is unchanged. */
153
+ recoveryWait?: {
154
+ kind: 'input_flag';
155
+ field: string;
156
+ default: boolean;
157
+ launchValue?: boolean;
158
+ };
152
159
  };
153
160
 
154
161
  export type AsyncOperationContract = AsyncOperationContractWire;
@@ -363,6 +370,8 @@ export function defineAsyncOperation(
363
370
  required(contract.result.path, 'result.path');
364
371
  if (contract.wait.kind === 'input_flag')
365
372
  required(contract.wait.field, 'wait.field');
373
+ if (contract.recoveryWait)
374
+ required(contract.recoveryWait.field, 'recoveryWait.field');
366
375
  if (typeof contract.compatibility?.asyncGetAction === 'string') {
367
376
  required(
368
377
  contract.compatibility.asyncGetAction,
@@ -567,8 +576,12 @@ export function readAsyncOperationPath(value: unknown, path: string): unknown {
567
576
  if (path === '$' || path === '') return value;
568
577
  let current = value;
569
578
  for (const segment of path.split('.').filter(Boolean)) {
570
- if (!current || typeof current !== 'object' || Array.isArray(current))
571
- return undefined;
579
+ if (!current || typeof current !== 'object') return undefined;
580
+ if (Array.isArray(current)) {
581
+ if (!/^(0|[1-9]\d*)$/.test(segment)) return undefined;
582
+ current = current[Number(segment)];
583
+ continue;
584
+ }
572
585
  current = (current as Record<string, unknown>)[segment];
573
586
  }
574
587
  return current;
@@ -196,6 +196,8 @@ export const RUNTIME_SCHEDULER_CANCEL_STATEMENT_TIMEOUT_MS = 15_000;
196
196
 
197
197
  export type RuntimeSchedulerPoolOptions = {
198
198
  url: string;
199
+ /** Background indexing must not occupy the lifecycle checkout budget. */
200
+ purpose?: 'observability';
199
201
  applicationName?: string;
200
202
  maxClients?: number;
201
203
  /**
@@ -783,7 +785,7 @@ function getPoolEntry(options: RuntimeSchedulerPoolOptions): PoolEntry {
783
785
  // The connection lifetime is part of a Pool's semantics. Do not let a
784
786
  // serverless short-idle caller accidentally reuse a long-lived worker pool
785
787
  // merely because both target the same runtime database.
786
- const key = `${connectionString}::${maxClients}::${idleTimeoutMs}::${maxAcquireQueueDepth}::${maxAcquireQueueWaitMs}`;
788
+ const key = `${connectionString}::${maxClients}::${idleTimeoutMs}::${maxAcquireQueueDepth}::${maxAcquireQueueWaitMs}${options.purpose === 'observability' ? '::observability' : ''}`;
787
789
  const existing = registry.get(key);
788
790
  if (existing) {
789
791
  // Next dev can retain the process-global pool across a module hot reload.
@@ -18023,10 +18023,15 @@ async function claimPostgresSchedulerOutboxPrefix(
18023
18023
  input.searchOnly === true
18024
18024
  ? runLedgerTerminalPendingSql(alias, options)
18025
18025
  : runLedgerUrgentLifecyclePendingSql(alias, options);
18026
+ // Search is locally indexed and acknowledged; it has no remote ingress
18027
+ // receipt to freeze. Preserve causal ordering/due/lease checks, but admit
18028
+ // its full prefix instead of inheriting lifecycle's singleton transport.
18026
18029
  const ingressPrefixSql =
18027
- supportsTerminalBatchMarker && input.allowIngressPrefix === true
18028
- ? `(head.event_type = ${RUN_SCHEDULER_OUTBOX_EVENT_TYPE_SQL_LITERAL.runEvents})`
18029
- : 'false';
18030
+ input.searchOnly === true
18031
+ ? 'true'
18032
+ : supportsTerminalBatchMarker && input.allowIngressPrefix === true
18033
+ ? `(head.event_type = ${RUN_SCHEDULER_OUTBOX_EVENT_TYPE_SQL_LITERAL.runEvents})`
18034
+ : 'false';
18030
18035
  return await client.query<{
18031
18036
  row_kind: 'claimed' | 'delivered';
18032
18037
  event_id: string;
@@ -18145,14 +18150,16 @@ async function claimPostgresSchedulerOutboxPrefix(
18145
18150
  -- Admission is causally first and must be projected alone. A later
18146
18151
  -- start/terminal append may not leapfrog a delayed queued row.
18147
18152
  AND (
18148
- head.event_type NOT IN (${RUN_EVENT_KIND_SQL_LITERAL.runCreated}, ${RUN_EVENT_KIND_SQL_LITERAL.runStarted}, ${RUN_SCHEDULER_OUTBOX_EVENT_TYPE_SQL_LITERAL.runEvents})
18153
+ ${input.searchOnly === true ? 'true' : 'false'}
18154
+ OR head.event_type NOT IN (${RUN_EVENT_KIND_SQL_LITERAL.runCreated}, ${RUN_EVENT_KIND_SQL_LITERAL.runStarted}, ${RUN_SCHEDULER_OUTBOX_EVENT_TYPE_SQL_LITERAL.runEvents})
18149
18155
  OR outbox.event_id = head.event_id
18150
18156
  OR (head.event_type = ${RUN_SCHEDULER_OUTBOX_EVENT_TYPE_SQL_LITERAL.runEvents}
18151
18157
  AND ${ingressPrefixSql})
18152
18158
  )
18153
18159
  -- Only a receiver with the exact-prefix verifier may combine ingress
18154
18160
  -- facts; old launch origins and schemas retain singleton receipts.
18155
- AND (outbox.event_type <> ${RUN_SCHEDULER_OUTBOX_EVENT_TYPE_SQL_LITERAL.runEvents}
18161
+ AND (${input.searchOnly === true ? 'true' : 'false'}
18162
+ OR outbox.event_type <> ${RUN_SCHEDULER_OUTBOX_EVENT_TYPE_SQL_LITERAL.runEvents}
18156
18163
  OR outbox.event_id = head.event_id
18157
18164
  OR ${ingressPrefixSql})
18158
18165
  -- Once any ingress prefix was attempted, its receipt is immutable,
@@ -19456,6 +19463,11 @@ export function postgresSchedulerCancellationOutboxLease(runId: string): {
19456
19463
  }
19457
19464
 
19458
19465
  export type RunLedgerOutboxDispatchInput = {
19466
+ /** Dedicated bounded checkout for indexing; claims/ACKs use withClient. */
19467
+ withObservabilityClient?: <T>(
19468
+ operation: (client: PostgresSchedulerQueryClient) => Promise<T>,
19469
+ ) => Promise<T>;
19470
+
19459
19471
  /** Only the existing background projector claims the independent search prefix. */
19460
19472
  searchOnly?: boolean;
19461
19473
  /** Only the existing background projector indexes deferred raw inputs. */
@@ -19995,63 +20007,98 @@ async function dispatchRunLedgerOutboxBatchWithScope(
19995
20007
  retryingEventIds: released,
19996
20008
  };
19997
20009
  }
19998
- for (const event of events) {
19999
- if (
20010
+ const deferredEvents = events.filter(
20011
+ (event) =>
20000
20012
  payloadField(
20001
20013
  event.payload,
20002
20014
  RUNTIME_EVENT_DEFERRED_PROJECTION_FIELD,
20003
- ) !== true
20004
- )
20005
- continue;
20006
- await withClient(async (client) => {
20007
- await renewPostgresRunLedgerDelivery(
20015
+ ) === true,
20016
+ );
20017
+ const materialize = async (prefix: typeof deferredEvents) => {
20018
+ await withClient((client) =>
20019
+ renewPostgresRunLedgerDelivery(
20008
20020
  client,
20009
20021
  {
20010
- eventIds: events.map((row) => row.eventId),
20022
+ eventIds: events.map((event) => event.eventId),
20011
20023
  leaseOwner,
20012
20024
  leaseSeconds: 120,
20013
20025
  },
20014
20026
  options,
20015
- );
20016
- // Keep projection below its renewed lease. The final payload update
20017
- // fences ownership; indexing locks neither outbox nor Run rows.
20027
+ ),
20028
+ );
20029
+ await (input.withObservabilityClient ?? withClient)(async (client) => {
20018
20030
  await client.query('BEGIN');
20019
20031
  try {
20020
- // Let PostgreSQL cancel before the pool's client-side timeout,
20021
- // so rollback completes while the checkout is still usable.
20022
20032
  await client.query(
20023
20033
  "SELECT set_config('statement_timeout', $1, true)",
20024
20034
  [
20025
20035
  `${Math.min(60_000, RUNTIME_RELIABILITY_POLICY.schedulerDatabase.queryTimeoutMs - 1_000)}ms`,
20026
20036
  ],
20027
20037
  );
20028
- const result = await client.query<{ payload: unknown }>(
20029
- `SELECT ${quoteIdent(schemaName(options))}.materialize_runtime_outbox_event($1, $2) AS payload`,
20030
- [event.eventId, leaseOwner],
20038
+ // The recursive dependency evaluates this already-bounded causal
20039
+ // prefix in order, in one database round trip. Its eight-fact
20040
+ // contract is unchanged. The timeout bounds the entire statement
20041
+ // below the lease; the installed function still fences every fact.
20042
+ const result = await client.query<{
20043
+ ordinal: number;
20044
+ payload: unknown;
20045
+ }>(
20046
+ `WITH RECURSIVE projected(ordinal, payload) AS (
20047
+ SELECT 1, ${quoteIdent(schemaName(options))}.materialize_runtime_outbox_event(($1::text[])[1], $2)
20048
+ UNION ALL
20049
+ SELECT prior.ordinal + 1,
20050
+ ${quoteIdent(schemaName(options))}.materialize_runtime_outbox_event(($1::text[])[prior.ordinal + 1], $2)
20051
+ FROM projected AS prior
20052
+ WHERE prior.ordinal < cardinality($1::text[])
20053
+ )
20054
+ SELECT ordinal, payload FROM projected ORDER BY ordinal`,
20055
+ [prefix.map((event) => event.eventId), leaseOwner],
20031
20056
  );
20032
20057
  await client.query('COMMIT');
20033
- event.payload = result.rows[0]!.payload;
20058
+ for (const row of result.rows)
20059
+ prefix[row.ordinal - 1]!.payload = row.payload;
20034
20060
  } catch (error) {
20035
20061
  await client.query('ROLLBACK');
20062
+ throw error;
20063
+ }
20064
+ });
20065
+ };
20066
+ try {
20067
+ await materialize(deferredEvents);
20068
+ } catch (error) {
20069
+ if (
20070
+ !(
20071
+ error &&
20072
+ typeof error === 'object' &&
20073
+ 'code' in error &&
20074
+ error.code === 'P0001'
20075
+ )
20076
+ ) {
20077
+ throw error;
20078
+ }
20079
+ // An invalid immutable fact must quarantine only itself. The aborted
20080
+ // batch wrote nothing; replay its small prefix one fact at a time to
20081
+ // identify that fact and retain the valid indexed predecessors.
20082
+ for (const event of deferredEvents) {
20083
+ try {
20084
+ await materialize([event]);
20085
+ } catch (factError) {
20036
20086
  if (
20037
- error &&
20038
- typeof error === 'object' &&
20039
- 'code' in error &&
20040
- error.code === 'P0001'
20087
+ factError &&
20088
+ typeof factError === 'object' &&
20089
+ 'code' in factError &&
20090
+ factError.code === 'P0001'
20041
20091
  ) {
20042
- // The installed projector's explicit RAISEs reject malformed
20043
- // or conflicting immutable facts. Retain them in the existing
20044
- // quarantine instead of retrying the same invalid input forever.
20045
20092
  throw new DeferredRuntimeProjectionInputError(
20046
20093
  event.eventId,
20047
- error instanceof Error
20048
- ? error.message
20094
+ factError instanceof Error
20095
+ ? factError.message
20049
20096
  : 'Invalid runtime event projection input.',
20050
20097
  );
20051
20098
  }
20052
- throw error;
20099
+ throw factError;
20053
20100
  }
20054
- });
20101
+ }
20055
20102
  }
20056
20103
  }
20057
20104
  return null;