deepline 0.3.55 → 0.3.57

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.
@@ -0,0 +1,102 @@
1
+ /**
2
+ * What an organization may watch.
3
+ *
4
+ * Monitors are priced and reasoned about in accounts, not in rows: an
5
+ * organization watches five thousand companies, and watches up to five signals
6
+ * on each of them. A flat monitor ceiling could not express either half - it
7
+ * let one company consume the whole allowance, and it made "how many accounts
8
+ * can I watch" unanswerable without knowing how many signals each would use.
9
+ *
10
+ * The two numbers below are the whole policy. Maximum monitors is their
11
+ * product and is deliberately not a third number to reason about.
12
+ */
13
+ import { ORG_LIMITS, OrgLimit } from '../org-limits/registry';
14
+
15
+ export const DEFAULT_ORG_MONITOR_ENTITY_LIMIT =
16
+ ORG_LIMITS[OrgLimit.MonitorWatchedEntities].defaultValue;
17
+
18
+ /** Signals one account may be watched with, e.g. jobs + funding + news. */
19
+ export const DEFAULT_MONITORS_PER_ENTITY =
20
+ ORG_LIMITS[OrgLimit.MonitorsPerEntity].defaultValue;
21
+
22
+ export type OrgMonitorLimitPolicy = {
23
+ /** Distinct accounts, contacts, campaigns, or connections. */
24
+ entityLimit: number;
25
+ /** Monitors admitted against any one of them. */
26
+ monitorsPerEntity: number;
27
+ overrideApplied: boolean;
28
+ };
29
+
30
+ /**
31
+ * What an organization gets before any per-organization override.
32
+ *
33
+ * Overrides used to be a source-controlled map keyed by organization id, so
34
+ * raising one customer's ceiling meant a deploy. They live in Convex now,
35
+ * against the ids declared in the limit registry, which is why this function
36
+ * no longer takes an organization: a caller that can read the override record
37
+ * resolves it there, and a caller that cannot is showing a default.
38
+ */
39
+ export function defaultOrgMonitorLimitPolicy(): OrgMonitorLimitPolicy {
40
+ return {
41
+ entityLimit: DEFAULT_ORG_MONITOR_ENTITY_LIMIT,
42
+ monitorsPerEntity: DEFAULT_MONITORS_PER_ENTITY,
43
+ overrideApplied: false,
44
+ };
45
+ }
46
+
47
+ export type OrgMonitorCapacity = {
48
+ /** Accounts already watched. */
49
+ used: number;
50
+ /** Accounts held by an in-flight create. */
51
+ reserved: number;
52
+ limit: number;
53
+ remaining: number;
54
+ over_by: number;
55
+ can_create: boolean;
56
+ monitors_per_entity: number;
57
+ };
58
+
59
+ /**
60
+ * Capacity in accounts.
61
+ *
62
+ * `used` and `reserved` count DISTINCT entities, so adding a second signal to
63
+ * an account the organization already watches consumes nothing here - that is
64
+ * what the per-entity limit governs instead.
65
+ */
66
+ export function orgMonitorCapacity(input: {
67
+ used: number;
68
+ reserved: number;
69
+ limit: number;
70
+ monitorsPerEntity?: number;
71
+ }): OrgMonitorCapacity {
72
+ const occupied = input.used + input.reserved;
73
+ return {
74
+ used: input.used,
75
+ reserved: input.reserved,
76
+ limit: input.limit,
77
+ remaining: Math.max(0, input.limit - occupied),
78
+ over_by: Math.max(0, occupied - input.limit),
79
+ can_create: occupied < input.limit,
80
+ monitors_per_entity:
81
+ input.monitorsPerEntity ?? DEFAULT_MONITORS_PER_ENTITY,
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Whether one more monitor may be admitted against a given account.
87
+ *
88
+ * Separate from capacity because the two refuse for different reasons and the
89
+ * customer needs to be told which: the organization is watching as many
90
+ * accounts as it may, or this account already carries as many signals as it
91
+ * may.
92
+ */
93
+ export function entityMonitorAdmission(input: {
94
+ monitorsOnEntity: number;
95
+ monitorsPerEntity: number;
96
+ }): { admitted: boolean; remaining: number } {
97
+ const remaining = Math.max(
98
+ 0,
99
+ input.monitorsPerEntity - input.monitorsOnEntity,
100
+ );
101
+ return { admitted: remaining > 0, remaining };
102
+ }
@@ -0,0 +1,244 @@
1
+ export type MonitorSdkValidationIssueEntityType =
2
+ | 'monitor'
3
+ | 'action'
4
+ | 'trigger'
5
+ | 'bundle'
6
+ | 'play'
7
+ | 'listener'
8
+ | 'table'
9
+ | 'tool'
10
+ | 'workflow';
11
+
12
+ export type MonitorSdkValidationIssue = {
13
+ severity: 'error' | 'warning';
14
+ code: string;
15
+ path: string;
16
+ message: string;
17
+ expected?: unknown;
18
+ received?: unknown;
19
+ suggestion?: string;
20
+ entity_type?: MonitorSdkValidationIssueEntityType;
21
+ entity_key?: string;
22
+ docs_ref?: string;
23
+ };
24
+
25
+ export type MonitorSdkValidationResult = {
26
+ valid: boolean;
27
+ issues: MonitorSdkValidationIssue[];
28
+ };
29
+
30
+ export const MONITOR_SDK_VALIDATION_ERROR_CODE = 'MONITOR_SDK_VALIDATION_ERROR';
31
+
32
+ export const MONITOR_DEPLOY_VALIDATION_ERROR_CODE =
33
+ 'monitor_deploy_validation_failed';
34
+
35
+ export class MonitorSdkValidationError extends Error {
36
+ readonly code = MONITOR_SDK_VALIDATION_ERROR_CODE;
37
+ readonly issues: MonitorSdkValidationIssue[];
38
+
39
+ constructor(
40
+ issues: MonitorSdkValidationIssue[],
41
+ message = 'Monitor SDK validation failed.',
42
+ // Some of these wrap a provider failure whose RETRYABILITY is the only
43
+ // thing a caller can act on. A wrapper that drops it turns a rate limit
44
+ // into a permanent refusal, so the cause travels with the issue list.
45
+ options?: { cause?: unknown },
46
+ ) {
47
+ super(message);
48
+ this.name = 'MonitorSdkValidationError';
49
+ this.issues = normalizeMonitorSdkValidationIssues(issues);
50
+ // Assigned rather than passed to `super`: this module compiles under two
51
+ // tsconfigs and only one of them types the `ErrorOptions` overload.
52
+ if (options?.cause !== undefined) {
53
+ (this as { cause?: unknown }).cause = options.cause;
54
+ }
55
+ }
56
+ }
57
+
58
+ const SENSITIVE_MONITOR_VALIDATION_KEYS = new Set([
59
+ 'access_token',
60
+ 'accessToken',
61
+ 'api_key',
62
+ 'apiKey',
63
+ 'authorization',
64
+ 'binding_id',
65
+ 'bindingId',
66
+ 'callback_token',
67
+ 'callbackToken',
68
+ 'callback_url',
69
+ 'callbackUrl',
70
+ 'credential',
71
+ 'credentials',
72
+ 'password',
73
+ 'provider_binding',
74
+ 'providerBinding',
75
+ 'provider_capability',
76
+ 'providerCapability',
77
+ 'provider_credentials',
78
+ 'providerCredentials',
79
+ 'provider_marker',
80
+ 'providerMarker',
81
+ 'refresh_token',
82
+ 'refreshToken',
83
+ 'runtime_action',
84
+ 'runtimeAction',
85
+ 'runtime_payload',
86
+ 'runtimePayload',
87
+ 'secret',
88
+ 'source_capability',
89
+ 'sourceCapability',
90
+ 'upstream_monitor_id',
91
+ 'upstreamMonitorId',
92
+ 'upstream_resource_id',
93
+ 'upstreamResourceId',
94
+ 'upstream_response',
95
+ 'upstreamResponse',
96
+ ]);
97
+
98
+ function sanitizeMonitorPublicString(value: string) {
99
+ return value.replace(
100
+ /https?:\/\/\S*\/api\/v2\/monitors\/provider-callbacks\/\S+/g,
101
+ '[redacted-callback-url]',
102
+ );
103
+ }
104
+
105
+ export function sanitizeMonitorPublicValue(
106
+ value: unknown,
107
+ seen = new WeakSet<object>(),
108
+ allowSetupWebhookEndpoint = false,
109
+ ): unknown {
110
+ if (typeof value === 'string') {
111
+ return sanitizeMonitorPublicString(value);
112
+ }
113
+ if (!value || typeof value !== 'object') return value;
114
+ if (seen.has(value)) return '[redacted-circular-reference]';
115
+ seen.add(value);
116
+ if (Array.isArray(value)) {
117
+ const sanitizedArray = value.map((entry) =>
118
+ sanitizeMonitorPublicValue(entry, seen, allowSetupWebhookEndpoint),
119
+ );
120
+ seen.delete(value);
121
+ return sanitizedArray;
122
+ }
123
+ const sanitized: Record<string, unknown> = {};
124
+ for (const [key, entry] of Object.entries(value)) {
125
+ if (SENSITIVE_MONITOR_VALIDATION_KEYS.has(key)) continue;
126
+ // A manual webhook endpoint is deliberately returned only as part of an
127
+ // authenticated deploy response, so the customer can paste it into the
128
+ // provider dashboard. Its token is otherwise redacted from monitor APIs.
129
+ if (
130
+ allowSetupWebhookEndpoint &&
131
+ key === 'webhook_endpoint' &&
132
+ typeof entry === 'string'
133
+ ) {
134
+ sanitized[key] = entry;
135
+ continue;
136
+ }
137
+ sanitized[key] = sanitizeMonitorPublicValue(entry, seen, key === 'setup');
138
+ }
139
+ seen.delete(value);
140
+ return sanitized;
141
+ }
142
+
143
+ function normalizeMonitorSdkIssueEntityType(
144
+ value: unknown,
145
+ ): MonitorSdkValidationIssueEntityType | null {
146
+ if (value === 'provider_capability') return 'tool';
147
+ return isMonitorSdkIssueEntityType(value) ? value : null;
148
+ }
149
+
150
+ export function monitorSdkIssue(
151
+ issue: Omit<MonitorSdkValidationIssue, 'severity'> & {
152
+ severity?: MonitorSdkValidationIssue['severity'];
153
+ },
154
+ ): MonitorSdkValidationIssue {
155
+ const entityType = normalizeMonitorSdkIssueEntityType(issue.entity_type);
156
+ return {
157
+ severity: issue.severity ?? 'error',
158
+ code: issue.code,
159
+ path: issue.path,
160
+ message: issue.message,
161
+ ...(Object.prototype.hasOwnProperty.call(issue, 'expected')
162
+ ? { expected: sanitizeMonitorPublicValue(issue.expected) }
163
+ : {}),
164
+ ...(Object.prototype.hasOwnProperty.call(issue, 'received')
165
+ ? { received: sanitizeMonitorPublicValue(issue.received) }
166
+ : {}),
167
+ ...(issue.suggestion ? { suggestion: issue.suggestion } : {}),
168
+ ...(entityType ? { entity_type: entityType } : {}),
169
+ ...(issue.entity_key ? { entity_key: issue.entity_key } : {}),
170
+ ...(issue.docs_ref ? { docs_ref: issue.docs_ref } : {}),
171
+ };
172
+ }
173
+
174
+ export function normalizeMonitorSdkValidationIssues(
175
+ issues: unknown,
176
+ ): MonitorSdkValidationIssue[] {
177
+ if (!Array.isArray(issues)) return [];
178
+ return issues
179
+ .filter((issue): issue is Record<string, unknown> =>
180
+ Boolean(issue && typeof issue === 'object' && !Array.isArray(issue)),
181
+ )
182
+ .map((issue) => {
183
+ const entityType = normalizeMonitorSdkIssueEntityType(issue.entity_type);
184
+ return monitorSdkIssue({
185
+ code:
186
+ typeof issue.code === 'string' && issue.code.trim()
187
+ ? issue.code.trim()
188
+ : 'monitor_validation_error',
189
+ path:
190
+ typeof issue.path === 'string' && issue.path.trim()
191
+ ? issue.path.trim()
192
+ : 'bundle',
193
+ message:
194
+ typeof issue.message === 'string' && issue.message.trim()
195
+ ? issue.message.trim()
196
+ : 'Monitor validation failed.',
197
+ severity:
198
+ issue.severity === 'warning' || issue.severity === 'error'
199
+ ? issue.severity
200
+ : 'error',
201
+ ...(Object.prototype.hasOwnProperty.call(issue, 'expected')
202
+ ? { expected: issue.expected }
203
+ : {}),
204
+ ...(Object.prototype.hasOwnProperty.call(issue, 'received')
205
+ ? { received: issue.received }
206
+ : {}),
207
+ ...(typeof issue.suggestion === 'string' && issue.suggestion.trim()
208
+ ? { suggestion: issue.suggestion.trim() }
209
+ : {}),
210
+ ...(entityType ? { entity_type: entityType } : {}),
211
+ ...(typeof issue.entity_key === 'string' && issue.entity_key.trim()
212
+ ? { entity_key: issue.entity_key.trim() }
213
+ : {}),
214
+ ...(typeof issue.docs_ref === 'string' && issue.docs_ref.trim()
215
+ ? { docs_ref: issue.docs_ref.trim() }
216
+ : {}),
217
+ });
218
+ });
219
+ }
220
+
221
+ export function hasMonitorSdkValidationIssues(value: unknown): value is {
222
+ issues: MonitorSdkValidationIssue[];
223
+ } {
224
+ return (
225
+ Boolean(value && typeof value === 'object' && !Array.isArray(value)) &&
226
+ Array.isArray((value as { issues?: unknown }).issues)
227
+ );
228
+ }
229
+
230
+ function isMonitorSdkIssueEntityType(
231
+ value: unknown,
232
+ ): value is MonitorSdkValidationIssueEntityType {
233
+ return (
234
+ value === 'monitor' ||
235
+ value === 'action' ||
236
+ value === 'trigger' ||
237
+ value === 'bundle' ||
238
+ value === 'play' ||
239
+ value === 'listener' ||
240
+ value === 'table' ||
241
+ value === 'tool' ||
242
+ value === 'workflow'
243
+ );
244
+ }
@@ -52,7 +52,7 @@ export const PREFERRED_RECEIPT_GATEWAY_MACHINE_ID_HEADER =
52
52
  /**
53
53
  * CLI→app marker (NOT a coordinator header — it lives here only because both
54
54
  * the SDK HTTP client and the run route already import this module). Set by
55
- * automated test harnesses (e.g. `tests/v2-plays`) so their intentionally
55
+ * automated test harnesses (e.g. `tests/gauntlet-e2e-tests`) so their intentionally
56
56
  * failing plays — depth-guard probes, error-path scenarios — do not page the
57
57
  * SDK CLI error channel as if a real customer run had failed. The run route
58
58
  * honors it ONLY in non-prod (see run/route.ts); a forged header from a real