deepline 0.1.76 → 0.1.77
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/cli/index.js +12 -3
- package/dist/cli/index.mjs +12 -3
- package/dist/index.d.mts +32 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +197 -2
- package/dist/index.mjs +197 -2
- package/dist/repo/sdk/src/release.ts +2 -2
- package/dist/repo/shared_libs/play-runtime/email-status.ts +301 -0
- package/dist/repo/shared_libs/play-runtime/tool-result.ts +58 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -229,10 +229,10 @@ var import_node_path2 = require("path");
|
|
|
229
229
|
|
|
230
230
|
// src/release.ts
|
|
231
231
|
var SDK_RELEASE = {
|
|
232
|
-
version: "0.1.
|
|
232
|
+
version: "0.1.77",
|
|
233
233
|
apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
|
|
234
234
|
supportPolicy: {
|
|
235
|
-
latest: "0.1.
|
|
235
|
+
latest: "0.1.77",
|
|
236
236
|
minimumSupported: "0.1.53",
|
|
237
237
|
deprecatedBelow: "0.1.53"
|
|
238
238
|
}
|
|
@@ -4190,13 +4190,18 @@ function registerDbCommands(program) {
|
|
|
4190
4190
|
"after",
|
|
4191
4191
|
`
|
|
4192
4192
|
Notes:
|
|
4193
|
-
|
|
4193
|
+
Agent-safe SQL for the active workspace customer database.
|
|
4194
|
+
Reads: SELECT, EXPLAIN, and read-only WITH can inspect permitted schemas.
|
|
4195
|
+
Writes: CREATE TABLE, INSERT, UPDATE, DELETE, ALTER, DROP, TRUNCATE, and
|
|
4196
|
+
CREATE INDEX must target schema-qualified storage tables, such as storage.agent_notes.
|
|
4197
|
+
If CREATE TABLE blocked (...) fails, use CREATE TABLE storage.blocked (...).
|
|
4194
4198
|
Results are bounded by the server and --max-rows. Use --json for stable output.
|
|
4195
4199
|
Use --format csv or --format markdown for agent-readable exports and display tables.
|
|
4196
4200
|
|
|
4197
4201
|
Examples:
|
|
4198
4202
|
deepline db query --sql "select * from companies limit 20"
|
|
4199
4203
|
deepline db query --sql "select domain, name from companies limit 20" --json
|
|
4204
|
+
deepline db query --sql "create table if not exists storage.agent_notes (id text primary key, note text not null)"
|
|
4200
4205
|
deepline db query --sql "select * from contacts" --max-rows 100 --json
|
|
4201
4206
|
deepline db query --sql "select * from contacts limit 20" --format csv --out contacts.csv
|
|
4202
4207
|
deepline db query --sql "select domain, name from companies limit 20" --format markdown
|
|
@@ -4208,12 +4213,16 @@ Examples:
|
|
|
4208
4213
|
Notes:
|
|
4209
4214
|
Requires --sql. Output is a compact table in a terminal and raw JSON with
|
|
4210
4215
|
--json or when stdout is piped. The active auth workspace determines scope.
|
|
4216
|
+
Read permitted schemas with SELECT, EXPLAIN, or read-only WITH.
|
|
4217
|
+
Write only to schema-qualified storage tables. For example, use
|
|
4218
|
+
CREATE TABLE storage.blocked (...) instead of CREATE TABLE blocked (...).
|
|
4211
4219
|
--format csv and --format markdown are explicit data/display formats and can
|
|
4212
4220
|
be written directly with --out.
|
|
4213
4221
|
|
|
4214
4222
|
Examples:
|
|
4215
4223
|
deepline db query --sql "select * from companies limit 20"
|
|
4216
4224
|
deepline db query --sql "select domain, name from companies limit 20" --json
|
|
4225
|
+
deepline db query --sql "create table if not exists storage.agent_notes (id text primary key, note text not null)"
|
|
4217
4226
|
deepline db psql --sql "select count(*) from contacts" --json
|
|
4218
4227
|
deepline db query --sql "select * from contacts limit 20" --format csv --out contacts.csv
|
|
4219
4228
|
deepline db query --sql "select domain, name from companies limit 20" --format markdown
|
package/dist/cli/index.mjs
CHANGED
|
@@ -206,10 +206,10 @@ import { join as join2 } from "path";
|
|
|
206
206
|
|
|
207
207
|
// src/release.ts
|
|
208
208
|
var SDK_RELEASE = {
|
|
209
|
-
version: "0.1.
|
|
209
|
+
version: "0.1.77",
|
|
210
210
|
apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
|
|
211
211
|
supportPolicy: {
|
|
212
|
-
latest: "0.1.
|
|
212
|
+
latest: "0.1.77",
|
|
213
213
|
minimumSupported: "0.1.53",
|
|
214
214
|
deprecatedBelow: "0.1.53"
|
|
215
215
|
}
|
|
@@ -4173,13 +4173,18 @@ function registerDbCommands(program) {
|
|
|
4173
4173
|
"after",
|
|
4174
4174
|
`
|
|
4175
4175
|
Notes:
|
|
4176
|
-
|
|
4176
|
+
Agent-safe SQL for the active workspace customer database.
|
|
4177
|
+
Reads: SELECT, EXPLAIN, and read-only WITH can inspect permitted schemas.
|
|
4178
|
+
Writes: CREATE TABLE, INSERT, UPDATE, DELETE, ALTER, DROP, TRUNCATE, and
|
|
4179
|
+
CREATE INDEX must target schema-qualified storage tables, such as storage.agent_notes.
|
|
4180
|
+
If CREATE TABLE blocked (...) fails, use CREATE TABLE storage.blocked (...).
|
|
4177
4181
|
Results are bounded by the server and --max-rows. Use --json for stable output.
|
|
4178
4182
|
Use --format csv or --format markdown for agent-readable exports and display tables.
|
|
4179
4183
|
|
|
4180
4184
|
Examples:
|
|
4181
4185
|
deepline db query --sql "select * from companies limit 20"
|
|
4182
4186
|
deepline db query --sql "select domain, name from companies limit 20" --json
|
|
4187
|
+
deepline db query --sql "create table if not exists storage.agent_notes (id text primary key, note text not null)"
|
|
4183
4188
|
deepline db query --sql "select * from contacts" --max-rows 100 --json
|
|
4184
4189
|
deepline db query --sql "select * from contacts limit 20" --format csv --out contacts.csv
|
|
4185
4190
|
deepline db query --sql "select domain, name from companies limit 20" --format markdown
|
|
@@ -4191,12 +4196,16 @@ Examples:
|
|
|
4191
4196
|
Notes:
|
|
4192
4197
|
Requires --sql. Output is a compact table in a terminal and raw JSON with
|
|
4193
4198
|
--json or when stdout is piped. The active auth workspace determines scope.
|
|
4199
|
+
Read permitted schemas with SELECT, EXPLAIN, or read-only WITH.
|
|
4200
|
+
Write only to schema-qualified storage tables. For example, use
|
|
4201
|
+
CREATE TABLE storage.blocked (...) instead of CREATE TABLE blocked (...).
|
|
4194
4202
|
--format csv and --format markdown are explicit data/display formats and can
|
|
4195
4203
|
be written directly with --out.
|
|
4196
4204
|
|
|
4197
4205
|
Examples:
|
|
4198
4206
|
deepline db query --sql "select * from companies limit 20"
|
|
4199
4207
|
deepline db query --sql "select domain, name from companies limit 20" --json
|
|
4208
|
+
deepline db query --sql "create table if not exists storage.agent_notes (id text primary key, note text not null)"
|
|
4200
4209
|
deepline db psql --sql "select count(*) from contacts" --json
|
|
4201
4210
|
deepline db query --sql "select * from contacts limit 20" --format csv --out contacts.csv
|
|
4202
4211
|
deepline db query --sql "select domain, name from companies limit 20" --format markdown
|
package/dist/index.d.mts
CHANGED
|
@@ -1997,6 +1997,37 @@ interface PlayDataset<T> extends AsyncIterable<T> {
|
|
|
1997
1997
|
};
|
|
1998
1998
|
}
|
|
1999
1999
|
|
|
2000
|
+
type EmailStatusVerdict = 'send' | 'send_with_caution' | 'verify_next' | 'hold' | 'drop';
|
|
2001
|
+
type EmailStatusValue = 'valid' | 'invalid' | 'catch_all' | 'valid_catch_all' | 'unknown' | 'do_not_mail' | 'spamtrap' | 'abuse' | 'disposable';
|
|
2002
|
+
type EmailStatusMapEntry = {
|
|
2003
|
+
status: EmailStatusValue;
|
|
2004
|
+
verdict?: EmailStatusVerdict;
|
|
2005
|
+
verified?: boolean;
|
|
2006
|
+
reason?: string;
|
|
2007
|
+
};
|
|
2008
|
+
type EmailStatusRule = EmailStatusMapEntry & {
|
|
2009
|
+
when: Record<string, string | number | boolean | null>;
|
|
2010
|
+
};
|
|
2011
|
+
type EmailStatusExtractorConfig = {
|
|
2012
|
+
provider: string;
|
|
2013
|
+
rawStatus?: string[];
|
|
2014
|
+
rawScore?: string[];
|
|
2015
|
+
valid?: string[];
|
|
2016
|
+
deliverability?: string[];
|
|
2017
|
+
catchAll?: string[];
|
|
2018
|
+
mxProvider?: string[];
|
|
2019
|
+
mxRecord?: string[];
|
|
2020
|
+
fraudScore?: string[];
|
|
2021
|
+
disposable?: string[];
|
|
2022
|
+
roleBased?: string[];
|
|
2023
|
+
freeEmail?: string[];
|
|
2024
|
+
abuse?: string[];
|
|
2025
|
+
spamtrap?: string[];
|
|
2026
|
+
suspect?: string[];
|
|
2027
|
+
statusMap?: Record<string, EmailStatusMapEntry>;
|
|
2028
|
+
rules?: EmailStatusRule[];
|
|
2029
|
+
};
|
|
2030
|
+
|
|
2000
2031
|
type ToolResultExecutionMetadata = {
|
|
2001
2032
|
idempotent: true;
|
|
2002
2033
|
cached: boolean;
|
|
@@ -2017,6 +2048,7 @@ type ToolResultExtractorDescriptor = {
|
|
|
2017
2048
|
transforms?: readonly string[];
|
|
2018
2049
|
enum?: readonly string[];
|
|
2019
2050
|
overrides?: readonly ToolResultExtractorOverride[];
|
|
2051
|
+
emailStatus?: EmailStatusExtractorConfig;
|
|
2020
2052
|
};
|
|
2021
2053
|
type ToolResultExtractorOverride = {
|
|
2022
2054
|
paths: readonly string[];
|
package/dist/index.d.ts
CHANGED
|
@@ -1997,6 +1997,37 @@ interface PlayDataset<T> extends AsyncIterable<T> {
|
|
|
1997
1997
|
};
|
|
1998
1998
|
}
|
|
1999
1999
|
|
|
2000
|
+
type EmailStatusVerdict = 'send' | 'send_with_caution' | 'verify_next' | 'hold' | 'drop';
|
|
2001
|
+
type EmailStatusValue = 'valid' | 'invalid' | 'catch_all' | 'valid_catch_all' | 'unknown' | 'do_not_mail' | 'spamtrap' | 'abuse' | 'disposable';
|
|
2002
|
+
type EmailStatusMapEntry = {
|
|
2003
|
+
status: EmailStatusValue;
|
|
2004
|
+
verdict?: EmailStatusVerdict;
|
|
2005
|
+
verified?: boolean;
|
|
2006
|
+
reason?: string;
|
|
2007
|
+
};
|
|
2008
|
+
type EmailStatusRule = EmailStatusMapEntry & {
|
|
2009
|
+
when: Record<string, string | number | boolean | null>;
|
|
2010
|
+
};
|
|
2011
|
+
type EmailStatusExtractorConfig = {
|
|
2012
|
+
provider: string;
|
|
2013
|
+
rawStatus?: string[];
|
|
2014
|
+
rawScore?: string[];
|
|
2015
|
+
valid?: string[];
|
|
2016
|
+
deliverability?: string[];
|
|
2017
|
+
catchAll?: string[];
|
|
2018
|
+
mxProvider?: string[];
|
|
2019
|
+
mxRecord?: string[];
|
|
2020
|
+
fraudScore?: string[];
|
|
2021
|
+
disposable?: string[];
|
|
2022
|
+
roleBased?: string[];
|
|
2023
|
+
freeEmail?: string[];
|
|
2024
|
+
abuse?: string[];
|
|
2025
|
+
spamtrap?: string[];
|
|
2026
|
+
suspect?: string[];
|
|
2027
|
+
statusMap?: Record<string, EmailStatusMapEntry>;
|
|
2028
|
+
rules?: EmailStatusRule[];
|
|
2029
|
+
};
|
|
2030
|
+
|
|
2000
2031
|
type ToolResultExecutionMetadata = {
|
|
2001
2032
|
idempotent: true;
|
|
2002
2033
|
cached: boolean;
|
|
@@ -2017,6 +2048,7 @@ type ToolResultExtractorDescriptor = {
|
|
|
2017
2048
|
transforms?: readonly string[];
|
|
2018
2049
|
enum?: readonly string[];
|
|
2019
2050
|
overrides?: readonly ToolResultExtractorOverride[];
|
|
2051
|
+
emailStatus?: EmailStatusExtractorConfig;
|
|
2020
2052
|
};
|
|
2021
2053
|
type ToolResultExtractorOverride = {
|
|
2022
2054
|
paths: readonly string[];
|
package/dist/index.js
CHANGED
|
@@ -241,10 +241,10 @@ var import_node_path2 = require("path");
|
|
|
241
241
|
|
|
242
242
|
// src/release.ts
|
|
243
243
|
var SDK_RELEASE = {
|
|
244
|
-
version: "0.1.
|
|
244
|
+
version: "0.1.77",
|
|
245
245
|
apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
|
|
246
246
|
supportPolicy: {
|
|
247
|
-
latest: "0.1.
|
|
247
|
+
latest: "0.1.77",
|
|
248
248
|
minimumSupported: "0.1.53",
|
|
249
249
|
deprecatedBelow: "0.1.53"
|
|
250
250
|
}
|
|
@@ -1906,6 +1906,160 @@ function formatPlayBootstrapFinderKindsForSentence() {
|
|
|
1906
1906
|
return `${allButLast.join(", ")} or ${last}`;
|
|
1907
1907
|
}
|
|
1908
1908
|
|
|
1909
|
+
// ../shared_libs/play-runtime/email-status.ts
|
|
1910
|
+
var DEFAULT_STATUS_MAP = {
|
|
1911
|
+
verified: { status: "valid", verdict: "send", verified: true },
|
|
1912
|
+
valid: { status: "valid", verdict: "send", verified: true },
|
|
1913
|
+
deliverable: { status: "valid", verdict: "send", verified: true },
|
|
1914
|
+
true: { status: "valid", verdict: "send", verified: true },
|
|
1915
|
+
invalid: { status: "invalid", verdict: "drop", verified: false },
|
|
1916
|
+
undeliverable: { status: "invalid", verdict: "drop", verified: false },
|
|
1917
|
+
false: { status: "invalid", verdict: "drop", verified: false },
|
|
1918
|
+
"catch-all": {
|
|
1919
|
+
status: "catch_all",
|
|
1920
|
+
verdict: "verify_next",
|
|
1921
|
+
verified: false
|
|
1922
|
+
},
|
|
1923
|
+
catch_all: {
|
|
1924
|
+
status: "catch_all",
|
|
1925
|
+
verdict: "verify_next",
|
|
1926
|
+
verified: false
|
|
1927
|
+
},
|
|
1928
|
+
valid_catch_all: {
|
|
1929
|
+
status: "valid_catch_all",
|
|
1930
|
+
verdict: "send_with_caution",
|
|
1931
|
+
verified: true
|
|
1932
|
+
},
|
|
1933
|
+
accept_all: {
|
|
1934
|
+
status: "catch_all",
|
|
1935
|
+
verdict: "verify_next",
|
|
1936
|
+
verified: false
|
|
1937
|
+
},
|
|
1938
|
+
unknown: { status: "unknown", verdict: "hold", verified: false },
|
|
1939
|
+
unavailable: { status: "unknown", verdict: "hold", verified: false },
|
|
1940
|
+
do_not_mail: { status: "do_not_mail", verdict: "drop", verified: false },
|
|
1941
|
+
spamtrap: { status: "spamtrap", verdict: "drop", verified: false },
|
|
1942
|
+
abuse: { status: "abuse", verdict: "drop", verified: false },
|
|
1943
|
+
disposable: { status: "disposable", verdict: "drop", verified: false }
|
|
1944
|
+
};
|
|
1945
|
+
function normalizeKey(value) {
|
|
1946
|
+
if (value == null) return null;
|
|
1947
|
+
if (typeof value === "boolean") return String(value);
|
|
1948
|
+
const normalized = String(value).trim().toLowerCase().replace(/\s+/g, "_");
|
|
1949
|
+
return normalized || null;
|
|
1950
|
+
}
|
|
1951
|
+
function boolish(value) {
|
|
1952
|
+
if (typeof value === "boolean") return value;
|
|
1953
|
+
if (typeof value === "number") return value === 1 ? true : value === 0 ? false : null;
|
|
1954
|
+
if (typeof value !== "string") return null;
|
|
1955
|
+
const normalized = value.trim().toLowerCase();
|
|
1956
|
+
if (["true", "yes", "y", "1"].includes(normalized)) return true;
|
|
1957
|
+
if (["false", "no", "n", "0"].includes(normalized)) return false;
|
|
1958
|
+
return null;
|
|
1959
|
+
}
|
|
1960
|
+
function numberish(value) {
|
|
1961
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
1962
|
+
if (typeof value !== "string" || value.trim() === "") return null;
|
|
1963
|
+
const parsed = Number(value);
|
|
1964
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
1965
|
+
}
|
|
1966
|
+
function stringish(value) {
|
|
1967
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
1968
|
+
}
|
|
1969
|
+
function deliverability(value) {
|
|
1970
|
+
const normalized = normalizeKey(value);
|
|
1971
|
+
return normalized === "high" || normalized === "medium" || normalized === "low" ? normalized : "unknown";
|
|
1972
|
+
}
|
|
1973
|
+
function mxClass(mxProvider, mxRecord) {
|
|
1974
|
+
const haystack = `${stringish(mxProvider) ?? ""} ${stringish(mxRecord) ?? ""}`.toLowerCase();
|
|
1975
|
+
if (!haystack.trim()) return "unknown";
|
|
1976
|
+
if (/proofpoint|pphosted|mimecast|barracuda|ess\.barracudanetworks|ironport|cisco|iphmx|messagelabs|symantec/.test(
|
|
1977
|
+
haystack
|
|
1978
|
+
)) {
|
|
1979
|
+
return "security_gateway";
|
|
1980
|
+
}
|
|
1981
|
+
if (/aspmx\.l\.google|google|g-suite|google workspace/.test(haystack)) {
|
|
1982
|
+
return "workspace_mailbox";
|
|
1983
|
+
}
|
|
1984
|
+
if (/protection\.outlook|office365|microsoft|outlook|exchange online/.test(haystack)) {
|
|
1985
|
+
return "workspace_mailbox";
|
|
1986
|
+
}
|
|
1987
|
+
if (/gmail|yahoo|icloud|aol|hotmail/.test(haystack)) return "consumer_mailbox";
|
|
1988
|
+
if (/postfix|exim|sendmail|zimbra|plesk|cpanel|mail\./.test(haystack)) return "on_prem";
|
|
1989
|
+
return "unknown";
|
|
1990
|
+
}
|
|
1991
|
+
function entryForStatus(key, map) {
|
|
1992
|
+
if (!key) return null;
|
|
1993
|
+
return map?.[key] ?? DEFAULT_STATUS_MAP[key] ?? null;
|
|
1994
|
+
}
|
|
1995
|
+
function read(values, name) {
|
|
1996
|
+
return values[name];
|
|
1997
|
+
}
|
|
1998
|
+
function matchesRule(rule, values) {
|
|
1999
|
+
return Object.entries(rule.when).every(([key, expected]) => {
|
|
2000
|
+
const actual = read(values, key);
|
|
2001
|
+
if (key.endsWith("Lt")) {
|
|
2002
|
+
const source = numberish(read(values, key.slice(0, -2)));
|
|
2003
|
+
return typeof expected === "number" && source != null && source < expected;
|
|
2004
|
+
}
|
|
2005
|
+
if (typeof expected === "boolean") return boolish(actual) === expected;
|
|
2006
|
+
if (typeof expected === "number") return numberish(actual) === expected;
|
|
2007
|
+
return normalizeKey(actual) === normalizeKey(expected);
|
|
2008
|
+
});
|
|
2009
|
+
}
|
|
2010
|
+
function buildEmailStatus({
|
|
2011
|
+
config,
|
|
2012
|
+
values
|
|
2013
|
+
}) {
|
|
2014
|
+
const rawStatus = read(values, "rawStatus");
|
|
2015
|
+
const rawScore = numberish(read(values, "rawScore"));
|
|
2016
|
+
const valid = boolish(read(values, "valid"));
|
|
2017
|
+
const catchAll = boolish(read(values, "catchAll"));
|
|
2018
|
+
const disposable = boolish(read(values, "disposable"));
|
|
2019
|
+
const abuse = boolish(read(values, "abuse"));
|
|
2020
|
+
const spamtrap = boolish(read(values, "spamtrap"));
|
|
2021
|
+
const suspect = boolish(read(values, "suspect"));
|
|
2022
|
+
const rawKey = normalizeKey(rawStatus);
|
|
2023
|
+
const mapped = config.rules?.find((rule) => matchesRule(rule, values)) ?? entryForStatus(rawKey, config.statusMap) ?? entryForStatus(valid == null ? null : String(valid), config.statusMap);
|
|
2024
|
+
const status = mapped?.status ?? (disposable ? "disposable" : abuse ? "abuse" : spamtrap ? "spamtrap" : catchAll ? "catch_all" : valid === true ? "valid" : valid === false ? "invalid" : "unknown");
|
|
2025
|
+
const defaultVerdict = status === "valid" ? "send" : status === "valid_catch_all" ? "send_with_caution" : status === "catch_all" ? "verify_next" : status === "unknown" ? "hold" : "drop";
|
|
2026
|
+
const verdict = mapped?.verdict ?? defaultVerdict;
|
|
2027
|
+
const verified = mapped?.verified ?? (status === "valid" || status === "valid_catch_all" || verdict === "send");
|
|
2028
|
+
const reasons = [
|
|
2029
|
+
mapped?.reason,
|
|
2030
|
+
catchAll ? "catch_all_domain" : null,
|
|
2031
|
+
mxClass(read(values, "mxProvider"), read(values, "mxRecord")) === "security_gateway" ? "security_gateway_mx" : null,
|
|
2032
|
+
suspect ? "provider_marked_suspect" : null
|
|
2033
|
+
].filter((reason) => typeof reason === "string");
|
|
2034
|
+
return {
|
|
2035
|
+
verdict,
|
|
2036
|
+
status,
|
|
2037
|
+
verified,
|
|
2038
|
+
confidence: rawScore,
|
|
2039
|
+
reasons,
|
|
2040
|
+
signals: {
|
|
2041
|
+
catch_all: catchAll,
|
|
2042
|
+
deliverability: deliverability(read(values, "deliverability")),
|
|
2043
|
+
mx_class: mxClass(read(values, "mxProvider"), read(values, "mxRecord")),
|
|
2044
|
+
mx_provider: stringish(read(values, "mxProvider")),
|
|
2045
|
+
mx_record: stringish(read(values, "mxRecord")),
|
|
2046
|
+
fraud_score: numberish(read(values, "fraudScore")),
|
|
2047
|
+
disposable,
|
|
2048
|
+
role_based: boolish(read(values, "roleBased")),
|
|
2049
|
+
free_email: boolish(read(values, "freeEmail")),
|
|
2050
|
+
abuse,
|
|
2051
|
+
spamtrap,
|
|
2052
|
+
suspect,
|
|
2053
|
+
valid
|
|
2054
|
+
},
|
|
2055
|
+
provider: {
|
|
2056
|
+
name: config.provider,
|
|
2057
|
+
raw_status: typeof rawStatus === "string" || typeof rawStatus === "boolean" || typeof rawStatus === "number" ? rawStatus : null,
|
|
2058
|
+
raw_score: rawScore
|
|
2059
|
+
}
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
|
|
1909
2063
|
// ../shared_libs/play-runtime/tool-result.ts
|
|
1910
2064
|
var TARGET_FALLBACK_KEYS = {
|
|
1911
2065
|
email: [/^email$/i, /^address$/i, /email/i],
|
|
@@ -2089,6 +2243,42 @@ function findFirstTargetByPath(result, paths) {
|
|
|
2089
2243
|
}
|
|
2090
2244
|
return null;
|
|
2091
2245
|
}
|
|
2246
|
+
function firstValueForPaths(result, paths) {
|
|
2247
|
+
return findFirstTargetByPath(result, paths);
|
|
2248
|
+
}
|
|
2249
|
+
function buildEmailStatusTarget(result, descriptor) {
|
|
2250
|
+
const config = descriptor.emailStatus;
|
|
2251
|
+
if (!config) return null;
|
|
2252
|
+
const values = {};
|
|
2253
|
+
const pathSets = {
|
|
2254
|
+
rawStatus: config.rawStatus,
|
|
2255
|
+
rawScore: config.rawScore,
|
|
2256
|
+
valid: config.valid,
|
|
2257
|
+
deliverability: config.deliverability,
|
|
2258
|
+
catchAll: config.catchAll,
|
|
2259
|
+
mxProvider: config.mxProvider,
|
|
2260
|
+
mxRecord: config.mxRecord,
|
|
2261
|
+
fraudScore: config.fraudScore,
|
|
2262
|
+
disposable: config.disposable,
|
|
2263
|
+
roleBased: config.roleBased,
|
|
2264
|
+
freeEmail: config.freeEmail,
|
|
2265
|
+
abuse: config.abuse,
|
|
2266
|
+
spamtrap: config.spamtrap,
|
|
2267
|
+
suspect: config.suspect
|
|
2268
|
+
};
|
|
2269
|
+
let firstPath = null;
|
|
2270
|
+
for (const [name, paths] of Object.entries(pathSets)) {
|
|
2271
|
+
const match = firstValueForPaths(result, paths);
|
|
2272
|
+
if (!match) continue;
|
|
2273
|
+
values[name] = match.value;
|
|
2274
|
+
firstPath ??= match.path;
|
|
2275
|
+
}
|
|
2276
|
+
if (!firstPath) return null;
|
|
2277
|
+
return {
|
|
2278
|
+
path: firstPath,
|
|
2279
|
+
value: buildEmailStatus({ config, values })
|
|
2280
|
+
};
|
|
2281
|
+
}
|
|
2092
2282
|
function findFirstTargetByKey(result, target, depth = 0, path = []) {
|
|
2093
2283
|
if (depth > 6) return null;
|
|
2094
2284
|
if (Array.isArray(result)) {
|
|
@@ -2238,6 +2428,11 @@ function deriveListKeys(input) {
|
|
|
2238
2428
|
function buildTargets(result, extractors, targetGetters) {
|
|
2239
2429
|
const targets = {};
|
|
2240
2430
|
for (const [target, descriptor] of Object.entries(extractors ?? {})) {
|
|
2431
|
+
const emailStatusTarget = buildEmailStatusTarget(result, descriptor);
|
|
2432
|
+
if (emailStatusTarget) {
|
|
2433
|
+
targets[target] = emailStatusTarget;
|
|
2434
|
+
continue;
|
|
2435
|
+
}
|
|
2241
2436
|
const fromExtractor = findFirstTargetByPath(result, descriptor.paths);
|
|
2242
2437
|
if (!fromExtractor) continue;
|
|
2243
2438
|
const transformed = coerceToEnum(
|
package/dist/index.mjs
CHANGED
|
@@ -179,10 +179,10 @@ import { join as join2 } from "path";
|
|
|
179
179
|
|
|
180
180
|
// src/release.ts
|
|
181
181
|
var SDK_RELEASE = {
|
|
182
|
-
version: "0.1.
|
|
182
|
+
version: "0.1.77",
|
|
183
183
|
apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
|
|
184
184
|
supportPolicy: {
|
|
185
|
-
latest: "0.1.
|
|
185
|
+
latest: "0.1.77",
|
|
186
186
|
minimumSupported: "0.1.53",
|
|
187
187
|
deprecatedBelow: "0.1.53"
|
|
188
188
|
}
|
|
@@ -1844,6 +1844,160 @@ function formatPlayBootstrapFinderKindsForSentence() {
|
|
|
1844
1844
|
return `${allButLast.join(", ")} or ${last}`;
|
|
1845
1845
|
}
|
|
1846
1846
|
|
|
1847
|
+
// ../shared_libs/play-runtime/email-status.ts
|
|
1848
|
+
var DEFAULT_STATUS_MAP = {
|
|
1849
|
+
verified: { status: "valid", verdict: "send", verified: true },
|
|
1850
|
+
valid: { status: "valid", verdict: "send", verified: true },
|
|
1851
|
+
deliverable: { status: "valid", verdict: "send", verified: true },
|
|
1852
|
+
true: { status: "valid", verdict: "send", verified: true },
|
|
1853
|
+
invalid: { status: "invalid", verdict: "drop", verified: false },
|
|
1854
|
+
undeliverable: { status: "invalid", verdict: "drop", verified: false },
|
|
1855
|
+
false: { status: "invalid", verdict: "drop", verified: false },
|
|
1856
|
+
"catch-all": {
|
|
1857
|
+
status: "catch_all",
|
|
1858
|
+
verdict: "verify_next",
|
|
1859
|
+
verified: false
|
|
1860
|
+
},
|
|
1861
|
+
catch_all: {
|
|
1862
|
+
status: "catch_all",
|
|
1863
|
+
verdict: "verify_next",
|
|
1864
|
+
verified: false
|
|
1865
|
+
},
|
|
1866
|
+
valid_catch_all: {
|
|
1867
|
+
status: "valid_catch_all",
|
|
1868
|
+
verdict: "send_with_caution",
|
|
1869
|
+
verified: true
|
|
1870
|
+
},
|
|
1871
|
+
accept_all: {
|
|
1872
|
+
status: "catch_all",
|
|
1873
|
+
verdict: "verify_next",
|
|
1874
|
+
verified: false
|
|
1875
|
+
},
|
|
1876
|
+
unknown: { status: "unknown", verdict: "hold", verified: false },
|
|
1877
|
+
unavailable: { status: "unknown", verdict: "hold", verified: false },
|
|
1878
|
+
do_not_mail: { status: "do_not_mail", verdict: "drop", verified: false },
|
|
1879
|
+
spamtrap: { status: "spamtrap", verdict: "drop", verified: false },
|
|
1880
|
+
abuse: { status: "abuse", verdict: "drop", verified: false },
|
|
1881
|
+
disposable: { status: "disposable", verdict: "drop", verified: false }
|
|
1882
|
+
};
|
|
1883
|
+
function normalizeKey(value) {
|
|
1884
|
+
if (value == null) return null;
|
|
1885
|
+
if (typeof value === "boolean") return String(value);
|
|
1886
|
+
const normalized = String(value).trim().toLowerCase().replace(/\s+/g, "_");
|
|
1887
|
+
return normalized || null;
|
|
1888
|
+
}
|
|
1889
|
+
function boolish(value) {
|
|
1890
|
+
if (typeof value === "boolean") return value;
|
|
1891
|
+
if (typeof value === "number") return value === 1 ? true : value === 0 ? false : null;
|
|
1892
|
+
if (typeof value !== "string") return null;
|
|
1893
|
+
const normalized = value.trim().toLowerCase();
|
|
1894
|
+
if (["true", "yes", "y", "1"].includes(normalized)) return true;
|
|
1895
|
+
if (["false", "no", "n", "0"].includes(normalized)) return false;
|
|
1896
|
+
return null;
|
|
1897
|
+
}
|
|
1898
|
+
function numberish(value) {
|
|
1899
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
1900
|
+
if (typeof value !== "string" || value.trim() === "") return null;
|
|
1901
|
+
const parsed = Number(value);
|
|
1902
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
1903
|
+
}
|
|
1904
|
+
function stringish(value) {
|
|
1905
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
1906
|
+
}
|
|
1907
|
+
function deliverability(value) {
|
|
1908
|
+
const normalized = normalizeKey(value);
|
|
1909
|
+
return normalized === "high" || normalized === "medium" || normalized === "low" ? normalized : "unknown";
|
|
1910
|
+
}
|
|
1911
|
+
function mxClass(mxProvider, mxRecord) {
|
|
1912
|
+
const haystack = `${stringish(mxProvider) ?? ""} ${stringish(mxRecord) ?? ""}`.toLowerCase();
|
|
1913
|
+
if (!haystack.trim()) return "unknown";
|
|
1914
|
+
if (/proofpoint|pphosted|mimecast|barracuda|ess\.barracudanetworks|ironport|cisco|iphmx|messagelabs|symantec/.test(
|
|
1915
|
+
haystack
|
|
1916
|
+
)) {
|
|
1917
|
+
return "security_gateway";
|
|
1918
|
+
}
|
|
1919
|
+
if (/aspmx\.l\.google|google|g-suite|google workspace/.test(haystack)) {
|
|
1920
|
+
return "workspace_mailbox";
|
|
1921
|
+
}
|
|
1922
|
+
if (/protection\.outlook|office365|microsoft|outlook|exchange online/.test(haystack)) {
|
|
1923
|
+
return "workspace_mailbox";
|
|
1924
|
+
}
|
|
1925
|
+
if (/gmail|yahoo|icloud|aol|hotmail/.test(haystack)) return "consumer_mailbox";
|
|
1926
|
+
if (/postfix|exim|sendmail|zimbra|plesk|cpanel|mail\./.test(haystack)) return "on_prem";
|
|
1927
|
+
return "unknown";
|
|
1928
|
+
}
|
|
1929
|
+
function entryForStatus(key, map) {
|
|
1930
|
+
if (!key) return null;
|
|
1931
|
+
return map?.[key] ?? DEFAULT_STATUS_MAP[key] ?? null;
|
|
1932
|
+
}
|
|
1933
|
+
function read(values, name) {
|
|
1934
|
+
return values[name];
|
|
1935
|
+
}
|
|
1936
|
+
function matchesRule(rule, values) {
|
|
1937
|
+
return Object.entries(rule.when).every(([key, expected]) => {
|
|
1938
|
+
const actual = read(values, key);
|
|
1939
|
+
if (key.endsWith("Lt")) {
|
|
1940
|
+
const source = numberish(read(values, key.slice(0, -2)));
|
|
1941
|
+
return typeof expected === "number" && source != null && source < expected;
|
|
1942
|
+
}
|
|
1943
|
+
if (typeof expected === "boolean") return boolish(actual) === expected;
|
|
1944
|
+
if (typeof expected === "number") return numberish(actual) === expected;
|
|
1945
|
+
return normalizeKey(actual) === normalizeKey(expected);
|
|
1946
|
+
});
|
|
1947
|
+
}
|
|
1948
|
+
function buildEmailStatus({
|
|
1949
|
+
config,
|
|
1950
|
+
values
|
|
1951
|
+
}) {
|
|
1952
|
+
const rawStatus = read(values, "rawStatus");
|
|
1953
|
+
const rawScore = numberish(read(values, "rawScore"));
|
|
1954
|
+
const valid = boolish(read(values, "valid"));
|
|
1955
|
+
const catchAll = boolish(read(values, "catchAll"));
|
|
1956
|
+
const disposable = boolish(read(values, "disposable"));
|
|
1957
|
+
const abuse = boolish(read(values, "abuse"));
|
|
1958
|
+
const spamtrap = boolish(read(values, "spamtrap"));
|
|
1959
|
+
const suspect = boolish(read(values, "suspect"));
|
|
1960
|
+
const rawKey = normalizeKey(rawStatus);
|
|
1961
|
+
const mapped = config.rules?.find((rule) => matchesRule(rule, values)) ?? entryForStatus(rawKey, config.statusMap) ?? entryForStatus(valid == null ? null : String(valid), config.statusMap);
|
|
1962
|
+
const status = mapped?.status ?? (disposable ? "disposable" : abuse ? "abuse" : spamtrap ? "spamtrap" : catchAll ? "catch_all" : valid === true ? "valid" : valid === false ? "invalid" : "unknown");
|
|
1963
|
+
const defaultVerdict = status === "valid" ? "send" : status === "valid_catch_all" ? "send_with_caution" : status === "catch_all" ? "verify_next" : status === "unknown" ? "hold" : "drop";
|
|
1964
|
+
const verdict = mapped?.verdict ?? defaultVerdict;
|
|
1965
|
+
const verified = mapped?.verified ?? (status === "valid" || status === "valid_catch_all" || verdict === "send");
|
|
1966
|
+
const reasons = [
|
|
1967
|
+
mapped?.reason,
|
|
1968
|
+
catchAll ? "catch_all_domain" : null,
|
|
1969
|
+
mxClass(read(values, "mxProvider"), read(values, "mxRecord")) === "security_gateway" ? "security_gateway_mx" : null,
|
|
1970
|
+
suspect ? "provider_marked_suspect" : null
|
|
1971
|
+
].filter((reason) => typeof reason === "string");
|
|
1972
|
+
return {
|
|
1973
|
+
verdict,
|
|
1974
|
+
status,
|
|
1975
|
+
verified,
|
|
1976
|
+
confidence: rawScore,
|
|
1977
|
+
reasons,
|
|
1978
|
+
signals: {
|
|
1979
|
+
catch_all: catchAll,
|
|
1980
|
+
deliverability: deliverability(read(values, "deliverability")),
|
|
1981
|
+
mx_class: mxClass(read(values, "mxProvider"), read(values, "mxRecord")),
|
|
1982
|
+
mx_provider: stringish(read(values, "mxProvider")),
|
|
1983
|
+
mx_record: stringish(read(values, "mxRecord")),
|
|
1984
|
+
fraud_score: numberish(read(values, "fraudScore")),
|
|
1985
|
+
disposable,
|
|
1986
|
+
role_based: boolish(read(values, "roleBased")),
|
|
1987
|
+
free_email: boolish(read(values, "freeEmail")),
|
|
1988
|
+
abuse,
|
|
1989
|
+
spamtrap,
|
|
1990
|
+
suspect,
|
|
1991
|
+
valid
|
|
1992
|
+
},
|
|
1993
|
+
provider: {
|
|
1994
|
+
name: config.provider,
|
|
1995
|
+
raw_status: typeof rawStatus === "string" || typeof rawStatus === "boolean" || typeof rawStatus === "number" ? rawStatus : null,
|
|
1996
|
+
raw_score: rawScore
|
|
1997
|
+
}
|
|
1998
|
+
};
|
|
1999
|
+
}
|
|
2000
|
+
|
|
1847
2001
|
// ../shared_libs/play-runtime/tool-result.ts
|
|
1848
2002
|
var TARGET_FALLBACK_KEYS = {
|
|
1849
2003
|
email: [/^email$/i, /^address$/i, /email/i],
|
|
@@ -2027,6 +2181,42 @@ function findFirstTargetByPath(result, paths) {
|
|
|
2027
2181
|
}
|
|
2028
2182
|
return null;
|
|
2029
2183
|
}
|
|
2184
|
+
function firstValueForPaths(result, paths) {
|
|
2185
|
+
return findFirstTargetByPath(result, paths);
|
|
2186
|
+
}
|
|
2187
|
+
function buildEmailStatusTarget(result, descriptor) {
|
|
2188
|
+
const config = descriptor.emailStatus;
|
|
2189
|
+
if (!config) return null;
|
|
2190
|
+
const values = {};
|
|
2191
|
+
const pathSets = {
|
|
2192
|
+
rawStatus: config.rawStatus,
|
|
2193
|
+
rawScore: config.rawScore,
|
|
2194
|
+
valid: config.valid,
|
|
2195
|
+
deliverability: config.deliverability,
|
|
2196
|
+
catchAll: config.catchAll,
|
|
2197
|
+
mxProvider: config.mxProvider,
|
|
2198
|
+
mxRecord: config.mxRecord,
|
|
2199
|
+
fraudScore: config.fraudScore,
|
|
2200
|
+
disposable: config.disposable,
|
|
2201
|
+
roleBased: config.roleBased,
|
|
2202
|
+
freeEmail: config.freeEmail,
|
|
2203
|
+
abuse: config.abuse,
|
|
2204
|
+
spamtrap: config.spamtrap,
|
|
2205
|
+
suspect: config.suspect
|
|
2206
|
+
};
|
|
2207
|
+
let firstPath = null;
|
|
2208
|
+
for (const [name, paths] of Object.entries(pathSets)) {
|
|
2209
|
+
const match = firstValueForPaths(result, paths);
|
|
2210
|
+
if (!match) continue;
|
|
2211
|
+
values[name] = match.value;
|
|
2212
|
+
firstPath ??= match.path;
|
|
2213
|
+
}
|
|
2214
|
+
if (!firstPath) return null;
|
|
2215
|
+
return {
|
|
2216
|
+
path: firstPath,
|
|
2217
|
+
value: buildEmailStatus({ config, values })
|
|
2218
|
+
};
|
|
2219
|
+
}
|
|
2030
2220
|
function findFirstTargetByKey(result, target, depth = 0, path = []) {
|
|
2031
2221
|
if (depth > 6) return null;
|
|
2032
2222
|
if (Array.isArray(result)) {
|
|
@@ -2176,6 +2366,11 @@ function deriveListKeys(input) {
|
|
|
2176
2366
|
function buildTargets(result, extractors, targetGetters) {
|
|
2177
2367
|
const targets = {};
|
|
2178
2368
|
for (const [target, descriptor] of Object.entries(extractors ?? {})) {
|
|
2369
|
+
const emailStatusTarget = buildEmailStatusTarget(result, descriptor);
|
|
2370
|
+
if (emailStatusTarget) {
|
|
2371
|
+
targets[target] = emailStatusTarget;
|
|
2372
|
+
continue;
|
|
2373
|
+
}
|
|
2179
2374
|
const fromExtractor = findFirstTargetByPath(result, descriptor.paths);
|
|
2180
2375
|
if (!fromExtractor) continue;
|
|
2181
2376
|
const transformed = coerceToEnum(
|
|
@@ -50,10 +50,10 @@ export type SdkRelease = {
|
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
export const SDK_RELEASE = {
|
|
53
|
-
version: '0.1.
|
|
53
|
+
version: '0.1.77',
|
|
54
54
|
apiContract: '2026-06-dataset-column-cell-stale-hard-cutover',
|
|
55
55
|
supportPolicy: {
|
|
56
|
-
latest: '0.1.
|
|
56
|
+
latest: '0.1.77',
|
|
57
57
|
minimumSupported: '0.1.53',
|
|
58
58
|
deprecatedBelow: '0.1.53',
|
|
59
59
|
},
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
export type EmailStatusVerdict =
|
|
2
|
+
| 'send'
|
|
3
|
+
| 'send_with_caution'
|
|
4
|
+
| 'verify_next'
|
|
5
|
+
| 'hold'
|
|
6
|
+
| 'drop';
|
|
7
|
+
|
|
8
|
+
export type EmailStatusValue =
|
|
9
|
+
| 'valid'
|
|
10
|
+
| 'invalid'
|
|
11
|
+
| 'catch_all'
|
|
12
|
+
| 'valid_catch_all'
|
|
13
|
+
| 'unknown'
|
|
14
|
+
| 'do_not_mail'
|
|
15
|
+
| 'spamtrap'
|
|
16
|
+
| 'abuse'
|
|
17
|
+
| 'disposable';
|
|
18
|
+
|
|
19
|
+
export type EmailDeliverability = 'high' | 'medium' | 'low' | 'unknown';
|
|
20
|
+
|
|
21
|
+
export type EmailMxClass =
|
|
22
|
+
| 'consumer_mailbox'
|
|
23
|
+
| 'workspace_mailbox'
|
|
24
|
+
| 'security_gateway'
|
|
25
|
+
| 'on_prem'
|
|
26
|
+
| 'unknown';
|
|
27
|
+
|
|
28
|
+
export type EmailStatus = {
|
|
29
|
+
verdict: EmailStatusVerdict;
|
|
30
|
+
status: EmailStatusValue;
|
|
31
|
+
verified: boolean;
|
|
32
|
+
confidence: number | null;
|
|
33
|
+
reasons: string[];
|
|
34
|
+
signals: {
|
|
35
|
+
catch_all: boolean | null;
|
|
36
|
+
deliverability: EmailDeliverability;
|
|
37
|
+
mx_class: EmailMxClass;
|
|
38
|
+
mx_provider: string | null;
|
|
39
|
+
mx_record: string | null;
|
|
40
|
+
fraud_score: number | null;
|
|
41
|
+
disposable: boolean | null;
|
|
42
|
+
role_based: boolean | null;
|
|
43
|
+
free_email: boolean | null;
|
|
44
|
+
abuse: boolean | null;
|
|
45
|
+
spamtrap: boolean | null;
|
|
46
|
+
suspect: boolean | null;
|
|
47
|
+
valid: boolean | null;
|
|
48
|
+
};
|
|
49
|
+
provider: {
|
|
50
|
+
name: string;
|
|
51
|
+
raw_status: string | boolean | number | null;
|
|
52
|
+
raw_score: number | null;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type EmailStatusMapEntry = {
|
|
57
|
+
status: EmailStatusValue;
|
|
58
|
+
verdict?: EmailStatusVerdict;
|
|
59
|
+
verified?: boolean;
|
|
60
|
+
reason?: string;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type EmailStatusRule = EmailStatusMapEntry & {
|
|
64
|
+
when: Record<string, string | number | boolean | null>;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type EmailStatusExtractorConfig = {
|
|
68
|
+
provider: string;
|
|
69
|
+
rawStatus?: string[];
|
|
70
|
+
rawScore?: string[];
|
|
71
|
+
valid?: string[];
|
|
72
|
+
deliverability?: string[];
|
|
73
|
+
catchAll?: string[];
|
|
74
|
+
mxProvider?: string[];
|
|
75
|
+
mxRecord?: string[];
|
|
76
|
+
fraudScore?: string[];
|
|
77
|
+
disposable?: string[];
|
|
78
|
+
roleBased?: string[];
|
|
79
|
+
freeEmail?: string[];
|
|
80
|
+
abuse?: string[];
|
|
81
|
+
spamtrap?: string[];
|
|
82
|
+
suspect?: string[];
|
|
83
|
+
statusMap?: Record<string, EmailStatusMapEntry>;
|
|
84
|
+
rules?: EmailStatusRule[];
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export type EmailStatusBuildInput = {
|
|
88
|
+
config: EmailStatusExtractorConfig;
|
|
89
|
+
values: Record<string, unknown>;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const DEFAULT_STATUS_MAP: Record<string, EmailStatusMapEntry> = {
|
|
93
|
+
verified: { status: 'valid', verdict: 'send', verified: true },
|
|
94
|
+
valid: { status: 'valid', verdict: 'send', verified: true },
|
|
95
|
+
deliverable: { status: 'valid', verdict: 'send', verified: true },
|
|
96
|
+
true: { status: 'valid', verdict: 'send', verified: true },
|
|
97
|
+
invalid: { status: 'invalid', verdict: 'drop', verified: false },
|
|
98
|
+
undeliverable: { status: 'invalid', verdict: 'drop', verified: false },
|
|
99
|
+
false: { status: 'invalid', verdict: 'drop', verified: false },
|
|
100
|
+
'catch-all': {
|
|
101
|
+
status: 'catch_all',
|
|
102
|
+
verdict: 'verify_next',
|
|
103
|
+
verified: false,
|
|
104
|
+
},
|
|
105
|
+
catch_all: {
|
|
106
|
+
status: 'catch_all',
|
|
107
|
+
verdict: 'verify_next',
|
|
108
|
+
verified: false,
|
|
109
|
+
},
|
|
110
|
+
valid_catch_all: {
|
|
111
|
+
status: 'valid_catch_all',
|
|
112
|
+
verdict: 'send_with_caution',
|
|
113
|
+
verified: true,
|
|
114
|
+
},
|
|
115
|
+
accept_all: {
|
|
116
|
+
status: 'catch_all',
|
|
117
|
+
verdict: 'verify_next',
|
|
118
|
+
verified: false,
|
|
119
|
+
},
|
|
120
|
+
unknown: { status: 'unknown', verdict: 'hold', verified: false },
|
|
121
|
+
unavailable: { status: 'unknown', verdict: 'hold', verified: false },
|
|
122
|
+
do_not_mail: { status: 'do_not_mail', verdict: 'drop', verified: false },
|
|
123
|
+
spamtrap: { status: 'spamtrap', verdict: 'drop', verified: false },
|
|
124
|
+
abuse: { status: 'abuse', verdict: 'drop', verified: false },
|
|
125
|
+
disposable: { status: 'disposable', verdict: 'drop', verified: false },
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
function normalizeKey(value: unknown): string | null {
|
|
129
|
+
if (value == null) return null;
|
|
130
|
+
if (typeof value === 'boolean') return String(value);
|
|
131
|
+
const normalized = String(value).trim().toLowerCase().replace(/\s+/g, '_');
|
|
132
|
+
return normalized || null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function boolish(value: unknown): boolean | null {
|
|
136
|
+
if (typeof value === 'boolean') return value;
|
|
137
|
+
if (typeof value === 'number') return value === 1 ? true : value === 0 ? false : null;
|
|
138
|
+
if (typeof value !== 'string') return null;
|
|
139
|
+
const normalized = value.trim().toLowerCase();
|
|
140
|
+
if (['true', 'yes', 'y', '1'].includes(normalized)) return true;
|
|
141
|
+
if (['false', 'no', 'n', '0'].includes(normalized)) return false;
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function numberish(value: unknown): number | null {
|
|
146
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
147
|
+
if (typeof value !== 'string' || value.trim() === '') return null;
|
|
148
|
+
const parsed = Number(value);
|
|
149
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function stringish(value: unknown): string | null {
|
|
153
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function deliverability(value: unknown): EmailDeliverability {
|
|
157
|
+
const normalized = normalizeKey(value);
|
|
158
|
+
return normalized === 'high' || normalized === 'medium' || normalized === 'low'
|
|
159
|
+
? normalized
|
|
160
|
+
: 'unknown';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function mxClass(mxProvider: unknown, mxRecord: unknown): EmailMxClass {
|
|
164
|
+
const haystack = `${stringish(mxProvider) ?? ''} ${stringish(mxRecord) ?? ''}`.toLowerCase();
|
|
165
|
+
if (!haystack.trim()) return 'unknown';
|
|
166
|
+
if (
|
|
167
|
+
/proofpoint|pphosted|mimecast|barracuda|ess\.barracudanetworks|ironport|cisco|iphmx|messagelabs|symantec/.test(
|
|
168
|
+
haystack,
|
|
169
|
+
)
|
|
170
|
+
) {
|
|
171
|
+
return 'security_gateway';
|
|
172
|
+
}
|
|
173
|
+
if (/aspmx\.l\.google|google|g-suite|google workspace/.test(haystack)) {
|
|
174
|
+
return 'workspace_mailbox';
|
|
175
|
+
}
|
|
176
|
+
if (/protection\.outlook|office365|microsoft|outlook|exchange online/.test(haystack)) {
|
|
177
|
+
return 'workspace_mailbox';
|
|
178
|
+
}
|
|
179
|
+
if (/gmail|yahoo|icloud|aol|hotmail/.test(haystack)) return 'consumer_mailbox';
|
|
180
|
+
if (/postfix|exim|sendmail|zimbra|plesk|cpanel|mail\./.test(haystack)) return 'on_prem';
|
|
181
|
+
return 'unknown';
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function entryForStatus(
|
|
185
|
+
key: string | null,
|
|
186
|
+
map: Record<string, EmailStatusMapEntry> | undefined,
|
|
187
|
+
): EmailStatusMapEntry | null {
|
|
188
|
+
if (!key) return null;
|
|
189
|
+
return map?.[key] ?? DEFAULT_STATUS_MAP[key] ?? null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function read(values: Record<string, unknown>, name: string): unknown {
|
|
193
|
+
return values[name];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function matchesRule(
|
|
197
|
+
rule: EmailStatusRule,
|
|
198
|
+
values: Record<string, unknown>,
|
|
199
|
+
): boolean {
|
|
200
|
+
return Object.entries(rule.when).every(([key, expected]) => {
|
|
201
|
+
const actual = read(values, key);
|
|
202
|
+
if (key.endsWith('Lt')) {
|
|
203
|
+
const source = numberish(read(values, key.slice(0, -2)));
|
|
204
|
+
return typeof expected === 'number' && source != null && source < expected;
|
|
205
|
+
}
|
|
206
|
+
if (typeof expected === 'boolean') return boolish(actual) === expected;
|
|
207
|
+
if (typeof expected === 'number') return numberish(actual) === expected;
|
|
208
|
+
return normalizeKey(actual) === normalizeKey(expected);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function buildEmailStatus({
|
|
213
|
+
config,
|
|
214
|
+
values,
|
|
215
|
+
}: EmailStatusBuildInput): EmailStatus {
|
|
216
|
+
const rawStatus = read(values, 'rawStatus');
|
|
217
|
+
const rawScore = numberish(read(values, 'rawScore'));
|
|
218
|
+
const valid = boolish(read(values, 'valid'));
|
|
219
|
+
const catchAll = boolish(read(values, 'catchAll'));
|
|
220
|
+
const disposable = boolish(read(values, 'disposable'));
|
|
221
|
+
const abuse = boolish(read(values, 'abuse'));
|
|
222
|
+
const spamtrap = boolish(read(values, 'spamtrap'));
|
|
223
|
+
const suspect = boolish(read(values, 'suspect'));
|
|
224
|
+
const rawKey = normalizeKey(rawStatus);
|
|
225
|
+
const mapped =
|
|
226
|
+
config.rules?.find((rule) => matchesRule(rule, values)) ??
|
|
227
|
+
entryForStatus(rawKey, config.statusMap) ??
|
|
228
|
+
entryForStatus(valid == null ? null : String(valid), config.statusMap);
|
|
229
|
+
|
|
230
|
+
const status =
|
|
231
|
+
mapped?.status ??
|
|
232
|
+
(disposable
|
|
233
|
+
? 'disposable'
|
|
234
|
+
: abuse
|
|
235
|
+
? 'abuse'
|
|
236
|
+
: spamtrap
|
|
237
|
+
? 'spamtrap'
|
|
238
|
+
: catchAll
|
|
239
|
+
? 'catch_all'
|
|
240
|
+
: valid === true
|
|
241
|
+
? 'valid'
|
|
242
|
+
: valid === false
|
|
243
|
+
? 'invalid'
|
|
244
|
+
: 'unknown');
|
|
245
|
+
const defaultVerdict: EmailStatusVerdict =
|
|
246
|
+
status === 'valid'
|
|
247
|
+
? 'send'
|
|
248
|
+
: status === 'valid_catch_all'
|
|
249
|
+
? 'send_with_caution'
|
|
250
|
+
: status === 'catch_all'
|
|
251
|
+
? 'verify_next'
|
|
252
|
+
: status === 'unknown'
|
|
253
|
+
? 'hold'
|
|
254
|
+
: 'drop';
|
|
255
|
+
const verdict = mapped?.verdict ?? defaultVerdict;
|
|
256
|
+
const verified =
|
|
257
|
+
mapped?.verified ??
|
|
258
|
+
(status === 'valid' || status === 'valid_catch_all' || verdict === 'send');
|
|
259
|
+
const reasons = [
|
|
260
|
+
mapped?.reason,
|
|
261
|
+
catchAll ? 'catch_all_domain' : null,
|
|
262
|
+
mxClass(read(values, 'mxProvider'), read(values, 'mxRecord')) ===
|
|
263
|
+
'security_gateway'
|
|
264
|
+
? 'security_gateway_mx'
|
|
265
|
+
: null,
|
|
266
|
+
suspect ? 'provider_marked_suspect' : null,
|
|
267
|
+
].filter((reason): reason is string => typeof reason === 'string');
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
verdict,
|
|
271
|
+
status,
|
|
272
|
+
verified,
|
|
273
|
+
confidence: rawScore,
|
|
274
|
+
reasons,
|
|
275
|
+
signals: {
|
|
276
|
+
catch_all: catchAll,
|
|
277
|
+
deliverability: deliverability(read(values, 'deliverability')),
|
|
278
|
+
mx_class: mxClass(read(values, 'mxProvider'), read(values, 'mxRecord')),
|
|
279
|
+
mx_provider: stringish(read(values, 'mxProvider')),
|
|
280
|
+
mx_record: stringish(read(values, 'mxRecord')),
|
|
281
|
+
fraud_score: numberish(read(values, 'fraudScore')),
|
|
282
|
+
disposable,
|
|
283
|
+
role_based: boolish(read(values, 'roleBased')),
|
|
284
|
+
free_email: boolish(read(values, 'freeEmail')),
|
|
285
|
+
abuse,
|
|
286
|
+
spamtrap,
|
|
287
|
+
suspect,
|
|
288
|
+
valid,
|
|
289
|
+
},
|
|
290
|
+
provider: {
|
|
291
|
+
name: config.provider,
|
|
292
|
+
raw_status:
|
|
293
|
+
typeof rawStatus === 'string' ||
|
|
294
|
+
typeof rawStatus === 'boolean' ||
|
|
295
|
+
typeof rawStatus === 'number'
|
|
296
|
+
? rawStatus
|
|
297
|
+
: null,
|
|
298
|
+
raw_score: rawScore,
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
}
|
|
@@ -9,7 +9,15 @@ export type {
|
|
|
9
9
|
ToolResultTargetMetadata,
|
|
10
10
|
ToolResultTargetAccessor,
|
|
11
11
|
} from './tool-result-types';
|
|
12
|
-
|
|
12
|
+
export type {
|
|
13
|
+
EmailDeliverability,
|
|
14
|
+
EmailMxClass,
|
|
15
|
+
EmailStatus,
|
|
16
|
+
EmailStatusValue,
|
|
17
|
+
EmailStatusVerdict,
|
|
18
|
+
} from './email-status';
|
|
19
|
+
|
|
20
|
+
import { buildEmailStatus } from './email-status';
|
|
13
21
|
import type {
|
|
14
22
|
SerializedToolExecuteResult,
|
|
15
23
|
ToolExecuteResult,
|
|
@@ -302,6 +310,50 @@ function findFirstTargetByPath(
|
|
|
302
310
|
return null;
|
|
303
311
|
}
|
|
304
312
|
|
|
313
|
+
function firstValueForPaths(
|
|
314
|
+
result: unknown,
|
|
315
|
+
paths: readonly string[] | undefined,
|
|
316
|
+
): ToolResultTargetMetadata | null {
|
|
317
|
+
return findFirstTargetByPath(result, paths);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function buildEmailStatusTarget(
|
|
321
|
+
result: unknown,
|
|
322
|
+
descriptor: ToolResultExtractorDescriptor,
|
|
323
|
+
): ToolResultTargetMetadata | null {
|
|
324
|
+
const config = descriptor.emailStatus;
|
|
325
|
+
if (!config) return null;
|
|
326
|
+
const values: Record<string, unknown> = {};
|
|
327
|
+
const pathSets: Record<string, readonly string[] | undefined> = {
|
|
328
|
+
rawStatus: config.rawStatus,
|
|
329
|
+
rawScore: config.rawScore,
|
|
330
|
+
valid: config.valid,
|
|
331
|
+
deliverability: config.deliverability,
|
|
332
|
+
catchAll: config.catchAll,
|
|
333
|
+
mxProvider: config.mxProvider,
|
|
334
|
+
mxRecord: config.mxRecord,
|
|
335
|
+
fraudScore: config.fraudScore,
|
|
336
|
+
disposable: config.disposable,
|
|
337
|
+
roleBased: config.roleBased,
|
|
338
|
+
freeEmail: config.freeEmail,
|
|
339
|
+
abuse: config.abuse,
|
|
340
|
+
spamtrap: config.spamtrap,
|
|
341
|
+
suspect: config.suspect,
|
|
342
|
+
};
|
|
343
|
+
let firstPath: string | null = null;
|
|
344
|
+
for (const [name, paths] of Object.entries(pathSets)) {
|
|
345
|
+
const match = firstValueForPaths(result, paths);
|
|
346
|
+
if (!match) continue;
|
|
347
|
+
values[name] = match.value;
|
|
348
|
+
firstPath ??= match.path;
|
|
349
|
+
}
|
|
350
|
+
if (!firstPath) return null;
|
|
351
|
+
return {
|
|
352
|
+
path: firstPath,
|
|
353
|
+
value: buildEmailStatus({ config, values }),
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
305
357
|
function findFirstTargetByKey(
|
|
306
358
|
result: unknown,
|
|
307
359
|
target: string,
|
|
@@ -507,6 +559,11 @@ function buildTargets(
|
|
|
507
559
|
): Record<string, ToolResultTargetMetadata> {
|
|
508
560
|
const targets: Record<string, ToolResultTargetMetadata> = {};
|
|
509
561
|
for (const [target, descriptor] of Object.entries(extractors ?? {})) {
|
|
562
|
+
const emailStatusTarget = buildEmailStatusTarget(result, descriptor);
|
|
563
|
+
if (emailStatusTarget) {
|
|
564
|
+
targets[target] = emailStatusTarget;
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
510
567
|
const fromExtractor = findFirstTargetByPath(result, descriptor.paths);
|
|
511
568
|
if (!fromExtractor) continue;
|
|
512
569
|
const transformed = coerceToEnum(
|