deepline 0.3.479 → 0.3.481

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 (152) hide show
  1. package/dist/bundling-sources/sdk/src/client-core.ts +35 -1
  2. package/dist/bundling-sources/sdk/src/http-core.ts +2 -0
  3. package/dist/bundling-sources/sdk/src/plays/run-request.ts +54 -31
  4. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  5. package/dist/bundling-sources/shared_libs/monitors/legacy-radar-forward-routes.ts +569 -0
  6. package/dist/bundling-sources/shared_libs/monitors/legacy-radar-retirement.ts +451 -0
  7. package/dist/bundling-sources/shared_libs/monitors/package.json +5 -1
  8. package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +18 -0
  9. package/dist/bundling-sources/shared_libs/play-runtime/runtime-array-input-receipts.ts +13 -6
  10. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +2 -0
  11. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +125 -22
  12. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-shared.ts +16 -53
  13. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +34 -48
  14. package/dist/bundling-sources/shared_libs/security/receipt-admission-token.ts +44 -0
  15. package/dist/cli/auth-EQWYBNIE.js +43 -0
  16. package/dist/cli/{auth-D7DYB57R.mjs → auth-FYY65LYD.mjs} +7 -5
  17. package/dist/cli/{billing-DT4JT4QN.mjs → billing-3MELYUTI.mjs} +7 -7
  18. package/dist/cli/{billing-DHGSNM6E.js → billing-TROHWBSE.js} +80 -80
  19. package/dist/cli/{chunk-GQCH6Y7W.js → chunk-27BYXQU4.js} +1 -1
  20. package/dist/cli/{chunk-CK2Q6AWR.js → chunk-4CIVXXSU.js} +26 -26
  21. package/dist/cli/{chunk-G6V34EQ5.js → chunk-5ONCPLWC.js} +16 -16
  22. package/dist/cli/{chunk-27GSBTWD.js → chunk-6VKDKATO.js} +96 -65
  23. package/dist/cli/{chunk-LXYDN5FE.js → chunk-6WWBVWFQ.js} +7 -7
  24. package/dist/cli/{chunk-JNFNNJ75.mjs → chunk-6ZMUAASO.mjs} +4 -4
  25. package/dist/cli/{chunk-6C3QY5JS.mjs → chunk-75LUK4E5.mjs} +1 -1
  26. package/dist/cli/{chunk-JKUFOP7M.mjs → chunk-7VVC4RZX.mjs} +1 -1
  27. package/dist/cli/{chunk-N7E25EU2.mjs → chunk-ASQJUUGB.mjs} +5 -5
  28. package/dist/cli/{chunk-FKEZ5DIJ.mjs → chunk-BJ5UFTWL.mjs} +28 -7
  29. package/dist/cli/{chunk-W4HIOBQQ.js → chunk-BJMEXKZY.js} +2 -2
  30. package/dist/cli/{chunk-YC2QBF2W.mjs → chunk-C7UIUKW6.mjs} +2 -2
  31. package/dist/cli/{chunk-AKVNUNL7.js → chunk-CM3KC74A.js} +3 -3
  32. package/dist/cli/{chunk-JFGOMQW6.mjs → chunk-CS2P3EQS.mjs} +2 -2
  33. package/dist/cli/{chunk-VKBLXJCK.js → chunk-CUSWZTS7.js} +8 -8
  34. package/dist/cli/{chunk-GSULE54L.js → chunk-D3UHG54V.js} +199 -178
  35. package/dist/cli/{chunk-3PHOAUU4.mjs → chunk-EJTU6X6C.mjs} +41 -10
  36. package/dist/cli/{chunk-AFHFZU5V.js → chunk-EPEAQA6O.js} +37 -37
  37. package/dist/cli/{chunk-K5RRY7DJ.mjs → chunk-EPZSX7FZ.mjs} +4 -4
  38. package/dist/cli/{chunk-27PXHD2P.mjs → chunk-FXLMDAAY.mjs} +1 -1
  39. package/dist/cli/chunk-GBAT5NMI.mjs +789 -0
  40. package/dist/cli/{chunk-Y2MVDYV6.js → chunk-GJOKFJ2Y.js} +10 -10
  41. package/dist/cli/{chunk-JDMR33HS.js → chunk-GVAENVIG.js} +28 -19
  42. package/dist/cli/{chunk-NELMCOWK.mjs → chunk-HNRELVCS.mjs} +27 -18
  43. package/dist/cli/{chunk-GICSDK72.mjs → chunk-HXG7TYRS.mjs} +2 -2
  44. package/dist/cli/{chunk-EMVWC5LP.js → chunk-J4D3PCQ2.js} +5 -5
  45. package/dist/cli/{chunk-6QIXWFPO.mjs → chunk-JE27DTQM.mjs} +3 -3
  46. package/dist/cli/{chunk-3GIM7XRH.js → chunk-KDU6TSGE.js} +2 -2
  47. package/dist/cli/{chunk-JMCTNS6S.js → chunk-KPXP2DIY.js} +7 -9
  48. package/dist/cli/chunk-KQSTV4MW.js +789 -0
  49. package/dist/cli/{chunk-RJVUILW4.mjs → chunk-LISZBUA7.mjs} +2 -2
  50. package/dist/cli/{chunk-D6MLXRMQ.mjs → chunk-LTOY2GUJ.mjs} +10 -10
  51. package/dist/cli/{chunk-FBEXJVQL.js → chunk-MKDWU3ZI.js} +93 -93
  52. package/dist/cli/{chunk-UOYA35OT.mjs → chunk-N5267HEH.mjs} +1 -1
  53. package/dist/cli/{chunk-XDFARN7U.mjs → chunk-PDJS6LFI.mjs} +2 -1
  54. package/dist/cli/{chunk-J5VDE42G.js → chunk-R4EFW2I2.js} +3 -3
  55. package/dist/cli/{chunk-GONL4EFT.mjs → chunk-RV2GB7KW.mjs} +2 -2
  56. package/dist/cli/{chunk-GZ43YXHT.mjs → chunk-S5BM6NPJ.mjs} +3 -5
  57. package/dist/cli/{chunk-PUOKABQF.mjs → chunk-S5WXSC3P.mjs} +5 -5
  58. package/dist/cli/{chunk-ZJRVHQSZ.mjs → chunk-SOGLBLMH.mjs} +4 -4
  59. package/dist/cli/{chunk-FV7WGK5X.js → chunk-SVPM2E2U.js} +5 -5
  60. package/dist/cli/{chunk-XBYW5EHY.mjs → chunk-UUGSZZZH.mjs} +1 -1
  61. package/dist/cli/{chunk-3SGA53DX.js → chunk-VBDR2DP6.js} +3 -3
  62. package/dist/cli/{chunk-O5BUFQ2P.mjs → chunk-VRAOWHXJ.mjs} +1 -1
  63. package/dist/cli/{chunk-TZCHXF4R.mjs → chunk-VTKAIGW6.mjs} +3 -3
  64. package/dist/cli/{chunk-33O2JGHE.js → chunk-WF5QE2IW.js} +17 -16
  65. package/dist/cli/{chunk-OVFCPV24.js → chunk-WICQQ7GY.js} +53 -53
  66. package/dist/cli/{chunk-W6N2S5N7.js → chunk-WTWRZ5ZI.js} +8 -8
  67. package/dist/cli/{chunk-N4M6CO5H.js → chunk-X4XZFIK6.js} +14 -14
  68. package/dist/cli/{chunk-VRZXGJXH.js → chunk-X6MEQBAT.js} +21 -21
  69. package/dist/cli/{csv-JXFOD7DJ.mjs → csv-2EG7JW7V.mjs} +4 -4
  70. package/dist/cli/{csv-DQWOZ2BL.js → csv-OUALOZBF.js} +13 -13
  71. package/dist/cli/{db-Q2OAAXRX.js → db-7BTOI6YB.js} +28 -28
  72. package/dist/cli/{db-NOGMZAQE.mjs → db-ECDQWI2Y.mjs} +7 -7
  73. package/dist/cli/describe-EXSH6OZD.js +48 -0
  74. package/dist/cli/{describe-VKZTZLX6.mjs → describe-KO6WKQIB.mjs} +18 -18
  75. package/dist/cli/describe-core.d.mts +2 -2
  76. package/dist/cli/describe-core.d.ts +2 -2
  77. package/dist/cli/{executions-XU3JMNI3.mjs → executions-3VMFBZT4.mjs} +7 -7
  78. package/dist/cli/{executions-B3GDZ4GQ.js → executions-VZSG3F3E.js} +9 -9
  79. package/dist/cli/{feedback-5IW3ZKRP.js → feedback-ASKBWRXR.js} +8 -8
  80. package/dist/cli/{feedback-R5UQXINE.mjs → feedback-SN7AVALN.mjs} +4 -4
  81. package/dist/cli/{git-7B4H7VEL.mjs → git-W2DZXEAY.mjs} +5 -5
  82. package/dist/cli/git-X2XUKYYX.js +25 -0
  83. package/dist/cli/index.js +167 -144
  84. package/dist/cli/index.mjs +73 -50
  85. package/dist/cli/{legacy-noops-AVT6T5QI.js → legacy-noops-6KOC6LV7.js} +8 -8
  86. package/dist/cli/{legacy-noops-KY7JAALE.mjs → legacy-noops-R2SML7ED.mjs} +5 -5
  87. package/dist/cli/{monitors-G7L7AFXR.mjs → monitors-4VGZGJBT.mjs} +8 -8
  88. package/dist/cli/{monitors-GZB6CWK2.js → monitors-OJXDYU4G.js} +92 -92
  89. package/dist/cli/{org-ZHHKAPCE.js → org-AELMZFUT.js} +73 -73
  90. package/dist/cli/{org-QDUBOQTH.mjs → org-UGN5ZG2Y.mjs} +7 -7
  91. package/dist/cli/{pages-5P3SPMN5.js → pages-L26N7MK3.js} +18 -18
  92. package/dist/cli/{pages-Q4WGQCST.mjs → pages-ZYPAAYSS.mjs} +7 -7
  93. package/dist/cli/{play-WRSHD4RE.mjs → play-ENYB7X4S.mjs} +125 -53
  94. package/dist/cli/{play-IRLKESZB.js → play-VTZWB3N5.js} +620 -548
  95. package/dist/cli/{providers-7CQUHS57.mjs → providers-BZQTVAQG.mjs} +7 -7
  96. package/dist/cli/{providers-LKEYLN6D.js → providers-HWBY6MGQ.js} +10 -10
  97. package/dist/cli/{quickstart-KS45M5SR.js → quickstart-D54IJ3PX.js} +6 -6
  98. package/dist/cli/{quickstart-I6NYGMEE.mjs → quickstart-SSQJDWPB.mjs} +4 -4
  99. package/dist/cli/{run-BLMOBNAN.js → run-45NKE7UY.js} +37 -37
  100. package/dist/cli/{run-AZETCL7R.mjs → run-FR3TPYT3.mjs} +23 -23
  101. package/dist/cli/{search-EEKEP67Y.mjs → search-CZCWSR5L.mjs} +8 -8
  102. package/dist/cli/search-V2GJKQQQ.js +32 -0
  103. package/dist/cli/{secrets-M6NU3TD2.js → secrets-KJ5ZEDWT.js} +16 -16
  104. package/dist/cli/{secrets-HAULG2EW.mjs → secrets-KKK55B4Y.mjs} +7 -7
  105. package/dist/cli/{sessions-5MB4XKOK.mjs → sessions-2YKA54PH.mjs} +5 -5
  106. package/dist/cli/{sessions-DSR5AHIA.js → sessions-QCNPUTDZ.js} +6 -6
  107. package/dist/cli/{settings-JUQP4HYQ.mjs → settings-4NI2AS5V.mjs} +12 -12
  108. package/dist/cli/{settings-6US327NQ.js → settings-KM3LCPWD.js} +79 -79
  109. package/dist/cli/{setup-JRH4UC47.mjs → setup-BZF3M54I.mjs} +33 -18
  110. package/dist/cli/{setup-5ZRSEQUD.js → setup-V5MTS2AA.js} +67 -52
  111. package/dist/cli/skills-CRFYW767.js +33 -0
  112. package/dist/cli/{skills-HFOZ5WDH.mjs → skills-NXYNB3LP.mjs} +7 -7
  113. package/dist/cli/{tools-CJEBZNPB.mjs → tools-57MWFRIZ.mjs} +17 -17
  114. package/dist/cli/tools-Q662HPVJ.js +55 -0
  115. package/dist/cli/{update-VQJEPAAK.mjs → update-QM6H677U.mjs} +9 -9
  116. package/dist/cli/update-TYHKF7F5.js +33 -0
  117. package/dist/cli/{workflow-EYWE3T75.mjs → workflow-NHYDIUWY.mjs} +8 -8
  118. package/dist/cli/{workflow-NKPDRZOP.js → workflow-TGEC4ITO.js} +18 -18
  119. package/dist/{client-B9N1bcGc.d.mts → client-DJ3r8VQJ.d.mts} +1 -1
  120. package/dist/{client-Btw075Hq.d.ts → client-JPSNljtj.d.ts} +1 -1
  121. package/dist/{client-core-TulbIeRB.d.mts → client-core-3SJlNsKU.d.mts} +21 -2
  122. package/dist/{client-core-DvGWlwtX.d.ts → client-core-rCoQ5Xz3.d.ts} +21 -2
  123. package/dist/index.d.mts +3 -3
  124. package/dist/index.d.ts +3 -3
  125. package/dist/index.js +10 -10
  126. package/dist/index.mjs +7 -7
  127. package/dist/install-integrity.json +106 -104
  128. package/dist/plays/bundle-play-file.mjs +5426 -12
  129. package/dist/plays/play-description.d.mts +1 -1
  130. package/dist/plays/play-description.d.ts +1 -1
  131. package/dist/plays/run-request.d.mts +1 -1
  132. package/dist/plays/run-request.d.ts +1 -1
  133. package/dist/plays/run-request.js +4 -2
  134. package/dist/plays/run-request.mjs +3 -1
  135. package/dist/release.d.mts +1 -1
  136. package/dist/release.d.ts +1 -1
  137. package/dist/release.js +2 -2
  138. package/dist/release.mjs +1 -1
  139. package/dist/server.d.mts +2 -2
  140. package/dist/server.d.ts +2 -2
  141. package/dist/server.js +6 -6
  142. package/dist/server.mjs +4 -4
  143. package/package.json +1 -1
  144. package/dist/cli/auth-MZDFNRPG.js +0 -41
  145. package/dist/cli/chunk-7UFFWPQ2.js +0 -6188
  146. package/dist/cli/chunk-ZBSUXXZS.mjs +0 -6188
  147. package/dist/cli/describe-THHHNUEA.js +0 -48
  148. package/dist/cli/git-BX27JAW4.js +0 -25
  149. package/dist/cli/search-PEQXXY2B.js +0 -32
  150. package/dist/cli/skills-P67KCVDR.js +0 -33
  151. package/dist/cli/tools-PDBEAI22.js +0 -55
  152. package/dist/cli/update-G6DI3BQW.js +0 -33
@@ -0,0 +1,451 @@
1
+ // Approved legacy-radar retirement: the pure rules.
2
+ //
3
+ // A legacy radar is an upstream TamRadar resource that Deepline did not create
4
+ // through a monitor definition, so it carries no `deepline_monitor_key` marker
5
+ // and the global audit can never prove it closable. An operator retires such
6
+ // radars only from a sealed approval (Convex tables
7
+ // `providerMonitorLegacyRetirementApprovals` / `...Items`), and only once the
8
+ // subject is still covered: by the required managed monitor, unchanged in
9
+ // configuration, active, and routed.
10
+ //
11
+ // Everything here is pure so the Convex action, the private operator bridge
12
+ // and the CLI apply one rule. It must stay Convex-safe: no Node imports.
13
+
14
+ export const LEGACY_RETIREMENT_MAX_BATCH = 100;
15
+
16
+ /**
17
+ * Radars legacy retirement refuses by ID before any provider call. KAR-120:
18
+ * the funding radar backs the public funding-updates tool and has no Convex
19
+ * definition. The provider's own deactivate contract refuses it on every
20
+ * path; a test pins that this list stays within what that contract refuses.
21
+ */
22
+ export const LEGACY_RETIREMENT_PROTECTED_RADAR_IDS: ReadonlySet<string> =
23
+ new Set(['3bc8ee34-e0e7-4f62-b34b-076c613f8ed6']);
24
+ /** One approval is sealed in one transaction, which reads every item once. */
25
+ export const LEGACY_RETIREMENT_MAX_ROWS = 5_000;
26
+ /** An attempt that recorded intent owns its item for this long. */
27
+ export const LEGACY_RETIREMENT_INTENT_LEASE_MS = 15 * 60_000;
28
+ /** Approved route tables are always a schema-qualified lower_snake name. */
29
+ export const LEGACY_RETIREMENT_ROUTE_TABLE_PATTERN = /^[a-z_]+\.[a-z_]+$/;
30
+
31
+ export const LEGACY_CONTACT_RADAR_TYPES = [
32
+ 'contact_job_changes',
33
+ 'contact_social_posts',
34
+ 'contact_social_engagements',
35
+ ] as const;
36
+ export const LEGACY_COMPANY_RADAR_TYPES = [
37
+ 'company_new_hires',
38
+ 'company_job_openings',
39
+ 'company_promotions',
40
+ 'company_reviews',
41
+ 'company_mentions',
42
+ 'company_social_posts',
43
+ 'company_social_posts_cxo',
44
+ 'company_social_engagements',
45
+ ] as const;
46
+
47
+ export function isLegacyContactRadarType(radarType: string): boolean {
48
+ return (LEGACY_CONTACT_RADAR_TYPES as readonly string[]).includes(radarType);
49
+ }
50
+
51
+ export function isLegacyCompanyRadarType(radarType: string): boolean {
52
+ return (LEGACY_COMPANY_RADAR_TYPES as readonly string[]).includes(radarType);
53
+ }
54
+
55
+ /** The monitor tool whose payload validation applies to a radar type. */
56
+ export function legacyRadarMonitorTool(radarType: string): string | null {
57
+ if (isLegacyContactRadarType(radarType))
58
+ return 'deepline_native.contact_radar';
59
+ if (isLegacyCompanyRadarType(radarType))
60
+ return 'deepline_native.company_radar';
61
+ return null;
62
+ }
63
+
64
+ export type LegacyExpectedConfig = {
65
+ radar_type: string;
66
+ /** Contact types only: lowercased LinkedIn `/in/<slug>`. */
67
+ profile_slug?: string;
68
+ /** Company types and `contact_job_changes`; optional on social posts. */
69
+ domain?: string;
70
+ filters: Record<string, unknown>;
71
+ };
72
+
73
+ function record(value: unknown): Record<string, unknown> | null {
74
+ return value && typeof value === 'object' && !Array.isArray(value)
75
+ ? (value as Record<string, unknown>)
76
+ : null;
77
+ }
78
+
79
+ function nonEmptyText(value: unknown): string | null {
80
+ return typeof value === 'string' && value.trim() ? value.trim() : null;
81
+ }
82
+
83
+ /** Throws on anything but the approved `{radar_type, profile_slug?, domain?, filters}` shape. */
84
+ export function parseLegacyExpectedConfig(text: string): LegacyExpectedConfig {
85
+ let parsed: unknown;
86
+ try {
87
+ parsed = JSON.parse(text);
88
+ } catch {
89
+ throw new Error('The required monitor expected config is not valid JSON.');
90
+ }
91
+ const value = record(parsed);
92
+ if (!value) {
93
+ throw new Error('The required monitor expected config must be an object.');
94
+ }
95
+ const unknownKeys = Object.keys(value).filter(
96
+ (key) => !['radar_type', 'profile_slug', 'domain', 'filters'].includes(key),
97
+ );
98
+ if (unknownKeys.length) {
99
+ throw new Error(
100
+ `The required monitor expected config has unknown keys: ${unknownKeys.join(', ')}.`,
101
+ );
102
+ }
103
+ const radarType = nonEmptyText(value.radar_type);
104
+ if (!radarType || !legacyRadarMonitorTool(radarType)) {
105
+ throw new Error(
106
+ 'The required monitor expected config needs a known contact or company radar_type.',
107
+ );
108
+ }
109
+ const filters = record(value.filters);
110
+ if (!filters) {
111
+ throw new Error(
112
+ 'The required monitor expected config needs a filters object.',
113
+ );
114
+ }
115
+ const slug =
116
+ value.profile_slug === undefined ? null : nonEmptyText(value.profile_slug);
117
+ // An empty or null domain is recorded as no domain. Where the radar type
118
+ // needs one, the match then refuses (`required_monitor_expected_domain_missing`)
119
+ // instead of guessing which company was meant.
120
+ const domain =
121
+ value.domain === undefined || value.domain === null
122
+ ? null
123
+ : typeof value.domain === 'string'
124
+ ? nonEmptyText(value.domain)
125
+ : undefined;
126
+ if (domain === undefined) {
127
+ throw new Error('domain must be a string when present.');
128
+ }
129
+ if (value.profile_slug !== undefined && !slug) {
130
+ throw new Error('profile_slug must be a non-empty string when present.');
131
+ }
132
+ if (isLegacyContactRadarType(radarType) && !slug) {
133
+ throw new Error(`${radarType} expected configs require profile_slug.`);
134
+ }
135
+ if (isLegacyCompanyRadarType(radarType) && slug) {
136
+ throw new Error(`${radarType} expected configs take no profile_slug.`);
137
+ }
138
+ return {
139
+ radar_type: radarType,
140
+ ...(slug ? { profile_slug: slug.toLowerCase() } : {}),
141
+ ...(domain ? { domain } : {}),
142
+ filters,
143
+ };
144
+ }
145
+
146
+ /** Lowercased `/in/<slug>` of a LinkedIn profile URL, or null. */
147
+ export function linkedinProfileSlug(url: unknown): string | null {
148
+ const text = nonEmptyText(url);
149
+ if (!text) return null;
150
+ const match = /\/in\/([^/?#]+)/i.exec(text);
151
+ if (!match?.[1]) return null;
152
+ let slug = match[1];
153
+ try {
154
+ slug = decodeURIComponent(slug);
155
+ } catch {
156
+ // A malformed escape is compared as written.
157
+ }
158
+ slug = slug.trim().toLowerCase();
159
+ return slug || null;
160
+ }
161
+
162
+ /** Scheme, `www.`, port, path and case removed: `https://WWW.Acme.com/x` -> `acme.com`. */
163
+ export function normalizeRadarDomain(value: unknown): string | null {
164
+ let text = nonEmptyText(value)?.toLowerCase();
165
+ if (!text) return null;
166
+ text = text.replace(/^[a-z][a-z0-9+.-]*:\/\//, '');
167
+ text = text.split(/[/?#]/, 1)[0] ?? '';
168
+ text = text.replace(/:\d+$/, '').replace(/\.$/, '');
169
+ text = text.replace(/^www\./, '');
170
+ return text || null;
171
+ }
172
+
173
+ // Fields that name the subject rather than narrow it, per radar family.
174
+ const CONTACT_SUBJECT_FIELDS = new Set([
175
+ 'radar_type',
176
+ 'profile_url',
177
+ 'domain',
178
+ 'email',
179
+ 'full_name',
180
+ ]);
181
+ const COMPANY_SUBJECT_FIELDS = new Set(['radar_type', 'domain']);
182
+ // Fields that never change which findings a radar delivers: the replay start,
183
+ // and the framework-managed callback URL and correlation markers.
184
+ const NON_CONFIGURATION_FIELDS = new Set([
185
+ 'updates_since',
186
+ 'webhook_url',
187
+ 'custom_fields',
188
+ ]);
189
+
190
+ function absent(value: unknown): boolean {
191
+ return (
192
+ value === undefined ||
193
+ value === null ||
194
+ (typeof value === 'string' && !value.trim()) ||
195
+ (Array.isArray(value) && value.length === 0)
196
+ );
197
+ }
198
+
199
+ function stableJson(value: unknown): string {
200
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(',')}]`;
201
+ if (value && typeof value === 'object') {
202
+ return `{${Object.entries(value as Record<string, unknown>)
203
+ .filter(([, entry]) => !absent(entry))
204
+ .sort(([left], [right]) => left.localeCompare(right))
205
+ .map(([key, entry]) => `${JSON.stringify(key)}:${stableJson(entry)}`)
206
+ .join(',')}}`;
207
+ }
208
+ return JSON.stringify(value) ?? 'null';
209
+ }
210
+
211
+ /**
212
+ * Whether a live monitor payload still covers exactly the approved subject.
213
+ * Returns null on a match, otherwise the refusal reason.
214
+ *
215
+ * - same `radar_type`;
216
+ * - contact types: the same LinkedIn `/in/<slug>`;
217
+ * - company types and `contact_job_changes`: the same normalized domain;
218
+ * - contact social types: a domain, when expected, may be missing (the one
219
+ * allowed broadening) but never different; an unexpected domain refuses;
220
+ * - no filter beyond the expected `filters` (empty values count as absent,
221
+ * `updates_since` and framework-managed fields are ignored).
222
+ */
223
+ export function legacyMonitorConfigMismatch(input: {
224
+ expected: LegacyExpectedConfig;
225
+ payload: unknown;
226
+ }): string | null {
227
+ const payload = record(input.payload);
228
+ if (!payload) return 'required_monitor_payload_missing';
229
+ const { expected } = input;
230
+ if (nonEmptyText(payload.radar_type) !== expected.radar_type) {
231
+ return 'required_monitor_radar_type_changed';
232
+ }
233
+ const contact = isLegacyContactRadarType(expected.radar_type);
234
+ if (contact) {
235
+ if (linkedinProfileSlug(payload.profile_url) !== expected.profile_slug) {
236
+ return 'required_monitor_profile_changed';
237
+ }
238
+ }
239
+ const liveDomain = normalizeRadarDomain(payload.domain);
240
+ const expectedDomain = normalizeRadarDomain(expected.domain);
241
+ if (
242
+ expectedDomain === null &&
243
+ (!contact || expected.radar_type === 'contact_job_changes')
244
+ ) {
245
+ return 'required_monitor_expected_domain_missing';
246
+ }
247
+ if (contact && expected.radar_type !== 'contact_job_changes') {
248
+ // Social radars: a missing domain is the one allowed broadening.
249
+ if (liveDomain !== null && liveDomain !== expectedDomain) {
250
+ return 'required_monitor_domain_changed';
251
+ }
252
+ } else if (liveDomain !== expectedDomain) {
253
+ return 'required_monitor_domain_changed';
254
+ }
255
+ const subject = contact ? CONTACT_SUBJECT_FIELDS : COMPANY_SUBJECT_FIELDS;
256
+ const liveFilters = Object.fromEntries(
257
+ Object.entries(payload).filter(
258
+ ([key, value]) =>
259
+ !subject.has(key) &&
260
+ !NON_CONFIGURATION_FIELDS.has(key) &&
261
+ !absent(value),
262
+ ),
263
+ );
264
+ if (stableJson(liveFilters) !== stableJson(expected.filters)) {
265
+ return 'required_monitor_filters_changed';
266
+ }
267
+ return null;
268
+ }
269
+
270
+ /** The exact text whose SHA-256 is an approval's ID-set hash. */
271
+ export function legacyIdSetDigestInput(radarIds: readonly string[]): string {
272
+ return [...radarIds].sort().join('\n');
273
+ }
274
+
275
+ const UUID_PATTERN =
276
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
277
+ const RESOURCE_KEY_PATTERN = /(resource|radar|monitor)_id$/i;
278
+
279
+ /**
280
+ * Every upstream radar a monitor definition names, in any binding slot:
281
+ * the promoted id, the live binding, a prior binding kept for cleanup, a
282
+ * creation attestation, or retired history. Over-collection only makes the
283
+ * retirement refuse more, so every UUID-shaped string counts too.
284
+ */
285
+ export function definitionReferencedRadarIds(row: {
286
+ providerResourceId?: string | null;
287
+ providerBinding?: unknown;
288
+ }): string[] {
289
+ const ids = new Set<string>();
290
+ const add = (value: unknown) => {
291
+ const text = nonEmptyText(value);
292
+ if (text) ids.add(text);
293
+ };
294
+ add(row.providerResourceId);
295
+ const visit = (value: unknown, key: string | null, depth: number) => {
296
+ if (depth > 12 || value === null || value === undefined) return;
297
+ if (typeof value === 'string') {
298
+ if (
299
+ UUID_PATTERN.test(value.trim()) ||
300
+ (key && RESOURCE_KEY_PATTERN.test(key))
301
+ )
302
+ add(value);
303
+ return;
304
+ }
305
+ if (Array.isArray(value)) {
306
+ for (const entry of value) visit(entry, key, depth + 1);
307
+ return;
308
+ }
309
+ if (typeof value === 'object') {
310
+ for (const [entryKey, entry] of Object.entries(
311
+ value as Record<string, unknown>,
312
+ ))
313
+ visit(entry, entryKey, depth + 1);
314
+ }
315
+ };
316
+ visit(row.providerBinding, null, 0);
317
+ return [...ids];
318
+ }
319
+
320
+ /** What the retirement needs to know about a required monitor right now. */
321
+ export type LegacyRequiredMonitorFacts = {
322
+ orgId: string;
323
+ monitorKey: string;
324
+ status: string;
325
+ desired: string | null;
326
+ observed: string | null;
327
+ bindingVersion: string | null;
328
+ payload: unknown;
329
+ };
330
+
331
+ /** The monitor's current binding version as stored in a forward route. */
332
+ export function legacyBindingVersionText(
333
+ providerBinding: unknown,
334
+ ): string | null {
335
+ const value = record(providerBinding)?.binding_version;
336
+ if (typeof value === 'number' && Number.isFinite(value)) return String(value);
337
+ return nonEmptyText(value);
338
+ }
339
+
340
+ /** Null when the required monitor covers the subject; otherwise the refusal. */
341
+ export function legacyRequiredMonitorRefusal(input: {
342
+ approvalOrgId: string;
343
+ monitorKey: string;
344
+ facts: LegacyRequiredMonitorFacts | null;
345
+ expectedConfig: string | null;
346
+ }): string | null {
347
+ const { facts } = input;
348
+ if (!facts) return 'required_monitor_missing';
349
+ if (
350
+ facts.orgId !== input.approvalOrgId ||
351
+ facts.monitorKey !== input.monitorKey
352
+ )
353
+ return 'required_monitor_wrong_org';
354
+ if (facts.status !== 'active') return 'required_monitor_not_active';
355
+ if (facts.desired !== 'active') return 'required_monitor_not_desired_active';
356
+ if (facts.observed !== 'active')
357
+ return 'required_monitor_not_observed_active';
358
+ if (!facts.bindingVersion) return 'required_monitor_unbound';
359
+ if (!input.expectedConfig) return 'required_monitor_expected_config_missing';
360
+ let expected: LegacyExpectedConfig;
361
+ try {
362
+ expected = parseLegacyExpectedConfig(input.expectedConfig);
363
+ } catch {
364
+ return 'required_monitor_expected_config_invalid';
365
+ }
366
+ return legacyMonitorConfigMismatch({ expected, payload: facts.payload });
367
+ }
368
+
369
+ export type LegacyRetirementItemState =
370
+ | 'approved'
371
+ | 'intent_recorded'
372
+ | 'retired'
373
+ | 'refused'
374
+ | 'withdrawn';
375
+
376
+ export type LegacyRetirementPreflightItem = {
377
+ radarId: string;
378
+ phase: string;
379
+ state: LegacyRetirementItemState;
380
+ requiredMonitorKey?: string | null;
381
+ requiredMonitorExpectedConfig?: string | null;
382
+ requiredRouteAccountId?: string | null;
383
+ dependsOnRadarId?: string | null;
384
+ executionBlocker?: string | null;
385
+ };
386
+
387
+ /** States the retire action re-evaluates. Refusals are reports, not terminal. */
388
+ export const LEGACY_RETIREMENT_SELECTABLE_STATES = [
389
+ 'approved',
390
+ 'refused',
391
+ 'intent_recorded',
392
+ ] as const satisfies readonly LegacyRetirementItemState[];
393
+
394
+ /**
395
+ * The action's checks, in order, before any bridge call. Null means the item
396
+ * may be sent to the bridge, which repeats the provider-side checks itself.
397
+ */
398
+ export function legacyRetirementPreflightRefusal(input: {
399
+ approval: { status: string; orgId: string };
400
+ item: LegacyRetirementPreflightItem;
401
+ phase: string | null;
402
+ referencedRadarIds: ReadonlySet<string>;
403
+ keeper: LegacyRetirementPreflightItem | null;
404
+ monitorFacts: ReadonlyMap<string, LegacyRequiredMonitorFacts | null>;
405
+ }): string | null {
406
+ const { approval, item } = input;
407
+ if (approval.status !== 'approved') return 'approval_not_sealed';
408
+ if (input.phase !== null && item.phase !== input.phase)
409
+ return 'phase_not_selected';
410
+ if (
411
+ !(LEGACY_RETIREMENT_SELECTABLE_STATES as readonly string[]).includes(
412
+ item.state,
413
+ )
414
+ )
415
+ return `item_${item.state}`;
416
+ if (input.referencedRadarIds.has(item.radarId))
417
+ return 'referenced_by_monitor_definition';
418
+ if (item.requiredMonitorKey) {
419
+ if (!item.requiredRouteAccountId) return 'required_route_account_missing';
420
+ const refusal = legacyRequiredMonitorRefusal({
421
+ approvalOrgId: approval.orgId,
422
+ monitorKey: item.requiredMonitorKey,
423
+ facts: input.monitorFacts.get(item.requiredMonitorKey) ?? null,
424
+ expectedConfig: item.requiredMonitorExpectedConfig ?? null,
425
+ });
426
+ if (refusal) return refusal;
427
+ }
428
+ if (item.dependsOnRadarId) {
429
+ // A duplicate carries its keeper's monitor and route itself, so the
430
+ // bridge proves that route before the delete; without them it would
431
+ // delete with no route check at all.
432
+ if (!item.requiredMonitorKey) return 'duplicate_required_monitor_missing';
433
+ if (!input.keeper) return 'keeper_not_in_approval';
434
+ if (input.keeper.state === 'withdrawn') return 'keeper_withdrawn';
435
+ if (!input.keeper.requiredMonitorKey) return 'keeper_has_no_monitor';
436
+ if (
437
+ input.keeper.requiredMonitorKey !== item.requiredMonitorKey ||
438
+ input.keeper.requiredRouteAccountId !== item.requiredRouteAccountId
439
+ )
440
+ return 'keeper_coverage_differs';
441
+ const keeperRefusal = legacyRequiredMonitorRefusal({
442
+ approvalOrgId: approval.orgId,
443
+ monitorKey: input.keeper.requiredMonitorKey,
444
+ facts: input.monitorFacts.get(input.keeper.requiredMonitorKey) ?? null,
445
+ expectedConfig: input.keeper.requiredMonitorExpectedConfig ?? null,
446
+ });
447
+ if (keeperRefusal) return `keeper_${keeperRefusal}`;
448
+ }
449
+ if (item.executionBlocker?.trim()) return 'execution_blocker';
450
+ return null;
451
+ }
@@ -118,7 +118,11 @@
118
118
  "./tracking-provider-integrations": "./tracking-provider-integrations.ts",
119
119
  "./tracking-provider-integrations.js": "./tracking-provider-integrations.ts",
120
120
  "./validation": "./validation.ts",
121
- "./validation.js": "./validation.ts"
121
+ "./validation.js": "./validation.ts",
122
+ "./legacy-radar-forward-routes": "./legacy-radar-forward-routes.ts",
123
+ "./legacy-radar-forward-routes.js": "./legacy-radar-forward-routes.ts",
124
+ "./legacy-radar-retirement": "./legacy-radar-retirement.ts",
125
+ "./legacy-radar-retirement.js": "./legacy-radar-retirement.ts"
122
126
  },
123
127
  "scripts": {
124
128
  "build": "tsc -p tsconfig.json --noEmit",
@@ -1612,6 +1612,24 @@
1612
1612
  "failureAlert": "convex_action_wrapper",
1613
1613
  "domainReportRequired": true
1614
1614
  },
1615
+ {
1616
+ "id": "convex.customer-db-preparation",
1617
+ "name": "Customer DB preparation",
1618
+ "scheduler": "convex_scheduled_action",
1619
+ "cadence": "on demand after account or workspace creation",
1620
+ "timezone": "UTC",
1621
+ "source": "packages/convex/scheduledJobsNode.ts",
1622
+ "locator": "prepareCustomerDb",
1623
+ "owner": "runtime",
1624
+ "criticality": "critical",
1625
+ "purpose": "Prepare one newly created workspace's Customer DB through its durable cold-start intent and signed deployment-pinned callback.",
1626
+ "successDefinition": "The exact org-scoped callback confirms readiness or an existing active/deleted/archived workspace is safely skipped.",
1627
+ "failureDefinition": "Preparation fails, its callback is unavailable, or the result does not confirm the durable request identity.",
1628
+ "startGraceMs": 120000,
1629
+ "runTimeoutMs": 180000,
1630
+ "failureAlert": "convex_action_wrapper",
1631
+ "domainReportRequired": true
1632
+ },
1615
1633
  {
1616
1634
  "id": "convex.sql-listener-dispatch-retry",
1617
1635
  "name": "SQL-listener dispatch retry",
@@ -421,11 +421,18 @@ export async function getRuntimeArrayInputManifest(
421
421
  const scope = runtimeArrayInputScope(port, input);
422
422
  const keys = runtimeArrayInputKeys(scope);
423
423
  return await port.withClient(async (client) => {
424
- const guard = await readArrayInputFact(
425
- client,
426
- port.table,
427
- arrayInputRunGuardKey(port, scope.runId),
424
+ const guardKey = arrayInputKeyHex(arrayInputRunGuardKey(port, scope.runId));
425
+ const beginKey = arrayInputKeyHex(keys.begin);
426
+ const sealKey = arrayInputKeyHex(keys.seal);
427
+ // These three immutable facts share a scope. One bounded PK lookup avoids
428
+ // serial database round trips while retaining every historical validation.
429
+ const { rows } = await client.query<ArrayInputReceiptRow & { key: string }>(
430
+ `SELECT encode(k, 'hex') AS key, status, run_id, output FROM ${port.table}
431
+ WHERE k IN (decode($1, 'hex'), decode($2, 'hex'), decode($3, 'hex'))`,
432
+ [guardKey, beginKey, sealKey],
428
433
  );
434
+ const facts = new Map(rows.map((row) => [row.key, row]));
435
+ const guard = facts.get(guardKey);
429
436
  if (guard) {
430
437
  const state = requireArrayInputFact(
431
438
  guard,
@@ -436,7 +443,7 @@ export async function getRuntimeArrayInputManifest(
436
443
  throw new Error('Runtime array input run is retired.');
437
444
  }
438
445
  }
439
- const begin = await readArrayInputFact(client, port.table, keys.begin);
446
+ const begin = facts.get(beginKey);
440
447
  if (!begin) return { state: 'absent' };
441
448
  const manifest = requireArrayInputFact(
442
449
  begin,
@@ -448,7 +455,7 @@ export async function getRuntimeArrayInputManifest(
448
455
  if (typeof manifest.hasSourceIdentities !== 'boolean') {
449
456
  throw new Error('Runtime array input begin identity flag is corrupt.');
450
457
  }
451
- const seal = await readArrayInputFact(client, port.table, keys.seal);
458
+ const seal = facts.get(sealKey);
452
459
  if (!seal)
453
460
  return {
454
461
  state: 'staging',
@@ -268,6 +268,8 @@ export type PlaySchedulerRunHandle = {
268
268
  */
269
269
  observe(options?: {
270
270
  signal?: AbortSignal;
271
+ /** Briefly poll terminal state more often for an interactive start stream. */
272
+ startupPolling?: boolean;
271
273
  }): AsyncIterable<PlaySchedulerProgressEvent>;
272
274
  /**
273
275
  * Cooperatively cancel the run. Resolves with the scheduler's typed