@sonordev/contracts 2.7.1 → 2.9.0
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/CHANGELOG.md +34 -0
- package/README.md +315 -0
- package/dist/broadcast-layouts.d.ts +133 -0
- package/dist/broadcast-layouts.js +83 -0
- package/dist/broadcast-layouts.mjs +56 -0
- package/dist/business-claims.d.ts +456 -0
- package/dist/business-claims.js +1378 -0
- package/dist/business-claims.mjs +1340 -0
- package/dist/outreach.d.ts +267 -0
- package/dist/outreach.js +351 -0
- package/dist/outreach.mjs +328 -0
- package/dist/page-titles.d.ts +80 -0
- package/dist/page-titles.js +124 -0
- package/dist/page-titles.mjs +120 -0
- package/dist/publication-branding.d.ts +143 -0
- package/dist/publication-branding.js +350 -0
- package/dist/publication-branding.mjs +328 -0
- package/dist/publication-charter.d.ts +14 -0
- package/package.json +28 -2
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
/** Bumped when a stored shape here (OutboundClaimReview) changes incompatibly. */
|
|
2
|
+
export declare const BUSINESS_CLAIMS_CONTRACT_VERSION = 1;
|
|
3
|
+
/**
|
|
4
|
+
* Decode HTML character references in one pass: named ones (the table
|
|
5
|
+
* above), decimal (') and hex ('). One pass, so a double-encoded
|
|
6
|
+
* "'" comes back as the literal "'" it was written as, never as
|
|
7
|
+
* "'". An invalid code point (zero, a surrogate, past U+10FFFF) is left as
|
|
8
|
+
* written, and   is a plain space.
|
|
9
|
+
*/
|
|
10
|
+
export declare function decodeHtmlEntities(text: string): string;
|
|
11
|
+
/** A quote shorter than this (after normalizeForQuote) grounds nothing. */
|
|
12
|
+
export declare const MIN_QUOTE_CHARS = 12;
|
|
13
|
+
/**
|
|
14
|
+
* Text as a quote is compared: entities decoded (named, decimal and hex),
|
|
15
|
+
* compatibility forms folded (… is ...), invisible characters removed, curly
|
|
16
|
+
* quotes straight, every dash a hyphen with no space around it, "$ 40" as
|
|
17
|
+
* "$40", whitespace collapsed, lowercase. Applied to both sides, so a quote
|
|
18
|
+
* copied off the page matches however the page spelled its punctuation, and
|
|
19
|
+
* nothing else does.
|
|
20
|
+
*/
|
|
21
|
+
export declare function normalizeForQuote(text: unknown): string;
|
|
22
|
+
/**
|
|
23
|
+
* The form every claim matcher reads: normalizeForQuote (NFKC, invisible
|
|
24
|
+
* characters removed, entities decoded, lowercase). Use it on a raw string
|
|
25
|
+
* you want to compare the way checkOutboundCopy does.
|
|
26
|
+
*/
|
|
27
|
+
export declare function normalizeMatchable(text: unknown): string;
|
|
28
|
+
/**
|
|
29
|
+
* Where `needle` occurs in `haystack` as a whole span, or -1: never the start
|
|
30
|
+
* or the tail of a longer number ("$90" inside "$900", "$4,000" inside
|
|
31
|
+
* "$4,000,000", "200" inside "$1,200") and never a word cut in half. Both
|
|
32
|
+
* sides are already normalized (normalizeForQuote).
|
|
33
|
+
*/
|
|
34
|
+
export declare function wholeSpanAt(haystack: string, needle: string): number;
|
|
35
|
+
/**
|
|
36
|
+
* Whether `quote` appears in the source text, verbatim after normalizeForQuote
|
|
37
|
+
* and as a whole span (wholeSpanAt: a quote that stops in the middle of a
|
|
38
|
+
* number or a word isn't the source's words). `normalizedSource` is already
|
|
39
|
+
* normalized (each source is normalized once). A quote shorter than
|
|
40
|
+
* `minChars` grounds nothing. A price keeps its punctuation here, so
|
|
41
|
+
* "$4,000" never matches "4 000".
|
|
42
|
+
*/
|
|
43
|
+
export declare function quoteFoundIn(quote: unknown, normalizedSource: string, minChars?: number): boolean;
|
|
44
|
+
/** Dollar amounts in normalized text: "$4,000" is 4000, "$4k" is 4000, "$12.50" is 12.5. */
|
|
45
|
+
export declare function moneyAmounts(normalized: string): number[];
|
|
46
|
+
/**
|
|
47
|
+
* Words just before a price that make it a floor, a ceiling or one end of a
|
|
48
|
+
* range. A dash or "to" counts only after a number ("$1,000-$3,000"), never
|
|
49
|
+
* after a name ("Essential - $1,000" is a price list, not a range).
|
|
50
|
+
*/
|
|
51
|
+
export declare const PRICE_RANGE_BEFORE: RegExp;
|
|
52
|
+
/** Just after a price: "+", or the other end of a range. */
|
|
53
|
+
export declare const PRICE_RANGE_AFTER: RegExp;
|
|
54
|
+
/**
|
|
55
|
+
* An offer's price checked against its quote: the price must appear in the
|
|
56
|
+
* quote exactly as written (after normalizeForQuote), as a whole span
|
|
57
|
+
* (wholeSpanAt: "$90" never passes against "$900"), and be a dollar amount or
|
|
58
|
+
* "free". A price that's one end of what the quote says ("$2,000" out of
|
|
59
|
+
* "$2,000 - $9,000", "$500" out of "starting at $500", "$25,000" out of
|
|
60
|
+
* "$25,000+") fails: the display has to carry the whole span. Returns min/max
|
|
61
|
+
* read from the amounts, or null when the price fails.
|
|
62
|
+
*/
|
|
63
|
+
export declare function checkQuotedPrice(display: unknown, quote: unknown): {
|
|
64
|
+
min?: number;
|
|
65
|
+
max?: number;
|
|
66
|
+
} | null;
|
|
67
|
+
/** The numbers written in normalized text, commas and trailing punctuation dropped: "1,200." is "1200". */
|
|
68
|
+
export declare function numbersIn(normalized: string): string[];
|
|
69
|
+
/** Every number written in `value` also appears in the quote: a claim can't carry a figure its source doesn't. */
|
|
70
|
+
export declare function numbersGrounded(value: unknown, quote: unknown): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* House style: no em or en dashes in anything a person reads. Takes them out
|
|
73
|
+
* of generated copy and stored values.
|
|
74
|
+
*
|
|
75
|
+
* - A range keeps a hyphen: a number range ("3-4 beds"), a price range
|
|
76
|
+
* ("$2,000 - $9,000"), a time range ("9:00 AM - 6:00 PM"), and an unspaced
|
|
77
|
+
* en dash between words ("Mon-Fri").
|
|
78
|
+
* - Any other em or en dash between words becomes a comma ("great light, call
|
|
79
|
+
* me").
|
|
80
|
+
*
|
|
81
|
+
* Never run a verbatim quote through this: a quote stays as it was written.
|
|
82
|
+
*/
|
|
83
|
+
export declare function removeDashes(text: string): string;
|
|
84
|
+
/** Whether a sentence reads as a note about how the copy was written (its instructions or evidence), not copy for the reader. */
|
|
85
|
+
export declare function isWriterNote(text: unknown): boolean;
|
|
86
|
+
export type OutboundFormat = 'html' | 'text';
|
|
87
|
+
export type OutboundChannel = 'email' | 'sms';
|
|
88
|
+
/**
|
|
89
|
+
* The text a reader sees in an email body, one line per block.
|
|
90
|
+
*
|
|
91
|
+
* - html: scripts, styles and comments dropped; source whitespace collapsed;
|
|
92
|
+
* a line break at br, p, li, div, headings, rows and other blocks; a link
|
|
93
|
+
* `<a href="u">t</a>` as "t (u)" unless t is the address itself; an
|
|
94
|
+
* image's alt and title text, and any other element's title, on their own
|
|
95
|
+
* line; inline tags removed without a space (so "guaran<b></b>teed" is one
|
|
96
|
+
* word); entities decoded once; spaces collapsed and empty lines dropped.
|
|
97
|
+
* - text: entities decoded once, nothing else.
|
|
98
|
+
*/
|
|
99
|
+
export declare function outboundPlainText(body: unknown, format: OutboundFormat): string;
|
|
100
|
+
/**
|
|
101
|
+
* Who wrote the copy, stamped once when the row is created
|
|
102
|
+
* (payload.claim_author) and never derived from who filed it:
|
|
103
|
+
* - signal: Signal's own generators, however they were asked;
|
|
104
|
+
* - agent: an MCP agent's own words;
|
|
105
|
+
* - operator: a fixed template or a person's own copy.
|
|
106
|
+
*/
|
|
107
|
+
export type OutboundAuthor = 'signal' | 'agent' | 'operator';
|
|
108
|
+
export declare const OUTBOUND_AUTHORS: ReadonlyArray<OutboundAuthor>;
|
|
109
|
+
/** Refused outright (400 outbound_copy_refused), at filing and again at send. */
|
|
110
|
+
export type OutboundBlockCode = 'empty_subject' | 'empty_body' | 'unrendered_token' | 'platform_banned_claim' | 'project_banned_claim' | 'forbidden_phrase' | 'price_mismatch' | 'mixed_script' | 'dash' | 'unsafe_markup' | 'prompt_control_token';
|
|
111
|
+
/** Needs a person's look (or, for an agent's own copy, is reported back): forces propose. */
|
|
112
|
+
export type OutboundFlagCode = 'price_unverifiable' | 'unsupported_number' | 'citation_invalid' | 'citation_mismatch' | 'citation_wrong_source' | 'banned_claim_negated' | 'writer_note' | 'number_from_research' | 'audit_flag' | 'audit_not_run';
|
|
113
|
+
/** House style. Never blocks and never forces propose. */
|
|
114
|
+
export type OutboundStyleCode = 'uncontracted' | 'dash' | 'first_person_firm';
|
|
115
|
+
export type OutboundFindingCode = OutboundBlockCode | OutboundFlagCode | OutboundStyleCode;
|
|
116
|
+
export interface OutboundFinding {
|
|
117
|
+
/** Stable: FNV-1a of the code, the normalized sentence and the normalized match. A new sentence is a new id. */
|
|
118
|
+
id: string;
|
|
119
|
+
code: OutboundFindingCode;
|
|
120
|
+
/** The sentence as the reader sees it (entities decoded). */
|
|
121
|
+
sentence: string;
|
|
122
|
+
/** What matched, as normalized for matching. */
|
|
123
|
+
match: string;
|
|
124
|
+
reason: string;
|
|
125
|
+
fix: string;
|
|
126
|
+
/** The Business Brief fact a citation finding is about. */
|
|
127
|
+
fact_id?: string;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* FNV-1a (32-bit) of a string's UTF-16 code units, as 8 lowercase hex digits.
|
|
131
|
+
* The one hash the contracts use for finding ids and fingerprints.
|
|
132
|
+
*/
|
|
133
|
+
export declare function fnv1a(text: string): string;
|
|
134
|
+
/** A finding's id: the same code, sentence and match always give the same id, anywhere. */
|
|
135
|
+
export declare function outboundFindingId(code: string, sentence: string, match: string): string;
|
|
136
|
+
/**
|
|
137
|
+
* Build a finding with its stable id. Signal's gate uses it for the findings
|
|
138
|
+
* only it can make (prompt_control_token, audit_flag, audit_not_run), so
|
|
139
|
+
* every finding's id is computed one way.
|
|
140
|
+
*/
|
|
141
|
+
export declare function makeOutboundFinding(code: OutboundFindingCode, sentence: string, match: string, extra?: {
|
|
142
|
+
reason?: string;
|
|
143
|
+
fix?: string;
|
|
144
|
+
fact_id?: string;
|
|
145
|
+
}): OutboundFinding;
|
|
146
|
+
export type PlatformBannedClaimId = 'guaranteed_ranking' | 'number_one_on_google' | 'guaranteed_results' | 'hundred_percent_guaranteed' | 'risk_free' | 'fake_scarcity' | 'google_endorsed' | 'pay_nothing_guarantee';
|
|
147
|
+
/** Claims no outbound copy may make, for any business. Generic on purpose: a project adds its own in its Business Brief. */
|
|
148
|
+
export declare const PLATFORM_BANNED_CLAIMS: ReadonlyArray<{
|
|
149
|
+
id: PlatformBannedClaimId;
|
|
150
|
+
description: string;
|
|
151
|
+
}>;
|
|
152
|
+
export interface PlatformClaimHit {
|
|
153
|
+
id: PlatformBannedClaimId;
|
|
154
|
+
/** Offset into the normalized text. */
|
|
155
|
+
index: number;
|
|
156
|
+
match: string;
|
|
157
|
+
/** True when a negation directly governing its guarantee or promise takes it back ("We can't guarantee rankings"). */
|
|
158
|
+
negated: boolean;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Every PLATFORM_BANNED_CLAIMS hit in a text. A negation suppresses a hit
|
|
162
|
+
* only when it directly governs the guarantee or promise ("can't
|
|
163
|
+
* guarantee", "no one can promise"), or the verb a hit starts with ("we
|
|
164
|
+
* can't get you to the top of Google", "not endorsed by Google"); "not
|
|
165
|
+
* only", "no other", "don't settle", "don't wait" and "don't miss" never do.
|
|
166
|
+
* A suppressed hit comes back with `negated: true`: still worth a person's
|
|
167
|
+
* look. A #1 or top-of-Google hit needs a verb that puts someone there, so
|
|
168
|
+
* an observation ("you're near the top of Google") isn't one.
|
|
169
|
+
*/
|
|
170
|
+
export declare function findPlatformBannedClaims(text: unknown): PlatformClaimHit[];
|
|
171
|
+
export interface PhraseHit {
|
|
172
|
+
/** The phrase as you passed it. */
|
|
173
|
+
phrase: string;
|
|
174
|
+
/** Offset into the normalized text. */
|
|
175
|
+
index: number;
|
|
176
|
+
/** The phrase as normalized. */
|
|
177
|
+
match: string;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Literal phrases found as whole spans, case-insensitive, after both sides
|
|
181
|
+
* are normalized (entities, invisible characters, quotes and dashes). The one
|
|
182
|
+
* matcher for a project's banned claims and a narrative's forbidden phrases.
|
|
183
|
+
*/
|
|
184
|
+
export declare function findPhrases(text: unknown, phrases: ReadonlyArray<unknown> | null | undefined): PhraseHit[];
|
|
185
|
+
export interface SpanHit {
|
|
186
|
+
/** Offset into the text the finder read (normalized for findUnrenderedTokens, as given otherwise). */
|
|
187
|
+
index: number;
|
|
188
|
+
match: string;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Template text that never got filled in: {{x}}, {%x%}, [[x]], %TOKEN%,
|
|
192
|
+
* ${x}, {x}, unresolved {a|b} spintax, a bracketed name or company
|
|
193
|
+
* placeholder ("[First Name]"), a greeting to undefined or null, and
|
|
194
|
+
* "[object Object]". Reads the normalized text.
|
|
195
|
+
*/
|
|
196
|
+
export declare function findUnrenderedTokens(text: unknown): SpanHit[];
|
|
197
|
+
/**
|
|
198
|
+
* Dashes the house voice never uses: an em dash anywhere, and an en dash
|
|
199
|
+
* between words. An en dash inside a number, time or $ range ("3–4",
|
|
200
|
+
* "9:00 AM – 6:00 PM", "$2,000 – $9,000") or unspaced between two
|
|
201
|
+
* words ("Mon–Fri") is a range, and a hyphen is never a dash. Reads the
|
|
202
|
+
* text as given (decode entities first), so it can tell the dashes apart.
|
|
203
|
+
*/
|
|
204
|
+
export declare function findDashes(text: unknown): Array<SpanHit & {
|
|
205
|
+
kind: 'em' | 'en';
|
|
206
|
+
}>;
|
|
207
|
+
/**
|
|
208
|
+
* Words that mix Latin letters with Cyrillic or Greek ones ("Gооgle"
|
|
209
|
+
* with two Cyrillic o's), which only look like ordinary words. A word that's
|
|
210
|
+
* all Cyrillic or all Greek is left alone.
|
|
211
|
+
*/
|
|
212
|
+
export declare function findMixedScript(text: unknown): SpanHit[];
|
|
213
|
+
/**
|
|
214
|
+
* Markup an agent's HTML email may not carry: <style>, <script>, on*=
|
|
215
|
+
* event handlers, CSS content: in a style attribute, embedded frames or
|
|
216
|
+
* objects, and javascript: links. Reads the HTML as written.
|
|
217
|
+
*/
|
|
218
|
+
export declare function findUnsafeMarkup(html: unknown): SpanHit[];
|
|
219
|
+
export type PriceShape = 'exact' | 'floor' | 'ceiling' | 'range';
|
|
220
|
+
export interface PriceMention {
|
|
221
|
+
/** Offset into the normalized text. */
|
|
222
|
+
index: number;
|
|
223
|
+
/** The whole mention, floor or ceiling words and the range included ("starting at $4,000", "$4,000-$15,000"). */
|
|
224
|
+
match: string;
|
|
225
|
+
shape: PriceShape;
|
|
226
|
+
min: number;
|
|
227
|
+
max: number;
|
|
228
|
+
/** The dollar amounts in it, as written. */
|
|
229
|
+
amounts: string[];
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Every price in a text, with its shape: exact ("$4,500"), a floor
|
|
233
|
+
* ("starting at $4,000", "$25,000+"), a ceiling ("up to $900") or a range
|
|
234
|
+
* ("$4,000 - $15,000", "between $4k and $15k"). Reads the normalized text.
|
|
235
|
+
*/
|
|
236
|
+
export declare function findPriceMentions(text: unknown): PriceMention[];
|
|
237
|
+
export interface ClaimNumber {
|
|
238
|
+
/** Offset into the normalized text. */
|
|
239
|
+
index: number;
|
|
240
|
+
/** The number as written, with its % or x ("45%", "3x", "120", "$300"). */
|
|
241
|
+
match: string;
|
|
242
|
+
/** Its digits, commas dropped ("1200", "4.9"): what grounding compares. */
|
|
243
|
+
value: string;
|
|
244
|
+
kind: 'percent' | 'multiplier' | 'count' | 'money';
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* The claim numbers in a text (see ClaimNumber) that none of `sources`
|
|
248
|
+
* carries. Times, dates, phones, years, addresses and the digits of a web or
|
|
249
|
+
* email address aren't claims and are skipped. Reads the normalized text.
|
|
250
|
+
*/
|
|
251
|
+
export declare function findUnsupportedNumbers(text: unknown, sources?: ReadonlyArray<unknown>): ClaimNumber[];
|
|
252
|
+
export type ClaimSubject = 'sender' | 'recipient' | 'unknown';
|
|
253
|
+
/**
|
|
254
|
+
* Whose claim the number at `index` is: the nearest pronoun or name before
|
|
255
|
+
* it in the same clause (a clause ends at , ; : or a dash, and at "and" or
|
|
256
|
+
* "but"). We, our, us (and I, me, my) and the sender's names mean the sender;
|
|
257
|
+
* you, your and the recipient's names mean the recipient. A bare I or we
|
|
258
|
+
* followed by an observation verb ("I noticed", "we measured") names neither.
|
|
259
|
+
* 'unknown' when the clause names neither: a caller treats that as the
|
|
260
|
+
* sender's unless the number is in the research about the recipient.
|
|
261
|
+
*/
|
|
262
|
+
export declare function claimSubjectOf(sentence: string, index: number, names?: {
|
|
263
|
+
senderNames?: ReadonlyArray<string> | null;
|
|
264
|
+
recipientNames?: ReadonlyArray<string> | null;
|
|
265
|
+
}): ClaimSubject;
|
|
266
|
+
/**
|
|
267
|
+
* "I" with the firm's work: build, built, design, offer, work with, put
|
|
268
|
+
* together, deliver ("I built your competitor's site"). The firm's work is
|
|
269
|
+
* "we"; "I" is for what the sender noticed, read or thinks. Quotes are left
|
|
270
|
+
* alone.
|
|
271
|
+
*/
|
|
272
|
+
export declare function findFirstPersonFirm(text: unknown): SpanHit[];
|
|
273
|
+
/** A claim the writer made and what backs it: a Business Brief fact, or the research about the recipient. */
|
|
274
|
+
export type OutboundClaim = {
|
|
275
|
+
text: string;
|
|
276
|
+
fact_id?: string | null;
|
|
277
|
+
} | {
|
|
278
|
+
text: string;
|
|
279
|
+
evidence: 'research';
|
|
280
|
+
};
|
|
281
|
+
/** A price as the Business Brief publishes it ("$4,000 - $15,000", "Starting at $500 a month"). */
|
|
282
|
+
export interface OutboundPrice {
|
|
283
|
+
display: string;
|
|
284
|
+
fact_id?: string | null;
|
|
285
|
+
}
|
|
286
|
+
/** A Business Brief fact a claim may cite by id. Only a verified one supports anything. */
|
|
287
|
+
export interface OutboundFact {
|
|
288
|
+
id: string;
|
|
289
|
+
text: string;
|
|
290
|
+
verified: boolean;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* The project's rules, pre-resolved by the caller from its Business Brief and
|
|
294
|
+
* narrative, so no brain shape lives here. Frozen into a review, they're what
|
|
295
|
+
* the send side re-checks the BLOCK set against.
|
|
296
|
+
*/
|
|
297
|
+
export interface OutboundRules {
|
|
298
|
+
/** Published prices. */
|
|
299
|
+
prices: ReadonlyArray<OutboundPrice>;
|
|
300
|
+
/** True once the brief publishes a price: then a $ amount that matches none of them is refused, not flagged. */
|
|
301
|
+
prices_known: boolean;
|
|
302
|
+
/** The project's banned claims, matched literally. */
|
|
303
|
+
banned: ReadonlyArray<string>;
|
|
304
|
+
/** The narrative's forbidden phrases, matched literally. */
|
|
305
|
+
forbidden: ReadonlyArray<string>;
|
|
306
|
+
facts?: ReadonlyArray<OutboundFact>;
|
|
307
|
+
/** The business's own names, for telling a claim about the sender from one about the recipient. */
|
|
308
|
+
sender_names?: ReadonlyArray<string>;
|
|
309
|
+
}
|
|
310
|
+
export interface OutboundCopy {
|
|
311
|
+
subject?: string | null;
|
|
312
|
+
body: string;
|
|
313
|
+
format: OutboundFormat;
|
|
314
|
+
channel: OutboundChannel;
|
|
315
|
+
}
|
|
316
|
+
export interface OutboundEvidence {
|
|
317
|
+
/** Primary evidence about the recipient (their homepage text, measured scores). The only research that grounds anything. */
|
|
318
|
+
primary?: string | null;
|
|
319
|
+
/** Earlier guesses (a cached model audit). Never grounds a claim; for the model audit's fence only. */
|
|
320
|
+
hints?: string | null;
|
|
321
|
+
}
|
|
322
|
+
export interface OutboundCheckContext {
|
|
323
|
+
author: OutboundAuthor;
|
|
324
|
+
/** Price, banned and forbidden rules run only when set. */
|
|
325
|
+
rules?: OutboundRules | null;
|
|
326
|
+
claims?: ReadonlyArray<OutboundClaim> | null;
|
|
327
|
+
evidence?: OutboundEvidence | null;
|
|
328
|
+
/**
|
|
329
|
+
* cited: a number must be backed by a claim the writer cited (Signal's copy).
|
|
330
|
+
* present: a number may also be backed by a verified fact or the primary
|
|
331
|
+
* evidence that carries it (an agent's own copy). Defaults to present for
|
|
332
|
+
* an agent and cited otherwise.
|
|
333
|
+
*/
|
|
334
|
+
numbers?: 'cited' | 'present';
|
|
335
|
+
recipient?: {
|
|
336
|
+
names?: ReadonlyArray<string> | null;
|
|
337
|
+
} | null;
|
|
338
|
+
}
|
|
339
|
+
export interface OutboundCheck {
|
|
340
|
+
/** Refused at filing and again at send. */
|
|
341
|
+
blocking: OutboundFinding[];
|
|
342
|
+
/** Forces propose; a person settles them (an agent's own are reported back). Always empty for operator copy. */
|
|
343
|
+
flags: OutboundFinding[];
|
|
344
|
+
/** House style. Never blocks. */
|
|
345
|
+
style: OutboundFinding[];
|
|
346
|
+
/** The body as the reader sees it (outboundPlainText). */
|
|
347
|
+
plain: string;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* The one outbound checker. Reads the copy as the reader sees it
|
|
351
|
+
* (outboundPlainText, then normalized for matching, entities decoded once),
|
|
352
|
+
* sentence by sentence, and sorts what it finds into:
|
|
353
|
+
*
|
|
354
|
+
* - blocking: empty subject (email), empty body (fewer than 8 letters in an
|
|
355
|
+
* email, none in a text), unrendered tokens, platform banned claims, the
|
|
356
|
+
* project's banned claims and the narrative's forbidden phrases, a price
|
|
357
|
+
* that matches no published one (once prices are known), mixed-script
|
|
358
|
+
* words, and for an agent's own copy dashes and unsafe HTML markup.
|
|
359
|
+
* - flags (never for operator copy): unverifiable prices, unsupported
|
|
360
|
+
* numbers, citations that are unknown, unverified, don't match or come
|
|
361
|
+
* from the wrong source, a banned claim a negation takes back, writer
|
|
362
|
+
* notes, and for an agent's own copy numbers taken from the research.
|
|
363
|
+
* - style: uncontracted forms, dashes (Signal and operator copy) and "I" for
|
|
364
|
+
* the firm's work.
|
|
365
|
+
*
|
|
366
|
+
* Research grounds a number or a price only in a sentence about the
|
|
367
|
+
* recipient (claimSubjectOf); in a sentence about the sender it's
|
|
368
|
+
* citation_wrong_source. A clause that names nobody counts as the
|
|
369
|
+
* recipient's only when the research about them carries that number, and a
|
|
370
|
+
* subject line may repeat a number the body cites that way. Price rules run
|
|
371
|
+
* only when ctx.rules is set. An author it doesn't know is checked as an
|
|
372
|
+
* agent, the strictest set.
|
|
373
|
+
*/
|
|
374
|
+
export declare function checkOutboundCopy(copy: OutboundCopy, ctx: OutboundCheckContext): OutboundCheck;
|
|
375
|
+
export declare const OUTBOUND_AUDIT_STATUSES: readonly ['clean', 'flagged', 'not_run', 'not_needed', 'stale'];
|
|
376
|
+
export type OutboundAuditStatus = (typeof OUTBOUND_AUDIT_STATUSES)[number];
|
|
377
|
+
export interface OutboundClaimAudit {
|
|
378
|
+
status: OutboundAuditStatus;
|
|
379
|
+
[key: string]: unknown;
|
|
380
|
+
}
|
|
381
|
+
/** A person said they checked these flags, at approval. */
|
|
382
|
+
export interface OutboundAcknowledgement {
|
|
383
|
+
by: string;
|
|
384
|
+
at: string;
|
|
385
|
+
/** The route it came through ('dashboard', 'drafts', ...). */
|
|
386
|
+
via: string;
|
|
387
|
+
flag_ids: string[];
|
|
388
|
+
}
|
|
389
|
+
/** An MCP agent stood behind the flags on its own copy. An assertion, never a review: it settles flags only on author agent copy. */
|
|
390
|
+
export interface OutboundAssertion {
|
|
391
|
+
by: string;
|
|
392
|
+
at: string;
|
|
393
|
+
flag_ids: string[];
|
|
394
|
+
}
|
|
395
|
+
/** payload.claim_review: what the checker and the audit found when the copy was filed, and who settled it. */
|
|
396
|
+
export interface OutboundClaimReview {
|
|
397
|
+
version: 1;
|
|
398
|
+
checked_at: string;
|
|
399
|
+
author: OutboundAuthor;
|
|
400
|
+
brain_version: number | null;
|
|
401
|
+
/** The rules the copy was checked against, frozen, so the send side re-checks the same ones. */
|
|
402
|
+
rules: OutboundRules | null;
|
|
403
|
+
flags: OutboundFinding[];
|
|
404
|
+
style: OutboundFinding[];
|
|
405
|
+
audit: OutboundClaimAudit;
|
|
406
|
+
acknowledged: OutboundAcknowledgement | null;
|
|
407
|
+
asserted: OutboundAssertion | null;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* A stored claim review, read strictly. Null means not reviewed: missing,
|
|
411
|
+
* from another version, or malformed anywhere (a caller treats a malformed
|
|
412
|
+
* review exactly like a missing one, never as a clean one).
|
|
413
|
+
*/
|
|
414
|
+
export declare function readOutboundClaimReview(value: unknown): OutboundClaimReview | null;
|
|
415
|
+
/**
|
|
416
|
+
* The flags nobody has settled: in neither acknowledged.flag_ids (a person,
|
|
417
|
+
* at approval) nor, on an agent's own copy only, asserted.flag_ids (the agent
|
|
418
|
+
* standing behind them). Empty means it may go out.
|
|
419
|
+
*/
|
|
420
|
+
export declare function unsettledFlags(review: OutboundClaimReview): OutboundFinding[];
|
|
421
|
+
/**
|
|
422
|
+
* The one wording for a refusal: every fix, numbered, each with the sentence
|
|
423
|
+
* it's about. Contracted, and never an em or en dash (one in a quoted
|
|
424
|
+
* sentence is spelled out as "[em dash]").
|
|
425
|
+
*/
|
|
426
|
+
export declare function outboundRefusalMessage(findings: ReadonlyArray<Pick<OutboundFinding, 'sentence' | 'reason' | 'fix'>>): string;
|
|
427
|
+
export type ReadBackStatus = 'verified' | 'empty' | 'mismatch' | 'unreadable';
|
|
428
|
+
export interface ReadBackVerdict {
|
|
429
|
+
status: ReadBackStatus;
|
|
430
|
+
/** Length of what was read, normalized. */
|
|
431
|
+
body_chars: number;
|
|
432
|
+
/** Length of what was meant to go out, normalized. */
|
|
433
|
+
intended_chars: number;
|
|
434
|
+
/** How much of the intended text, from the first character it stops matching, isn't there. 0 when verified. */
|
|
435
|
+
chars_missing: number;
|
|
436
|
+
/** Links in the intended copy that aren't in what was read. */
|
|
437
|
+
urls_missing: string[];
|
|
438
|
+
}
|
|
439
|
+
/** A body to compare: a string (read as HTML when it has a tag), or the body with its format. */
|
|
440
|
+
export type ReadBackBody = string | {
|
|
441
|
+
body: string | null | undefined;
|
|
442
|
+
format?: OutboundFormat;
|
|
443
|
+
};
|
|
444
|
+
/**
|
|
445
|
+
* Whether what a mailbox says it sent is what was meant to go out. Both sides
|
|
446
|
+
* are read as the reader sees them and normalized, so markup, entities,
|
|
447
|
+
* quotes and whitespace don't count.
|
|
448
|
+
*
|
|
449
|
+
* - verified: the whole intended text is in what was read (a footer or a
|
|
450
|
+
* quoted header around it is fine), and so is every link it carries.
|
|
451
|
+
* - empty: what was read has fewer than 8 letters.
|
|
452
|
+
* - mismatch: anything else, with how much of the intended text is missing
|
|
453
|
+
* (from the first point it stops matching) and which links are.
|
|
454
|
+
* - unreadable: nothing could be read.
|
|
455
|
+
*/
|
|
456
|
+
export declare function readBackVerdict(intended: ReadBackBody, read: ReadBackBody | null | undefined): ReadBackVerdict;
|