deepline 0.3.480 → 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.
- package/dist/bundling-sources/sdk/src/client-core.ts +35 -1
- package/dist/bundling-sources/sdk/src/http-core.ts +2 -0
- package/dist/bundling-sources/sdk/src/plays/run-request.ts +54 -31
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/monitors/legacy-radar-forward-routes.ts +569 -0
- package/dist/bundling-sources/shared_libs/monitors/legacy-radar-retirement.ts +451 -0
- package/dist/bundling-sources/shared_libs/monitors/package.json +5 -1
- package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +18 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-array-input-receipts.ts +13 -6
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +125 -22
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-shared.ts +16 -53
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +34 -48
- package/dist/bundling-sources/shared_libs/security/receipt-admission-token.ts +44 -0
- package/dist/cli/auth-EQWYBNIE.js +43 -0
- package/dist/cli/{auth-KMKFQX7X.mjs → auth-FYY65LYD.mjs} +5 -5
- package/dist/cli/{billing-G3Y5FTQM.mjs → billing-3MELYUTI.mjs} +7 -7
- package/dist/cli/{billing-R5RCTEW6.js → billing-TROHWBSE.js} +80 -80
- package/dist/cli/{chunk-4V6KEXCM.js → chunk-27BYXQU4.js} +1 -1
- package/dist/cli/{chunk-OQOXR6JL.js → chunk-4CIVXXSU.js} +26 -26
- package/dist/cli/{chunk-RPJSVGSC.js → chunk-5ONCPLWC.js} +16 -16
- package/dist/cli/{chunk-K7E2DO5P.js → chunk-6VKDKATO.js} +53 -53
- package/dist/cli/{chunk-WQBJHB63.js → chunk-6WWBVWFQ.js} +7 -7
- package/dist/cli/{chunk-LLEBPM3N.mjs → chunk-6ZMUAASO.mjs} +4 -4
- package/dist/cli/{chunk-IQ6WL36K.mjs → chunk-75LUK4E5.mjs} +1 -1
- package/dist/cli/{chunk-AKCYZDBO.mjs → chunk-7VVC4RZX.mjs} +1 -1
- package/dist/cli/{chunk-JBQKBDDN.mjs → chunk-ASQJUUGB.mjs} +5 -5
- package/dist/cli/{chunk-DIDWIPMA.mjs → chunk-BJ5UFTWL.mjs} +28 -7
- package/dist/cli/{chunk-JH77OHD5.js → chunk-BJMEXKZY.js} +2 -2
- package/dist/cli/{chunk-53LLOGNV.mjs → chunk-C7UIUKW6.mjs} +2 -2
- package/dist/cli/{chunk-LHU6BMDV.js → chunk-CM3KC74A.js} +3 -3
- package/dist/cli/{chunk-XNXE7YWI.mjs → chunk-CS2P3EQS.mjs} +2 -2
- package/dist/cli/{chunk-APFFHSPL.js → chunk-CUSWZTS7.js} +8 -8
- package/dist/cli/{chunk-6AYQK7Z6.js → chunk-D3UHG54V.js} +199 -178
- package/dist/cli/{chunk-PM2CT7HZ.mjs → chunk-EJTU6X6C.mjs} +3 -3
- package/dist/cli/{chunk-FZX2DGQZ.js → chunk-EPEAQA6O.js} +37 -37
- package/dist/cli/{chunk-57MJL2HU.mjs → chunk-EPZSX7FZ.mjs} +4 -4
- package/dist/cli/{chunk-FZ42SO3Q.mjs → chunk-FXLMDAAY.mjs} +1 -1
- package/dist/cli/chunk-GBAT5NMI.mjs +789 -0
- package/dist/cli/{chunk-VPO2D7WV.js → chunk-GJOKFJ2Y.js} +10 -10
- package/dist/cli/{chunk-JDMR33HS.js → chunk-GVAENVIG.js} +28 -19
- package/dist/cli/{chunk-NELMCOWK.mjs → chunk-HNRELVCS.mjs} +27 -18
- package/dist/cli/{chunk-SUTQCM7U.mjs → chunk-HXG7TYRS.mjs} +2 -2
- package/dist/cli/{chunk-PDDUX5UY.js → chunk-J4D3PCQ2.js} +5 -5
- package/dist/cli/{chunk-PC42DFZC.mjs → chunk-JE27DTQM.mjs} +3 -3
- package/dist/cli/{chunk-MEJJD7PA.js → chunk-KDU6TSGE.js} +2 -2
- package/dist/cli/{chunk-NVTVS4AV.js → chunk-KPXP2DIY.js} +7 -9
- package/dist/cli/chunk-KQSTV4MW.js +789 -0
- package/dist/cli/{chunk-2AYWC63S.mjs → chunk-LISZBUA7.mjs} +2 -2
- package/dist/cli/{chunk-44TVNYYV.mjs → chunk-LTOY2GUJ.mjs} +10 -10
- package/dist/cli/{chunk-OGXG46LY.js → chunk-MKDWU3ZI.js} +93 -93
- package/dist/cli/{chunk-MTMOYZMK.mjs → chunk-N5267HEH.mjs} +1 -1
- package/dist/cli/{chunk-T7EWDIVM.mjs → chunk-PDJS6LFI.mjs} +2 -1
- package/dist/cli/{chunk-QEV3GFDS.js → chunk-R4EFW2I2.js} +3 -3
- package/dist/cli/{chunk-KQNQCZLB.mjs → chunk-RV2GB7KW.mjs} +2 -2
- package/dist/cli/{chunk-PYIWO4GS.mjs → chunk-S5BM6NPJ.mjs} +3 -5
- package/dist/cli/{chunk-7LMLHUCP.mjs → chunk-S5WXSC3P.mjs} +5 -5
- package/dist/cli/{chunk-63QH6FL3.mjs → chunk-SOGLBLMH.mjs} +4 -4
- package/dist/cli/{chunk-5IVGZCKY.js → chunk-SVPM2E2U.js} +5 -5
- package/dist/cli/{chunk-ZSWY5E3W.mjs → chunk-UUGSZZZH.mjs} +1 -1
- package/dist/cli/{chunk-TBJ23WKT.js → chunk-VBDR2DP6.js} +3 -3
- package/dist/cli/{chunk-TXFURKHD.mjs → chunk-VRAOWHXJ.mjs} +1 -1
- package/dist/cli/{chunk-ASARTL6E.mjs → chunk-VTKAIGW6.mjs} +3 -3
- package/dist/cli/{chunk-DQYJXVAF.js → chunk-WF5QE2IW.js} +17 -16
- package/dist/cli/{chunk-NBXG4S2X.js → chunk-WICQQ7GY.js} +53 -53
- package/dist/cli/{chunk-FFSOXQEM.js → chunk-WTWRZ5ZI.js} +8 -8
- package/dist/cli/{chunk-LBZLV6QF.js → chunk-X4XZFIK6.js} +14 -14
- package/dist/cli/{chunk-A3PZKD47.js → chunk-X6MEQBAT.js} +21 -21
- package/dist/cli/{csv-P3AEI4G4.mjs → csv-2EG7JW7V.mjs} +4 -4
- package/dist/cli/{csv-57D62T7L.js → csv-OUALOZBF.js} +13 -13
- package/dist/cli/{db-FWFYU34R.js → db-7BTOI6YB.js} +28 -28
- package/dist/cli/{db-PJQMPZJP.mjs → db-ECDQWI2Y.mjs} +7 -7
- package/dist/cli/describe-EXSH6OZD.js +48 -0
- package/dist/cli/{describe-F57VZZML.mjs → describe-KO6WKQIB.mjs} +18 -18
- package/dist/cli/describe-core.d.mts +2 -2
- package/dist/cli/describe-core.d.ts +2 -2
- package/dist/cli/{executions-ZAJGMAAT.mjs → executions-3VMFBZT4.mjs} +7 -7
- package/dist/cli/{executions-YBT7NTY5.js → executions-VZSG3F3E.js} +9 -9
- package/dist/cli/{feedback-YLYY2EG4.js → feedback-ASKBWRXR.js} +8 -8
- package/dist/cli/{feedback-L3ZQCSY6.mjs → feedback-SN7AVALN.mjs} +4 -4
- package/dist/cli/{git-M7735NWJ.mjs → git-W2DZXEAY.mjs} +5 -5
- package/dist/cli/git-X2XUKYYX.js +25 -0
- package/dist/cli/index.js +167 -144
- package/dist/cli/index.mjs +73 -50
- package/dist/cli/{legacy-noops-TX37UP3U.js → legacy-noops-6KOC6LV7.js} +8 -8
- package/dist/cli/{legacy-noops-PKNMQMGY.mjs → legacy-noops-R2SML7ED.mjs} +5 -5
- package/dist/cli/{monitors-U7AUIOFZ.mjs → monitors-4VGZGJBT.mjs} +8 -8
- package/dist/cli/{monitors-5YTGQSGD.js → monitors-OJXDYU4G.js} +92 -92
- package/dist/cli/{org-N6DSSLLD.js → org-AELMZFUT.js} +73 -73
- package/dist/cli/{org-SRKZRVVH.mjs → org-UGN5ZG2Y.mjs} +7 -7
- package/dist/cli/{pages-YPN54RCC.js → pages-L26N7MK3.js} +18 -18
- package/dist/cli/{pages-UOPJUFCF.mjs → pages-ZYPAAYSS.mjs} +7 -7
- package/dist/cli/{play-4IK4WXWU.mjs → play-ENYB7X4S.mjs} +125 -53
- package/dist/cli/{play-MEYZEYOV.js → play-VTZWB3N5.js} +620 -548
- package/dist/cli/{providers-UGHUYP4C.mjs → providers-BZQTVAQG.mjs} +7 -7
- package/dist/cli/{providers-VS5MK4ZH.js → providers-HWBY6MGQ.js} +10 -10
- package/dist/cli/{quickstart-GW2ERQ2A.js → quickstart-D54IJ3PX.js} +6 -6
- package/dist/cli/{quickstart-YBOQSLFV.mjs → quickstart-SSQJDWPB.mjs} +4 -4
- package/dist/cli/{run-IVGMLY3J.js → run-45NKE7UY.js} +37 -37
- package/dist/cli/{run-44JT5GJM.mjs → run-FR3TPYT3.mjs} +23 -23
- package/dist/cli/{search-43NY73VR.mjs → search-CZCWSR5L.mjs} +8 -8
- package/dist/cli/search-V2GJKQQQ.js +32 -0
- package/dist/cli/{secrets-DABUD3B7.js → secrets-KJ5ZEDWT.js} +16 -16
- package/dist/cli/{secrets-ONDQGW3B.mjs → secrets-KKK55B4Y.mjs} +7 -7
- package/dist/cli/{sessions-AIAKKLSR.mjs → sessions-2YKA54PH.mjs} +5 -5
- package/dist/cli/{sessions-4ILQMBKR.js → sessions-QCNPUTDZ.js} +6 -6
- package/dist/cli/{settings-HSGKYOJT.mjs → settings-4NI2AS5V.mjs} +12 -12
- package/dist/cli/{settings-BWNLL7TP.js → settings-KM3LCPWD.js} +79 -79
- package/dist/cli/{setup-LE4OVH4M.mjs → setup-BZF3M54I.mjs} +9 -9
- package/dist/cli/{setup-37QOD4UT.js → setup-V5MTS2AA.js} +47 -47
- package/dist/cli/skills-CRFYW767.js +33 -0
- package/dist/cli/{skills-A3ZVR2YX.mjs → skills-NXYNB3LP.mjs} +7 -7
- package/dist/cli/{tools-ITKPPUDT.mjs → tools-57MWFRIZ.mjs} +17 -17
- package/dist/cli/tools-Q662HPVJ.js +55 -0
- package/dist/cli/{update-XJOSCOK5.mjs → update-QM6H677U.mjs} +9 -9
- package/dist/cli/update-TYHKF7F5.js +33 -0
- package/dist/cli/{workflow-KYQYFSND.mjs → workflow-NHYDIUWY.mjs} +8 -8
- package/dist/cli/{workflow-LERZ3TYV.js → workflow-TGEC4ITO.js} +18 -18
- package/dist/{client-B9N1bcGc.d.mts → client-DJ3r8VQJ.d.mts} +1 -1
- package/dist/{client-Btw075Hq.d.ts → client-JPSNljtj.d.ts} +1 -1
- package/dist/{client-core-TulbIeRB.d.mts → client-core-3SJlNsKU.d.mts} +21 -2
- package/dist/{client-core-DvGWlwtX.d.ts → client-core-rCoQ5Xz3.d.ts} +21 -2
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +10 -10
- package/dist/index.mjs +7 -7
- package/dist/install-integrity.json +106 -104
- package/dist/plays/bundle-play-file.mjs +5426 -12
- package/dist/plays/play-description.d.mts +1 -1
- package/dist/plays/play-description.d.ts +1 -1
- package/dist/plays/run-request.d.mts +1 -1
- package/dist/plays/run-request.d.ts +1 -1
- package/dist/plays/run-request.js +4 -2
- package/dist/plays/run-request.mjs +3 -1
- package/dist/release.d.mts +1 -1
- package/dist/release.d.ts +1 -1
- package/dist/release.js +2 -2
- package/dist/release.mjs +1 -1
- package/dist/server.d.mts +2 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.js +6 -6
- package/dist/server.mjs +4 -4
- package/package.json +1 -1
- package/dist/cli/auth-7FOB243B.js +0 -43
- package/dist/cli/chunk-7UFFWPQ2.js +0 -6188
- package/dist/cli/chunk-ZBSUXXZS.mjs +0 -6188
- package/dist/cli/describe-ZAYUBNUQ.js +0 -48
- package/dist/cli/git-A3433UTN.js +0 -25
- package/dist/cli/search-2XTYY7BF.js +0 -32
- package/dist/cli/skills-TULQXRQJ.js +0 -33
- package/dist/cli/tools-QYYBWDXU.js +0 -55
- package/dist/cli/update-XSMY7KWH.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
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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 =
|
|
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 =
|
|
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
|