@vxil/sdk 0.3.0 → 0.4.1

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/index.d.ts CHANGED
@@ -1,15 +1,77 @@
1
- /** The released API majors, as a CLOSED union — one per released major (docs/
2
- * feature-versioning.md §3). `'v1'` is the only released major, so pinning
3
- * anything else (`apiVersion: 'v2'`) is a COMPILE error until a v2 GAs and
4
- * widens this union. Every path the SDK builds is major-versioned; the default
5
- * is the compile-time constant `'v1'` (never a floating `latest` alias). */
6
1
  export type ApiVersion = 'v1';
7
2
  /** A per-feature API-version override key: the URL namespace that leads a path
8
3
  * (`/v1/<namespace>/…`). Matches the segment `versionedPath` rewrites, so an
9
4
  * override changes exactly that namespace's paths. Renamed client accessors map
10
5
  * onto their real namespaces (`vx.search` → `search`, `vx.feeds` → `feeds`). */
11
6
  export type FeatureKey = 'users' | 'notifications' | 'config' | 'features' | 'audit' | 'usage' | 'jobs' | 'auth' | 'rate-limits' | 'files' | 'cms' | 'comments' | 'dm' | 'feeds' | 'realtime' | 'orgs' | 'webhooks' | 'search' | 'ai' | 'rag' | 'payments' | 'fn' | 'copilot';
7
+ /** Retry policy for the client (DEFAULT OFF: `attempts` is `0`). Only
8
+ * idempotent requests are ever retried — GET/HEAD/PUT/DELETE, and a POST only
9
+ * when the call carries an `Idempotency-Key` header (`{ idempotencyKey }` on
10
+ * the money routes) — so a retry can never double-apply a write. */
11
+ export interface VxilRetryOptions {
12
+ /** How many RETRIES to make after the first attempt (`2` ⇒ up to 3 requests).
13
+ * Default `0` = retries off. */
14
+ attempts?: number;
15
+ /** Response statuses that trigger a retry. Default `[429, 502, 503, 504]`. */
16
+ retryOn?: number[];
17
+ /** Base delay before the first retry; doubles each retry (with jitter in
18
+ * `[½, 1]` of the computed delay). Default `250`. */
19
+ backoffMs?: number;
20
+ /** The longest the client will ever wait between attempts. Also the cap on
21
+ * an honoured `Retry-After`: when the server asks for MORE than this the
22
+ * client does not retry and surfaces the error (its `retryAfter` tells you
23
+ * how long the server asked for). Default `10_000`. */
24
+ maxBackoffMs?: number;
25
+ /** Use the response's `Retry-After` header (seconds or an HTTP-date) as the
26
+ * delay instead of the backoff schedule when present. Default `true`. */
27
+ respectRetryAfter?: boolean;
28
+ /** Also retry when `fetch` itself fails (DNS, connection reset, a
29
+ * `timeoutMs` timeout) — again only for idempotent requests. Default `true`
30
+ * (inert while `attempts` is `0`). */
31
+ retryOnNetworkError?: boolean;
32
+ }
33
+ /** What a hook sees of a request: a FROZEN descriptor. Credential headers
34
+ * (`authorization`, `x-vxil-end-user`) are never included. */
35
+ export interface VxilRequestInfo {
36
+ readonly method: string;
37
+ readonly url: string;
38
+ readonly headers: Readonly<Record<string, string>>;
39
+ /** 1 for the first attempt, 2 for the first retry, … */
40
+ readonly attempt: number;
41
+ }
42
+ /** Request lifecycle hooks. Each may be async; a thrown error propagates to
43
+ * the caller (the request is not sent / the response is not returned). */
44
+ export interface VxilHooks {
45
+ /** Runs before EVERY attempt (so a retry can carry a fresh trace id). May
46
+ * return extra headers to add for that attempt — a correlation id, a
47
+ * tracing header. The credential headers cannot be set from a hook (they
48
+ * are dropped); rotate an end-user session with `vx.asEndUser(token)`. */
49
+ beforeRequest?: (request: VxilRequestInfo) => void | Record<string, string> | Promise<void | Record<string, string>>;
50
+ /** Runs after every response that arrives (2xx or not, retried or final),
51
+ * before the retry decision. The body has already been read into the
52
+ * client — inspect `response.status` / `response.headers`, not the body. */
53
+ afterResponse?: (info: {
54
+ request: VxilRequestInfo;
55
+ response: Response;
56
+ durationMs: number;
57
+ }) => void | Promise<void>;
58
+ /** Runs right before the client sleeps for a retry. `status` is set for a
59
+ * retryable response, `error` for a network failure / timeout. */
60
+ onRetry?: (info: {
61
+ request: VxilRequestInfo;
62
+ delayMs: number;
63
+ status?: number;
64
+ error?: unknown;
65
+ }) => void | Promise<void>;
66
+ }
12
67
  export interface VxilOptions {
68
+ /** The tenant API key (`vxil_live_…` / `vxil_test_…`).
69
+ *
70
+ * A key may optionally be minted with an **IP allowlist** (`allowed_cidrs`,
71
+ * set in the dashboard at mint time). When it is, a call from an address
72
+ * outside that list is refused at the edge with `403 ip_not_allowed` —
73
+ * before it reaches any feature — and the key is otherwise unchanged. Keys
74
+ * minted without one (the default) are not IP-restricted. */
13
75
  apiKey: string;
14
76
  /** defaults to the production edge */
15
77
  baseUrl?: string;
@@ -22,17 +84,30 @@ export interface VxilOptions {
22
84
  * a mobile/SPA holds a public/thin-client key + the signed-in user's session
23
85
  * token and calls the edge directly, safely scoped to that user. Absent ⇒
24
86
  * **no header** ⇒ server-caller mode, exactly as today (backward compatible).
25
- * See docs/end-user-principals-design.md §4.1, §10. For a per-call override on
87
+ * See https://vxil.com/docs/guide/09-security-and-multitenancy. For a per-call override on
26
88
  * an otherwise server-mode client, use `vx.asEndUser(token)`. */
27
89
  endUserToken?: string;
28
90
  /** Global API major every request pins (default `'v1'`). Path-major, per
29
- * docs/feature-versioning.md — a breaking change ships as a new major on a
91
+ * the versioning section of https://vxil.com/docs/guide/12-going-to-production — a breaking change ships as a new major on a
30
92
  * new path, never in place. */
31
93
  apiVersion?: ApiVersion;
32
94
  /** Per-feature API-major overrides (`{ cms: 'v2' }`); each takes precedence
33
95
  * over `apiVersion` for that namespace only. Optional; the compiled default
34
96
  * is `'v1'` for every feature. */
35
97
  apiVersions?: Partial<Record<FeatureKey, ApiVersion>>;
98
+ /** Retry policy — DEFAULT OFF (`attempts: 0`). Only idempotent requests are
99
+ * ever retried (GET/HEAD/PUT/DELETE, and a POST only when it carries an
100
+ * `Idempotency-Key`), so a retry can never double-apply a write. See
101
+ * `VxilRetryOptions` for the schedule and `Retry-After` handling. */
102
+ retry?: VxilRetryOptions;
103
+ /** Per-attempt timeout in milliseconds (default: none). On expiry the
104
+ * request — body read included — is aborted and a `VxilError`
105
+ * `{ status: 0, code: 'request_timeout' }` is thrown. A timeout counts as a
106
+ * network error for `retry`. */
107
+ timeoutMs?: number;
108
+ /** Request lifecycle hooks — correlation ids, logging, metrics, a retry
109
+ * audit. Nothing runs unless set. See `VxilHooks`. */
110
+ hooks?: VxilHooks;
36
111
  }
37
112
  /** Rewrite a built `/v1/<ns>/…` path onto the version configured for its
38
113
  * namespace: the per-namespace override wins, else the global default. PURE and
@@ -44,13 +119,24 @@ export interface Meta {
44
119
  request_id: string;
45
120
  tenant_id?: string;
46
121
  }
122
+ /** Every non-2xx answer is thrown as this class, carrying the structured error
123
+ * envelope. `status` is the HTTP status — or `0` when no response arrived
124
+ * (a `timeoutMs` timeout, `code: 'request_timeout'`). */
47
125
  export declare class VxilError extends Error {
48
126
  readonly status: number;
49
127
  readonly code: string;
50
128
  readonly hint?: string | undefined;
51
129
  readonly fixUrl?: string | undefined;
52
130
  readonly requestId?: string | undefined;
53
- constructor(status: number, code: string, message: string, hint?: string | undefined, fixUrl?: string | undefined, requestId?: string | undefined);
131
+ /** Seconds the server asked the caller to wait — parsed from `Retry-After`
132
+ * (delta-seconds or an HTTP-date) when the answer carried it (a rate
133
+ * limit, a busy upstream); otherwise undefined. */
134
+ readonly retryAfter?: number | undefined;
135
+ constructor(status: number, code: string, message: string, hint?: string | undefined, fixUrl?: string | undefined, requestId?: string | undefined,
136
+ /** Seconds the server asked the caller to wait — parsed from `Retry-After`
137
+ * (delta-seconds or an HTTP-date) when the answer carried it (a rate
138
+ * limit, a busy upstream); otherwise undefined. */
139
+ retryAfter?: number | undefined);
54
140
  }
55
141
  export interface VxilUser {
56
142
  id: string;
@@ -78,6 +164,44 @@ export interface Delivery {
78
164
  queued_at: string;
79
165
  sent_at: string | null;
80
166
  failed_at: string | null;
167
+ /** Engagement stamps from the signed provider webhook (first-wins). `null`
168
+ * means "no such event received", NOT "did not happen". */
169
+ delivered_at?: string | null;
170
+ opened_at?: string | null;
171
+ clicked_at?: string | null;
172
+ }
173
+ /** An ADDITIVE, non-fatal note on a 202 send — today only `mock_provider`
174
+ * (the send is recorded but no email leaves vxil). */
175
+ export interface NotificationWarning {
176
+ code: string;
177
+ message: string;
178
+ hint?: string;
179
+ }
180
+ export interface NotificationSendResult {
181
+ delivery_id?: string;
182
+ inbox_message_id?: string;
183
+ status: string;
184
+ /** present only for a scheduled send (`send_at` / `delay_seconds`) */
185
+ scheduled_for?: string;
186
+ warnings?: NotificationWarning[];
187
+ }
188
+ export interface Campaign {
189
+ campaign_id: string;
190
+ name: string;
191
+ channel: string;
192
+ template_id: string;
193
+ audience_ref: string;
194
+ schedule_cron: string | null;
195
+ status: 'draft' | 'scheduled' | 'running' | 'done' | 'paused' | 'cancelled';
196
+ created_at: string;
197
+ /** the jobs schedule row this campaign owns; null = no cron registered */
198
+ schedule_id?: string | null;
199
+ last_run_at?: string | null;
200
+ }
201
+ export interface CampaignState {
202
+ campaign_id: string;
203
+ status: string;
204
+ schedule_id: string | null;
81
205
  }
82
206
  export interface FeatureConfig {
83
207
  feature: string;
@@ -230,8 +354,14 @@ export interface PaymentsWebhookEvent {
230
354
  provider: string;
231
355
  provider_evt_id: string;
232
356
  event_type: string;
233
- /** received | processed | error | sig_failed | parse_failed | reprocessed */
357
+ /** received | processed | error | sig_failed | parse_failed | reprocessed |
358
+ * ignored (a well-formed provider type we deliberately do not fold) |
359
+ * unowned (no end user resolvable) | rejected_environment (a sandbox event
360
+ * the tenant did not opt into) — the last three are 200-acked, never folded. */
234
361
  outcome: string | null;
362
+ /** the PROVIDER-reported environment of the delivery ('production' |
363
+ * 'sandbox') — a separate axis from the API key's live/test label. */
364
+ environment?: 'production' | 'sandbox';
235
365
  signature_ok: boolean;
236
366
  error: string | null;
237
367
  received_at: string | null;
@@ -244,6 +374,70 @@ export interface PaymentsWebhookEvent {
244
374
  raw_body?: string | null;
245
375
  provider_event_ts?: string | null;
246
376
  }
377
+ /** The outcome of a provider re-sync / Restore Purchases (payments.md §7d). */
378
+ export interface PaymentsSyncOutcome {
379
+ provider: string;
380
+ synced: number;
381
+ changed: number;
382
+ unchanged: number;
383
+ errors: string[];
384
+ changes: Array<{
385
+ provider_sub_id: string;
386
+ status: string;
387
+ tier: string | null;
388
+ until: string | null;
389
+ }>;
390
+ dry_run?: boolean;
391
+ }
392
+ /** A lifecycle simulation run (payments.md §7d; mock/dev tenants only). */
393
+ export interface PaymentsSimulationResult {
394
+ scenario: string;
395
+ run_id: string;
396
+ user_id: string;
397
+ tier: string;
398
+ tenant_kind: 'standard' | 'dev' | 'unknown';
399
+ note: string;
400
+ steps: Array<{
401
+ event_type: string;
402
+ status: number;
403
+ outcome: string;
404
+ }>;
405
+ oracle: {
406
+ expected: Record<string, 'premium' | 'free'>;
407
+ observed: Record<string, {
408
+ tier: string;
409
+ until: string | null;
410
+ }>;
411
+ pass: boolean;
412
+ };
413
+ }
414
+ /** The ONE complete entitlement read (GET /v1/payments/entitlements; payments.md
415
+ * §3a). `until` is the winning subscription's current_period_end (+ the
416
+ * configured grace window when it is past_due); null on the free baseline. */
417
+ export interface PaymentsEntitlementView {
418
+ user_id: string;
419
+ tier: string;
420
+ entitlements: string[];
421
+ quotas: Record<string, number>;
422
+ source_sub_id: string | null;
423
+ since: string | null;
424
+ until: string | null;
425
+ as_of: string;
426
+ environment: 'production' | 'sandbox';
427
+ /** present only when `?quota=` was asked for */
428
+ quota?: {
429
+ name: string;
430
+ value: number | null;
431
+ };
432
+ /** present only when `?credit_type=` was asked for */
433
+ credits?: {
434
+ credit_type: string;
435
+ balance: number;
436
+ held: number;
437
+ available: number;
438
+ period_end: string | null;
439
+ };
440
+ }
247
441
  export interface FileObject {
248
442
  object_id: string;
249
443
  filename: string;
@@ -252,6 +446,26 @@ export interface FileObject {
252
446
  status: 'pending' | 'available' | 'deleted';
253
447
  created_at: string;
254
448
  }
449
+ /** A live shared link as listed by GET /v1/files/{id}/shared-links (files.md
450
+ * §6). `downloads` is the burn-on-read counter; `max_downloads` null =
451
+ * unlimited (1 = a one-time link). A link that hit its cap, expired, or was
452
+ * revoked no longer lists. */
453
+ export interface FileSharedLink {
454
+ link_id: string;
455
+ created_at: string;
456
+ expires_at: string;
457
+ downloads: number;
458
+ max_downloads: number | null;
459
+ }
460
+ /** Options for POST /v1/files/{id}/shared-links. `expires_at` (ISO 8601) takes
461
+ * precedence over `ttl_seconds`; both are clamped to the tenant's
462
+ * `sharedLinks.maxTtl`. `max_downloads` ≥ 1 caps successful public
463
+ * resolutions (1 = one-time link); omit for unlimited. */
464
+ export interface CreateSharedLinkOptions {
465
+ ttl_seconds?: number;
466
+ expires_at?: string;
467
+ max_downloads?: number | null;
468
+ }
255
469
  /** One recognized OCR text block (files.md §1.1). bbox is [x, y, w, h] in the
256
470
  * provider's unit space; page is 1-based. confidence is 0..1 normalized per
257
471
  * provider (Textract native 0..100 divided by 100; mock pins 0.95); absent
@@ -448,6 +662,57 @@ export interface AiEmbedResult {
448
662
  usage: AiUsage;
449
663
  generation_id: string;
450
664
  }
665
+ /** The per-request knobs every verdict route shares (POST /v1/ai/classify and
666
+ * /v1/ai/judge): a stored template may author the whole prompt (rendered with
667
+ * the verdict vars — `{{input}}`, `{{labels}}`, `{{rubric}}`, `{{examples}}` /
668
+ * `{{candidate}}`, `{{candidates}}`, `{{criteria}}`, `{{scale_min}}`,
669
+ * `{{scale_max}}` — plus your own `vars`); provider/model override the tenant
670
+ * defaults; `max_tokens` defaults to min(config maxTokens, 512) and
671
+ * `temperature` to 0. Verdicts are sync-only (no stream / job mode). */
672
+ export interface AiVerdictOptions {
673
+ template?: string;
674
+ template_version?: number;
675
+ vars?: Record<string, unknown>;
676
+ provider?: string;
677
+ model?: string;
678
+ max_tokens?: number;
679
+ temperature?: number;
680
+ user_id?: string;
681
+ }
682
+ /** A classify verdict (POST /v1/ai/classify). `label` (single) or `labels`
683
+ * (multi:true — every applicable label, possibly empty) is guaranteed to be
684
+ * drawn from the request's `labels`: the allowed set is forced through the
685
+ * structured-output schema, so a 200 can never carry an off-list label. */
686
+ export interface AiClassifyResult {
687
+ generation_id: string;
688
+ label?: string;
689
+ labels?: string[];
690
+ /** 0..1 */
691
+ confidence: number;
692
+ rationale?: string;
693
+ usage: AiUsage;
694
+ /** true when the verdict only validated after the one repair pass. */
695
+ repaired?: boolean;
696
+ cached: boolean;
697
+ }
698
+ /** A judge verdict (POST /v1/ai/judge). One `candidate` → `score` (an integer
699
+ * inside `scale`, default 0..10) + `verdict: 'pass' | 'fail'`; several
700
+ * `candidates` → `scores` (one per candidate, in order) + `best` (the index of
701
+ * the top score) + `verdict: 'tie'` when the top score is shared. Sending
702
+ * `candidates` ALWAYS yields `scores` + `best` — a one-element list keeps the
703
+ * richer `score`/`pass|fail` fields too, so the shape never flips out from
704
+ * under a variable-length array. */
705
+ export interface AiJudgeResult {
706
+ generation_id: string;
707
+ score?: number;
708
+ scores?: number[];
709
+ verdict?: 'pass' | 'fail' | 'tie';
710
+ best?: number;
711
+ rationale?: string;
712
+ usage: AiUsage;
713
+ repaired?: boolean;
714
+ cached: boolean;
715
+ }
451
716
  /** Per-user token rollups (GET /v1/ai/usage). */
452
717
  export interface AiUsageReport {
453
718
  input_tokens: number;
@@ -850,6 +1115,35 @@ type DisabledFeatures<S extends VxilSchemaShape> = {
850
1115
  export type EnabledVxil<S extends VxilSchemaShape> = Omit<Vxil<S>, DisabledFeatures<S>> & {
851
1116
  [P in DisabledFeatures<S>]: DisabledFeature<FeatureMap[P] & string>;
852
1117
  };
1118
+ /** One config-declared per-record ACTION (cms.md §17): a button on a record row
1119
+ * that invokes the deployed tenant function `fn` ONCE with `{ collection,
1120
+ * item_id, action, actor, item }`. Exactly one human-initiated step — no
1121
+ * conditions, no chaining, no scheduling. ≤8 per collection. */
1122
+ export interface CmsActionDef {
1123
+ /** /^[a-z][a-z0-9_]{0,31}$/ — the URL key of `items.runAction` */
1124
+ key: string;
1125
+ /** the button label (1–64 chars) */
1126
+ label: string;
1127
+ /** the deployed function name (/^[a-z][a-z0-9-]{0,47}$/) */
1128
+ fn: string;
1129
+ }
1130
+ /** One live item referencing another through a relation field (cms.md §11.1). */
1131
+ export interface CmsBacklink {
1132
+ collection: string;
1133
+ field: string;
1134
+ item_id: string;
1135
+ status: string;
1136
+ }
1137
+ /** The bounded reverse read (`items.backlinks`): `count` = rows in THIS page
1138
+ * (≤ `limit`), never a total; `has_more` = the cap was hit. */
1139
+ export interface CmsBacklinksPage {
1140
+ collection: string;
1141
+ item_id: string;
1142
+ backlinks: CmsBacklink[];
1143
+ count: number;
1144
+ has_more: boolean;
1145
+ limit: number;
1146
+ }
853
1147
  /** A write guard (cms.md §10): "after this write, at most `max` live items
854
1148
  * match `filter`". Requires `lock` — an unlocked guard is racy by construction. */
855
1149
  export interface CmsGuard {
@@ -1035,6 +1329,12 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1035
1329
  /** The end-user session token threaded as `X-Vxil-End-User` when set (end-user
1036
1330
  * mode). Undefined ⇒ no header ⇒ server-caller mode (unchanged). */
1037
1331
  private readonly endUserToken;
1332
+ /** The ONE path every request takes: retry + timeout + hooks around
1333
+ * `fetchImpl`. With none of the three configured it is a single fetch. */
1334
+ private readonly transport;
1335
+ private readonly retryOpts;
1336
+ private readonly timeoutMs;
1337
+ private readonly hooks;
1038
1338
  constructor(opts: VxilOptions);
1039
1339
  /** The header bag every request layers on top of `authorization`: the
1040
1340
  * `X-Vxil-End-User` session token in end-user mode, nothing in server mode.
@@ -1043,9 +1343,9 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1043
1343
  /** Return a client that sends `X-Vxil-End-User: <token>` on every request —
1044
1344
  * a per-call/scoped override of end-user mode over an otherwise server-mode
1045
1345
  * client, mirroring how the edge threads the verified principal. The base
1046
- * URL, api key, fetch impl and version pins are inherited unchanged; only the
1047
- * end-user token is (re)set. Pass a falsy token to get back a server-mode
1048
- * client (drops the header). See docs/end-user-principals-design.md §4.1. */
1346
+ * URL, api key, fetch impl, version pins and the retry/timeout/hooks options
1347
+ * are inherited unchanged; only the end-user token is (re)set. Pass a falsy token to get back a server-mode
1348
+ * client (drops the header). See https://vxil.com/docs/guide/09-security-and-multitenancy. */
1049
1349
  asEndUser(endUserToken: string | undefined): Vxil<S>;
1050
1350
  /** Build the versioned request path — the ONE place a wire path is finalized.
1051
1351
  * Every request (call(), audit.export, the fn proxy) routes through here, so
@@ -1117,15 +1417,72 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1117
1417
  user_id: string;
1118
1418
  template: "magic-link" | "welcome" | "transactional";
1119
1419
  data: Record<string, unknown>;
1420
+ /** Explicit wins; otherwise the recipient's stored `locale` attribute
1421
+ * (set it via `users.upsert({ attributes: { locale } })`), then
1422
+ * `config.defaultLocale`. */
1120
1423
  locale?: string;
1121
1424
  /** 'inbox'/'both' require config inboxEnabled */
1122
1425
  channel?: "email" | "inbox" | "both";
1426
+ /** hold the send until this ISO instant (≤ 30 d out) */
1427
+ send_at?: string;
1428
+ /** …or this many seconds from now (≤ 30 d). At most one of the two. */
1429
+ delay_seconds?: number;
1123
1430
  }, opts?: {
1124
1431
  idempotencyKey?: string;
1432
+ }) => Promise<NotificationSendResult>;
1433
+ /** Up to 100 sends in one call. Each item is INDEPENDENTLY atomic (recorded
1434
+ * + enqueued, or neither) and `results` is index-aligned with `items`. */
1435
+ sendBatch: (items: Array<{
1436
+ user_id: string;
1437
+ template: "magic-link" | "welcome" | "transactional";
1438
+ data: Record<string, unknown>;
1439
+ locale?: string;
1440
+ channel?: "email" | "inbox" | "both";
1441
+ send_at?: string;
1442
+ delay_seconds?: number;
1443
+ /** per-item 24 h idempotency key (the header equivalent for one item) */
1444
+ idempotency_key?: string;
1445
+ }>) => Promise<{
1446
+ results: Array<{
1447
+ index: number;
1448
+ delivery_id?: string;
1449
+ inbox_message_id?: string;
1450
+ status?: string;
1451
+ scheduled_for?: string;
1452
+ error?: {
1453
+ code: string;
1454
+ message: string;
1455
+ hint?: string;
1456
+ };
1457
+ }>;
1458
+ count: number;
1459
+ accepted: number;
1460
+ failed: number;
1461
+ warnings?: NotificationWarning[];
1462
+ }>;
1463
+ /** Render a template exactly as a send would, WITHOUT sending. `missing`
1464
+ * lists the required data keys you left out. Needs `notifications:read`. */
1465
+ preview: (templateId: string, input?: {
1466
+ data?: Record<string, unknown>;
1467
+ locale?: string;
1125
1468
  }) => Promise<{
1126
- delivery_id?: string;
1127
- inbox_message_id?: string;
1128
- status: string;
1469
+ template_id: string;
1470
+ locale: string;
1471
+ overridden: boolean;
1472
+ subject: string;
1473
+ html: string;
1474
+ text: string;
1475
+ missing: string[];
1476
+ }>;
1477
+ /** Read-only deliverability probe: is `config.fromEmail`'s domain verified
1478
+ * at the provider? FAIL-SOFT — `verified: 'unknown'` whenever it cannot be
1479
+ * determined (mock provider, no key, provider unreachable). */
1480
+ senderDomain: () => Promise<{
1481
+ domain: string | null;
1482
+ verified: boolean | "unknown";
1483
+ provider: string;
1484
+ checked_at: string;
1485
+ reason?: string;
1129
1486
  }>;
1130
1487
  inbox: {
1131
1488
  list: (q: {
@@ -1153,6 +1510,8 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1153
1510
  user_id?: string;
1154
1511
  status?: string;
1155
1512
  limit?: number;
1513
+ /** A7: only deliveries that reached this engagement state */
1514
+ engagement?: "delivered" | "opened" | "clicked";
1156
1515
  }) => Promise<Delivery[]>;
1157
1516
  suppressions: {
1158
1517
  list: () => Promise<Array<{
@@ -1194,17 +1553,36 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1194
1553
  }) => Promise<{
1195
1554
  campaign_id: string;
1196
1555
  status: string;
1556
+ schedule_id: string | null;
1197
1557
  }>;
1198
- list: () => Promise<Array<{
1558
+ list: () => Promise<Campaign[]>;
1559
+ /** Stop a scheduled campaign from firing (the jobs cron is torn down). */
1560
+ pause: (campaignId: string) => Promise<CampaignState>;
1561
+ /** Re-register the cron (back to `scheduled`, or `draft` with no cron). */
1562
+ resume: (campaignId: string) => Promise<CampaignState>;
1563
+ /** Terminal stop — the cron is removed and the campaign cannot resume. */
1564
+ cancel: (campaignId: string) => Promise<CampaignState>;
1565
+ /** Soft-delete: hidden from the list, cron removed; the dedupe ledger
1566
+ * survives so a re-created campaign cannot re-send to a served user. */
1567
+ remove: (campaignId: string) => Promise<CampaignState & {
1568
+ deleted?: boolean;
1569
+ }>;
1570
+ /** Push ONE audience page (≤ 1000 rows) into a campaign run — the shape a
1571
+ * tenant audience callback uses. Omit `audience` for a campaign whose
1572
+ * `audience_ref` is the built-in `users:all` selector. */
1573
+ run: (campaignId: string, audience?: Array<{
1574
+ end_user_id: string;
1575
+ locale?: string | null;
1576
+ }>) => Promise<{
1199
1577
  campaign_id: string;
1200
- name: string;
1201
- channel: string;
1202
- template_id: string;
1203
- audience_ref: string;
1204
- schedule_cron: string | null;
1205
- status: string;
1206
- created_at: string;
1207
- }>>;
1578
+ audience_source: "supplied" | "builtin" | "external";
1579
+ audience_size: number;
1580
+ sent: number;
1581
+ deferred: number;
1582
+ skipped_freq_cap: number;
1583
+ skipped_no_email: number;
1584
+ already_sent: number;
1585
+ }>;
1208
1586
  };
1209
1587
  };
1210
1588
  readonly config: {
@@ -1242,7 +1620,7 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1242
1620
  * ADVISORY vxil plan — which features to enable, a materialized `config_draft`,
1243
1621
  * and which truly-unique logic needs a tenant function. Deterministic + pure;
1244
1622
  * it PROPOSES a plan, it never applies anything (review it, then push the
1245
- * config). Needs `features:read`. (POST /v1/plan; docs/planner-feature-design.md) */
1623
+ * config). Needs `features:read`. (POST /v1/plan; https://vxil.com/docs/guide/10-agents-and-mcp) */
1246
1624
  readonly planner: {
1247
1625
  create: (description: string, name?: string) => Promise<VxilPlan>;
1248
1626
  };
@@ -1454,6 +1832,16 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1454
1832
  delete: (ruleId: string) => Promise<void>;
1455
1833
  };
1456
1834
  };
1835
+ /** vxil-auth. SCOPES (vxil.com/docs/guide/09-security-and-multitenancy): every flow here that
1836
+ * obtains, renews, verifies or ends the caller's OWN session (signUp/signIn,
1837
+ * magicLink, otp, anonymous, stepUp, oauth, sessions.refresh/revoke/verify,
1838
+ * password reset) accepts the narrow `auth:signin` — the ONLY auth scope a
1839
+ * key baked into a browser/mobile bundle should carry. `sessions.list` needs
1840
+ * `auth:read`; `sessions.revokeById` and `users.erase` need `auth:write`
1841
+ * (administrative — server keys only). In end-user mode (`endUserToken` /
1842
+ * `asEndUser`) the administrative calls bind to the signed-in user: own
1843
+ * sessions only, own devices only, erase self only. `auth:write` still
1844
+ * satisfies every call. */
1457
1845
  readonly auth: {
1458
1846
  signUp: (input: {
1459
1847
  email: string;
@@ -1486,9 +1874,18 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1486
1874
  * Server-side guessing budget (config otp.maxAttempts), single active code,
1487
1875
  * resend cooldown (429 otp_rate_limited). */
1488
1876
  otp: {
1877
+ /** `test_code` is present ONLY when the address matches auth config
1878
+ * `otp.testRecipients` (store-review / CI accounts): no mail is sent and
1879
+ * the code comes back instead. `captcha_token` is required when the
1880
+ * tenant configured `security.captchaSecretRef`. */
1489
1881
  request: (input: {
1490
1882
  email: string;
1491
- }) => Promise<void>;
1883
+ locale?: string;
1884
+ captcha_token?: string;
1885
+ }) => Promise<{
1886
+ sent: true;
1887
+ test_code?: string;
1888
+ }>;
1492
1889
  verify: (input: {
1493
1890
  email: string;
1494
1891
  code: string;
@@ -1510,7 +1907,16 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1510
1907
  request: (input: {
1511
1908
  token: string;
1512
1909
  email: string;
1513
- }) => Promise<void>;
1910
+ locale?: string;
1911
+ }) => Promise<{
1912
+ sent: true;
1913
+ test_code?: string;
1914
+ }>;
1915
+ /** On success the guest keeps its user id (`user_id` unchanged).
1916
+ * If the claimed email ALREADY has an account, the guest is MERGED
1917
+ * into it: `user_id` is the existing account, `merged: true`, and a
1918
+ * fresh `session.token` (same session, re-signed for the merged
1919
+ * identity — swap it client-side; other guest sessions are revoked). */
1514
1920
  verify: (input: {
1515
1921
  token: string;
1516
1922
  email: string;
@@ -1519,6 +1925,11 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1519
1925
  user_id: string;
1520
1926
  email: string;
1521
1927
  verified: boolean;
1928
+ merged?: true;
1929
+ session?: {
1930
+ token: string;
1931
+ expires_at: string;
1932
+ };
1522
1933
  }>;
1523
1934
  };
1524
1935
  };
@@ -1528,7 +1939,11 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1528
1939
  stepUp: {
1529
1940
  request: (input: {
1530
1941
  token: string;
1531
- }) => Promise<void>;
1942
+ locale?: string;
1943
+ }) => Promise<{
1944
+ sent: true;
1945
+ test_code?: string;
1946
+ }>;
1532
1947
  verify: (input: {
1533
1948
  token: string;
1534
1949
  code: string;
@@ -1546,11 +1961,22 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1546
1961
  * (email → tombstone, password/name/avatar cleared) and revoke every
1547
1962
  * live session. Idempotent — a second call on an already-erased user is a
1548
1963
  * no-op. Meant to be called from a "delete my account" server route or
1549
- * vxil function (which declares `auth:write`). */
1964
+ * vxil function (which declares `auth:write`). In END-USER mode the call
1965
+ * is SELF-only: `userId` must be the signed-in user, any other id throws
1966
+ * 403 `server_only` (a thin client can never erase someone else). */
1550
1967
  erase: (userId: string) => Promise<{
1551
1968
  user_id: string;
1552
1969
  erased: boolean;
1553
1970
  }>;
1971
+ /** END-USER MODE ONLY (`endUserToken` / `asEndUser`): the signed-in user
1972
+ * deep-merges a bounded `attributes` object into its OWN shared identity
1973
+ * row (null deletes a key; ≤8 KB, ≤64 top-level keys, depth ≤8). Profile
1974
+ * fields are not patchable here. In server mode the call throws 422
1975
+ * `end_user_mode_required` — use `users.patch(id, …)` instead. */
1976
+ patchMe: (attributes: Record<string, unknown>) => Promise<{
1977
+ user_id: string;
1978
+ attributes: Record<string, unknown>;
1979
+ }>;
1554
1980
  };
1555
1981
  sessions: {
1556
1982
  verify: (token: string) => Promise<{
@@ -1568,11 +1994,26 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1568
1994
  session: AuthSession;
1569
1995
  }>;
1570
1996
  revoke: (token: string) => Promise<void>;
1997
+ /** "Sign out everywhere": revoke EVERY live session of a user in one call
1998
+ * (one statement, one batched edge-cache write; each session's
1999
+ * revoked_reason = 'revoke_all'). SERVER mode names the user (`userId`,
2000
+ * scope `auth:write`); in END-USER mode the signed-in user's own sessions
2001
+ * go and `userId` is ignored (scope `auth:signin` suffices). Idempotent:
2002
+ * a user with no live sessions returns `revoked: 0`. */
2003
+ revokeAll: (userId?: string) => Promise<{
2004
+ user_id: string;
2005
+ revoked: number;
2006
+ session_ids: string[];
2007
+ }>;
1571
2008
  /** Force-revoke a SPECIFIC session by its `session_id` (from `list`) — the
1572
2009
  * server-forced device-lockout path beyond the client-cooperative
1573
- * by-token `revoke`. Throws 404 when the id is unknown or already revoked. */
2010
+ * by-token `revoke`. Throws 404 when the id is unknown or already revoked
2011
+ * — or, in end-user mode, when it belongs to another user (a signed-in
2012
+ * user can kick only their OWN devices). Scope `auth:write`. */
1574
2013
  revokeById: (sessionId: string) => Promise<void>;
1575
- /** List a user's active sessions (the account "signed-in devices" surface). */
2014
+ /** List a user's active sessions (the account "signed-in devices" surface).
2015
+ * Scope `auth:read`. In end-user mode the list is bound to the signed-in
2016
+ * user regardless of `userId` (the verified principal wins). */
1576
2017
  list: (userId: string) => Promise<Array<{
1577
2018
  session_id: string;
1578
2019
  created_at: string;
@@ -1591,21 +2032,48 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1591
2032
  password: string;
1592
2033
  }) => Promise<void>;
1593
2034
  };
2035
+ /** Email verification (the flow behind auth config `emailVerification.required`,
2036
+ * which makes password sign-in answer 403 `email_not_verified` until the
2037
+ * address is confirmed). `request` always resolves (anti-enumeration —
2038
+ * a link is mailed only to an existing, unverified user; `redirect_url`
2039
+ * is your page that reads `?token=` and calls `confirm`). `confirm` is
2040
+ * single-use and expires after 24h. */
2041
+ emailVerification: {
2042
+ request: (input: {
2043
+ email: string;
2044
+ redirect_url: string;
2045
+ locale?: string;
2046
+ captcha_token?: string;
2047
+ }) => Promise<void>;
2048
+ confirm: (token: string) => Promise<{
2049
+ user_id: string;
2050
+ verified: true;
2051
+ }>;
2052
+ };
1594
2053
  /** Social sign-in. The web `start`/`callback` flows are browser redirects
1595
- * (not JSON calls); `native` is the mobile token-exchange the SDK wraps. */
2054
+ * (not JSON calls): send the browser to
2055
+ * `GET /v1/auth/oauth/{provider}/start?redirect_uri=…` (server-side, with
2056
+ * your key) and hand the returned `code`+`state` to `…/callback`; `native`
2057
+ * is the mobile / broker token-exchange the SDK wraps. `oidc` is the
2058
+ * tenant's generic OIDC / SSO issuer (auth config `providers.oidc` —
2059
+ * Okta / Entra / Auth0 / any OpenID Connect IdP, or a SAML broker that
2060
+ * speaks OIDC); it rides the same three routes. */
1596
2061
  oauth: {
1597
2062
  /** Native social sign-in: exchange a provider `id_token`/`access_token`
1598
2063
  * for a vxil session (`linked` marks whether the user was created or
1599
2064
  * matched to an existing identity). */
1600
- native: (provider: "google" | "apple" | "github" | "facebook" | "mock", input: {
2065
+ native: (provider: "google" | "apple" | "github" | "facebook" | "mock" | "oidc", input: {
1601
2066
  id_token?: string;
1602
2067
  access_token?: string;
1603
2068
  nonce?: string;
2069
+ anonymous_token?: string;
1604
2070
  }) => Promise<{
1605
2071
  user_id: string;
1606
2072
  session: AuthSession;
1607
2073
  verified: boolean;
1608
- linked: "created" | "existing";
2074
+ linked: "created" | "existing" | "promoted" | "merged";
2075
+ /** sessions taken over by auth config session.maxConcurrent (present once opted in) */
2076
+ took_over?: string[];
1609
2077
  }>;
1610
2078
  };
1611
2079
  };
@@ -1613,8 +2081,10 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1613
2081
  createPolicy: (input: {
1614
2082
  name: string;
1615
2083
  key_template: string;
1616
- limit: number;
1617
- window_seconds: number;
2084
+ /** omitted ⇒ seeded from the tenant's rate-limits config `defaults` (F8-54) */
2085
+ limit?: number;
2086
+ /** omitted ⇒ seeded from the tenant's rate-limits config `defaults` (F8-54) */
2087
+ window_seconds?: number;
1618
2088
  behavior?: "block" | "shape";
1619
2089
  }) => Promise<RateLimitPolicy>;
1620
2090
  listPolicies: () => Promise<RateLimitPolicy[]>;
@@ -1704,7 +2174,7 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1704
2174
  create: (input: {
1705
2175
  collection: string;
1706
2176
  singular?: string;
1707
- /** End-user owner-scoping (docs/end-user-principals-design.md §5.1):
2177
+ /** End-user owner-scoping (https://vxil.com/docs/guide/09-security-and-multitenancy):
1708
2178
  * names an existing `string` field that holds the owner id. When set,
1709
2179
  * the cms worker auto-scopes owned reads/writes to the VERIFIED
1710
2180
  * end-user principal (default-deny) in end-user mode; a no-op in
@@ -1732,14 +2202,31 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1732
2202
  * scalar-valued types only. Concurrent duplicates → 409 unique_violation. */
1733
2203
  unique?: boolean;
1734
2204
  /** relation fields only: what a delete of the referenced item does to
1735
- * this one (bounded fan-out, cms.md §11). */
1736
- on_delete?: "cascade" | "set_null";
2205
+ * this one — cascade / set_null (bounded fan-out, cms.md §11) or
2206
+ * `restrict` (§11.1: the delete is refused with 409 `referenced`
2207
+ * while a live reference exists). */
2208
+ on_delete?: "cascade" | "set_null" | "restrict";
2209
+ /** FIELD-LEVEL read security (cms.md §18): the end-user ORG ROLE slugs
2210
+ * allowed to READ this field. Omitted/`[]` = ungated. A non-empty list
2211
+ * is FAIL-SAFE — in verified end-user mode the field is OMITTED from
2212
+ * every read (get / list / query / `$expand` / the write-response echo)
2213
+ * unless the session's verified roles intersect it, it is UNQUERYABLE
2214
+ * (filter/sort/group-by on it is a 422), and it is NEVER served on the
2215
+ * anonymous public lane. A SERVER caller (an API key with no end-user
2216
+ * session) still sees every field. Writes are unaffected. ≤16 entries,
2217
+ * each `^[a-z0-9][a-z0-9_-]{0,31}$` (the `orgs` role alphabet). */
2218
+ read_roles?: string[];
1737
2219
  }>;
2220
+ /** Per-record action buttons (cms.md §17): `[{ key, label, fn }]` —
2221
+ * exactly ONE human-initiated step each; `fn` names a deployed tenant
2222
+ * function invoked by `items.runAction`. ≤8 per collection. */
2223
+ actions?: CmsActionDef[];
1738
2224
  }) => Promise<{
1739
2225
  collection: string;
1740
2226
  fields: number;
1741
2227
  owner_field?: string;
1742
2228
  public?: boolean;
2229
+ actions?: CmsActionDef[];
1743
2230
  }>;
1744
2231
  list: () => Promise<Array<{
1745
2232
  collection: string;
@@ -1748,6 +2235,14 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1748
2235
  owner_field?: string | null;
1749
2236
  }>>;
1750
2237
  addField: (collection: string, field: Record<string, unknown>) => Promise<void>;
2238
+ /** Set (or clear, with `null`/`[]`) a field's READ-ROLE gate (cms.md §18) —
2239
+ * the same-type in-place alter on the fields route. Re-sends the field's
2240
+ * `type` (required by the alter path); every OTHER attribute the field
2241
+ * carries is re-sent from `rest`, because the alter overwrites the whole
2242
+ * definition. In verified end-user mode a gated field is omitted from every
2243
+ * read unless the session's verified roles intersect `roles`; server-caller
2244
+ * reads and ALL writes are unaffected. */
2245
+ setFieldReadRoles: (collection: string, field: string, type: "string" | "text" | "int" | "float" | "bool" | "datetime" | "json" | "relation" | "file", roles: string[] | null, rest?: Record<string, unknown>) => Promise<void>;
1751
2246
  /** Set (or clear, with `null`) the collection's end-user owner-scope flag
1752
2247
  * (design §5.1). Names an existing `string` field that holds the owner id. */
1753
2248
  setOwnerField: (collection: string, ownerField: string | null) => Promise<void>;
@@ -1757,6 +2252,11 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1757
2252
  * the owner_field are never exposed. `false` closes the lane (and purges the
1758
2253
  * edge cache). Read the public side with `cmsPublicUrl` / `listCmsPublic`. */
1759
2254
  setPublic: (collection: string, isPublic: boolean) => Promise<void>;
2255
+ /** Replace the collection's per-record ACTION list (cms.md §17) — the
2256
+ * `{ actions }` fields-route meta-op; `[]` clears. Each action is ONE
2257
+ * human-initiated step: the dashboard renders it as a button per record,
2258
+ * and `items.runAction` invokes its deployed function. */
2259
+ setActions: (collection: string, actions: CmsActionDef[]) => Promise<void>;
1760
2260
  };
1761
2261
  items: {
1762
2262
  /** `lock` serializes same-key writers (per-tenant advisory lock); `guard`
@@ -1814,6 +2314,27 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1814
2314
  set_null: number;
1815
2315
  }>;
1816
2316
  publish: (collection: string, itemId: string) => Promise<Record<string, unknown>>;
2317
+ /** P1-12 (cms.md §11.1): the BOUNDED reverse read — which live items
2318
+ * reference this one, through which relation field. Owner-scoped in
2319
+ * end-user mode like `get`. `count` is the page returned (≤ limit, max
2320
+ * 100), never a total; `has_more` says the cap was hit. A DELETE refused
2321
+ * with 409 `referenced` (an `on_delete: 'restrict'` edge) names the same
2322
+ * refs in its `referencing` field. */
2323
+ backlinks: (collection: string, itemId: string, opts?: {
2324
+ limit?: number;
2325
+ }) => Promise<CmsBacklinksPage>;
2326
+ /** P1-7 (cms.md §17): run ONE declared per-record action — invokes the
2327
+ * action's deployed function with `{ collection, item_id, action, actor,
2328
+ * item }` and returns its result. 404 when the key is not declared;
2329
+ * 502 `action_failed` (with `upstream.code` = the function's error
2330
+ * class) when the function fails. Requires cms:write. */
2331
+ runAction: (collection: string, itemId: string, key: string) => Promise<{
2332
+ collection: string;
2333
+ item_id: string;
2334
+ action: string;
2335
+ fn: string;
2336
+ result: unknown;
2337
+ }>;
1817
2338
  /** cms-rel B2 (cms.md §12): bounded group-by aggregate. fns count|sum|
1818
2339
  * min|max|avg over index-slot-bound fields; groupBy ≤2 slot-bound
1819
2340
  * fields; filter = the full query DSL incl. ONE-hop dotted join terms
@@ -1953,7 +2474,7 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1953
2474
  * only the envelope: membership, read-cursors, mute, and a directed block
1954
2475
  * list; the messages themselves are `comments` rows on the
1955
2476
  * `dm:<conversation_id>` topic. Gated by its own `dm` config, independent of
1956
- * comments. See docs/features/dm.md.
2477
+ * comments. See vxil.com/docs/guide/06-feature-catalog.
1957
2478
  */
1958
2479
  readonly dm: {
1959
2480
  /** Read the resolved DM config (defaults merged with the stored partial). */
@@ -2375,6 +2896,24 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
2375
2896
  }>;
2376
2897
  }>;
2377
2898
  };
2899
+ /** Every audit event the platform can emit, with the subscribable prefixes
2900
+ * and their counts — the discovery surface for `subscribe({ event_prefixes })`.
2901
+ * Static reference data (CI-generated from the emitters), identical for every
2902
+ * tenant. `level` is the event CLASS ('lifecycle' | 'failure'), not the
2903
+ * payload's 'info'|'warn'|'error' severity key. */
2904
+ eventCatalog: () => Promise<{
2905
+ count: number;
2906
+ events: Array<{
2907
+ name: string;
2908
+ feature: string;
2909
+ level: "lifecycle" | "failure";
2910
+ payload_keys?: string[];
2911
+ }>;
2912
+ prefixes: Array<{
2913
+ prefix: string;
2914
+ count: number;
2915
+ }>;
2916
+ }>;
2378
2917
  };
2379
2918
  readonly orgs: {
2380
2919
  create: (input: {
@@ -2657,20 +3196,20 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
2657
3196
  expires_at: string | null;
2658
3197
  }>;
2659
3198
  sharedLinks: {
2660
- /** Public (unauthenticated) URL for the object; revocable. */
2661
- create: (objectId: string, opts?: {
2662
- ttl_seconds?: number;
2663
- }) => Promise<{
3199
+ /** Public (unauthenticated) URL for the object; revocable. Optional
3200
+ * `max_downloads` (1 = one-time link) and an absolute `expires_at`; the
3201
+ * public resolver answers 410 `link_exhausted` / `link_expired` past
3202
+ * either bound. */
3203
+ create: (objectId: string, opts?: CreateSharedLinkOptions) => Promise<{
2664
3204
  link_id: string;
2665
3205
  url: string;
2666
- expires_at: string | null;
3206
+ expires_in: number;
3207
+ expires_at: string;
3208
+ max_downloads: number | null;
3209
+ downloads: number;
2667
3210
  }>;
2668
- /** List an object's active (unrevoked, unexpired) shared links. */
2669
- list: (objectId: string) => Promise<Array<{
2670
- link_id: string;
2671
- created_at: string;
2672
- expires_at: string | null;
2673
- }>>;
3211
+ /** List an object's active (unrevoked, unexpired, unexhausted) shared links. */
3212
+ list: (objectId: string) => Promise<FileSharedLink[]>;
2674
3213
  revoke: (linkId: string) => Promise<void>;
2675
3214
  };
2676
3215
  };
@@ -2896,6 +3435,47 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
2896
3435
  done: boolean;
2897
3436
  max_seq: number;
2898
3437
  }>;
3438
+ /** Classify an input into one of YOUR labels (or, with `multi:true`, every
3439
+ * label that applies) — a schema-forced verdict over the generate path:
3440
+ * the allowed labels ride the structured-output schema as an enum, so the
3441
+ * model cannot answer off-list; the reply is `{ label | labels,
3442
+ * confidence, rationale }`. Same metering/cache/provider rules as
3443
+ * `generate` (a still-invalid verdict is a billed 422
3444
+ * output_schema_mismatch). Caps: 2–64 labels (≤64 chars, unique), input
3445
+ * ≤32k chars (a string or ordered `{ text }` parts joined into ONE
3446
+ * input), rubric ≤4k, ≤16 examples. */
3447
+ classify: (input: AiVerdictOptions & {
3448
+ input: string | Array<{
3449
+ text: string;
3450
+ }>;
3451
+ labels: string[];
3452
+ multi?: boolean;
3453
+ rubric?: string;
3454
+ examples?: Array<{
3455
+ input: string;
3456
+ label: string;
3457
+ }>;
3458
+ }) => Promise<AiClassifyResult>;
3459
+ /** Grade a candidate response (or rank up to 8) against YOUR criteria —
3460
+ * a schema-forced integer score inside `scale` (default 0..10) plus
3461
+ * `pass|fail` for one candidate, or `scores[]` + `best` (+ `verdict:'tie'`)
3462
+ * for several — `candidates` always yields `scores`/`best`, even at
3463
+ * length 1. Same metering/cache/provider rules as `generate`. Caps:
3464
+ * input ≤16k chars, candidates ≤8 × 4k chars, criteria ≤16 items / 4k
3465
+ * chars, scale span ≤100. */
3466
+ judge: (input: AiVerdictOptions & {
3467
+ input: string;
3468
+ candidate?: string;
3469
+ candidates?: string[];
3470
+ criteria: string | Array<{
3471
+ name: string;
3472
+ weight?: number;
3473
+ }>;
3474
+ scale?: {
3475
+ min: number;
3476
+ max: number;
3477
+ };
3478
+ }) => Promise<AiJudgeResult>;
2899
3479
  /** Embed a batch of strings (1–256). */
2900
3480
  embed: (input: {
2901
3481
  input: string[];
@@ -3045,27 +3625,43 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
3045
3625
  */
3046
3626
  readonly payments: {
3047
3627
  /** Provider + ledger capability surface (+ the `missing` set an agent can act
3048
- * on to recommend a provider). */
3628
+ * on to recommend a provider). `client` is the RevenueCat tenant's PUBLIC
3629
+ * SDK handout (project id + public SDK key — never the secret key); null
3630
+ * for every other provider. */
3049
3631
  capabilities: () => Promise<{
3050
3632
  provider: string;
3051
3633
  capabilities: string[];
3052
3634
  ledger_capabilities: string[];
3053
3635
  missing: string[];
3636
+ client: {
3637
+ revenuecat: {
3638
+ project_id: string;
3639
+ public_sdk_key: string;
3640
+ };
3641
+ } | null;
3054
3642
  }>;
3055
3643
  /** The user's effective entitlement snapshot (the multi-sub tier fold):
3056
- * tier + boolean entitlements + numeric quotas. */
3057
- getEntitlements: (userId: string) => Promise<{
3058
- user_id: string;
3059
- tier: string;
3060
- entitlements: string[];
3061
- quotas: Record<string, number>;
3062
- }>;
3644
+ * tier + boolean entitlements + numeric quotas — plus the access window
3645
+ * (`since` / `until` = the winning subscription's period end, or end +
3646
+ * grace when it is past_due), `as_of` (server time), the `environment`
3647
+ * the winning subscription was written from, and OPTIONAL inline reads:
3648
+ * `quota` inlines one quota, `creditType` inlines the same owner-bound
3649
+ * balance `getBalance` returns — ONE call for a thin client's paywall.
3650
+ * A non-2xx answer means UNKNOWN: render the last cached answer, never
3651
+ * free (payments.md §3a). */
3652
+ getEntitlements: (userId: string, opts?: {
3653
+ quota?: string;
3654
+ creditType?: string;
3655
+ }) => Promise<PaymentsEntitlementView>;
3063
3656
  /** Boolean gate: does the user hold `entitlement`? Returns granted + its
3064
- * source (subscription/tier) + the resolved tier. */
3657
+ * source (subscription/tier) + the resolved tier, plus `until` (how long
3658
+ * the answer is good for) and `as_of`. Non-2xx = unknown, never free. */
3065
3659
  hasEntitlement: (userId: string, entitlement: string) => Promise<{
3066
3660
  granted: boolean;
3067
3661
  source: string | null;
3068
3662
  tier: string;
3663
+ until: string | null;
3664
+ as_of: string;
3069
3665
  }>;
3070
3666
  /** A single numeric quota for the user (e.g. `seats`, `api_calls`); null when
3071
3667
  * the resolved tier declares no such quota. Convenience over getEntitlements. */
@@ -3216,10 +3812,107 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
3216
3812
  status: "pending" | "succeeded" | "failed";
3217
3813
  amount_cents: number;
3218
3814
  }>;
3219
- /** The provider-hosted billing-management portal URL for an end user (Stripe
3220
- * billing portal; mock is deterministic; RC/Paddle/PayPal → 501). */
3815
+ /** Where the user MANAGES their subscription, resolved by FUNDING SOURCE
3816
+ * (the provider of their live subscription, not the tenant's primary):
3817
+ * `kind: 'portal'` = a provider-minted portal session URL (Stripe, Paddle
3818
+ * via the captured customer id, mock); `'store'` = the App Store / Play
3819
+ * Store manage-subscriptions page for a RevenueCat row that recorded its
3820
+ * store; `'none'` (url null) = nothing to link (PayPal, a manual grant, no
3821
+ * live subscription on a portal-less primary). */
3221
3822
  getCustomerPortalUrl: (userId: string) => Promise<{
3222
- url: string;
3823
+ url: string | null;
3824
+ kind: "portal" | "store" | "none";
3825
+ provider: string | null;
3826
+ }>;
3827
+ /** SERVER-ONLY. A support / promotional / migration grant: a `manual`
3828
+ * subscription row (no provider behind it) folded like a webhook —
3829
+ * entitlements + the tier's recurring credits apply; `until` null =
3830
+ * open-ended, else access ends at `until`. Idempotent on
3831
+ * `idempotency_key` (a replay returns the first grant, `replayed: true`).
3832
+ * 422 unknown_tier when the tier is not in ledger.tierMap. */
3833
+ grantSubscription: (input: {
3834
+ user_id: string;
3835
+ tier: string;
3836
+ until?: string | null;
3837
+ reason: string;
3838
+ idempotency_key: string;
3839
+ }) => Promise<{
3840
+ subscription_id: string | null;
3841
+ user_id: string;
3842
+ tier: string;
3843
+ status: string;
3844
+ until: string | null;
3845
+ replayed: boolean;
3846
+ }>;
3847
+ /** SERVER-ONLY. Revoke a MANUAL grant now (409 not_manual for a provider
3848
+ * subscription — use `cancel`). Never touches a provider. */
3849
+ revokeSubscription: (subscriptionId: string) => Promise<{
3850
+ subscription_id: string;
3851
+ status: string;
3852
+ cancel_at: string | null;
3853
+ revoked_at: string | null;
3854
+ }>;
3855
+ /** SERVER-ONLY. Re-read a customer's subscriptions FROM the provider and
3856
+ * fold them through the same conditional upsert a webhook uses (never a
3857
+ * second write path). `dry_run` diffs without writing. Capped 30/min per
3858
+ * project (429 rate_limited). The migration backfill (`vxil migrate
3859
+ * payments --from-provider …`) drives this per customer. */
3860
+ syncSubscriptions: (input: {
3861
+ user_id?: string;
3862
+ customer_ref?: string;
3863
+ provider?: "mock" | "stripe" | "paddle" | "revenuecat" | "paypal";
3864
+ dry_run?: boolean;
3865
+ }) => Promise<PaymentsSyncOutcome>;
3866
+ /** The server leg of "Restore Purchases": in end-user mode the verified
3867
+ * principal's OWN provider state is re-read (no user id needed); in server
3868
+ * mode pass `user_id`. 3 per hour per user (429). Returns the sync outcome
3869
+ * plus the fresh entitlement view in one answer. */
3870
+ restoreSubscriptions: (input?: {
3871
+ user_id?: string;
3872
+ provider?: "mock" | "stripe" | "paddle" | "revenuecat" | "paypal";
3873
+ }) => Promise<PaymentsSyncOutcome & {
3874
+ entitlements: PaymentsEntitlementView;
3875
+ }>;
3876
+ /** SERVER-ONLY. Account merge: move EVERY provider's subscriptions,
3877
+ * customer links and available credit balances of `from_user_id` onto
3878
+ * `into_user_id` — at-most-once per pair. The consumer of the
3879
+ * `auth.user.merged { from, into }` audit event. */
3880
+ reKeySubscriptions: (input: {
3881
+ from_user_id: string;
3882
+ into_user_id: string;
3883
+ }) => Promise<{
3884
+ from_user_id: string;
3885
+ into_user_id: string;
3886
+ moved: number;
3887
+ credits_moved: number;
3888
+ }>;
3889
+ /** SERVER-ONLY, mock/dev tenants ONLY (403 simulation_not_allowed
3890
+ * elsewhere). Drive a scripted lifecycle (renewal, expiry, refund-pair,
3891
+ * past-due-grace, cross-platform-unlock, transfer) through the mock
3892
+ * webhook path and judge it with the conformance oracle. */
3893
+ simulate: (input: {
3894
+ scenario: "refund-pair" | "cross-platform-unlock" | "renewal" | "expiry" | "past-due-grace" | "transfer";
3895
+ user_id: string;
3896
+ tier?: string;
3897
+ }) => Promise<PaymentsSimulationResult>;
3898
+ /** The last report-only reconciliation sweep result for this project
3899
+ * (`run: null` before the first daily tick). */
3900
+ reconcile: () => Promise<{
3901
+ run: {
3902
+ run_id: string;
3903
+ ran_at: string | null;
3904
+ clean: boolean;
3905
+ discrepancy_count: number;
3906
+ lapsed_count: number;
3907
+ findings: Array<{
3908
+ kind: string;
3909
+ count: number;
3910
+ ids: string[];
3911
+ }>;
3912
+ } | null;
3913
+ enforce_period_end: {
3914
+ slack_hours: number;
3915
+ } | null;
3223
3916
  }>;
3224
3917
  /** List charges newest-first (the refund enabler — discover the charge_id).
3225
3918
  * Filters: user_id, status, limit (clamped 1..100, default 50). */
@@ -3249,7 +3942,9 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
3249
3942
  list: (q?: {
3250
3943
  provider?: "mock" | "stripe" | "paddle" | "revenuecat" | "paypal";
3251
3944
  event_type?: string;
3252
- outcome?: "received" | "processed" | "error" | "sig_failed" | "parse_failed" | "reprocessed";
3945
+ outcome?: "received" | "processed" | "error" | "sig_failed" | "parse_failed" | "reprocessed" | "ignored" | "unowned" | "rejected_environment";
3946
+ /** the provider-reported environment axis (not the API key's label) */
3947
+ environment?: "production" | "sandbox";
3253
3948
  since?: string;
3254
3949
  cursor?: string;
3255
3950
  limit?: number;