deepline 0.3.88 → 0.3.89
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.ts +534 -4
- package/dist/bundling-sources/sdk/src/config.ts +77 -0
- package/dist/bundling-sources/sdk/src/http.ts +76 -30
- package/dist/bundling-sources/sdk/src/index.ts +6 -0
- package/dist/bundling-sources/sdk/src/monitor-fleet-contract.ts +1 -1
- package/dist/bundling-sources/sdk/src/play.ts +15 -1
- package/dist/bundling-sources/sdk/src/plays/bundle-play-file.ts +2 -2
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/sdk/src/types.ts +83 -4
- package/dist/bundling-sources/shared_libs/integration-contracts/AGENTS.md +9 -0
- package/dist/bundling-sources/shared_libs/integration-contracts/README.md +3 -0
- package/dist/bundling-sources/shared_libs/integration-contracts/index.ts +2 -0
- package/dist/bundling-sources/shared_libs/monitors/analytics-install-guides.ts +77 -0
- package/dist/bundling-sources/shared_libs/monitors/billing/charges.ts +125 -0
- package/dist/bundling-sources/shared_libs/monitors/billing/policy-builders.ts +686 -0
- package/dist/bundling-sources/shared_libs/monitors/billing/pricing.ts +54 -0
- package/dist/bundling-sources/shared_libs/monitors/billing/public-pricing.ts +72 -0
- package/dist/bundling-sources/shared_libs/monitors/billing/session.ts +45 -0
- package/dist/bundling-sources/shared_libs/monitors/billing/state.ts +454 -0
- package/dist/bundling-sources/shared_libs/monitors/billing/tamradar.ts +260 -0
- package/dist/bundling-sources/shared_libs/monitors/billing/types.ts +215 -0
- package/dist/bundling-sources/shared_libs/monitors/callback-security.ts +333 -0
- package/dist/bundling-sources/shared_libs/monitors/cursor-page.ts +42 -0
- package/dist/bundling-sources/shared_libs/monitors/deactivation-reasons.ts +16 -0
- package/dist/bundling-sources/shared_libs/monitors/framework-managed-payload-fields.ts +71 -0
- package/dist/bundling-sources/shared_libs/monitors/generated/provider-monitor-callback-admission-targets.ts +16 -0
- package/dist/bundling-sources/shared_libs/monitors/http-error-response.ts +267 -0
- package/dist/bundling-sources/shared_libs/monitors/inbound-publish-key.ts +39 -0
- package/dist/bundling-sources/shared_libs/monitors/inbound-queue.ts +82 -0
- package/dist/bundling-sources/shared_libs/monitors/lifecycle-notifications.ts +473 -0
- package/dist/bundling-sources/shared_libs/monitors/org-monitor-limits.ts +1 -1
- package/dist/bundling-sources/shared_libs/monitors/package.json +29 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-callback-verification.ts +1125 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-batch-operation.ts +263 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-capability.ts +964 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-consumer-health.ts +56 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-errors.ts +131 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-identity-claims.ts +535 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-legacy-aliases.ts +16 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-priority-execution.ts +56 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-public-aliases.ts +28 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-runtime-read-eligibility.ts +38 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-state.ts +122 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-status.ts +10 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-upstream-identity.ts +112 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-webhook-monitor-runtime.ts +300 -0
- package/dist/bundling-sources/shared_libs/monitors/quickstart-prompts.ts +22 -0
- package/dist/bundling-sources/shared_libs/monitors/snitcher-install-validation.ts +93 -0
- package/dist/bundling-sources/shared_libs/monitors/snitcher-ip2company.ts +332 -0
- package/dist/bundling-sources/shared_libs/monitors/snitcher-radar.ts +221 -0
- package/dist/bundling-sources/shared_libs/monitors/starter-templates.ts +105 -0
- package/dist/bundling-sources/shared_libs/monitors/tracking-provider-guidance.ts +73 -0
- package/dist/bundling-sources/shared_libs/monitors/tracking-provider-integrations.ts +81 -0
- package/dist/bundling-sources/shared_libs/monitors/tsconfig.json +10 -0
- package/dist/bundling-sources/shared_libs/play-runtime/activity-observation.ts +6 -11
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +90 -12
- package/dist/bundling-sources/shared_libs/play-runtime/async-play-invocation.ts +78 -0
- package/dist/bundling-sources/shared_libs/play-runtime/checkpoint-boundaries.ts +70 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +407 -53
- package/dist/bundling-sources/shared_libs/play-runtime/coordinator-headers.ts +1 -32
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +57 -0
- package/dist/bundling-sources/shared_libs/play-runtime/default-batch-strategies.ts +20 -1
- package/dist/bundling-sources/shared_libs/play-runtime/execution-capabilities.ts +6 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-protocol.ts +7 -1
- package/dist/bundling-sources/shared_libs/play-runtime/lane-retirement-gate.ts +189 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +28 -0
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +23 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +18 -4
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-start-admission.ts +68 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runtime-sandbox-reconciliation.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +10 -8
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-capacity-policy.ts +20 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +6 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-env.ts +6 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-scheduler-topology.ts +24 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +30 -1
- package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +1 -1
- package/dist/bundling-sources/shared_libs/plays/artifact-types.ts +6 -3
- package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +454 -17
- package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +19 -4
- package/dist/bundling-sources/shared_libs/plays/contracts.ts +6 -14
- package/dist/bundling-sources/shared_libs/plays/core.ts +11 -6
- package/dist/bundling-sources/shared_libs/plays/docflow-binding-owner.ts +16 -11
- package/dist/bundling-sources/shared_libs/plays/email-status.ts +1 -1
- package/dist/bundling-sources/shared_libs/plays/resolve-static-pipeline.ts +1 -1
- package/dist/bundling-sources/shared_libs/plays/slug.ts +115 -0
- package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +44 -14
- package/dist/bundling-sources/shared_libs/plays/tool-category-descriptions.ts +2 -2
- package/dist/bundling-sources/shared_libs/product-notifications/catalog.ts +877 -0
- package/dist/bundling-sources/shared_libs/product-notifications/configuration-draft.ts +456 -0
- package/dist/bundling-sources/shared_libs/product-notifications/configuration.ts +560 -0
- package/dist/bundling-sources/shared_libs/product-notifications/content.ts +28 -0
- package/dist/bundling-sources/shared_libs/product-notifications/default-policy.ts +59 -0
- package/dist/bundling-sources/shared_libs/product-notifications/email.ts +246 -0
- package/dist/bundling-sources/shared_libs/product-notifications/evaluation.ts +280 -0
- package/dist/bundling-sources/shared_libs/product-notifications/events.ts +433 -0
- package/dist/bundling-sources/shared_libs/product-notifications/legacy-slack.ts +80 -0
- package/dist/bundling-sources/shared_libs/product-notifications/workspace.ts +38 -0
- package/dist/bundling-sources/shared_libs/runtime-env.ts +6 -0
- package/dist/bundling-sources/shared_libs/security/header-safe-env.ts +28 -0
- package/dist/bundling-sources/shared_libs/security/internal-actor.ts +50 -0
- package/dist/bundling-sources/shared_libs/security/secret-redaction.ts +267 -0
- package/dist/cli/index.js +1070 -193
- package/dist/cli/index.mjs +1057 -180
- package/dist/{compiler-manifest-Dbov16F7.d.mts → compiler-manifest-DsLbQaew.d.mts} +132 -8
- package/dist/{compiler-manifest-Dbov16F7.d.ts → compiler-manifest-DsLbQaew.d.ts} +132 -8
- package/dist/index.d.mts +834 -12
- package/dist/index.d.ts +834 -12
- package/dist/index.js +469 -40
- package/dist/index.mjs +469 -40
- package/dist/install-integrity.json +65 -4
- package/dist/plays/bundle-play-file.d.mts +8 -3
- package/dist/plays/bundle-play-file.d.ts +8 -3
- package/dist/plays/bundle-play-file.mjs +100 -20
- package/package.json +3 -1
- /package/dist/bundling-sources/shared_libs/{integrations/bettercontact-execution-policy.ts → integration-contracts/bettercontact/execution-policy.ts} +0 -0
- /package/dist/bundling-sources/shared_libs/{integrations/theirstack-execution-policy.ts → integration-contracts/theirstack/execution-policy.ts} +0 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
const PARALLEL_WEBHOOK_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
|
|
4
|
+
const STANDARD_WEBHOOK_SECRET_PREFIX = 'whsec_';
|
|
5
|
+
|
|
6
|
+
type WebhookVerificationOk = {
|
|
7
|
+
ok: true;
|
|
8
|
+
mode: 'verified' | 'unsigned_non_prod';
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type WebhookVerificationFailure = {
|
|
12
|
+
ok: false;
|
|
13
|
+
status: 401 | 500;
|
|
14
|
+
code: string;
|
|
15
|
+
message: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type WebhookVerificationResult =
|
|
19
|
+
| WebhookVerificationOk
|
|
20
|
+
| WebhookVerificationFailure;
|
|
21
|
+
|
|
22
|
+
export type ProviderCallbackBindingValidationFailure = {
|
|
23
|
+
ok: false;
|
|
24
|
+
status: 409;
|
|
25
|
+
code: 'provider_callback_binding_mismatch';
|
|
26
|
+
message: string;
|
|
27
|
+
fields: string[];
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type ProviderCallbackBindingValidationResult =
|
|
31
|
+
| { ok: true }
|
|
32
|
+
| ProviderCallbackBindingValidationFailure;
|
|
33
|
+
|
|
34
|
+
function normalizedEnvFlag(value: string | undefined) {
|
|
35
|
+
return value?.trim().toLowerCase();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function readRecord(value: unknown): Record<string, unknown> {
|
|
39
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
40
|
+
? (value as Record<string, unknown>)
|
|
41
|
+
: {};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function readString(value: unknown) {
|
|
45
|
+
if (typeof value === 'number' && Number.isFinite(value)) return String(value);
|
|
46
|
+
if (typeof value !== 'string') return null;
|
|
47
|
+
const trimmed = value.trim();
|
|
48
|
+
return trimmed || null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function readStringAtPath(record: Record<string, unknown>, path: string) {
|
|
52
|
+
const value = path.split('.').reduce<unknown>((current, part) => {
|
|
53
|
+
if (!current || typeof current !== 'object' || Array.isArray(current)) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
return (current as Record<string, unknown>)[part];
|
|
57
|
+
}, record);
|
|
58
|
+
return readString(value);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function addString(
|
|
62
|
+
values: Set<string>,
|
|
63
|
+
value: string | number | null | undefined,
|
|
64
|
+
) {
|
|
65
|
+
const string = readString(value);
|
|
66
|
+
if (string) values.add(string);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function genericWebhookIdsFromPayload(payload: Record<string, unknown>) {
|
|
70
|
+
const values = new Set<string>();
|
|
71
|
+
for (const path of [
|
|
72
|
+
'webhook_id',
|
|
73
|
+
'webhookId',
|
|
74
|
+
'hook_id',
|
|
75
|
+
'hookId',
|
|
76
|
+
'webhook.id',
|
|
77
|
+
'data.webhook_id',
|
|
78
|
+
'data.webhookId',
|
|
79
|
+
'data.hook_id',
|
|
80
|
+
'data.hookId',
|
|
81
|
+
'result.webhook_id',
|
|
82
|
+
'result.webhookId',
|
|
83
|
+
'result.data.webhook_id',
|
|
84
|
+
'result.data.webhookId',
|
|
85
|
+
]) {
|
|
86
|
+
addString(values, readStringAtPath(payload, path));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const events = Array.isArray(payload.events) ? payload.events : [];
|
|
90
|
+
for (const item of events) {
|
|
91
|
+
const event = readRecord(item);
|
|
92
|
+
addString(values, readStringAtPath(event, 'webhook_id'));
|
|
93
|
+
addString(values, readStringAtPath(event, 'webhookId'));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return values;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function isTheirstackWebhookBindingPayload(payload: Record<string, unknown>) {
|
|
100
|
+
return Boolean(
|
|
101
|
+
readString(payload.id) &&
|
|
102
|
+
readString(payload.search_id) &&
|
|
103
|
+
readString(payload.event_type) &&
|
|
104
|
+
readString(payload.url),
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function theirstackWebhookIdsFromPayload(payload: Record<string, unknown>) {
|
|
109
|
+
const values = new Set<string>();
|
|
110
|
+
addString(values, payload.webhook_id as string | number | null | undefined);
|
|
111
|
+
addString(values, payload.webhookId as string | number | null | undefined);
|
|
112
|
+
if (isTheirstackWebhookBindingPayload(payload)) {
|
|
113
|
+
addString(values, payload.id as string | number | null | undefined);
|
|
114
|
+
}
|
|
115
|
+
return values;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function theirstackSavedSearchIdsFromPayload(payload: Record<string, unknown>) {
|
|
119
|
+
const values = new Set<string>();
|
|
120
|
+
addString(values, payload.search_id as string | number | null | undefined);
|
|
121
|
+
addString(
|
|
122
|
+
values,
|
|
123
|
+
payload.saved_search_id as string | number | null | undefined,
|
|
124
|
+
);
|
|
125
|
+
return values;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function mismatchedFields(input: {
|
|
129
|
+
expected: string | null;
|
|
130
|
+
actual: Set<string>;
|
|
131
|
+
field: string;
|
|
132
|
+
}) {
|
|
133
|
+
if (!input.expected || input.actual.size === 0) return [];
|
|
134
|
+
return [...input.actual].some((value) => value !== input.expected)
|
|
135
|
+
? [input.field]
|
|
136
|
+
: [];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function theirstackSavedSearchIdFromProviderBinding(
|
|
140
|
+
providerBinding: Record<string, unknown>,
|
|
141
|
+
) {
|
|
142
|
+
const upstreamResponse = readRecord(providerBinding.upstream_response);
|
|
143
|
+
const savedSearch = readRecord(upstreamResponse.saved_search);
|
|
144
|
+
const webhook = readRecord(upstreamResponse.webhook);
|
|
145
|
+
return (
|
|
146
|
+
readString(savedSearch.id) ??
|
|
147
|
+
readString(readRecord(savedSearch.data).id) ??
|
|
148
|
+
readString(webhook.search_id) ??
|
|
149
|
+
readString(readRecord(webhook.data).search_id)
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function validateProviderCallbackBinding(input: {
|
|
154
|
+
provider: string;
|
|
155
|
+
payload: Record<string, unknown>;
|
|
156
|
+
providerBinding: Record<string, unknown>;
|
|
157
|
+
upstreamWebhookId?: string | null;
|
|
158
|
+
}): ProviderCallbackBindingValidationResult {
|
|
159
|
+
const fields =
|
|
160
|
+
input.provider === 'theirstack'
|
|
161
|
+
? [
|
|
162
|
+
...mismatchedFields({
|
|
163
|
+
expected: input.upstreamWebhookId ?? null,
|
|
164
|
+
actual: theirstackWebhookIdsFromPayload(input.payload),
|
|
165
|
+
field: 'webhook_id',
|
|
166
|
+
}),
|
|
167
|
+
...mismatchedFields({
|
|
168
|
+
expected: theirstackSavedSearchIdFromProviderBinding(
|
|
169
|
+
input.providerBinding,
|
|
170
|
+
),
|
|
171
|
+
actual: theirstackSavedSearchIdsFromPayload(input.payload),
|
|
172
|
+
field: 'search_id',
|
|
173
|
+
}),
|
|
174
|
+
]
|
|
175
|
+
: mismatchedFields({
|
|
176
|
+
expected: input.upstreamWebhookId ?? null,
|
|
177
|
+
actual: genericWebhookIdsFromPayload(input.payload),
|
|
178
|
+
field: 'webhook_id',
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
if (fields.length === 0) {
|
|
182
|
+
return { ok: true };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
ok: false,
|
|
187
|
+
status: 409,
|
|
188
|
+
code: 'provider_callback_binding_mismatch',
|
|
189
|
+
message:
|
|
190
|
+
'Callback payload provider identifiers do not match the authenticated Deepline monitor binding.',
|
|
191
|
+
fields: Array.from(new Set(fields)),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function shouldRequireParallelWebhookSignature() {
|
|
196
|
+
const explicit = normalizedEnvFlag(
|
|
197
|
+
process.env.DEEPLINE_REQUIRE_PARALLEL_WEBHOOK_SIGNATURE,
|
|
198
|
+
);
|
|
199
|
+
if (explicit === 'true' || explicit === '1' || explicit === 'yes') {
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
if (explicit === 'false' || explicit === '0' || explicit === 'no') {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
return process.env.NODE_ENV === 'production';
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function parallelWebhookSigningSecret() {
|
|
209
|
+
return (
|
|
210
|
+
process.env.DEEPLINE_PARALLEL_WEBHOOK_SECRET?.trim() ||
|
|
211
|
+
process.env.PARALLEL_WEBHOOK_SECRET?.trim() ||
|
|
212
|
+
null
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function signingKey(secret: string) {
|
|
217
|
+
if (secret.startsWith(STANDARD_WEBHOOK_SECRET_PREFIX)) {
|
|
218
|
+
return Buffer.from(
|
|
219
|
+
secret.slice(STANDARD_WEBHOOK_SECRET_PREFIX.length),
|
|
220
|
+
'base64',
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
return secret;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function readStandardWebhookHeader(headers: Headers, name: string) {
|
|
227
|
+
return (
|
|
228
|
+
headers.get(name) ?? headers.get(`svix-${name.replace('webhook-', '')}`)
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function parseTimestampMs(value: string) {
|
|
233
|
+
const trimmed = value.trim();
|
|
234
|
+
if (/^\d+$/.test(trimmed)) {
|
|
235
|
+
const numeric = Number(trimmed);
|
|
236
|
+
if (!Number.isFinite(numeric)) return null;
|
|
237
|
+
return numeric > 10_000_000_000 ? numeric : numeric * 1000;
|
|
238
|
+
}
|
|
239
|
+
const parsed = Date.parse(trimmed);
|
|
240
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function readSignatures(header: string) {
|
|
244
|
+
return header
|
|
245
|
+
.split(/\s+/)
|
|
246
|
+
.map((part) => part.trim())
|
|
247
|
+
.filter(Boolean)
|
|
248
|
+
.flatMap((part) => {
|
|
249
|
+
const [version, signature] = part.split(',', 2);
|
|
250
|
+
return version === 'v1' && signature ? [signature] : [];
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function signaturesEqual(expectedBase64: string, actualBase64: string) {
|
|
255
|
+
const expected = Buffer.from(expectedBase64, 'base64');
|
|
256
|
+
const actual = Buffer.from(actualBase64, 'base64');
|
|
257
|
+
if (expected.length === 0 || actual.length === 0) return false;
|
|
258
|
+
if (expected.length !== actual.length) return false;
|
|
259
|
+
return timingSafeEqual(expected, actual);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function verifyParallelWebhookRequest(input: {
|
|
263
|
+
headers: Headers;
|
|
264
|
+
rawBody: string;
|
|
265
|
+
nowMs?: number;
|
|
266
|
+
}): WebhookVerificationResult {
|
|
267
|
+
const secret = parallelWebhookSigningSecret();
|
|
268
|
+
if (!secret) {
|
|
269
|
+
if (shouldRequireParallelWebhookSignature()) {
|
|
270
|
+
return {
|
|
271
|
+
ok: false,
|
|
272
|
+
status: 500,
|
|
273
|
+
code: 'parallel_webhook_secret_missing',
|
|
274
|
+
message: 'Parallel webhook signing secret is not configured.',
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
return { ok: true, mode: 'unsigned_non_prod' };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const webhookId = readStandardWebhookHeader(input.headers, 'webhook-id');
|
|
281
|
+
const webhookTimestamp = readStandardWebhookHeader(
|
|
282
|
+
input.headers,
|
|
283
|
+
'webhook-timestamp',
|
|
284
|
+
);
|
|
285
|
+
const webhookSignature = readStandardWebhookHeader(
|
|
286
|
+
input.headers,
|
|
287
|
+
'webhook-signature',
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
if (!webhookId || !webhookTimestamp || !webhookSignature) {
|
|
291
|
+
return {
|
|
292
|
+
ok: false,
|
|
293
|
+
status: 401,
|
|
294
|
+
code: 'parallel_webhook_signature_missing',
|
|
295
|
+
message: 'Parallel webhook signature headers are missing.',
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const timestampMs = parseTimestampMs(webhookTimestamp);
|
|
300
|
+
const nowMs = input.nowMs ?? Date.now();
|
|
301
|
+
if (
|
|
302
|
+
timestampMs === null ||
|
|
303
|
+
Math.abs(nowMs - timestampMs) > PARALLEL_WEBHOOK_SIGNATURE_TOLERANCE_MS
|
|
304
|
+
) {
|
|
305
|
+
return {
|
|
306
|
+
ok: false,
|
|
307
|
+
status: 401,
|
|
308
|
+
code: 'parallel_webhook_timestamp_invalid',
|
|
309
|
+
message: 'Parallel webhook timestamp is outside the accepted window.',
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const signedPayload = `${webhookId}.${webhookTimestamp}.${input.rawBody}`;
|
|
314
|
+
const expectedSignature = createHmac('sha256', signingKey(secret))
|
|
315
|
+
.update(signedPayload)
|
|
316
|
+
.digest('base64');
|
|
317
|
+
const signatures = readSignatures(webhookSignature);
|
|
318
|
+
if (
|
|
319
|
+
signatures.length === 0 ||
|
|
320
|
+
!signatures.some((signature) =>
|
|
321
|
+
signaturesEqual(expectedSignature, signature),
|
|
322
|
+
)
|
|
323
|
+
) {
|
|
324
|
+
return {
|
|
325
|
+
ok: false,
|
|
326
|
+
status: 401,
|
|
327
|
+
code: 'parallel_webhook_signature_invalid',
|
|
328
|
+
message: 'Parallel webhook signature is invalid.',
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return { ok: true, mode: 'verified' };
|
|
333
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public cursor-page contract for API collections.
|
|
3
|
+
*
|
|
4
|
+
* Cursors are owned by the backing store (Convex in the common case). Treat
|
|
5
|
+
* them as opaque: return `next_cursor` verbatim and accept it verbatim on the
|
|
6
|
+
* next request. Do not wrap it with an offset or attempt to decode it.
|
|
7
|
+
*/
|
|
8
|
+
export type CursorPage<T> = {
|
|
9
|
+
items: T[];
|
|
10
|
+
returned: number;
|
|
11
|
+
has_more: boolean;
|
|
12
|
+
next_cursor: string | null;
|
|
13
|
+
total?: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function cursorPage<T>(input: {
|
|
17
|
+
items: T[];
|
|
18
|
+
nextCursor?: string | null;
|
|
19
|
+
total?: number;
|
|
20
|
+
}): CursorPage<T> {
|
|
21
|
+
const nextCursor = input.nextCursor?.trim() || null;
|
|
22
|
+
return {
|
|
23
|
+
items: input.items,
|
|
24
|
+
returned: input.items.length,
|
|
25
|
+
has_more: nextCursor !== null,
|
|
26
|
+
next_cursor: nextCursor,
|
|
27
|
+
...(typeof input.total === 'number' ? { total: input.total } : {}),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Parses an opaque cursor query parameter without assigning it meaning.
|
|
33
|
+
* Returns null only for an oversized value so routes can give callers a
|
|
34
|
+
* deterministic 400 before passing unbounded input to their datastore.
|
|
35
|
+
*/
|
|
36
|
+
export function readOpaqueCursor(
|
|
37
|
+
value: string | null,
|
|
38
|
+
): string | undefined | null {
|
|
39
|
+
const cursor = value?.trim();
|
|
40
|
+
if (!cursor) return undefined;
|
|
41
|
+
return cursor.length <= 8_192 ? cursor : null;
|
|
42
|
+
}
|
|
@@ -49,6 +49,22 @@ export const MONITOR_DEACTIVATION_REASONS: readonly MonitorDeactivationReasonDef
|
|
|
49
49
|
resolution:
|
|
50
50
|
'Add credits. Monitors are reactivated automatically once the balance is positive again.',
|
|
51
51
|
},
|
|
52
|
+
{
|
|
53
|
+
id: 'monthly_cap_exceeded',
|
|
54
|
+
owner: 'policy',
|
|
55
|
+
summary:
|
|
56
|
+
'Deactivated because the organization reached its monthly monitor spending cap.',
|
|
57
|
+
resolution:
|
|
58
|
+
'Raise or reset the monthly cap, then explicitly reactivate the monitors you want restored.',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: 'billing_failure',
|
|
62
|
+
owner: 'policy',
|
|
63
|
+
summary:
|
|
64
|
+
'Deactivated because monitor event billing was denied.',
|
|
65
|
+
resolution:
|
|
66
|
+
'Resolve the billing restriction, then explicitly reactivate the monitors you want restored.',
|
|
67
|
+
},
|
|
52
68
|
{
|
|
53
69
|
id: 'entitlement_expired',
|
|
54
70
|
owner: 'policy',
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { ProviderMonitorCapabilityDefinition } from './provider-monitor-capability';
|
|
2
|
+
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Framework-managed monitor payload fields.
|
|
5
|
+
//
|
|
6
|
+
// When a monitor payload schema is DERIVED from the provider's OpenAPI request
|
|
7
|
+
// body (see `deriveMonitorPayloadSchemaFromDeployAction`), some accepted request
|
|
8
|
+
// fields are set by the Deepline monitor framework at deploy time, not by the
|
|
9
|
+
// customer. Those fields must be stripped from the customer-facing payload
|
|
10
|
+
// schema so `monitors deploy` neither asks for nor accepts them.
|
|
11
|
+
//
|
|
12
|
+
// This policy is PROVIDER-AGNOSTIC on purpose: the excluded set is computed from
|
|
13
|
+
// the capability's own `runtime` config, so any future OpenAPI-backed monitor
|
|
14
|
+
// provider inherits the exact same behavior with zero provider-specific code.
|
|
15
|
+
// The only provider-specific input is the capability authoring file itself.
|
|
16
|
+
//
|
|
17
|
+
// Two framework-managed fields exist today:
|
|
18
|
+
//
|
|
19
|
+
// - `webhook_url`: the per-monitor callback URL. The framework mints Deepline's
|
|
20
|
+
// callback URL and injects it into the create-radar request via the
|
|
21
|
+
// capability's `webhookRegistration.injectCallbackUrl` descriptor. This
|
|
22
|
+
// applies whenever the capability delivers inbound over a per-monitor webhook
|
|
23
|
+
// (`runtime.inbound === 'webhook'` with
|
|
24
|
+
// `webhookRegistrationStrategy === 'per_monitor_create_url'`). Account-level
|
|
25
|
+
// webhooks (`account_marker`) register no per-monitor URL, so `webhook_url`
|
|
26
|
+
// is not framework-managed for them.
|
|
27
|
+
//
|
|
28
|
+
// - `custom_fields`: the correlation marker object. `mergeCustomFields`
|
|
29
|
+
// (provider-monitor-adapters.ts) stamps the Deepline monitor key + org
|
|
30
|
+
// correlation markers into `custom_fields` on every provider-native monitor
|
|
31
|
+
// deploy payload. It is always framework-managed for
|
|
32
|
+
// `provider_native_monitor` capabilities.
|
|
33
|
+
//
|
|
34
|
+
// The concrete field NAMES (`webhook_url`, `custom_fields`) are the request-body
|
|
35
|
+
// keys the upstream OpenAPI uses for these two managed concepts. They are named
|
|
36
|
+
// constants here, keyed to the framework concept, not to any one provider.
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
/** OpenAPI request field carrying the per-monitor callback URL. */
|
|
40
|
+
export const FRAMEWORK_CALLBACK_URL_PAYLOAD_FIELD = 'webhook_url';
|
|
41
|
+
|
|
42
|
+
/** OpenAPI request field carrying Deepline correlation markers. */
|
|
43
|
+
export const FRAMEWORK_CORRELATION_MARKER_PAYLOAD_FIELD = 'custom_fields';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The payload fields the monitor framework manages (injects) for a capability,
|
|
47
|
+
* derived entirely from its `runtime` config. Any request-body field in this set
|
|
48
|
+
* is excluded from a derived customer-facing payload schema.
|
|
49
|
+
*/
|
|
50
|
+
export function frameworkManagedPayloadFields(
|
|
51
|
+
runtime: ProviderMonitorCapabilityDefinition['runtime'],
|
|
52
|
+
): Set<string> {
|
|
53
|
+
const managed = new Set<string>();
|
|
54
|
+
|
|
55
|
+
// Per-monitor webhook capabilities have the callback URL injected by the
|
|
56
|
+
// framework via the webhook-registration descriptor.
|
|
57
|
+
if (
|
|
58
|
+
runtime.inbound === 'webhook' &&
|
|
59
|
+
runtime.webhookRegistrationStrategy === 'per_monitor_create_url'
|
|
60
|
+
) {
|
|
61
|
+
managed.add(FRAMEWORK_CALLBACK_URL_PAYLOAD_FIELD);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Provider-native monitors always carry Deepline correlation markers in
|
|
65
|
+
// `custom_fields`, stamped by mergeCustomFields at deploy time.
|
|
66
|
+
if (runtime.kind === 'provider_native_monitor') {
|
|
67
|
+
managed.add(FRAMEWORK_CORRELATION_MARKER_PAYLOAD_FIELD);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return managed;
|
|
71
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Generated by scripts/generate-integration-artifacts.ts. Do not edit.
|
|
2
|
+
// Convex-safe declaration of monitor capabilities that authenticate inbound
|
|
3
|
+
// callbacks with an opaque callback token. Provider monitor declarations own
|
|
4
|
+
// this contract; do not add provider allowlists to Convex migrations.
|
|
5
|
+
export const GENERATED_PROVIDER_MONITOR_CALLBACK_TOKEN_ADMISSION_TARGETS = {
|
|
6
|
+
"deepline_native.company_radar": { provider: "deepline_native" },
|
|
7
|
+
"deepline_native.contact_radar": { provider: "deepline_native" },
|
|
8
|
+
"deepline_native.industry_radar": { provider: "deepline_native" },
|
|
9
|
+
"findymail.signal_monitor": { provider: "findymail" },
|
|
10
|
+
"heyreach.campaign_events": { provider: "heyreach" },
|
|
11
|
+
"rb2b.visitor_events": { provider: "rb2b" },
|
|
12
|
+
"smartlead.campaign_events": { provider: "smartlead" },
|
|
13
|
+
"vector.visitor_events": { provider: "vector" },
|
|
14
|
+
} as const satisfies Record<string, { provider: string }>;
|
|
15
|
+
|
|
16
|
+
export type GeneratedProviderMonitorCallbackTokenAdmissionTarget = keyof typeof GENERATED_PROVIDER_MONITOR_CALLBACK_TOKEN_ADMISSION_TARGETS;
|