@extrovert.dev/mcp 0.1.0-pre.3
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/LICENSE +21 -0
- package/README.md +313 -0
- package/dist/bin.d.ts +13 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +89 -0
- package/dist/bin.js.map +1 -0
- package/dist/client.d.ts +956 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +1354 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +51 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +53 -0
- package/dist/config.js.map +1 -0
- package/dist/contract.d.ts +62 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +78 -0
- package/dist/contract.js.map +1 -0
- package/dist/extract.d.ts +25 -0
- package/dist/extract.d.ts.map +1 -0
- package/dist/extract.js +131 -0
- package/dist/extract.js.map +1 -0
- package/dist/fixtures.d.ts +676 -0
- package/dist/fixtures.d.ts.map +1 -0
- package/dist/fixtures.js +2685 -0
- package/dist/fixtures.js.map +1 -0
- package/dist/http.d.ts +18 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +124 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +20 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +36 -0
- package/dist/server.js.map +1 -0
- package/dist/stdio.d.ts +8 -0
- package/dist/stdio.d.ts.map +1 -0
- package/dist/stdio.js +22 -0
- package/dist/stdio.js.map +1 -0
- package/dist/tools.d.ts +27 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +2752 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +1037 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +83 -0
- package/dist/types.js.map +1 -0
- package/package.json +72 -0
|
@@ -0,0 +1,676 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offline fixture store.
|
|
3
|
+
*
|
|
4
|
+
* Every method here maps 1:1 onto a REST call in `client.ts`. Live MCP sessions
|
|
5
|
+
* use the Go REST API by default; this store is used for tests and offline demos
|
|
6
|
+
* when `EXTROVERT_MOCK=1`.
|
|
7
|
+
*
|
|
8
|
+
* The store is intentionally stateful within a process: creating an inbox,
|
|
9
|
+
* sending mail, and waiting for a reply all mutate the same in-memory data, so
|
|
10
|
+
* the example agent flow (redeem -> create_inbox -> send -> wait_for_email)
|
|
11
|
+
* produces coherent results offline.
|
|
12
|
+
*/
|
|
13
|
+
import type { Attachment, AttachmentDownload, AttachmentInput, BatchUpdateResult, Category, ContactListDirection, ContactListEntry, ContactListKind, DeleteResult, Domain, DomainOffboard, EnrollmentResult, GraduationStatus, Inbox, Job, KeyTier, MailboxCredentials, Message, Page, Review, ReviewDecisionContext, ReviewerDecisionResult, ReviewPolicy, RiskDial, CategoryPacingState, ReviewEventsResult, ReviewFeedback, ReviewTurn, Rule, RuleAuditEntry, ProblemField, ScanBacklogStatus, SendEmailResult, SendResult, SubmitForReviewResult, SuppressionEntry, SuppressionPrecheck, ReputationRollup, ReputationFinding, ListDeliverabilityFindingsInput, Thread, ThreadDetail, SignUpResult, VerifyResult, WaitForEmailResult, Webhook, WebhookEvent, WhoAmI } from "./types.js";
|
|
14
|
+
import type { AckReviewEventInput, GetRuleAuditInput, GetRulesInput, InboxMetadataPatch, ListReviewEventsInput, ListReviewsInput, PostReviewChatInput, ProposeCategoryInput, RestampReviewInput, ReviewerDecideInput, SaveRuleInput, SubmitForReviewInput, SubmitForwardForReviewInput, SubmitRevisionInput, SubmitReplyForReviewInput, UpdateCategoryInput, WaitForReviewEventInput } from "./client.js";
|
|
15
|
+
/**
|
|
16
|
+
* The recipient the mock seeds an active org-scope suppression for, so the
|
|
17
|
+
* suppression reads (check/list/revoke) and the `recipient_suppressed`
|
|
18
|
+
* send-rejection path have deterministic data offline.
|
|
19
|
+
*/
|
|
20
|
+
export declare const SEEDED_SUPPRESSED_RECIPIENT = "unsubscribed@example.com";
|
|
21
|
+
/**
|
|
22
|
+
* The recipient whose delivery ALWAYS fails at the mock's outbound-provider boundary, so the
|
|
23
|
+
* `approved -> failed` edge and its terminal `send_failed` nudge are drivable
|
|
24
|
+
* offline. Without it the mock could only ever demonstrate the happy path, and a
|
|
25
|
+
* drain loop that never sees `send_failed` is a drain loop nobody proved
|
|
26
|
+
* terminates on failure — which is exactly how the missing terminal nudges
|
|
27
|
+
* survived unnoticed.
|
|
28
|
+
*/
|
|
29
|
+
export declare const SEEDED_SEND_FAILURE_RECIPIENT = "bounce@example.com";
|
|
30
|
+
export declare class FixtureStore {
|
|
31
|
+
private inboxes;
|
|
32
|
+
private messages;
|
|
33
|
+
/** message id -> stored attachments (mock mirror of the real MIME parts). */
|
|
34
|
+
private attachments;
|
|
35
|
+
/** webhook id -> registered webhook (mock mirror of extrovert_webhooks). */
|
|
36
|
+
private webhooks;
|
|
37
|
+
/** entry id -> contact-list entry (mock mirror of extrovert_contact_lists). */
|
|
38
|
+
private contactLists;
|
|
39
|
+
/** domain name -> onboarded domain (mock mirror of extrovert_domains). */
|
|
40
|
+
private domains;
|
|
41
|
+
/** job id -> async job status (mock mirror of extrovert_jobs; currently only
|
|
42
|
+
* the domain-offboard teardown enqueues one). */
|
|
43
|
+
private jobs;
|
|
44
|
+
/** suppression id -> recipient opt-out row (mock mirror of extrovert_suppressions). */
|
|
45
|
+
private suppressions;
|
|
46
|
+
/** review id -> review request (mock mirror of extrovert_review_requests). */
|
|
47
|
+
private reviews;
|
|
48
|
+
/** review id -> append-only thread turns (mock mirror of the turn log). */
|
|
49
|
+
private reviewTurns;
|
|
50
|
+
/**
|
|
51
|
+
* review id -> reviewer hand-back count (M8 Slice B circuit breaker (a)). The wire
|
|
52
|
+
* Review shape doesn't carry hop_count, so the mock tracks it here to surface the
|
|
53
|
+
* max_hops breaker on the decision context + reviewer_decide.
|
|
54
|
+
*/
|
|
55
|
+
private reviewHopCounts;
|
|
56
|
+
/**
|
|
57
|
+
* review id -> durable nudges for that review (mock mirror of
|
|
58
|
+
* extrovert_review_nudges), oldest-first with a per-review monotonic seq. The
|
|
59
|
+
* authoritative liveness queue (spec §4.5) the agent drains/acks.
|
|
60
|
+
*/
|
|
61
|
+
private reviewEvents;
|
|
62
|
+
/** review id -> the agent's last-acked seq (the per-(agent, review) cursor). */
|
|
63
|
+
private reviewEventCursors;
|
|
64
|
+
/** review id -> the thread a queued reply/forward delivers into (materialized at submit). */
|
|
65
|
+
private reviewThreads;
|
|
66
|
+
/** review id -> the opaque parent message id a queued reply threads to. */
|
|
67
|
+
private reviewParents;
|
|
68
|
+
/** category id -> category (mock mirror of extrovert_categories, D9/D10). */
|
|
69
|
+
private categories;
|
|
70
|
+
/** rule id -> writing rule (mock mirror of extrovert_writing_rules, D2/D11). */
|
|
71
|
+
private rules;
|
|
72
|
+
/** udo id -> change/undo audit row (mock mirror of extrovert_rule_undo_log). */
|
|
73
|
+
private ruleAudit;
|
|
74
|
+
/** human_email -> mock self-signup state (in-memory OTP). */
|
|
75
|
+
private signups;
|
|
76
|
+
/** "<scope>:<client_id>" -> the created resource id, mirroring the server's
|
|
77
|
+
* idempotency replay (a repeat with the same key returns the first result). */
|
|
78
|
+
private idempotency;
|
|
79
|
+
private readonly agentId;
|
|
80
|
+
/**
|
|
81
|
+
* The ceiling tier of the session's key (redesign §3.1). Default `project`
|
|
82
|
+
* (legacy bare `pk_agent_` behavior). Drives the bare-vs-wildcard list ceiling:
|
|
83
|
+
* an `org` key must pick a breadth (`breadth_required`); a non-org key cannot use
|
|
84
|
+
* the org wildcard (`forbidden_scope`). Mirrors the live choke-point so the
|
|
85
|
+
* isolation contract is exercised offline.
|
|
86
|
+
*/
|
|
87
|
+
private readonly keyTier;
|
|
88
|
+
/**
|
|
89
|
+
* The org's review policy, mirrored offline so the mock enforces the SAME tree
|
|
90
|
+
* the server does.
|
|
91
|
+
*
|
|
92
|
+
* The default is `require_review` — deliberately, and matching the column
|
|
93
|
+
* default every real account gets. A mock that defaulted to `allow_direct` would
|
|
94
|
+
* teach every offline agent that a bare send just sends, which is precisely the
|
|
95
|
+
* lie that let the wire bug live for months: the mock passed while the real API
|
|
96
|
+
* refused. Override it with `EXTROVERT_MOCK_REVIEW_POLICY=allow_direct` when a
|
|
97
|
+
* test needs a delivered message rather than a queued review.
|
|
98
|
+
*/
|
|
99
|
+
private readonly reviewPolicy;
|
|
100
|
+
/**
|
|
101
|
+
* The `front_run_next` nudge keys already enqueued, so N identical retries
|
|
102
|
+
* against a terminal review collapse to ONE row (the server dedupes on a
|
|
103
|
+
* deterministic key over reason + review + terminal state + parent revision).
|
|
104
|
+
*/
|
|
105
|
+
private frontRunKeys;
|
|
106
|
+
constructor(opts?: {
|
|
107
|
+
keyTier?: KeyTier;
|
|
108
|
+
reviewPolicy?: ReviewPolicy;
|
|
109
|
+
});
|
|
110
|
+
/** The resolved review policy for this account (mock mirror of the inbox read). */
|
|
111
|
+
effectiveReviewPolicy(): ReviewPolicy;
|
|
112
|
+
/**
|
|
113
|
+
* Persist a review and recompute the derived `closed` flag from its state. Every
|
|
114
|
+
* mutation goes through here so `closed` can never drift from `state` — an agent
|
|
115
|
+
* polling `closed` after a crash is trusting exactly this.
|
|
116
|
+
*/
|
|
117
|
+
private commitReview;
|
|
118
|
+
/**
|
|
119
|
+
* The mock mirror of the submit-time D3 gate. A resolved-review send REQUIRES an
|
|
120
|
+
* intent; a bare send/reply/forward has none by construction, so under anything
|
|
121
|
+
* but an explicitly-asserted direct mode on an `allow_direct` account it is
|
|
122
|
+
* refused — nothing sent, nothing queued.
|
|
123
|
+
*/
|
|
124
|
+
private resolvedModeIsReview;
|
|
125
|
+
/**
|
|
126
|
+
* The SUBMIT-TIME pre-flight: contact lists and list-unsubscribe suppression,
|
|
127
|
+
* run against the resolved recipient set BEFORE the intent gate.
|
|
128
|
+
*
|
|
129
|
+
* The order matters and mirrors the server exactly. A blocked or suppressed
|
|
130
|
+
* recipient is a fact about the message that no amount of intent will fix, so
|
|
131
|
+
* answering `intent_required` first would send the agent off to add an intent
|
|
132
|
+
* and retry straight into the same wall. Running it before the review is created
|
|
133
|
+
* also means no human is ever handed a draft that could never have been sent.
|
|
134
|
+
*/
|
|
135
|
+
private preflight;
|
|
136
|
+
/**
|
|
137
|
+
* Reject a BARE send/reply/forward under a policy that requires review. The
|
|
138
|
+
* error mirrors the server's 422 `intent_required` remediation, including the
|
|
139
|
+
* `retry_with` example, so an offline agent recovers exactly the way it would
|
|
140
|
+
* against the live API.
|
|
141
|
+
*/
|
|
142
|
+
private requireDirectSendAllowed;
|
|
143
|
+
signUp(input: {
|
|
144
|
+
human_email: string;
|
|
145
|
+
username?: string;
|
|
146
|
+
}): SignUpResult;
|
|
147
|
+
verify(otp: string): VerifyResult;
|
|
148
|
+
whoami(): WhoAmI;
|
|
149
|
+
redeemEnrollment(token: string, agentHandle?: string): EnrollmentResult;
|
|
150
|
+
createInbox(opts: {
|
|
151
|
+
username?: string;
|
|
152
|
+
domain?: string;
|
|
153
|
+
displayName?: string;
|
|
154
|
+
inboundWebhookUrl?: string;
|
|
155
|
+
metadata?: InboxMetadataPatch;
|
|
156
|
+
projectId?: string;
|
|
157
|
+
clientId?: string;
|
|
158
|
+
}): Inbox;
|
|
159
|
+
listInboxes(opts?: {
|
|
160
|
+
limit?: number;
|
|
161
|
+
project?: string;
|
|
162
|
+
wildcard?: boolean;
|
|
163
|
+
} | number): Page<Inbox>;
|
|
164
|
+
getInbox(idOrAddress: string): Inbox | undefined;
|
|
165
|
+
/** Update an inbox's settings in place (mirrors PATCH /v1/inboxes/{inbox_id}). */
|
|
166
|
+
updateInbox(idOrAddress: string, opts: {
|
|
167
|
+
displayName?: string;
|
|
168
|
+
inboundWebhookUrl?: string;
|
|
169
|
+
dailySendLimit?: number;
|
|
170
|
+
metadata?: InboxMetadataPatch | null;
|
|
171
|
+
projectId?: string;
|
|
172
|
+
}): Inbox | undefined;
|
|
173
|
+
getCredentials(idOrAddress: string): MailboxCredentials;
|
|
174
|
+
deleteInbox(idOrAddress: string): {
|
|
175
|
+
id: string;
|
|
176
|
+
deleted: true;
|
|
177
|
+
} | undefined;
|
|
178
|
+
/**
|
|
179
|
+
* A BARE send — no mode/intent/category_id. Policy-gated exactly like the live
|
|
180
|
+
* endpoint: only an `allow_direct` account delivers here; under `require_review`
|
|
181
|
+
* (the default) this is 422 `intent_required` with the remediation attached, and
|
|
182
|
+
* nothing is sent or queued.
|
|
183
|
+
*/
|
|
184
|
+
sendEmail(opts: {
|
|
185
|
+
inbox: string;
|
|
186
|
+
to: string[];
|
|
187
|
+
subject: string;
|
|
188
|
+
text: string;
|
|
189
|
+
html?: string;
|
|
190
|
+
cc?: string[];
|
|
191
|
+
bcc?: string[];
|
|
192
|
+
reply_to?: string;
|
|
193
|
+
headers?: Record<string, string>;
|
|
194
|
+
attachments?: AttachmentInput[];
|
|
195
|
+
}): SendEmailResult;
|
|
196
|
+
/**
|
|
197
|
+
* The actual mock delivery, shared by the bare direct path and the review
|
|
198
|
+
* loop's approve/auto-send dispatch. Policy is enforced by the CALLERS, never
|
|
199
|
+
* here: an approved review has already passed the human and must deliver.
|
|
200
|
+
*/
|
|
201
|
+
private deliverSend;
|
|
202
|
+
/**
|
|
203
|
+
* Thread-aware reply (mock). Resolves the parent by message_id or the latest
|
|
204
|
+
* message in thread_id, derives recipients/subject server-side, and returns the
|
|
205
|
+
* canonical `{message_id, thread_id}` — matching the real API contract.
|
|
206
|
+
*/
|
|
207
|
+
replyEmail(opts: {
|
|
208
|
+
inbox: string;
|
|
209
|
+
threadId?: string;
|
|
210
|
+
messageId?: string;
|
|
211
|
+
text?: string;
|
|
212
|
+
html?: string;
|
|
213
|
+
cc?: string[];
|
|
214
|
+
bcc?: string[];
|
|
215
|
+
replyTo?: string;
|
|
216
|
+
replyAll?: boolean;
|
|
217
|
+
attachments?: AttachmentInput[];
|
|
218
|
+
}): SendResult;
|
|
219
|
+
/** The mock reply delivery, shared by the direct path and approval dispatch. */
|
|
220
|
+
private deliverReply;
|
|
221
|
+
/**
|
|
222
|
+
* Forward an existing message to new recipients (mock). Policy-gated like send
|
|
223
|
+
* and reply: a forward quotes an entire received thread to arbitrary NEW
|
|
224
|
+
* recipients, so leaving it ungated would make it the documented bypass.
|
|
225
|
+
*/
|
|
226
|
+
forwardEmail(opts: {
|
|
227
|
+
inbox: string;
|
|
228
|
+
messageId: string;
|
|
229
|
+
to: string[];
|
|
230
|
+
cc?: string[];
|
|
231
|
+
bcc?: string[];
|
|
232
|
+
text?: string;
|
|
233
|
+
html?: string;
|
|
234
|
+
}): SendResult;
|
|
235
|
+
/** The mock forward delivery, shared by the direct path and approval dispatch. */
|
|
236
|
+
private deliverForward;
|
|
237
|
+
/**
|
|
238
|
+
* Submit a new message for review (mock). Mirrors the server's deterministic
|
|
239
|
+
* routing: a `direct` mode is sent immediately (`kind:"sent"`); otherwise the
|
|
240
|
+
* message is parked in `needs_review` (`kind:"queued_for_review"`). Intent is
|
|
241
|
+
* required when the resolved mode is review (D3).
|
|
242
|
+
*/
|
|
243
|
+
submitForReview(input: SubmitForReviewInput): SubmitForReviewResult;
|
|
244
|
+
/** Submit an in-thread reply for review (mock). Same routing as submitForReview. */
|
|
245
|
+
submitReplyForReview(input: SubmitReplyForReviewInput): SubmitForReviewResult;
|
|
246
|
+
/**
|
|
247
|
+
* Submit a forward for review (mock). Same routing as submitForReview, with the
|
|
248
|
+
* forward's subject + quoted body MATERIALIZED here so the human reviews the
|
|
249
|
+
* exact bytes that go out rather than a body re-derived from the live parent at
|
|
250
|
+
* approval time (which would silently discard the reviewer's edit).
|
|
251
|
+
*/
|
|
252
|
+
submitForwardForReview(input: SubmitForwardForReviewInput): SubmitForReviewResult;
|
|
253
|
+
/** Resolve a reply's parent by message id, else the latest message in a thread. */
|
|
254
|
+
private resolveReplyParent;
|
|
255
|
+
/**
|
|
256
|
+
* Record the review row that governed a DIRECT (policy-permitted) send, park it
|
|
257
|
+
* in the terminal `auto_sent` state with `send_path: agent_direct`, and emit its
|
|
258
|
+
* one terminal `sent` nudge.
|
|
259
|
+
*
|
|
260
|
+
* The direct path used to hand back no handle at all, which meant an agent that
|
|
261
|
+
* crashed between the request and the response could never ask what became of
|
|
262
|
+
* the message. Leaving the dominant path handle-less would have entrenched
|
|
263
|
+
* exactly the crash-recovery hole the review loop exists to close.
|
|
264
|
+
*/
|
|
265
|
+
private recordDirectReview;
|
|
266
|
+
/**
|
|
267
|
+
* Enqueue the ONE terminal nudge a finished review is allowed to produce.
|
|
268
|
+
*
|
|
269
|
+
* The invariant a drain loop is written against: every review that reaches
|
|
270
|
+
* `sent`, `auto_sent`, `failed` or `cancelled` emits exactly one terminal nudge,
|
|
271
|
+
* and it is the last and highest-`seq` nudge that review will ever produce. The
|
|
272
|
+
* payload carries everything the agent needs to stop — including WHY a send
|
|
273
|
+
* failed, which was previously stored and exposed on no surface at all.
|
|
274
|
+
*/
|
|
275
|
+
private enqueueTerminalNudge;
|
|
276
|
+
/**
|
|
277
|
+
* Best-effort `front_run_next` nudge for an agent that tried to mutate a review
|
|
278
|
+
* somebody else already finished. It is enqueued OUTSIDE any transition (there
|
|
279
|
+
* is none) and deduped on a deterministic key, so a retry loop hitting the same
|
|
280
|
+
* 409 with the same parent_revision collapses N identical nudges to ONE row.
|
|
281
|
+
*/
|
|
282
|
+
private enqueueFrontRunNudge;
|
|
283
|
+
/**
|
|
284
|
+
* The mock mirror of the 409 taxonomy for a review-mutating verb. A terminal row
|
|
285
|
+
* and a wrong-phase row are DIFFERENT errors on purpose: one must never be
|
|
286
|
+
* retried, the other needs a different verb. Collapsing them to a single
|
|
287
|
+
* "conflict" is what made a skill's one 409 handler retry a sent message forever.
|
|
288
|
+
*/
|
|
289
|
+
private assertMutable;
|
|
290
|
+
/** List review requests (mock), newest-first, with optional state/category/inbox filters. */
|
|
291
|
+
listReviews(input?: ListReviewsInput): Page<Review>;
|
|
292
|
+
/** Get one review request (mock). */
|
|
293
|
+
getReview(id: string): Review;
|
|
294
|
+
/** Get a review's append-only thread turns (mock). */
|
|
295
|
+
getReviewTurns(id: string): Page<ReviewTurn>;
|
|
296
|
+
/**
|
|
297
|
+
* Get the human's assembled feedback for a review (mock; M5): the diff + the
|
|
298
|
+
* human comments/rejection turns + the decision (derived from state) + the rules
|
|
299
|
+
* born from this review. Mirrors the server's $0-LLM assembly.
|
|
300
|
+
*/
|
|
301
|
+
getReviewFeedback(id: string): ReviewFeedback;
|
|
302
|
+
/**
|
|
303
|
+
* Post a chat turn on a review's thread (mock; M5): append an agent_question turn,
|
|
304
|
+
* flip in_review -> chatting on the first turn, enqueue a feedback_added event.
|
|
305
|
+
* Idempotent-key dedup is the transport's concern (replayed there); the mock just
|
|
306
|
+
* appends one turn per call.
|
|
307
|
+
*/
|
|
308
|
+
postReviewChat(input: PostReviewChatInput): Review;
|
|
309
|
+
/**
|
|
310
|
+
* Post a new agent draft under a parent_revision CAS (mock; M5). A mismatch is a
|
|
311
|
+
* 409 STALE with NO mutation (D17); a clean CAS re-renders the draft in place
|
|
312
|
+
* (revision++), returns to needs_review, and enqueues a redraft_requested event.
|
|
313
|
+
*/
|
|
314
|
+
submitRevision(input: SubmitRevisionInput): Review;
|
|
315
|
+
/** Withdraw a pending review (mock; M5) to the terminal cancelled state. */
|
|
316
|
+
cancelReview(id: string): Review;
|
|
317
|
+
/**
|
|
318
|
+
* Re-stamp a draft's rules-version WITHOUT redrafting (mock; D19/§8 $0 escape valve).
|
|
319
|
+
* Advances the version the draft is current against; no revision bump, no body change.
|
|
320
|
+
* A terminal/approved draft 409s; against_version < 0 is invalid.
|
|
321
|
+
*/
|
|
322
|
+
restampReview(input: RestampReviewInput): Review;
|
|
323
|
+
/** Get the reviewer's decision context for a review (mock; §9). */
|
|
324
|
+
getReviewDecisionContext(id: string): ReviewDecisionContext;
|
|
325
|
+
/**
|
|
326
|
+
* Submit a reviewer decision (mock; §9). approve/edit → the platform "sends" with the
|
|
327
|
+
* composer's creds (kind=sent, send_path=reviewer_approved); reject → back to the
|
|
328
|
+
* composer (needs_review, hop_count++) UNLESS a breaker forces the human; escalate →
|
|
329
|
+
* the human queue. revision/version are the CAS (409 STALE on mismatch, NO mutation).
|
|
330
|
+
*/
|
|
331
|
+
reviewerDecide(input: ReviewerDecideInput): ReviewerDecisionResult;
|
|
332
|
+
/**
|
|
333
|
+
* Browse the registry (mock), newest-first, excluding merged/soft-deleted
|
|
334
|
+
* (merged_into set). `match` is a pure lexical filter (every token must appear in
|
|
335
|
+
* name+description) — NO LLM, mirroring the server.
|
|
336
|
+
*/
|
|
337
|
+
listCategories(match?: string): Page<Category>;
|
|
338
|
+
/** Get one category (mock); throws NotFound on an unknown id. */
|
|
339
|
+
getCategory(id: string): Category;
|
|
340
|
+
/** Propose a category (mock): stands immediately, author_kind=agent. */
|
|
341
|
+
proposeCategory(input: ProposeCategoryInput): Category;
|
|
342
|
+
/** Rename / re-describe a category (mock) — metadata only (D10). */
|
|
343
|
+
updateCategory(input: UpdateCategoryInput): Category;
|
|
344
|
+
/** The mock account-default risk dial (mirrors the server defaults). */
|
|
345
|
+
private accountDial;
|
|
346
|
+
/**
|
|
347
|
+
* Read the effective risk dial (mock): the account default + every category with
|
|
348
|
+
* an inherited (null override) effective dial. The mock category carries no risk-
|
|
349
|
+
* dial overrides, so every category inherits — effective == account.
|
|
350
|
+
*/
|
|
351
|
+
getRiskDial(): RiskDial;
|
|
352
|
+
/** The next rung up the graduation ladder ("" if none). */
|
|
353
|
+
private nextGraduationState;
|
|
354
|
+
/**
|
|
355
|
+
* Read a category's graduation gate status (mock). The mock category has no
|
|
356
|
+
* counters, so it reports zero clean approvals / zero drift against the account
|
|
357
|
+
* defaults; can_graduate is true for supervised→auto_notify (no maturity gate).
|
|
358
|
+
*/
|
|
359
|
+
getGraduationStatus(categoryId: string): GraduationStatus;
|
|
360
|
+
/**
|
|
361
|
+
* Propose graduating a category (mock): records nothing mutating and returns the
|
|
362
|
+
* current gate status. It does NOT flip the bit (D16) — the category state is
|
|
363
|
+
* unchanged.
|
|
364
|
+
*/
|
|
365
|
+
proposeGraduation(categoryId: string, _evidence?: Record<string, unknown>): GraduationStatus;
|
|
366
|
+
/**
|
|
367
|
+
* Read the D19/§8 backlog-reconciliation status (mock): counts the QUEUED drafts in a
|
|
368
|
+
* category that are stale vs current-enough against the current rules-version. The
|
|
369
|
+
* mock has no per-draft composed_* stamps, so every queued draft reads current-enough;
|
|
370
|
+
* the contract shape is exercised (the integer-compare is covered by the Go tests).
|
|
371
|
+
*/
|
|
372
|
+
getBacklogStatus(categoryId: string): ScanBacklogStatus;
|
|
373
|
+
/**
|
|
374
|
+
* Read the demand-driven pacing state (mock — M7 Slice B/§8): the cursor + effective
|
|
375
|
+
* window/ceiling/interval + each queued draft's classification. The mock has no cursor
|
|
376
|
+
* (nothing reviewed) and no composed_* stamps, so every queued draft reads in-window-
|
|
377
|
+
* fresh until the window fills, then ahead; the contract shape is exercised (the
|
|
378
|
+
* cursor/staleness mechanics are covered by the Go tests).
|
|
379
|
+
*/
|
|
380
|
+
getPacingState(categoryId: string): CategoryPacingState;
|
|
381
|
+
/** rank a rule for the §7 ladder (mock mirror of the server's deterministic order). */
|
|
382
|
+
private ruleRank;
|
|
383
|
+
/**
|
|
384
|
+
* Get the ORDERED active rule set (mock). Applies the §7 precedence ladder and the
|
|
385
|
+
* category-before-general concatenation, mirroring the server (NO LLM).
|
|
386
|
+
*/
|
|
387
|
+
getRules(input?: GetRulesInput): Page<Rule>;
|
|
388
|
+
/** Save / edit a rule (mock) — append-only by supersession (D11). */
|
|
389
|
+
saveRule(input: SaveRuleInput): Rule;
|
|
390
|
+
/** Promote a rule between the category and general layers (mock, via supersession). */
|
|
391
|
+
promoteRule(id: string, toScope: "general" | "category"): Rule;
|
|
392
|
+
/** Retire a rule (mock) — soft delete; history survives. */
|
|
393
|
+
retireRule(id: string): Rule;
|
|
394
|
+
/** Read the rule/category change audit log (mock). */
|
|
395
|
+
getRuleAudit(input?: GetRuleAuditInput): Page<RuleAuditEntry>;
|
|
396
|
+
/** Undo a rule change (mock) — restore the prior version; idempotent (re-undo 409). */
|
|
397
|
+
undoRuleChange(udoId: string): Rule;
|
|
398
|
+
/** recordRuleAudit appends one change/undo audit row (mock). */
|
|
399
|
+
private recordRuleAudit;
|
|
400
|
+
/**
|
|
401
|
+
* enqueueReviewEvent appends a durable nudge for a review with the next
|
|
402
|
+
* per-review monotonic seq (mock mirror of the server's enqueue-on-transition).
|
|
403
|
+
* Used by the seed + (in a fuller mock) by transition handlers.
|
|
404
|
+
*/
|
|
405
|
+
private enqueueReviewEvent;
|
|
406
|
+
/** Drain the next un-acked review events (mock), FIFO per review + cursors. */
|
|
407
|
+
listReviewEvents(input?: ListReviewEventsInput): ReviewEventsResult;
|
|
408
|
+
/**
|
|
409
|
+
* Long-poll for a review event (mock). Offline there is nothing to wait FOR, so
|
|
410
|
+
* it returns the immediate drain (empty when caught up) — matching the server's
|
|
411
|
+
* "empty on timeout" contract.
|
|
412
|
+
*/
|
|
413
|
+
waitForReviewEvent(input?: WaitForReviewEventInput): ReviewEventsResult;
|
|
414
|
+
/** Ack review events (mock): advance per-review cursors monotonically. */
|
|
415
|
+
ackReviewEvent(input: AckReviewEventInput): {
|
|
416
|
+
cursors: ReviewEventsResult["cursors"];
|
|
417
|
+
};
|
|
418
|
+
/**
|
|
419
|
+
* createReviewRecord mints a needs_review row + the intent (agent_note) and
|
|
420
|
+
* initial-draft (agent_draft) turns, mirroring the server's submit-time writes.
|
|
421
|
+
*/
|
|
422
|
+
private createReviewRecord;
|
|
423
|
+
listMessages(opts: {
|
|
424
|
+
inbox: string;
|
|
425
|
+
limit?: number;
|
|
426
|
+
offset?: number;
|
|
427
|
+
unreadOnly?: boolean;
|
|
428
|
+
from?: string;
|
|
429
|
+
to?: string;
|
|
430
|
+
subject?: string;
|
|
431
|
+
}): Page<Message>;
|
|
432
|
+
/** Fetch a single message by id across all inboxes (mirrors GET /v1/messages/{id}). */
|
|
433
|
+
getMessage(id: string): Message;
|
|
434
|
+
/** Toggle the \Seen flag for a message by id (mirrors PATCH .../messages/{id}). */
|
|
435
|
+
markRead(id: string, read: boolean): Message;
|
|
436
|
+
listThreads(opts: {
|
|
437
|
+
inbox: string;
|
|
438
|
+
limit?: number;
|
|
439
|
+
}): Page<Thread>;
|
|
440
|
+
/** Fetch one thread (with its messages, oldest-first) by id under an inbox. */
|
|
441
|
+
getThread(idOrAddress: string, threadId: string): ThreadDetail;
|
|
442
|
+
/**
|
|
443
|
+
* Delete a message by id (mirrors DELETE .../messages/{id}). The mock moves the
|
|
444
|
+
* message to a Trash folder (soft delete) or removes it outright when expunge is
|
|
445
|
+
* set or it already lives in Trash. Throws NotFoundError when absent.
|
|
446
|
+
*/
|
|
447
|
+
deleteMessage(idOrAddress: string, id: string, expunge: boolean): DeleteResult;
|
|
448
|
+
/**
|
|
449
|
+
* Delete every message in a thread by id (mirrors DELETE .../threads/{id}).
|
|
450
|
+
* Moves them to Trash (soft) or removes them (expunge / already in Trash).
|
|
451
|
+
*/
|
|
452
|
+
deleteThread(idOrAddress: string, threadId: string, expunge: boolean): DeleteResult;
|
|
453
|
+
/**
|
|
454
|
+
* Batch mark read/unread and/or move folder for a list of message ids under one
|
|
455
|
+
* inbox (mirrors PATCH .../messages/batch). Ids not present in the inbox are
|
|
456
|
+
* reported in `failed`; the rest in `updated`.
|
|
457
|
+
*/
|
|
458
|
+
batchUpdateMessages(idOrAddress: string, ids: string[], read: boolean | undefined, folder: string | undefined): BatchUpdateResult;
|
|
459
|
+
/** Build the canonical Thread wire shape (snippet, participant strings). */
|
|
460
|
+
private toThread;
|
|
461
|
+
search(opts: {
|
|
462
|
+
query: string;
|
|
463
|
+
inbox?: string;
|
|
464
|
+
limit?: number;
|
|
465
|
+
}): Page<Message>;
|
|
466
|
+
/**
|
|
467
|
+
* Offline `wait_for_email`. Resolves as soon as a matching inbound message is
|
|
468
|
+
* present. Because `sendEmail` queues an auto-reply ~1.2s out, the demo flow
|
|
469
|
+
* resolves quickly; otherwise it resolves against an already-seeded OTP mail.
|
|
470
|
+
*/
|
|
471
|
+
waitForEmail(opts: {
|
|
472
|
+
inbox: string;
|
|
473
|
+
from?: string;
|
|
474
|
+
subject?: string;
|
|
475
|
+
regex?: string;
|
|
476
|
+
linkHint?: string;
|
|
477
|
+
timeoutMs: number;
|
|
478
|
+
pollMs?: number;
|
|
479
|
+
}): Promise<WaitForEmailResult>;
|
|
480
|
+
private resolveInbox;
|
|
481
|
+
private requireInbox;
|
|
482
|
+
private appendMessage;
|
|
483
|
+
/** List a message's attachment metadata (mirrors the list endpoint). */
|
|
484
|
+
listAttachments(messageId: string): Page<Attachment>;
|
|
485
|
+
/** Fetch one attachment's bytes + metadata (mirrors the download endpoint). */
|
|
486
|
+
getAttachment(messageId: string, attachmentId: string): AttachmentDownload;
|
|
487
|
+
/** Register a webhook; returns the row WITH the one-time signing secret. */
|
|
488
|
+
registerWebhook(input: {
|
|
489
|
+
url: string;
|
|
490
|
+
events?: WebhookEvent[];
|
|
491
|
+
inbox?: string;
|
|
492
|
+
clientId?: string;
|
|
493
|
+
}): Webhook;
|
|
494
|
+
/** List webhooks (secret redacted). */
|
|
495
|
+
listWebhooks(): Page<Webhook>;
|
|
496
|
+
/** Get one webhook (secret redacted). Throws NotFoundError when absent. */
|
|
497
|
+
getWebhook(id: string): Webhook;
|
|
498
|
+
/**
|
|
499
|
+
* Update a webhook in place (secret redacted in the response). Every field is
|
|
500
|
+
* optional; an unset field leaves the stored value untouched (PATCH semantics).
|
|
501
|
+
* Throws NotFoundError when absent.
|
|
502
|
+
*/
|
|
503
|
+
updateWebhook(id: string, input: {
|
|
504
|
+
url?: string;
|
|
505
|
+
events?: WebhookEvent[];
|
|
506
|
+
inbox?: string;
|
|
507
|
+
active?: boolean;
|
|
508
|
+
}): Webhook;
|
|
509
|
+
/** Delete a webhook. Throws NotFoundError when absent. */
|
|
510
|
+
deleteWebhook(id: string): {
|
|
511
|
+
id: string;
|
|
512
|
+
deleted: true;
|
|
513
|
+
};
|
|
514
|
+
/** Add one allow/block entry scoped to an inbox. */
|
|
515
|
+
addContactListEntry(inbox: string, input: {
|
|
516
|
+
kind: ContactListKind;
|
|
517
|
+
direction?: ContactListDirection;
|
|
518
|
+
pattern?: string;
|
|
519
|
+
}): ContactListEntry;
|
|
520
|
+
/** List the entries governing an inbox (inbox-specific + account-wide). */
|
|
521
|
+
listContactListEntries(inbox: string): Page<ContactListEntry>;
|
|
522
|
+
/** Delete a contact-list entry by id. Throws NotFoundError when absent. */
|
|
523
|
+
deleteContactListEntry(_inbox: string, id: string): {
|
|
524
|
+
id: string;
|
|
525
|
+
deleted: true;
|
|
526
|
+
};
|
|
527
|
+
/** Onboard a domain. Mirrors the server's per-mode record set + status. */
|
|
528
|
+
onboardDomain(input: {
|
|
529
|
+
domain: string;
|
|
530
|
+
mode?: "shared" | "ns_delegated" | "manual" | "purchased";
|
|
531
|
+
mail_host_ip?: string;
|
|
532
|
+
scope?: "org" | "project";
|
|
533
|
+
project_id?: string;
|
|
534
|
+
}): Domain;
|
|
535
|
+
/** List onboarded domains (records omitted on the summary, mirroring the server). */
|
|
536
|
+
listDomains(): Page<Domain>;
|
|
537
|
+
/** Get one domain's detail + the records to set, inline. Throws NotFoundError when absent. */
|
|
538
|
+
getDomain(domain: string): Domain;
|
|
539
|
+
/** Trigger/refresh verification; returns the (re-read) detail. Throws when absent. */
|
|
540
|
+
verifyDomain(domain: string): Domain;
|
|
541
|
+
/**
|
|
542
|
+
* Offboard (remove) a domain. Throws NotFoundError when absent. Mirrors the live
|
|
543
|
+
* API's async contract: it returns an accepted teardown job (there is no job
|
|
544
|
+
* runner in-fixture, so the row is removed synchronously and a synthetic
|
|
545
|
+
* succeeded job is reported).
|
|
546
|
+
*/
|
|
547
|
+
offboardDomain(domain: string): DomainOffboard;
|
|
548
|
+
/** Get one async job's poll status (mirrors `GET /v1/jobs/{job_id}`). Throws NotFoundError when absent. */
|
|
549
|
+
getJob(jobId: string): Job;
|
|
550
|
+
/**
|
|
551
|
+
* Pre-check whether the caller's org suppresses a recipient (mirrors
|
|
552
|
+
* `GET /v1/suppressions?recipient=…`): `{recipient, suppressed, rows}` over the
|
|
553
|
+
* active (non-revoked) org rows for that canonicalized recipient.
|
|
554
|
+
*/
|
|
555
|
+
precheckSuppression(recipient: string): SuppressionPrecheck;
|
|
556
|
+
/** Offline deliverability rollup (mirrors `GET /v1/reputation`): healthy, no data. */
|
|
557
|
+
getReputation(): ReputationRollup;
|
|
558
|
+
/** Offline findings list (mirrors `GET /v1/reputation/findings`): empty. */
|
|
559
|
+
listDeliverabilityFindings(_input?: ListDeliverabilityFindingsInput): Page<ReputationFinding>;
|
|
560
|
+
/** List the caller's own org suppression rows (mirrors the paged `GET /v1/suppressions`). */
|
|
561
|
+
listSuppressions(input: {
|
|
562
|
+
scope?: "org" | "shared_domain" | "global";
|
|
563
|
+
include_revoked?: boolean;
|
|
564
|
+
limit?: number;
|
|
565
|
+
cursor?: string;
|
|
566
|
+
}): Page<SuppressionEntry>;
|
|
567
|
+
/**
|
|
568
|
+
* Revoke one org-scope suppression row (mirrors `POST /v1/suppressions/{id}/revoke`);
|
|
569
|
+
* a reason is required. Throws NotFoundError when the id is unknown or not the
|
|
570
|
+
* caller's own org row (global/shared rows are platform-operator only → 404).
|
|
571
|
+
*/
|
|
572
|
+
revokeSuppression(id: string, reason: string): SuppressionEntry;
|
|
573
|
+
/**
|
|
574
|
+
* Reject the WHOLE send if ANY recipient has an active org-scope suppression,
|
|
575
|
+
* naming exactly the suppressed addresses (never the scope/origin) so the caller
|
|
576
|
+
* can drop them and retry — mirroring the live `recipient_suppressed` (422) path.
|
|
577
|
+
*/
|
|
578
|
+
private enforceSuppression;
|
|
579
|
+
/**
|
|
580
|
+
* Enforce the send-direction contact lists for an inbox: reject a block-listed
|
|
581
|
+
* recipient, or any recipient outside the allowlist when allowlist mode is on.
|
|
582
|
+
*/
|
|
583
|
+
private enforceSendPolicy;
|
|
584
|
+
/** Drop a believable inbound OTP reply into the thread shortly after a send. */
|
|
585
|
+
private queueAutoReply;
|
|
586
|
+
private seed;
|
|
587
|
+
}
|
|
588
|
+
/** Thrown when an inbox/thread cannot be resolved (maps to API 404). */
|
|
589
|
+
export declare class NotFoundError extends Error {
|
|
590
|
+
constructor(message: string);
|
|
591
|
+
}
|
|
592
|
+
/** Thrown when a send is rejected by an inbox's contact lists (maps to API 403). */
|
|
593
|
+
export declare class BlockedError extends Error {
|
|
594
|
+
constructor(message: string);
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Thrown when a send is rejected because one or more recipients have opted out
|
|
598
|
+
* (list-unsubscribe / suppression; maps to API 422 `recipient_suppressed`). The
|
|
599
|
+
* message names exactly the suppressed addresses so the agent can drop them and
|
|
600
|
+
* retry; `recipients` carries the same list machine-readably.
|
|
601
|
+
*/
|
|
602
|
+
export declare class SuppressedError extends Error {
|
|
603
|
+
readonly recipients: string[];
|
|
604
|
+
constructor(recipients: string[]);
|
|
605
|
+
}
|
|
606
|
+
/** Thrown when a project_id assertion does not match the key's bound project (maps to API 403). */
|
|
607
|
+
export declare class ForbiddenError extends Error {
|
|
608
|
+
constructor(message: string);
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Thrown when an org-tier key issues a bare list that needs an explicit breadth
|
|
612
|
+
* pick (maps to API 400 `breadth_required`; redesign §4.1). The message names the
|
|
613
|
+
* next call (a project id or the org wildcard).
|
|
614
|
+
*/
|
|
615
|
+
export declare class BreadthRequiredError extends Error {
|
|
616
|
+
constructor(message: string);
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Thrown when a send/reply/forward is refused because the account's review policy
|
|
620
|
+
* requires an intent (maps to API 422 `intent_required`; D3).
|
|
621
|
+
*
|
|
622
|
+
* `problemErrors` carries the SAME `{field, code, detail}` hints the live problem
|
|
623
|
+
* body does — including the `retry_with` example — because the offline error is
|
|
624
|
+
* useless as practice if it is less actionable than the real one.
|
|
625
|
+
*/
|
|
626
|
+
export declare class IntentRequiredError extends Error {
|
|
627
|
+
readonly problemErrors?: ProblemField[];
|
|
628
|
+
constructor(message: string, problemErrors?: ProblemField[]);
|
|
629
|
+
}
|
|
630
|
+
/** Thrown on a conflicting/idempotent-replay mutation (maps to API 409). */
|
|
631
|
+
export declare class ConflictError extends Error {
|
|
632
|
+
constructor(message: string);
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Thrown when `text` and its deprecated `body` alias disagree (maps to API 400
|
|
636
|
+
* `conflicting_alias`). There is no safe guess: picking a winner would relay the
|
|
637
|
+
* wrong bytes from the customer's own domain to a real recipient.
|
|
638
|
+
*/
|
|
639
|
+
export declare class ConflictingAliasError extends Error {
|
|
640
|
+
readonly problemErrors: ProblemField[];
|
|
641
|
+
constructor(message: string);
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Base for the review-loop 409s that carry the RECOVERY FACTS as problem fields:
|
|
645
|
+
* the current state / revision / version, plus one `allowed_action` per legal verb.
|
|
646
|
+
* A stale-CAS retry therefore needs no extra `get_review`, and a wrong-state agent
|
|
647
|
+
* is told what IS legal instead of retrying the same verb forever.
|
|
648
|
+
*/
|
|
649
|
+
export declare class ReviewConflictError extends ConflictError {
|
|
650
|
+
readonly problemErrors: ProblemField[];
|
|
651
|
+
constructor(message: string, review: Review);
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* 409 `stale` — the `(revision[,version])` you named is no longer current and
|
|
655
|
+
* NOTHING was mutated. The ONE 409 worth retrying: re-read, re-apply your edit on
|
|
656
|
+
* top of the other party's, resubmit with the new parent_revision. Bounded (<=3).
|
|
657
|
+
*/
|
|
658
|
+
export declare class StaleError extends ReviewConflictError {
|
|
659
|
+
constructor(message: string, review: Review);
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* 409 `wrong_state` — this VERB is illegal from the current state, but the draft
|
|
663
|
+
* is still live. NEVER retry the same verb; read the `allowed_action` hints and
|
|
664
|
+
* pick a legal one.
|
|
665
|
+
*/
|
|
666
|
+
export declare class WrongStateError extends ReviewConflictError {
|
|
667
|
+
constructor(message: string, review: Review);
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* 409 `terminal` — sent / auto_sent / cancelled. Nothing will ever succeed on this
|
|
671
|
+
* review. STOP; a `front_run_next` nudge is waiting in the drain.
|
|
672
|
+
*/
|
|
673
|
+
export declare class TerminalError extends ReviewConflictError {
|
|
674
|
+
constructor(message: string, review: Review);
|
|
675
|
+
}
|
|
676
|
+
//# sourceMappingURL=fixtures.d.ts.map
|