@oxygen-agent/cli 1.1011.853 → 1.1011.974
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/README.md +1 -1
- package/dist/admin-lead-magnet-commands.d.ts +7 -0
- package/dist/admin-lead-magnet-commands.js +128 -0
- package/dist/command-manifest.js +11 -0
- package/dist/functions-commands.js +1 -1
- package/dist/index.js +179 -132
- package/dist/workspace-utility-flags.d.ts +7 -0
- package/dist/workspace-utility-flags.js +45 -0
- package/node_modules/@oxygen/shared/dist/billing-anchors.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/billing-anchors.js +7 -1
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +135 -4
- package/node_modules/@oxygen/shared/dist/contracts/browser-operations.js +3 -3
- package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +10 -8
- package/node_modules/@oxygen/shared/dist/contracts/limits-read.js +11 -1
- package/node_modules/@oxygen/shared/dist/contracts/schema.js +10 -6
- package/node_modules/@oxygen/shared/dist/contracts/spintax.d.ts +18 -0
- package/node_modules/@oxygen/shared/dist/contracts/spintax.js +11 -0
- package/node_modules/@oxygen/shared/dist/contracts/workspace-utilities.d.ts +162 -0
- package/node_modules/@oxygen/shared/dist/contracts/workspace-utilities.js +46 -0
- package/node_modules/@oxygen/shared/dist/credit-topup-review.d.ts +11 -0
- package/node_modules/@oxygen/shared/dist/credit-topup-review.js +3 -0
- package/node_modules/@oxygen/shared/dist/emailguard-placement-probe.d.ts +70 -0
- package/node_modules/@oxygen/shared/dist/emailguard-placement-probe.js +161 -0
- package/node_modules/@oxygen/shared/dist/future-signup-events.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/future-signup-events.js +4 -0
- package/node_modules/@oxygen/shared/dist/partner-coupon-bonus.d.ts +45 -0
- package/node_modules/@oxygen/shared/dist/partner-coupon-bonus.js +57 -0
- package/node_modules/@oxygen/shared/dist/table-filter-operators.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/table-filter-operators.js +50 -0
- package/node_modules/@oxygen/shared/dist/telemetry.js +4 -0
- package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
- package/node_modules/@oxygen/shared/dist/version.js +1 -1
- package/node_modules/@oxygen/workflows/dist/index.d.ts +1 -0
- package/node_modules/@oxygen/workflows/dist/index.js +1 -0
- package/node_modules/@oxygen/workflows/dist/recipe-timestamp.d.ts +22 -0
- package/node_modules/@oxygen/workflows/dist/recipe-timestamp.js +72 -0
- package/package.json +1 -1
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
export declare const betaFeaturesUpdateContract: import("./schema.js").ContractObject<{
|
|
2
|
+
readonly enabled: import("./schema.js").BooleanProperty;
|
|
3
|
+
}, "enabled">;
|
|
4
|
+
export declare const providerLimitSelectorContract: import("./schema.js").ContractObject<{
|
|
5
|
+
readonly provider: import("./schema.js").StringProperty;
|
|
6
|
+
readonly operation: import("./schema.js").StringProperty;
|
|
7
|
+
}, "provider">;
|
|
8
|
+
export declare const providerLimitUpdateContract: import("./schema.js").ContractObject<{
|
|
9
|
+
readonly provider: import("./schema.js").StringProperty;
|
|
10
|
+
readonly operation: {
|
|
11
|
+
oneOf: [import("./schema.js").StringProperty, import("./schema.js").NullProperty];
|
|
12
|
+
description?: string;
|
|
13
|
+
};
|
|
14
|
+
readonly limit: {
|
|
15
|
+
type: "integer";
|
|
16
|
+
description?: string;
|
|
17
|
+
minimum?: number;
|
|
18
|
+
maximum?: number;
|
|
19
|
+
"x-clamp"?: true;
|
|
20
|
+
};
|
|
21
|
+
readonly window_seconds: {
|
|
22
|
+
type: "integer";
|
|
23
|
+
description?: string;
|
|
24
|
+
minimum?: number;
|
|
25
|
+
maximum?: number;
|
|
26
|
+
"x-clamp"?: true;
|
|
27
|
+
};
|
|
28
|
+
}, "limit" | "provider" | "window_seconds">;
|
|
29
|
+
export declare const SUPPRESSION_BULK_MAX_KEYS = 500;
|
|
30
|
+
export declare const utilityListWindowProperties: {
|
|
31
|
+
view: {
|
|
32
|
+
type: "string";
|
|
33
|
+
enum: readonly ("full" | "summary")[];
|
|
34
|
+
description?: string;
|
|
35
|
+
};
|
|
36
|
+
max_chars: {
|
|
37
|
+
type: "integer";
|
|
38
|
+
description?: string;
|
|
39
|
+
minimum?: number;
|
|
40
|
+
maximum?: number;
|
|
41
|
+
"x-clamp"?: true;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
export declare const utilityListWindowContract: import("./schema.js").ContractObject<{
|
|
45
|
+
view: {
|
|
46
|
+
type: "string";
|
|
47
|
+
enum: readonly ("full" | "summary")[];
|
|
48
|
+
description?: string;
|
|
49
|
+
};
|
|
50
|
+
max_chars: {
|
|
51
|
+
type: "integer";
|
|
52
|
+
description?: string;
|
|
53
|
+
minimum?: number;
|
|
54
|
+
maximum?: number;
|
|
55
|
+
"x-clamp"?: true;
|
|
56
|
+
};
|
|
57
|
+
}, never>;
|
|
58
|
+
export declare const providerLimitListContract: import("./schema.js").ContractObject<{
|
|
59
|
+
readonly view: {
|
|
60
|
+
type: "string";
|
|
61
|
+
enum: readonly ("full" | "summary")[];
|
|
62
|
+
description?: string;
|
|
63
|
+
};
|
|
64
|
+
readonly max_chars: {
|
|
65
|
+
type: "integer";
|
|
66
|
+
description?: string;
|
|
67
|
+
minimum?: number;
|
|
68
|
+
maximum?: number;
|
|
69
|
+
"x-clamp"?: true;
|
|
70
|
+
};
|
|
71
|
+
readonly provider: import("./schema.js").StringProperty;
|
|
72
|
+
readonly limit: {
|
|
73
|
+
type: "integer";
|
|
74
|
+
description?: string;
|
|
75
|
+
minimum?: number;
|
|
76
|
+
maximum?: number;
|
|
77
|
+
"x-clamp"?: true;
|
|
78
|
+
};
|
|
79
|
+
readonly offset: {
|
|
80
|
+
type: "integer";
|
|
81
|
+
description?: string;
|
|
82
|
+
minimum?: number;
|
|
83
|
+
maximum?: number;
|
|
84
|
+
"x-clamp"?: true;
|
|
85
|
+
};
|
|
86
|
+
}, never>;
|
|
87
|
+
export declare const emailSuppressionListContract: import("./schema.js").ContractObject<{
|
|
88
|
+
readonly view: {
|
|
89
|
+
type: "string";
|
|
90
|
+
enum: readonly ("full" | "summary")[];
|
|
91
|
+
description?: string;
|
|
92
|
+
};
|
|
93
|
+
readonly max_chars: {
|
|
94
|
+
type: "integer";
|
|
95
|
+
description?: string;
|
|
96
|
+
minimum?: number;
|
|
97
|
+
maximum?: number;
|
|
98
|
+
"x-clamp"?: true;
|
|
99
|
+
};
|
|
100
|
+
readonly reason: {
|
|
101
|
+
type: "string";
|
|
102
|
+
enum: readonly ("complaint" | "hard_bounce" | "manual" | "unsubscribe")[];
|
|
103
|
+
description?: string;
|
|
104
|
+
};
|
|
105
|
+
readonly search: import("./schema.js").StringProperty;
|
|
106
|
+
readonly limit: {
|
|
107
|
+
type: "integer";
|
|
108
|
+
description?: string;
|
|
109
|
+
minimum?: number;
|
|
110
|
+
maximum?: number;
|
|
111
|
+
"x-clamp"?: true;
|
|
112
|
+
};
|
|
113
|
+
readonly offset: {
|
|
114
|
+
type: "integer";
|
|
115
|
+
description?: string;
|
|
116
|
+
minimum?: number;
|
|
117
|
+
maximum?: number;
|
|
118
|
+
"x-clamp"?: true;
|
|
119
|
+
};
|
|
120
|
+
}, never>;
|
|
121
|
+
export declare const emailDomainSuppressionListContract: import("./schema.js").ContractObject<{
|
|
122
|
+
readonly view: {
|
|
123
|
+
type: "string";
|
|
124
|
+
enum: readonly ("full" | "summary")[];
|
|
125
|
+
description?: string;
|
|
126
|
+
};
|
|
127
|
+
readonly max_chars: {
|
|
128
|
+
type: "integer";
|
|
129
|
+
description?: string;
|
|
130
|
+
minimum?: number;
|
|
131
|
+
maximum?: number;
|
|
132
|
+
"x-clamp"?: true;
|
|
133
|
+
};
|
|
134
|
+
readonly limit: {
|
|
135
|
+
type: "integer";
|
|
136
|
+
description?: string;
|
|
137
|
+
minimum?: number;
|
|
138
|
+
maximum?: number;
|
|
139
|
+
"x-clamp"?: true;
|
|
140
|
+
};
|
|
141
|
+
readonly offset: {
|
|
142
|
+
type: "integer";
|
|
143
|
+
description?: string;
|
|
144
|
+
minimum?: number;
|
|
145
|
+
maximum?: number;
|
|
146
|
+
"x-clamp"?: true;
|
|
147
|
+
};
|
|
148
|
+
readonly reason: {
|
|
149
|
+
type: "string";
|
|
150
|
+
enum: readonly ("complaint" | "manual" | "policy")[];
|
|
151
|
+
description?: string;
|
|
152
|
+
};
|
|
153
|
+
readonly search: import("./schema.js").StringProperty;
|
|
154
|
+
}, never>;
|
|
155
|
+
export declare const emailSuppressionRemoveContract: import("./schema.js").ContractObject<{
|
|
156
|
+
readonly email: import("./schema.js").StringProperty;
|
|
157
|
+
readonly emails: import("./schema.js").ArrayProperty<import("./schema.js").StringProperty>;
|
|
158
|
+
}, never>;
|
|
159
|
+
export declare const emailDomainSuppressionRemoveContract: import("./schema.js").ContractObject<{
|
|
160
|
+
readonly domain: import("./schema.js").StringProperty;
|
|
161
|
+
readonly domains: import("./schema.js").ArrayProperty<import("./schema.js").StringProperty>;
|
|
162
|
+
}, never>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { arr, bool, enumOf, int, nullable, obj, str } from "./schema.js";
|
|
2
|
+
export const betaFeaturesUpdateContract = obj({
|
|
3
|
+
enabled: bool({ description: "Show beta features in this workspace's navigation. Admin or owner required." }),
|
|
4
|
+
}, { required: ["enabled"] });
|
|
5
|
+
export const providerLimitSelectorContract = obj({
|
|
6
|
+
provider: str({ minLength: 1, description: "Provider ID, e.g. hubspot or leadmagic." }),
|
|
7
|
+
operation: str({ description: "Exact operation; omit to target the provider-wide policy, not every operation policy." }),
|
|
8
|
+
}, { required: ["provider"] });
|
|
9
|
+
export const providerLimitUpdateContract = obj({
|
|
10
|
+
provider: providerLimitSelectorContract.properties.provider,
|
|
11
|
+
operation: nullable(providerLimitSelectorContract.properties.operation),
|
|
12
|
+
limit: int({ minimum: 1, description: "Enforced calls allowed per window." }),
|
|
13
|
+
window_seconds: int({ minimum: 1, description: "Window in seconds; 86400 sets an explicit daily policy." }),
|
|
14
|
+
}, { required: ["provider", "limit", "window_seconds"] });
|
|
15
|
+
export const SUPPRESSION_BULK_MAX_KEYS = 500;
|
|
16
|
+
export const utilityListWindowProperties = {
|
|
17
|
+
view: enumOf(["full", "summary"], { description: "Full keeps native fields; summary names omitted evidence fields in projection. HTTP/CLI default full." }),
|
|
18
|
+
max_chars: int({ minimum: 2_000, maximum: 40_000, description: "Native context window; MCP caps it at 10000 (external) or 4000 (built-in). Paging.next_offset follows the last exposed row." }),
|
|
19
|
+
};
|
|
20
|
+
export const utilityListWindowContract = obj(utilityListWindowProperties);
|
|
21
|
+
export const providerLimitListContract = obj({
|
|
22
|
+
provider: providerLimitSelectorContract.properties.provider,
|
|
23
|
+
limit: int({ minimum: 1, maximum: 500, description: "Saved policies per page; omit for the legacy full list." }),
|
|
24
|
+
offset: int({ minimum: 0, description: "Current-policy offset; repeat the same provider filter. Concurrent edits may shift rows." }),
|
|
25
|
+
...utilityListWindowProperties,
|
|
26
|
+
});
|
|
27
|
+
export const emailSuppressionListContract = obj({
|
|
28
|
+
reason: enumOf(["hard_bounce", "complaint", "unsubscribe", "manual"]),
|
|
29
|
+
search: str({ description: "Case-insensitive address substring." }),
|
|
30
|
+
limit: int({ minimum: 1, maximum: 500, clamp: true, description: "Page size, 1-500; API default 100." }),
|
|
31
|
+
offset: int({ minimum: 0, description: "Current-ledger offset; repeat the same filters. Concurrent changes may shift rows." }),
|
|
32
|
+
...utilityListWindowProperties,
|
|
33
|
+
});
|
|
34
|
+
export const emailDomainSuppressionListContract = obj({
|
|
35
|
+
...emailSuppressionListContract.properties,
|
|
36
|
+
reason: enumOf(["manual", "complaint", "policy"]),
|
|
37
|
+
search: str({ description: "Case-insensitive bare-domain substring." }),
|
|
38
|
+
});
|
|
39
|
+
export const emailSuppressionRemoveContract = obj({
|
|
40
|
+
email: str({ minLength: 1, description: "One full email address. Supply email or emails, never both." }),
|
|
41
|
+
emails: arr(str(), { minItems: 1, maxItems: SUPPRESSION_BULK_MAX_KEYS, description: "Addresses to unblock. Supply emails or email, never both." }),
|
|
42
|
+
});
|
|
43
|
+
export const emailDomainSuppressionRemoveContract = obj({
|
|
44
|
+
domain: str({ minLength: 1, description: "One bare domain, e.g. acme.com. Supply domain or domains, never both." }),
|
|
45
|
+
domains: arr(str(), { minItems: 1, maxItems: SUPPRESSION_BULK_MAX_KEYS, description: "Bare domains to unblock. Supply domains or domain, never both." }),
|
|
46
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Fixed customer outcome for a verified paid top-up awaiting manual review. */
|
|
2
|
+
export declare const CREDIT_TOPUP_REVIEW_CODE = "credit_topup_review_required";
|
|
3
|
+
export declare const CREDIT_TOPUP_REVIEW_MESSAGE = "Payment received. Credits need review. Check billing or contact support before buying again.";
|
|
4
|
+
export type CreditTopupReviewReason = "organization_missing" | "credits_invalid" | "quoted_amount_invalid" | "payment_intent_missing";
|
|
5
|
+
/** Contains no Stripe identifiers, invalid metadata or staff diagnostics. */
|
|
6
|
+
export type CreditTopupReviewNotice = {
|
|
7
|
+
code: typeof CREDIT_TOPUP_REVIEW_CODE;
|
|
8
|
+
message: typeof CREDIT_TOPUP_REVIEW_MESSAGE;
|
|
9
|
+
count: number;
|
|
10
|
+
web_url: string;
|
|
11
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Fixed customer outcome for a verified paid top-up awaiting manual review. */
|
|
2
|
+
export const CREDIT_TOPUP_REVIEW_CODE = "credit_topup_review_required";
|
|
3
|
+
export const CREDIT_TOPUP_REVIEW_MESSAGE = "Payment received. Credits need review. Check billing or contact support before buying again.";
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
type MailboxTransport = "zapbox" | "native_oauth" | "delegation" | "provisioning" | "ineligible";
|
|
2
|
+
export type EmailGuardSeedSendPlan = {
|
|
3
|
+
placement_test_id: string;
|
|
4
|
+
from: string;
|
|
5
|
+
transport: MailboxTransport;
|
|
6
|
+
recipients: string[];
|
|
7
|
+
to: string;
|
|
8
|
+
bcc: string[];
|
|
9
|
+
subject: string;
|
|
10
|
+
body: string;
|
|
11
|
+
test_phrase: string;
|
|
12
|
+
allow_paused_probe?: true;
|
|
13
|
+
plan_hash: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Build the exact external-write plan shown in preview and bound by plan_hash.
|
|
17
|
+
* EmailGuard requires one new message to its whole seed set containing the
|
|
18
|
+
* unique filter phrase. Oxygen uses one primary To recipient plus Bcc for the
|
|
19
|
+
* remaining seeds so every inbox receives the same MIME message without
|
|
20
|
+
* exposing EmailGuard's full seed set to each recipient.
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildEmailGuardSeedSendPlan(input: {
|
|
23
|
+
placementTestId: string;
|
|
24
|
+
from: string;
|
|
25
|
+
transport: MailboxTransport;
|
|
26
|
+
seedAddresses: unknown;
|
|
27
|
+
testPhrase: string | null;
|
|
28
|
+
subject: string | null;
|
|
29
|
+
bodyText: string | null;
|
|
30
|
+
allowPausedProbe?: boolean;
|
|
31
|
+
}): EmailGuardSeedSendPlan;
|
|
32
|
+
/** One permanent delivery identity for the lifetime of this provider-created test. */
|
|
33
|
+
export declare function emailGuardProbeDeliveryKey(testId: string): string;
|
|
34
|
+
export declare function emailGuardSeedSendPayload(plan: EmailGuardSeedSendPlan): {
|
|
35
|
+
kind: "email_send";
|
|
36
|
+
to: string;
|
|
37
|
+
subject: string;
|
|
38
|
+
body: string;
|
|
39
|
+
bcc?: string[];
|
|
40
|
+
apply_mailbox_signature: boolean;
|
|
41
|
+
};
|
|
42
|
+
export declare function placementTestAllowsPausedProbe(results: unknown): boolean;
|
|
43
|
+
/** Untrusted job labels never supply pause authority: prove the durable exact approval. */
|
|
44
|
+
export declare function approvedPausedEmailGuardProbeMatches(input: {
|
|
45
|
+
organizationId: string;
|
|
46
|
+
mailboxId: string;
|
|
47
|
+
from: string;
|
|
48
|
+
transport: MailboxTransport;
|
|
49
|
+
job: {
|
|
50
|
+
id: string;
|
|
51
|
+
source: string;
|
|
52
|
+
idempotencyKey: string | null;
|
|
53
|
+
payload: Record<string, unknown>;
|
|
54
|
+
};
|
|
55
|
+
test: {
|
|
56
|
+
id: string;
|
|
57
|
+
provider: string;
|
|
58
|
+
status: string;
|
|
59
|
+
externalId: string | null;
|
|
60
|
+
mailboxIds: string[];
|
|
61
|
+
seedAddresses: unknown;
|
|
62
|
+
testPhrase: string | null;
|
|
63
|
+
subject: string | null;
|
|
64
|
+
bodyText: string | null;
|
|
65
|
+
results: unknown;
|
|
66
|
+
};
|
|
67
|
+
}): boolean;
|
|
68
|
+
/** Compare every externally meaningful queued field, permitting only private correlation beside it. */
|
|
69
|
+
export declare function emailGuardProbePayloadMatches(plan: EmailGuardSeedSendPlan, payload: Record<string, unknown>): boolean;
|
|
70
|
+
export {};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { CliHttpError } from "./cli-http-error.js";
|
|
3
|
+
const MAX_SEED_RECIPIENTS = 50;
|
|
4
|
+
const DEFAULT_SUBJECT = "Oxygen inbox-placement test";
|
|
5
|
+
const DEFAULT_BODY = "This message is an inbox-placement test sent from Oxygen.";
|
|
6
|
+
function singleEmail(value) {
|
|
7
|
+
if (typeof value !== "string")
|
|
8
|
+
return null;
|
|
9
|
+
const address = value.trim().toLowerCase();
|
|
10
|
+
if (!address || !address.includes("@") || /[\s\r\n,;]/.test(address)) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return address;
|
|
14
|
+
}
|
|
15
|
+
function seedAddresses(value) {
|
|
16
|
+
if (!Array.isArray(value)) {
|
|
17
|
+
throw new CliHttpError(409, "emailguard_seed_set_missing", "EmailGuard did not return a usable seed-recipient list for this placement test.", { exitCode: 1 });
|
|
18
|
+
}
|
|
19
|
+
const recipients = [];
|
|
20
|
+
for (const raw of value) {
|
|
21
|
+
const address = singleEmail(raw);
|
|
22
|
+
if (!address) {
|
|
23
|
+
throw new CliHttpError(409, "emailguard_seed_set_invalid", "EmailGuard returned an invalid seed recipient; no email was sent.", {
|
|
24
|
+
details: { recipient: typeof raw === "string" ? raw : null },
|
|
25
|
+
exitCode: 1,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
if (!recipients.includes(address))
|
|
29
|
+
recipients.push(address);
|
|
30
|
+
}
|
|
31
|
+
if (recipients.length === 0 || recipients.length > MAX_SEED_RECIPIENTS) {
|
|
32
|
+
throw new CliHttpError(409, "emailguard_seed_set_invalid", `EmailGuard must return between 1 and ${MAX_SEED_RECIPIENTS} unique seed recipients; no email was sent.`, { details: { recipient_count: recipients.length }, exitCode: 1 });
|
|
33
|
+
}
|
|
34
|
+
return recipients;
|
|
35
|
+
}
|
|
36
|
+
function headerLine(value, fallback) {
|
|
37
|
+
const line = value?.trim() || fallback;
|
|
38
|
+
if (/[\r\n]/.test(line) || line.length > 998) {
|
|
39
|
+
throw new CliHttpError(400, "invalid_request", "The placement-test subject must be a single line no longer than 998 characters.", { exitCode: 1 });
|
|
40
|
+
}
|
|
41
|
+
return line;
|
|
42
|
+
}
|
|
43
|
+
function messageBody(value, phrase) {
|
|
44
|
+
const base = value?.trim() || DEFAULT_BODY;
|
|
45
|
+
if (base.includes(phrase))
|
|
46
|
+
return base;
|
|
47
|
+
return `${base}\n\nEmailGuard test phrase: ${phrase}`;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Build the exact external-write plan shown in preview and bound by plan_hash.
|
|
51
|
+
* EmailGuard requires one new message to its whole seed set containing the
|
|
52
|
+
* unique filter phrase. Oxygen uses one primary To recipient plus Bcc for the
|
|
53
|
+
* remaining seeds so every inbox receives the same MIME message without
|
|
54
|
+
* exposing EmailGuard's full seed set to each recipient.
|
|
55
|
+
*/
|
|
56
|
+
export function buildEmailGuardSeedSendPlan(input) {
|
|
57
|
+
const from = singleEmail(input.from);
|
|
58
|
+
if (!from) {
|
|
59
|
+
throw new CliHttpError(409, "mailbox_not_sendable", "The placement-test sender address is invalid.", {
|
|
60
|
+
exitCode: 1,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
if (input.transport === "ineligible") {
|
|
64
|
+
throw new CliHttpError(409, "mailbox_not_sendable", `Mailbox ${from} has no usable send transport. Connect Zapbox, mailbox OAuth, or deployment delegation before sending the deliverability-test probe.`, { details: { mailbox: from }, exitCode: 1 });
|
|
65
|
+
}
|
|
66
|
+
const phrase = input.testPhrase?.trim();
|
|
67
|
+
if (!phrase) {
|
|
68
|
+
throw new CliHttpError(409, "emailguard_test_phrase_missing", "EmailGuard did not return the mandatory filter phrase; no email was sent.", { exitCode: 1 });
|
|
69
|
+
}
|
|
70
|
+
const recipients = seedAddresses(input.seedAddresses);
|
|
71
|
+
const subject = headerLine(input.subject, DEFAULT_SUBJECT);
|
|
72
|
+
const body = messageBody(input.bodyText, phrase);
|
|
73
|
+
const planWithoutHash = {
|
|
74
|
+
placement_test_id: input.placementTestId,
|
|
75
|
+
from,
|
|
76
|
+
transport: input.transport,
|
|
77
|
+
recipients,
|
|
78
|
+
to: recipients[0],
|
|
79
|
+
bcc: recipients.slice(1),
|
|
80
|
+
subject,
|
|
81
|
+
body,
|
|
82
|
+
test_phrase: phrase,
|
|
83
|
+
...(input.allowPausedProbe ? { allow_paused_probe: true } : {}),
|
|
84
|
+
};
|
|
85
|
+
const planHash = createHash("sha256")
|
|
86
|
+
.update(JSON.stringify({ version: 1, ...planWithoutHash }))
|
|
87
|
+
.digest("hex");
|
|
88
|
+
return { ...planWithoutHash, plan_hash: planHash };
|
|
89
|
+
}
|
|
90
|
+
/** One permanent delivery identity for the lifetime of this provider-created test. */
|
|
91
|
+
export function emailGuardProbeDeliveryKey(testId) {
|
|
92
|
+
return `placement_test:${createHash("sha256").update(testId).digest("hex")}`;
|
|
93
|
+
}
|
|
94
|
+
export function emailGuardSeedSendPayload(plan) {
|
|
95
|
+
return {
|
|
96
|
+
kind: "email_send", to: plan.to, subject: plan.subject, body: plan.body,
|
|
97
|
+
...(plan.bcc.length ? { bcc: plan.bcc } : {}), apply_mailbox_signature: false,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function record(value) {
|
|
101
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
102
|
+
? value : {};
|
|
103
|
+
}
|
|
104
|
+
function equalJson(left, right) {
|
|
105
|
+
if (left === right)
|
|
106
|
+
return true;
|
|
107
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
108
|
+
return Array.isArray(left) && Array.isArray(right) && left.length === right.length
|
|
109
|
+
&& left.every((value, index) => equalJson(value, right[index]));
|
|
110
|
+
}
|
|
111
|
+
if (!left || !right || typeof left !== "object" || typeof right !== "object")
|
|
112
|
+
return false;
|
|
113
|
+
const a = record(left);
|
|
114
|
+
const b = record(right);
|
|
115
|
+
const keys = Object.keys(a);
|
|
116
|
+
return keys.length === Object.keys(b).length && keys.every(key => Object.hasOwn(b, key) && equalJson(a[key], b[key]));
|
|
117
|
+
}
|
|
118
|
+
export function placementTestAllowsPausedProbe(results) {
|
|
119
|
+
const ledger = record(results);
|
|
120
|
+
const create = record(ledger.create_operation);
|
|
121
|
+
return ledger.paused_probe_opt_in === true
|
|
122
|
+
&& ["succeeded", "recovered"].includes(String(create.phase))
|
|
123
|
+
&& typeof create.plan_hash === "string" && /^[a-f0-9]{64}$/.test(create.plan_hash);
|
|
124
|
+
}
|
|
125
|
+
/** Untrusted job labels never supply pause authority: prove the durable exact approval. */
|
|
126
|
+
export function approvedPausedEmailGuardProbeMatches(input) {
|
|
127
|
+
const { test, job } = input;
|
|
128
|
+
if (test.provider !== "emailguard" || test.status !== "sending" || !test.externalId
|
|
129
|
+
|| test.mailboxIds.length !== 1 || test.mailboxIds[0] !== input.mailboxId
|
|
130
|
+
|| job.source !== "placement_test" || !placementTestAllowsPausedProbe(test.results))
|
|
131
|
+
return false;
|
|
132
|
+
const receipt = record(record(test.results).seed_delivery);
|
|
133
|
+
const authorization = record(receipt.authorization);
|
|
134
|
+
const create = record(record(test.results).create_operation);
|
|
135
|
+
const correlation = record(job.payload.correlation);
|
|
136
|
+
if (!["claimed", "queued"].includes(String(receipt.status)) || authorization.version !== 1
|
|
137
|
+
|| authorization.organization_id !== input.organizationId || authorization.mailbox_id !== input.mailboxId
|
|
138
|
+
|| authorization.create_plan_hash !== create.plan_hash || authorization.allow_paused_probe !== true
|
|
139
|
+
|| authorization.idempotency_key !== emailGuardProbeDeliveryKey(test.id)
|
|
140
|
+
|| job.idempotencyKey !== authorization.idempotency_key
|
|
141
|
+
|| (receipt.job_id != null && receipt.job_id !== job.id)
|
|
142
|
+
|| correlation.source !== "placement_test" || correlation.placement_test_id !== test.id
|
|
143
|
+
|| correlation.organization_id !== input.organizationId
|
|
144
|
+
|| record(correlation.daily_slot).mailbox_id !== input.mailboxId)
|
|
145
|
+
return false;
|
|
146
|
+
try {
|
|
147
|
+
const plan = buildEmailGuardSeedSendPlan({ placementTestId: test.id, from: input.from, transport: input.transport,
|
|
148
|
+
seedAddresses: test.seedAddresses, testPhrase: test.testPhrase, subject: test.subject, bodyText: test.bodyText, allowPausedProbe: true });
|
|
149
|
+
const { correlation: _correlation, ...message } = job.payload;
|
|
150
|
+
return authorization.plan_hash === plan.plan_hash && equalJson(receipt.plan, plan)
|
|
151
|
+
&& equalJson(message, emailGuardSeedSendPayload(plan));
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/** Compare every externally meaningful queued field, permitting only private correlation beside it. */
|
|
158
|
+
export function emailGuardProbePayloadMatches(plan, payload) {
|
|
159
|
+
const { correlation: _correlation, ...message } = payload;
|
|
160
|
+
return equalJson(message, emailGuardSeedSendPayload(plan));
|
|
161
|
+
}
|
|
@@ -11,7 +11,7 @@ export declare const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_CONTRACT_VERSION_V1: "fu
|
|
|
11
11
|
export declare const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_CONTRACT_VERSION_V2: "future_signup_lifecycle_projection.v2";
|
|
12
12
|
export declare const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_CONTRACT_VERSION: "future_signup_lifecycle_projection.v1";
|
|
13
13
|
export declare const FUTURE_SIGNUP_CRM_DESTINATION = "oxygen_company_crm";
|
|
14
|
-
export declare const FUTURE_SIGNUP_ACTIVATION_COMMANDS: readonly ["agent create", "crm activity log", "crm assert", "crm object add-attr", "crm object create", "crm relationships define", "crm relationships upsert", "knowledge page upsert", "publishing posts create", "sequences create", "tables create", "tables duplicate", "tables import-csv", "tables import-file", "tables import-staged", "tables import-url", "tables insert", "tables relate", "tables update", "tables upsert", "workflows apply"];
|
|
14
|
+
export declare const FUTURE_SIGNUP_ACTIVATION_COMMANDS: readonly ["agent create", "crm activity log", "crm assert", "crm object add-attr", "crm object create", "crm objects add-attr", "crm objects create", "crm relationships define", "crm relationships upsert", "knowledge page upsert", "publishing posts create", "sequences create", "tables create", "tables duplicate", "tables import-csv", "tables import-file", "tables import-staged", "tables import-url", "tables insert", "tables relate", "tables update", "tables upsert", "workflows apply"];
|
|
15
15
|
export declare function isFutureSignupActivationCommand(command: string): boolean;
|
|
16
16
|
export declare function futureSignupJourneyId(clerkUserId: string): string;
|
|
17
17
|
export declare function futureSignupFactId(clerkUserId: string): string;
|
|
@@ -15,8 +15,12 @@ export const FUTURE_SIGNUP_ACTIVATION_COMMANDS = [
|
|
|
15
15
|
"agent create",
|
|
16
16
|
"crm activity log",
|
|
17
17
|
"crm assert",
|
|
18
|
+
// Operations recorded before the label matched the real `crm objects`
|
|
19
|
+
// command keep the singular form; both name the same command.
|
|
18
20
|
"crm object add-attr",
|
|
19
21
|
"crm object create",
|
|
22
|
+
"crm objects add-attr",
|
|
23
|
+
"crm objects create",
|
|
20
24
|
"crm relationships define",
|
|
21
25
|
"crm relationships upsert",
|
|
22
26
|
"knowledge page upsert",
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type RepricingSwitchState } from "./repricing.js";
|
|
2
|
+
/** September 30 P3 ratification; both existing PARTNERS2026 coupon cohorts. */
|
|
3
|
+
export declare const PARTNER_COUPON_IDS: readonly ["jcQhT5r5", "0wTy2bAv"];
|
|
4
|
+
export declare const PARTNER_COUPON_BONUS_EXCLUSION_EFFECTIVE_AT_ENV = "OXYGEN_PARTNER_COUPON_BONUS_EXCLUSION_EFFECTIVE_AT";
|
|
5
|
+
export declare function readPartnerCouponBonusExclusion(options?: {
|
|
6
|
+
now?: Date;
|
|
7
|
+
env?: Record<string, string | undefined>;
|
|
8
|
+
}): RepricingSwitchState;
|
|
9
|
+
export type PartnerCouponBinding = {
|
|
10
|
+
organizationId: string;
|
|
11
|
+
subscriptionId: string;
|
|
12
|
+
customerId: string;
|
|
13
|
+
priceId: string;
|
|
14
|
+
planKey: string;
|
|
15
|
+
periodStart: string;
|
|
16
|
+
periodEnd: string;
|
|
17
|
+
};
|
|
18
|
+
export type PartnerCouponApplication = {
|
|
19
|
+
id: string;
|
|
20
|
+
layer: "subscription" | "plan_item" | "customer";
|
|
21
|
+
couponId: string | null;
|
|
22
|
+
start: number | null;
|
|
23
|
+
end: number | null;
|
|
24
|
+
deleted: boolean;
|
|
25
|
+
};
|
|
26
|
+
/** Trusted server capture, separate from externally supplied Stripe metadata. */
|
|
27
|
+
export type PartnerCouponAuthority = {
|
|
28
|
+
version: 1;
|
|
29
|
+
capturedAt: string;
|
|
30
|
+
binding: PartnerCouponBinding;
|
|
31
|
+
planItemId: string | null;
|
|
32
|
+
status: "complete" | "unknown";
|
|
33
|
+
reason: string | null;
|
|
34
|
+
applications: PartnerCouponApplication[];
|
|
35
|
+
};
|
|
36
|
+
export type PartnerCouponAuthorityStatus = {
|
|
37
|
+
state: "active_partner_coupon" | "no_active_partner_coupon" | "unknown";
|
|
38
|
+
capturedAt: string | null;
|
|
39
|
+
basis: "captured_stripe_discount_applications" | "unavailable";
|
|
40
|
+
reason: string | null;
|
|
41
|
+
};
|
|
42
|
+
/** Evaluate only the captured instant; a stored absence is never a live check. */
|
|
43
|
+
export declare function readCapturedPartnerCouponAuthority(authority: PartnerCouponAuthority | null | undefined, binding: PartnerCouponBinding | null): PartnerCouponAuthorityStatus;
|
|
44
|
+
/** Live issuance evaluates known application bounds at its clock, retaining capture provenance. */
|
|
45
|
+
export declare function readPartnerCouponAuthorityAt(authority: PartnerCouponAuthority | null | undefined, binding: PartnerCouponBinding | null, applicationInstant?: Date): PartnerCouponAuthorityStatus;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { parseRepricingEffectiveAt } from "./repricing.js";
|
|
2
|
+
/** September 30 P3 ratification; both existing PARTNERS2026 coupon cohorts. */
|
|
3
|
+
export const PARTNER_COUPON_IDS = ["jcQhT5r5", "0wTy2bAv"];
|
|
4
|
+
export const PARTNER_COUPON_BONUS_EXCLUSION_EFFECTIVE_AT_ENV = "OXYGEN_PARTNER_COUPON_BONUS_EXCLUSION_EFFECTIVE_AT";
|
|
5
|
+
export function readPartnerCouponBonusExclusion(options = {}) {
|
|
6
|
+
const raw = (options.env ?? process.env)[PARTNER_COUPON_BONUS_EXCLUSION_EFFECTIVE_AT_ENV];
|
|
7
|
+
const effectiveAt = parseRepricingEffectiveAt(raw);
|
|
8
|
+
if (effectiveAt === null)
|
|
9
|
+
return { status: "unset", effectiveAt: null };
|
|
10
|
+
if (effectiveAt === undefined)
|
|
11
|
+
return { status: "invalid", effectiveAt: null, raw: raw?.trim() ?? "" };
|
|
12
|
+
return (options.now ?? new Date()).getTime() >= effectiveAt.getTime()
|
|
13
|
+
? { status: "in_force", effectiveAt }
|
|
14
|
+
: { status: "scheduled", effectiveAt };
|
|
15
|
+
}
|
|
16
|
+
/** Evaluate only the captured instant; a stored absence is never a live check. */
|
|
17
|
+
export function readCapturedPartnerCouponAuthority(authority, binding) {
|
|
18
|
+
return readPartnerCouponAuthorityAt(authority, binding);
|
|
19
|
+
}
|
|
20
|
+
/** Live issuance evaluates known application bounds at its clock, retaining capture provenance. */
|
|
21
|
+
export function readPartnerCouponAuthorityAt(authority, binding, applicationInstant) {
|
|
22
|
+
const unknown = (reason) => ({
|
|
23
|
+
state: "unknown", capturedAt: authority?.capturedAt ?? null, basis: "unavailable", reason,
|
|
24
|
+
});
|
|
25
|
+
if (!authority || authority.version !== 1 || !binding)
|
|
26
|
+
return unknown("authority_missing");
|
|
27
|
+
const capturedAt = parseRepricingEffectiveAt(authority.capturedAt);
|
|
28
|
+
if (!capturedAt || !Number.isFinite(capturedAt.getTime()))
|
|
29
|
+
return unknown("capture_time_invalid");
|
|
30
|
+
if (!authority.binding || Object.keys(binding).some(key => authority.binding[key] !== binding[key])) {
|
|
31
|
+
return unknown("authority_binding_changed");
|
|
32
|
+
}
|
|
33
|
+
if (authority.status !== "complete" || !authority.planItemId || !Array.isArray(authority.applications)) {
|
|
34
|
+
return unknown(authority.reason ?? "authority_incomplete");
|
|
35
|
+
}
|
|
36
|
+
const instant = (applicationInstant ?? capturedAt).getTime() / 1000;
|
|
37
|
+
if (!Number.isFinite(instant))
|
|
38
|
+
return unknown("application_time_invalid");
|
|
39
|
+
for (const application of authority.applications) {
|
|
40
|
+
if (!application || !application.id || !["subscription", "plan_item", "customer"].includes(application.layer)
|
|
41
|
+
|| typeof application.deleted !== "boolean")
|
|
42
|
+
return unknown("application_invalid");
|
|
43
|
+
if (application.deleted)
|
|
44
|
+
continue;
|
|
45
|
+
if (!application.couponId || !Number.isSafeInteger(application.start) || application.start < 0
|
|
46
|
+
|| (application.end !== null && (!Number.isSafeInteger(application.end) || application.end < application.start))) {
|
|
47
|
+
return unknown("application_invalid");
|
|
48
|
+
}
|
|
49
|
+
if (PARTNER_COUPON_IDS.includes(application.couponId)
|
|
50
|
+
&& application.start <= instant && (application.end === null || instant < application.end)) {
|
|
51
|
+
return { state: "active_partner_coupon", capturedAt: authority.capturedAt,
|
|
52
|
+
basis: "captured_stripe_discount_applications", reason: null };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return { state: "no_active_partner_coupon", capturedAt: authority.capturedAt,
|
|
56
|
+
basis: "captured_stripe_discount_applications", reason: null };
|
|
57
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const FILTER_TREE_OPERATORS: readonly ["is", "is_not", "contains", "not_contains", "starts_with", "ends_with", "eq", "neq", "gt", "gte", "lt", "lte", "before", "after", "on_or_before", "on_or_after", "is_true", "is_false", "is_any_of", "is_none_of", "is_empty", "is_not_empty"];
|
|
2
|
+
export type FilterTreeOperator = (typeof FILTER_TREE_OPERATORS)[number];
|
|
3
|
+
/** The filter-tree operators a column whose SQL value has `dataType` accepts. */
|
|
4
|
+
export declare function filterTreeOperatorsFor(dataType: string): FilterTreeOperator[];
|
|
5
|
+
/** The filter-tree operator a rejected operator name most likely meant, if any. */
|
|
6
|
+
export declare function filterTreeOperatorSuggestion(operator: string): FilterTreeOperator | null;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Operators a filter-tree leaf (`tables query --filter-tree-json`, MCP
|
|
2
|
+
// `filterTree`, saved views) accepts. The SQL builder in
|
|
3
|
+
// packages/tenant-db/src/filters.ts, the CLI help and the MCP schema all read
|
|
4
|
+
// this one list, so the operators a caller is told about are exactly the ones
|
|
5
|
+
// the server accepts.
|
|
6
|
+
export const FILTER_TREE_OPERATORS = [
|
|
7
|
+
"is",
|
|
8
|
+
"is_not",
|
|
9
|
+
"contains",
|
|
10
|
+
"not_contains",
|
|
11
|
+
"starts_with",
|
|
12
|
+
"ends_with",
|
|
13
|
+
"eq",
|
|
14
|
+
"neq",
|
|
15
|
+
"gt",
|
|
16
|
+
"gte",
|
|
17
|
+
"lt",
|
|
18
|
+
"lte",
|
|
19
|
+
"before",
|
|
20
|
+
"after",
|
|
21
|
+
"on_or_before",
|
|
22
|
+
"on_or_after",
|
|
23
|
+
"is_true",
|
|
24
|
+
"is_false",
|
|
25
|
+
"is_any_of",
|
|
26
|
+
"is_none_of",
|
|
27
|
+
"is_empty",
|
|
28
|
+
"is_not_empty",
|
|
29
|
+
];
|
|
30
|
+
// is_true/is_false compare without a cast, so only a boolean value accepts them.
|
|
31
|
+
// Every other operator casts the stored value and works on any column.
|
|
32
|
+
const BOOLEAN_ONLY_OPERATORS = new Set(["is_true", "is_false"]);
|
|
33
|
+
/** The filter-tree operators a column whose SQL value has `dataType` accepts. */
|
|
34
|
+
export function filterTreeOperatorsFor(dataType) {
|
|
35
|
+
if (dataType === "boolean")
|
|
36
|
+
return [...FILTER_TREE_OPERATORS];
|
|
37
|
+
return FILTER_TREE_OPERATORS.filter((operator) => !BOOLEAN_ONLY_OPERATORS.has(operator));
|
|
38
|
+
}
|
|
39
|
+
// The flat `--filter-json` dialect spells some operators differently. Callers
|
|
40
|
+
// moving between the two dialects send these names to a filter tree.
|
|
41
|
+
const FLAT_FILTER_OPERATOR_EQUIVALENTS = {
|
|
42
|
+
is_null: "is_empty",
|
|
43
|
+
is_not_null: "is_not_empty",
|
|
44
|
+
in: "is_any_of",
|
|
45
|
+
not_in: "is_none_of",
|
|
46
|
+
};
|
|
47
|
+
/** The filter-tree operator a rejected operator name most likely meant, if any. */
|
|
48
|
+
export function filterTreeOperatorSuggestion(operator) {
|
|
49
|
+
return FLAT_FILTER_OPERATOR_EQUIVALENTS[operator.trim().toLowerCase()] ?? null;
|
|
50
|
+
}
|
|
@@ -239,6 +239,10 @@ const METRIC_SAFE_ATTRIBUTE_KEYS = new Set([
|
|
|
239
239
|
"queue.lane",
|
|
240
240
|
"queue.role",
|
|
241
241
|
"queue.state",
|
|
242
|
+
// oxygen.worker.queue.uncertain: the store path that quarantined a row
|
|
243
|
+
// (EXECUTION_UNCERTAIN_SOURCES in @oxygen/work-queue, or "unknown"). Never the
|
|
244
|
+
// kind's resource or effect key.
|
|
245
|
+
"queue.uncertain_source",
|
|
242
246
|
"scope",
|
|
243
247
|
"rail",
|
|
244
248
|
"overall_status",
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export declare const OXYGEN_VERSION: string;
|
|
2
|
-
export declare const OXYGEN_MINIMUM_CLI_VERSION = "1.
|
|
2
|
+
export declare const OXYGEN_MINIMUM_CLI_VERSION = "1.917.5";
|