@vxil/sdk 0.17.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -160,7 +160,11 @@ export interface Delivery {
160
160
  template_id: string;
161
161
  locale: string;
162
162
  to_email: string;
163
- status: 'queued' | 'sent' | 'failed' | 'suppressed';
163
+ /** `test_sink` (2026-10-04): the recipient matched the project's
164
+ * `notifications.suppression.testRecipients` — rendered in full and recorded,
165
+ * but no provider was called and the send is not billed. Read what would
166
+ * have gone out with `notifications.delivery(id)` (`rendered`). */
167
+ status: 'queued' | 'sent' | 'failed' | 'suppressed' | 'test_sink';
164
168
  attempts: number;
165
169
  provider: string;
166
170
  provider_message_id: string | null;
@@ -185,6 +189,27 @@ export interface Delivery {
185
189
  * `delivery_id` on the 202.) */
186
190
  deliver_after?: string | null;
187
191
  }
192
+ /** One delivery by id (`notifications.delivery(id)`): the row plus a render.
193
+ * For a `test_sink` row `rendered` is the FULL render the send produced (its
194
+ * data, its locale — an `ar` template carries `dir="rtl"`) and `rendered_from`
195
+ * is `'send'` — EXCEPT auth mail (the `magic-link` / `otp-code` templates and
196
+ * the auth feature's password-reset / e-mail-verification mail) and a row
197
+ * sunk at delivery time (an address added to `testRecipients` after the send
198
+ * was queued): those are rendered WITHOUT the send's data (`rendered_from:
199
+ * 'send_redacted'`), so a sign-in link, code or reset link is never stored or
200
+ * readable. For every other row the template variables were never stored, so
201
+ * `rendered` is the template re-rendered with no data (`rendered_from:
202
+ * 'template'`), or null when it cannot be rendered. */
203
+ export interface DeliveryDetail extends Delivery {
204
+ last_error_msg?: string | null;
205
+ rendered: {
206
+ subject: string;
207
+ html: string;
208
+ text: string;
209
+ locale: string;
210
+ } | null;
211
+ rendered_from?: 'send' | 'send_redacted' | 'template';
212
+ }
188
213
  /** An ADDITIVE, non-fatal note on a 202 send — today only `mock_provider`
189
214
  * (the send is recorded but no email leaves vxil). */
190
215
  export interface NotificationWarning {
@@ -403,9 +428,20 @@ export interface JobRun {
403
428
  * problem (400 / 413 / 422 — e.g. a callback key the collection does not
404
429
  * declare) is retried with only the status column (a progress update tries
405
430
  * the declared `progress_fields` first), so an undeclared key no longer
406
- * strands the row; this says what was dropped. Never flips the run; not cleared by a later
407
- * successful mirror (compare `generation_status` / `at`). */
431
+ * strands the row; this says what was dropped (`status_written`: whether
432
+ * the record holds the status word). Never flips the run. A later TERMINAL
433
+ * mirror that lands cleanly moves it to `last_mirror_error` (null here
434
+ * again); a non-terminal success leaves it. A write the record refuses
435
+ * because it already holds a terminal word, or because its
436
+ * `status_mirror.fence_field` names another run, is skipped quietly —
437
+ * never a mirror_error. */
408
438
  mirror_error?: JobRunMirrorError | null;
439
+ /** the single-run read, generation runs only: the `mirror_error` a later
440
+ * clean terminal mirror healed; null otherwise */
441
+ last_mirror_error?: JobRunMirrorError | null;
442
+ /** the single-run read, generation runs only: when that healing terminal
443
+ * mirror landed (ISO); null otherwise */
444
+ mirror_ok_at?: string | null;
409
445
  /** the single-run read only: the enqueue `concurrency_key` this run holds
410
446
  * while running or waiting; null for an unkeyed run */
411
447
  concurrency_key?: string | null;
@@ -458,6 +494,7 @@ export interface JobEnqueueResult {
458
494
  export interface JobRunsQuery {
459
495
  job_name?: string;
460
496
  state?: JobRun['state'] | string;
497
+ /** exact run_ids, at most 50 distinct (more answers 422 — page in chunks of 50) */
461
498
  ids?: string[];
462
499
  batch_id?: string;
463
500
  /** ISO: runs created at or after */
@@ -501,13 +538,25 @@ export interface JobRunMirrorError {
501
538
  code: string | null;
502
539
  /** the target's message (≤ 200 chars), e.g. "unknown field 'video_url'" */
503
540
  message: string | null;
541
+ /** the target's hint (≤ 200 chars) when it sent one — e.g. the engine
542
+ * detail of a cms validate hook that could not evaluate */
543
+ hint?: string;
504
544
  /** when it was refused (ISO) */
505
545
  at: string;
546
+ /** whether the record holds `generation_status` after this write: true
547
+ * when a narrower retry carrying the status word landed (or the write left
548
+ * the word out because the record already held it); false when no write
549
+ * carrying it landed — the full write was already the status column alone,
550
+ * the refusal was not a body problem (401 / 403 / 404 / 409 / 429 / 5xx,
551
+ * a timeout), or every narrower retry was refused. Absent on errors
552
+ * recorded before it existed. */
553
+ status_written?: boolean;
506
554
  /** present when a narrower retry LANDED: the keys that were not written */
507
555
  fields_dropped?: string[];
508
- /** present when every narrower retry was refused too (the last one's HTTP
509
- * status, 0 = no answer): the status word itself was refused and the
510
- * record still holds the previous status */
556
+ /** present when narrower retries ran and none landed: the LAST one's HTTP
557
+ * status (0 = no answer). The ladder stops at the first refusal that is not
558
+ * a body problem, so read `status_written`, not this, for "did the status
559
+ * word land" */
511
560
  retry_status?: number;
512
561
  }
513
562
  /** A run's latest progress report (`JobRun.progress`): only these keys pass. */
@@ -748,6 +797,28 @@ export interface PaymentsSimulationResult {
748
797
  pass: boolean;
749
798
  };
750
799
  }
800
+ /** `payments.moveSubscriptionUntil` (the test clock) — the moved row and the
801
+ * user's entitlement snapshot after the refold. */
802
+ export interface PaymentsTestClockResult {
803
+ subscription_id: string;
804
+ user_id: string;
805
+ tier: string | null;
806
+ /** 'lapsed' after a move into the past, 'active' after one into the future */
807
+ status: string;
808
+ since: string | null;
809
+ until: string;
810
+ previous_until: string | null;
811
+ /** true when THIS move ended access (payments.subscription.lapsed emitted) */
812
+ lapsed: boolean;
813
+ /** why the project may use the test clock */
814
+ lane: 'mock' | 'sandbox' | 'dev';
815
+ entitlement: {
816
+ tier: string;
817
+ entitlements: string[];
818
+ quotas: Record<string, number>;
819
+ source_sub_id: string | null;
820
+ };
821
+ }
751
822
  /** The ONE complete entitlement read (GET /v1/payments/entitlements; guide ch. 6,
752
823
  * payments). `until` is the winning subscription's current_period_end (+ the
753
824
  * configured grace window when it is past_due); null on the free baseline. */
@@ -880,6 +951,10 @@ export interface FileUploadUrl {
880
951
  expires_in: number;
881
952
  /** When the OBJECT expires (null = never). */
882
953
  expires_at: string | null;
954
+ /** The headers the PUT to `upload_url` must send, exactly — each one is
955
+ * signed into the URL: `content-type`, plus `x-amz-checksum-sha256` when
956
+ * the request declared `checksum_sha256`. */
957
+ upload_headers?: Record<string, string>;
883
958
  }
884
959
  /** `files.usage` answer. */
885
960
  export interface FilesUsage {
@@ -893,7 +968,15 @@ export interface FilesUsage {
893
968
  maxObjectBytes?: number;
894
969
  maxTotalBytes?: number;
895
970
  };
896
- available: Record<string, number | null>;
971
+ /** Headroom left under the quotas. Never negative: when usage sits ABOVE a
972
+ * lowered quota (or a plan downgrade) both read 0 and `over_quota` is true
973
+ * — new uploads answer 422 quota_exceeded until usage drops back under. */
974
+ available: {
975
+ object_slots: number;
976
+ bytes_remaining: number;
977
+ over_quota?: boolean;
978
+ [key: string]: number | boolean | null | undefined;
979
+ };
897
980
  /** Your plan's storage ceiling, and whether `quotas.maxTotalBytes` is that
898
981
  * ceiling (`plan`) or your own lower value (`config`). */
899
982
  storage_plan?: {
@@ -1361,7 +1444,8 @@ export interface PaymentsSubscriptionStatusEventPayload {
1361
1444
  current_period_end: string | null;
1362
1445
  environment: PaymentsEventEnvironment;
1363
1446
  /** what wrote it: the provider event type, `subscription.synced` /
1364
- * `subscription.restored`, or `period_end_enforced` */
1447
+ * `subscription.restored`, `period_end_enforced`, or `test_clock` (a
1448
+ * `moveSubscriptionUntil` on a non-production project ended the row) */
1365
1449
  event_type: string;
1366
1450
  slack_hours: number | null;
1367
1451
  level: 'info';
@@ -1502,6 +1586,24 @@ export interface PaymentsConformanceEventPayload {
1502
1586
  level: 'info' | 'error';
1503
1587
  state: 'ok' | 'broken';
1504
1588
  }
1589
+ /** `payments.test_clock.moved` — the test clock moved a manual grant's or a
1590
+ * purchase pass's end (`moveSubscriptionUntil`; non-production projects only).
1591
+ * `lapsed` is true when this move ended access (a `payments.subscription.lapsed`
1592
+ * with `event_type: 'test_clock'` follows); `lane` names why the project was
1593
+ * allowed: `mock` provider, a `sandbox` provider environment, or a `dev`
1594
+ * (preview / `vxil dev up`) project. */
1595
+ export interface PaymentsTestClockMovedEventPayload {
1596
+ end_user_id: string;
1597
+ subscription_id: string;
1598
+ provider_sub_id: string;
1599
+ tier: string | null;
1600
+ previous_until: string | null;
1601
+ until: string;
1602
+ lapsed: boolean;
1603
+ lane: 'mock' | 'sandbox' | 'dev';
1604
+ level: 'info';
1605
+ state: 'ok';
1606
+ }
1505
1607
  /** Event name → typed `data`, for the events that settle work you started and
1506
1608
  * (since 2026-10-01) every `payments.*` event. `VxilEventPayload<'job.generation.failed'>`
1507
1609
  * names one; everything else on the catalog is `Record<string, unknown>` until
@@ -1536,6 +1638,7 @@ export interface VxilEventPayloads {
1536
1638
  'payments.reconcile.discrepancy': PaymentsReconcileDiscrepancyEventPayload;
1537
1639
  'payments.reconcile.ok': PaymentsReconcileOkEventPayload;
1538
1640
  'payments.conformance': PaymentsConformanceEventPayload;
1641
+ 'payments.test_clock.moved': PaymentsTestClockMovedEventPayload;
1539
1642
  }
1540
1643
  export type VxilEventPayload<E extends string> = E extends keyof VxilEventPayloads ? VxilEventPayloads[E] : Record<string, unknown>;
1541
1644
  /** The `trigger` label on the envelope. The config spells two of them in
@@ -1543,9 +1646,11 @@ export type VxilEventPayload<E extends string> = E extends keyof VxilEventPayloa
1543
1646
  export type FunctionEnvelopeTrigger = 'http' | 'cms-hook' | 'auth-hook' | 'queue' | 'cron' | 'webhook';
1544
1647
  /** The audiences a function's scoped callback tokens are minted for — one per
1545
1648
  * feature its declared scopes reach. `control-plane` carries `users:*` /
1546
- * `usage:read` only. There is deliberately no `functions` audience: a function
1547
- * cannot call another function. */
1548
- export type FunctionCallbackAudience = 'cms' | 'payments' | 'notifications' | 'comments' | 'files' | 'ai' | 'rag' | 'vector-search' | 'activity-feed' | 'orgs' | 'auth' | 'jobs' | 'realtime' | 'rate-limits' | 'control-plane';
1649
+ * `usage:read` / `audit:read` only; `webhooks` carries `webhooks:read` only (read the inbound
1650
+ * sources and received events — e.g. `GET /v1/webhooks/events?after=`). There
1651
+ * is deliberately no `functions` audience: a function cannot call another
1652
+ * function. */
1653
+ export type FunctionCallbackAudience = 'cms' | 'payments' | 'notifications' | 'comments' | 'files' | 'ai' | 'rag' | 'vector-search' | 'activity-feed' | 'orgs' | 'auth' | 'jobs' | 'realtime' | 'rate-limits' | 'control-plane' | 'webhooks';
1549
1654
  /** One short-lived scoped token per audience your scopes imply
1550
1655
  * (`env.scoped_jwts.cms`, `env.scoped_jwts['control-plane']`); an audience your
1551
1656
  * scopes do not reach is absent. Send it as `Authorization: Bearer …` to
@@ -1607,6 +1712,43 @@ export interface WebhookTriggerPayload<D = Record<string, unknown>> {
1607
1712
  truncated: true;
1608
1713
  } | null;
1609
1714
  }
1715
+ /** The verification parameters of an inbound `hmac` source (`webhooks.sources.create`). */
1716
+ export interface WebhookHmacVerify {
1717
+ /** the header carrying the signature (case-insensitive) */
1718
+ header: string;
1719
+ algorithm?: 'sha256' | 'sha1';
1720
+ encoding?: 'hex' | 'base64';
1721
+ /** stripped before comparing (e.g. `sha256=`); required on the request when set */
1722
+ prefix?: string;
1723
+ /** a header carrying the signing timestamp (unix seconds or ISO 8601): the
1724
+ * signed message becomes `<timestamp><timestamp_separator><body>` */
1725
+ timestamp_header?: string;
1726
+ /** default '.'; '' to concatenate */
1727
+ timestamp_separator?: string;
1728
+ /** default 300, 1..3600 */
1729
+ tolerance_seconds?: number;
1730
+ event_id_header?: string;
1731
+ event_type_header?: string;
1732
+ }
1733
+ /** `payload.data` of a `webhook` invocation fired by an inbound source whose
1734
+ * `target_function` is this function (`payload.event === 'inbound_webhook.received'`). */
1735
+ export interface InboundWebhookTriggerData {
1736
+ vxil_event_id: string;
1737
+ source_id: string;
1738
+ provider: string;
1739
+ provider_event_id: string | null;
1740
+ event_type: string | null;
1741
+ sig_verified: boolean;
1742
+ /** the provider's JSON body; null when it was larger than 48 000 bytes
1743
+ * (`payload_omitted: true`) — read it with
1744
+ * `GET /v1/webhooks/events?event_id=<vxil_event_id>` (scope webhooks:read) */
1745
+ payload: Record<string, unknown> | unknown[] | null;
1746
+ payload_omitted?: true;
1747
+ /** true on a `POST /v1/webhooks/sources/:id/test` probe */
1748
+ test?: true;
1749
+ /** true on a `POST /v1/webhooks/events/:id/replay` */
1750
+ replayed?: true;
1751
+ }
1610
1752
  /** `POST /v1/fn/:name`: `payload` is the JSON request body (the query
1611
1753
  * parameters on a GET). */
1612
1754
  export interface HttpFunctionEnvelope<P = unknown> extends FunctionEnvelopeBase {
@@ -2744,6 +2886,11 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
2744
2886
  readonly fn: { [K in keyof S["functions"] & string]: FnInvoker<S["functions"][K]["Input"], S["functions"][K]["Output"]>; };
2745
2887
  private call;
2746
2888
  readonly users: {
2889
+ /** Create or replace a tenant user by id. Upserting a deleted id brings it
2890
+ * back; upserting an ERASED id re-creates it as a new, empty user
2891
+ * (`erased_at` cleared — the erase already removed the old data). While
2892
+ * the erased user's files are still being erased the call fails with 409
2893
+ * `user_erase_pending`; retry later. `bulk` is all-or-nothing. */
2747
2894
  upsert: (user: {
2748
2895
  id: string;
2749
2896
  email?: string;
@@ -2758,6 +2905,15 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
2758
2905
  get: (id: string, opts?: {
2759
2906
  includeDeleted?: boolean;
2760
2907
  }) => Promise<VxilUser>;
2908
+ /**
2909
+ * Update a user. `email` / `display_name` / `avatar_url` are replaced
2910
+ * (null clears them). `attributes` is DEEP-MERGED into the stored
2911
+ * attributes (RFC 7396 merge patch): objects merge key by key, arrays and
2912
+ * scalars replace, and `null` deletes a key AT ANY DEPTH — including inside
2913
+ * a subtree the row does not have yet, where the null is simply dropped
2914
+ * (`{ attributes: { prefs: { phone: null } } }` never stores a null).
2915
+ * Scope users:write.
2916
+ */
2761
2917
  patch: (id: string, patch: {
2762
2918
  email?: string | null;
2763
2919
  display_name?: string | null;
@@ -2780,7 +2936,7 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
2780
2936
  * Merge a REGISTRY-ONLY user (a row you created with upsert that never
2781
2937
  * signed in) INTO another user of the tenant: its attributes fill the
2782
2938
  * survivor's gaps (the survivor wins on conflicts), the merged id is
2783
- * soft-deleted, its owner-scoped cms / files / payments rows move to the
2939
+ * soft-deleted, its owner-scoped cms / files / payments / jobs rows move to the
2784
2940
  * survivor, and `auth.user.merged { from, into, method: 'registry_merge' }`
2785
2941
  * is audited. `rekeyed: false` means a re-key could not finish inside the
2786
2942
  * request — the event is the backstop (call the feature's re-key route
@@ -2999,8 +3155,9 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
2999
3155
  /** Reset the row and re-enqueue the original message. */
3000
3156
  replay: (deliveryId: string) => Promise<void>;
3001
3157
  };
3002
- /** A single delivery by id (the list is `deliveries()`). */
3003
- delivery: (deliveryId: string) => Promise<Delivery>;
3158
+ /** A single delivery by id (the list is `deliveries()`), with its render —
3159
+ * the full one for a `test_sink` row (see `DeliveryDetail`). */
3160
+ delivery: (deliveryId: string) => Promise<DeliveryDetail>;
3004
3161
  /** Email broadcast campaigns (guide ch. 6, notifications): audience-ref fan-out
3005
3162
  * with quiet-hours + frequency-cap policy. `schedule_cron` sets a recurring
3006
3163
  * send (status `scheduled`); omit it for a `draft`. */
@@ -3053,6 +3210,9 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
3053
3210
  skipped_freq_cap: number;
3054
3211
  skipped_no_email: number;
3055
3212
  already_sent: number;
3213
+ /** recipients in `suppression.testRecipients` on this page: recorded
3214
+ * `test_sink`, never sent, never billed */
3215
+ test_sink?: number;
3056
3216
  }>;
3057
3217
  };
3058
3218
  };
@@ -3099,11 +3259,23 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
3099
3259
  brief?: boolean;
3100
3260
  }) => Promise<VxilPlan>;
3101
3261
  };
3262
+ /** The project audit log. Needs `audit:read` (the narrow read-only scope —
3263
+ * declare it on a function to read the log from its callback token) or
3264
+ * `features:read`. Server-only: a public / thin-client key cannot read it. */
3102
3265
  readonly audit: {
3266
+ /**
3267
+ * The audit trail, newest first. `subject` narrows to the events about ONE
3268
+ * thing: a user id (`payload.user_id` / `payload.id`), a cms record
3269
+ * (`payload.item_id` — the record's history), a verified end user
3270
+ * (`payload.end_user_id` — what that person changed through a thin-client
3271
+ * key or a function acting for them, and their payments events), or a
3272
+ * principal (`actor`). Server keys only.
3273
+ */
3103
3274
  list: (q?: {
3104
3275
  since?: string;
3105
3276
  cursor?: string;
3106
3277
  limit?: number;
3278
+ subject?: string;
3107
3279
  }) => Promise<Array<{
3108
3280
  id: string;
3109
3281
  event: string;
@@ -3121,6 +3293,8 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
3121
3293
  until?: string;
3122
3294
  after_id?: string;
3123
3295
  limit?: number;
3296
+ /** the same subject filter as `list` */
3297
+ subject?: string;
3124
3298
  }) => Promise<{
3125
3299
  events: Array<{
3126
3300
  id: string;
@@ -3225,7 +3399,16 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
3225
3399
  *
3226
3400
  * The answer's `generation_status` is `pending` on a fresh enqueue; a replay
3227
3401
  * of the same `idempotency_key` (`deduplicated: true`) carries the run's
3228
- * status NOW (`processing`, `completed` or `failed` too).
3402
+ * status NOW (`processing`, `completed` or `failed` too). The key is bound
3403
+ * to the request BODY: re-sending it with a different body (another
3404
+ * payload — a rebuilt `deadline_at` included —, provider, completion,
3405
+ * timeout, reserve…) will answer 422 `idempotency_key_reused` naming the
3406
+ * original run (the ai-v1 rule). That check is being rolled out: today a
3407
+ * mismatch is only logged, on every project, and the send is still
3408
+ * deduplicated to the first run; a changelog entry will announce
3409
+ * enforcement — so keep the body deterministic now. A 409 `generation_in_progress`
3410
+ * (Retry-After 5) means the run that key names is still placing its
3411
+ * credit hold.
3229
3412
  *
3230
3413
  * `reserve_credits` takes a PROVISIONAL held credit debit at enqueue
3231
3414
  * (linked to the run), committed on `completed` and reversed on
@@ -3233,7 +3416,8 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
3233
3416
  * `config.generation.maxReserveCredits` cap; in end-user mode `user_id` is
3234
3417
  * FORCED to the verified end-user (a mismatched user_id → 400). A 402 aborts
3235
3418
  * the enqueue (insufficient balance; the run ends with job.generation.failed
3236
- * `ReserveInsufficient` and the 402 names its `run_id`); a runaway over the
3419
+ * `ReserveInsufficient` and the 402 names its `run_id`; the key is released,
3420
+ * so the same request after a top-up starts a new run); a runaway over the
3237
3421
  * per-tenant outstanding-holds ceiling → 429 (Retry-After 5). Payments
3238
3422
  * unreachable at the hold → 503 `payments_unavailable` (Retry-After 15;
3239
3423
  * nothing started, nothing held — re-send the same request), unless
@@ -3254,8 +3438,9 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
3254
3438
  *
3255
3439
  * `opts.retryOnCapacity: { maxWaitMs }` retries a CAPACITY 429
3256
3440
  * (`generation_concurrency_exceeded`, `reserve_holds_exceeded`,
3257
- * `queue_full`) and a 503 `payments_unavailable`, honouring Retry-After
3258
- * with jitter, with the SAME
3441
+ * `queue_full`), a 503 `payments_unavailable` / `enqueue_interrupted` and a
3442
+ * 409 `generation_in_progress` (the key's run is still placing its credit
3443
+ * hold), honouring Retry-After with jitter, with the SAME
3259
3444
  * idempotency_key (one is generated when the input has none), until
3260
3445
  * maxWaitMs has passed — then the last 429 is thrown. */
3261
3446
  generation: (input: {
@@ -3299,6 +3484,13 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
3299
3484
  record_id: string;
3300
3485
  column?: string;
3301
3486
  progress_fields?: Array<"progress" | "stage" | "message">;
3487
+ /** cms only: a declared string field the mirror stamps with this
3488
+ * run's `run_id` on its first write; every later write of the run
3489
+ * requires it (a cms `if` precondition), so an older run's late
3490
+ * writes never land on a row a newer run has claimed. A write the
3491
+ * record refuses that way — or because it already holds
3492
+ * `completed` / `failed` — is skipped, not a `mirror_error`. */
3493
+ fence_field?: string;
3302
3494
  };
3303
3495
  timeout?: {
3304
3496
  after_ms: number;
@@ -3376,19 +3568,65 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
3376
3568
  /** Cancel a run that has not started its current attempt (`queued`,
3377
3569
  * `delayed`, `retrying`) or a plain run handed off to its signed callback
3378
3570
  * (`waiting` after its handler answered 202 — the callback URL is
3379
- * consumed). A `running` run (a delivery in flight, or a generation
3380
- * waiting on its provider), a run waiting on an event, or a terminal run
3381
- * answers `409 not_cancellable`. A generation's credit hold is released. */
3571
+ * consumed). A generation's credit hold is released — a STARTED
3572
+ * generation's too (its provider accepted it): it ends `failed`
3573
+ * (`Cancelled`) and a late provider callback is a no-op, but the provider
3574
+ * is NOT told, so its work and its charge may still complete. A plain
3575
+ * `running` run (a delivery in flight), a run waiting on an event, or a
3576
+ * terminal run answers `409 not_cancellable`. */
3382
3577
  cancel: (runId: string) => Promise<{
3383
3578
  run_id: string;
3384
3579
  state: string;
3385
3580
  }>;
3386
3581
  /** Clone a terminal run into a fresh queued run. A generation run answers
3387
- * `409 not_replayable` — submit the generation again instead. */
3582
+ * `409 not_replayable` — submit the generation again instead; a redacted
3583
+ * or erased run answers `409 run_redacted` (its payload is gone). */
3388
3584
  replay: (runId: string) => Promise<{
3389
3585
  run_id: string;
3390
3586
  replayed_from: string;
3391
3587
  }>;
3588
+ /** SERVER-ONLY (403 server_only in end-user mode). Account merge: move the
3589
+ * credit-hold owner (`reserve_credits.user_id`) of every run of
3590
+ * `from_user_id`, in any state, onto `into_user_id`, so the surviving
3591
+ * account reads, cancels and replays the generation runs it started as a
3592
+ * guest. ONE bounded page (≤500 runs) per call — call again until `done`
3593
+ * (idempotent: a second call moves 0). The jobs consumer of
3594
+ * `auth.user.merged { from, into }`; auth and `users.merge` call it after
3595
+ * a merge, the event is the backstop. Emits one `jobs.runs.rekeyed` when
3596
+ * a call moved at least one run (none on a zero-move call). Unlike the
3597
+ * files re-key it does not check that `into_user_id` exists.
3598
+ * Scope jobs:write. */
3599
+ reKey: (input: {
3600
+ from_user_id: string;
3601
+ into_user_id: string;
3602
+ }) => Promise<{
3603
+ moved: number;
3604
+ done: boolean;
3605
+ }>;
3606
+ /** SERVER-ONLY (403 server_only in end-user mode). Redact FINISHED runs
3607
+ * that hold personal data: the payload becomes `{ erased: true }`, the
3608
+ * result, progress, callback body and error text are cleared, a
3609
+ * generation's provider request body/headers are dropped and its credit
3610
+ * hold's user id is pseudonymized (unless its settle is still owed). The
3611
+ * row itself stays (state, timings, job_name). The same fields a GDPR
3612
+ * erase clears on the runs it can link to a user — use this for runs
3613
+ * nothing links to one (a plain queue run, an async function call).
3614
+ * `{ run_ids }` (≤500, one call; open runs are left alone and counted in
3615
+ * `skipped_open`) or `{ user_id }` (the generation runs whose credit hold
3616
+ * names that user — ONE page of ≤500 per call, call again until `done`).
3617
+ * Idempotent. Audited as `jobs.runs.redacted` (counts only; none when a
3618
+ * call redacted nothing). NOT cleared: concurrency_key, debounce_key,
3619
+ * idempotency_key and target_url — keep user ids out of those (hash them).
3620
+ * A redacted run does not replay. Scope jobs:write. */
3621
+ redact: (input: {
3622
+ run_ids: string[];
3623
+ } | {
3624
+ user_id: string;
3625
+ }) => Promise<{
3626
+ redacted: number;
3627
+ skipped_open: number;
3628
+ done: boolean;
3629
+ }>;
3392
3630
  /**
3393
3631
  * Suspend the RUNNING run until an event (call from the executing
3394
3632
  * handler, then return 200 — the suspension wins). `state: 'resumed'` =
@@ -3513,9 +3751,10 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
3513
3751
  sent: true;
3514
3752
  test_link?: string;
3515
3753
  }>;
3516
- /** `opts.anonymous_token` is REQUIRED for a link that was requested with
3517
- * one (the guest claim above): the same guest session must present it,
3518
- * else `401 invalid_session`. `merged` is true only when the guest was
3754
+ /** An unknown, expired or already-used link answers `401 invalid_token`
3755
+ * (request a new one with `request`). `opts.anonymous_token` is REQUIRED
3756
+ * for a link that was requested with one (the guest claim above): the
3757
+ * same guest session must present it, else `401 invalid_session`. `merged` is true only when the guest was
3519
3758
  * folded into an existing account (then `user_id` is that account), and
3520
3759
  * `rekeyed` rides beside it: true ⇒ the guest's payments / cms / files
3521
3760
  * rows were already moved onto `user_id` when this returned; false ⇒
@@ -3578,8 +3817,10 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
3578
3817
  /** On success the guest keeps its user id (`user_id` unchanged).
3579
3818
  * If the claimed email ALREADY has an account, the guest is MERGED
3580
3819
  * into it: `user_id` is the existing account, `merged: true`, and a
3581
- * fresh `session.token` (same session, re-signed for the merged
3582
- * identity — swap it client-side; other guest sessions are revoked).
3820
+ * fresh `session.token` (the same session carried over to the merged
3821
+ * identity under a new session id — swap it client-side: the old
3822
+ * guest token is revoked, the refresh token keeps working; other
3823
+ * guest sessions are revoked).
3583
3824
  * `rekeyed` (present on a merge): true ⇒ the guest's payments / cms /
3584
3825
  * files rows were already moved onto `user_id` when this returned, so
3585
3826
  * the merged user's next request finds them; false ⇒ the move ran
@@ -3700,7 +3941,7 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
3700
3941
  }) => Promise<AuthImportResult>;
3701
3942
  /** END-USER MODE ONLY (`endUserToken` / `asEndUser`): the signed-in user
3702
3943
  * deep-merges a bounded `attributes` object into its OWN shared identity
3703
- * row (null deletes a key; ≤8 KB, ≤64 top-level keys, depth ≤8). Profile
3944
+ * row (null deletes a key at any depth — RFC 7396; ≤8 KB, ≤64 top-level keys, depth ≤8). Profile
3704
3945
  * fields are not patchable here. In server mode the call throws 422
3705
3946
  * `end_user_mode_required` — use `users.patch(id, …)` instead. */
3706
3947
  patchMe: (attributes: Record<string, unknown>) => Promise<{
@@ -3811,15 +4052,34 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
3811
4052
  verified: true;
3812
4053
  }>;
3813
4054
  };
3814
- /** Social sign-in. The web `start`/`callback` flows are browser redirects
3815
- * (not JSON calls): send the browser to
3816
- * `GET /v1/auth/oauth/{provider}/start?redirect_uri=…` (server-side, with
3817
- * your key) and hand the returned `code`+`state` to `…/callback`; `native`
4055
+ /** Social sign-in. The web flow: `startUrl` fetches the provider authorize
4056
+ * URL with your key (a browser cannot follow the header-carrying 302 of
4057
+ * `GET /v1/auth/oauth/{provider}/start` itself), you navigate the browser
4058
+ * there, and hand the returned `code`+`state` to `…/callback`; `native`
3818
4059
  * is the mobile / broker token-exchange the SDK wraps. `oidc` is the
3819
4060
  * tenant's generic OIDC / SSO issuer (auth config `providers.oidc` —
3820
4061
  * Okta / Entra / Auth0 / any OpenID Connect IdP, or a SAML broker that
3821
4062
  * speaks OIDC); it rides the same three routes. */
3822
4063
  oauth: {
4064
+ /** Start the web sign-in from a browser SPA: answers the provider
4065
+ * `authorize_url` (PKCE challenge, one-shot `state`, and for `oidc` a
4066
+ * nonce bound to the flow — all held server-side) for you to navigate
4067
+ * to: `location.assign((await vx.auth.oauth.startUrl('oidc',
4068
+ * { redirect_uri })).authorize_url)`. The provider returns to
4069
+ * `redirect_uri` with `?code&state`, which your page sends to
4070
+ * `GET /v1/auth/oauth/{provider}/callback` for the session. The same
4071
+ * rules as the redirect form apply: `redirect_uri` must match the auth
4072
+ * config `security.allowedRedirectOrigins` when set (else
4073
+ * `422 redirect_not_allowed`), and the state expires after
4074
+ * `expires_in` seconds (600). `anonymous_token` (a guest bearer)
4075
+ * promotes or merges that guest at the callback. */
4076
+ startUrl: (provider: "google" | "apple" | "github" | "facebook" | "mock" | "oidc", input: {
4077
+ redirect_uri: string;
4078
+ anonymous_token?: string;
4079
+ }) => Promise<{
4080
+ authorize_url: string;
4081
+ expires_in: number;
4082
+ }>;
3823
4083
  /** Native social sign-in: exchange a provider `id_token`/`access_token`
3824
4084
  * for a vxil session (`linked` marks whether the user was created or
3825
4085
  * matched to an existing identity). */
@@ -3970,6 +4230,20 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
3970
4230
  * (end-user reads and writes are `403 server_only`, even with an
3971
4231
  * owner_field). Server keys are never affected. */
3972
4232
  end_user_access?: CmsEndUserAccess;
4233
+ /** Role slugs one of which a VERIFIED end user must hold to WRITE this
4234
+ * collection (create / update / $inc / delete / publish / transaction
4235
+ * step / batch / filtered delete, and an end-user delete's cascade into
4236
+ * it) — otherwise `403 role_required`. Reads are unaffected; server keys
4237
+ * are never affected. Omitted/`[]` = ungated. ≤16 entries, each
4238
+ * `^[a-z0-9][a-z0-9_-]{0,31}$` (the `orgs` role alphabet). */
4239
+ write_roles?: string[];
4240
+ /** Retention (guide ch. 4): live items created more than this many days
4241
+ * ago (1..3650) are soft-deleted by the nightly platform sweep — like a
4242
+ * normal delete (no restore; purged at least 30 days later, unique values freed), with ONE
4243
+ * `cms.collection.retention_swept` audit event per collection per night
4244
+ * instead of per-item delete events. A collection another collection
4245
+ * references with an `on_delete` rule is skipped. Omit/null = keep. */
4246
+ retain_days?: number | null;
3973
4247
  fields?: Array<{
3974
4248
  field: string;
3975
4249
  type: "string" | "text" | "int" | "float" | "bool" | "datetime" | "json" | "relation" | "file";
@@ -4019,6 +4293,10 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
4019
4293
  actions?: CmsActionDef[];
4020
4294
  /** present when not the default 'readwrite' */
4021
4295
  end_user_access?: CmsEndUserAccess;
4296
+ /** present when the collection is role-gated for end-user writes */
4297
+ write_roles?: string[];
4298
+ /** present when retention is set */
4299
+ retain_days?: number;
4022
4300
  }>;
4023
4301
  list: () => Promise<Array<{
4024
4302
  collection: string;
@@ -4027,6 +4305,10 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
4027
4305
  owner_field?: string | null;
4028
4306
  /** the collection's end-user access mode (absent from an older server = 'readwrite') */
4029
4307
  end_user_access?: CmsEndUserAccess;
4308
+ /** the roles an end user needs to write it (null / absent = ungated) */
4309
+ write_roles?: string[] | null;
4310
+ /** retention in days (null = keep until deleted; absent from an older server) */
4311
+ retain_days?: number | null;
4030
4312
  }>>;
4031
4313
  addField: (collection: string, field: Record<string, unknown>) => Promise<void>;
4032
4314
  /** Set (or clear, with `null`/`[]`) a field's READ-ROLE gate (guide ch. 4) —
@@ -4069,6 +4351,25 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
4069
4351
  collection: string;
4070
4352
  end_user_access: CmsEndUserAccess;
4071
4353
  }>;
4354
+ /** Set (or clear, with `[]` / `null`) the collection's END-USER WRITE
4355
+ * ROLES (guide ch. 9): a verified end user must hold one of `roles` (the
4356
+ * session's verified role claims — orgs roles) to write the collection,
4357
+ * otherwise every write door answers `403 role_required`. Reads are
4358
+ * unaffected; server keys are never affected. Audited; takes effect on
4359
+ * the next write. */
4360
+ setWriteRoles: (collection: string, roles: string[] | null) => Promise<{
4361
+ collection: string;
4362
+ write_roles: string[];
4363
+ }>;
4364
+ /** Set (1..3650 days) or clear (`null`) the collection's RETENTION (guide
4365
+ * ch. 4): the nightly platform sweep soft-deletes live items created more
4366
+ * than `days` ago — bounded per project per night, no restore, purged at
4367
+ * least 30 days later like any delete, one `cms.collection.retention_swept` audit event
4368
+ * per collection per sweep. Audited (`cms.collection.retain_days.set`). */
4369
+ setRetention: (collection: string, days: number | null) => Promise<{
4370
+ collection: string;
4371
+ retain_days: number | null;
4372
+ }>;
4072
4373
  /** Re-project the collection's index slots after an `index_slot` move
4073
4374
  * (guide ch. 4). Slots are projected on WRITE only, so until this runs,
4074
4375
  * stored rows keep their OLD projection: the new slot is NULL and the
@@ -4120,6 +4421,29 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
4120
4421
  version: number;
4121
4422
  data: Record<string, unknown>;
4122
4423
  }>;
4424
+ /** Upsert by a declared `unique` field (guide ch. 4): creates the item, or
4425
+ * — when a live item already holds `data[field]` — merges `patch` into
4426
+ * THAT item instead (one transaction; update hooks, guards, version bump
4427
+ * and a `cms.item.updated` audit, exactly like `patch`; null clears a
4428
+ * field). `created` tells which happened (201 vs 200). Concurrent upserts
4429
+ * of one key serialize, so they never create a duplicate. */
4430
+ upsert: (collection: string, input: {
4431
+ data: Record<string, unknown>;
4432
+ onConflict: {
4433
+ field: string;
4434
+ patch: Record<string, unknown>;
4435
+ };
4436
+ status?: "draft" | "published";
4437
+ lock?: string;
4438
+ guard?: CmsGuard;
4439
+ guards?: CmsGuardTerm[];
4440
+ }) => Promise<{
4441
+ item_id: string;
4442
+ status: string;
4443
+ version: number;
4444
+ data: Record<string, unknown>;
4445
+ created: boolean;
4446
+ }>;
4123
4447
  /** `expand` inlines relation/file fields into `data` (guide ch. 4): the
4124
4448
  * full item envelope, a `{ object_id, $ref: 'files' }` stub for a file,
4125
4449
  * the bare id on a cycle/depth cut, `null` for an invisible target. */
@@ -4128,7 +4452,9 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
4128
4452
  }) => Promise<Record<string, unknown>>;
4129
4453
  /**
4130
4454
  * The bounded query DSL. filter ops: $eq $ne $gt $gte $lt $lte $in
4131
- * $contains $startsWith (LIKE-escaped; substring-searchable on s*-slotted
4455
+ * ($ne is NULL-safe: a row whose field is absent or null counts as not
4456
+ * equal; on a dotted join term the row still needs a readable target,
4457
+ * so a null or dangling relation is excluded) $contains $startsWith (LIKE-escaped; substring-searchable on s*-slotted
4132
4458
  * fields) $arrayContains/$anyOf (json/relation array containment, indexed);
4133
4459
  * range/sort needs slot-indexed fields. This is the
4134
4460
  * DELIBERATELY-UNTYPED escape hatch: it accepts everything the server
@@ -4518,6 +4844,32 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
4518
4844
  };
4519
4845
  warnings?: string[];
4520
4846
  }>;
4847
+ /**
4848
+ * Log records of ONE function, one per invocation (outcome, wall/cpu ms,
4849
+ * console lines, exceptions), oldest first. Pass the returned `next_since`
4850
+ * back as `since` to read only newer records (a tail); without it, the most
4851
+ * recent records of the last 24 hours. Kept 14 days. Needs
4852
+ * `functions:read`. (GET /v1/functions/:name/logs)
4853
+ */
4854
+ logs: (name: string, opts?: {
4855
+ since?: string;
4856
+ }) => Promise<{
4857
+ lines: Array<{
4858
+ ts: string;
4859
+ outcome: string;
4860
+ wall_ms: number | null;
4861
+ cpu_ms: number | null;
4862
+ logs: Array<{
4863
+ level: string;
4864
+ message: string;
4865
+ }>;
4866
+ exceptions: Array<{
4867
+ name: string;
4868
+ message: string;
4869
+ }>;
4870
+ }>;
4871
+ next_since: string | null;
4872
+ }>;
4521
4873
  };
4522
4874
  /** The MCP aggregation surface (the `mcp` feature). */
4523
4875
  readonly mcp: {
@@ -4813,20 +5165,38 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
4813
5165
  last_error_msg?: string | null;
4814
5166
  }>;
4815
5167
  sources: {
4816
- /** Register an inbound source; the receiver URL is returned ONCE. */
5168
+ /** Register an inbound source; the receiver URL is returned ONCE.
5169
+ *
5170
+ * Destination: `forward_url` (your endpoint) OR `target_function` (a
5171
+ * deployed function that declares a bare `trigger: { kind: 'webhook' }`
5172
+ * binding — it receives `payload.event === 'inbound_webhook.received'`
5173
+ * with the inbound envelope as `payload.data`), never both; neither =
5174
+ * store only (read with `events.list`).
5175
+ *
5176
+ * `provider: 'hmac'` is the parameterized preset for any provider that
5177
+ * signs an HMAC of the raw body (Shopify, Intercom, Linear, Zendesk, …):
5178
+ * `verify` says which header, sha256|sha1, hex|base64, an optional prefix
5179
+ * and an optional timestamp header + tolerance. Like stripe/paddle/slack it
5180
+ * fails closed (401) until the source's signing secret is set. */
4817
5181
  create: (input: {
4818
- provider: "stripe" | "paddle" | "github" | "slack" | "revenuecat" | "generic";
5182
+ provider: "stripe" | "paddle" | "github" | "slack" | "revenuecat" | "generic" | "hmac";
4819
5183
  name: string;
4820
5184
  forward_url?: string;
5185
+ target_function?: string;
5186
+ verify?: WebhookHmacVerify;
4821
5187
  }) => Promise<{
4822
5188
  source_id: string;
4823
5189
  receiver_url_path: string;
4824
5190
  provider: string;
5191
+ target_function?: string | null;
4825
5192
  }>;
4826
5193
  list: () => Promise<Array<{
4827
5194
  source_id: string;
4828
5195
  provider: string;
4829
5196
  name: string;
5197
+ forward_url?: string | null;
5198
+ target_function?: string | null;
5199
+ verify?: WebhookHmacVerify | null;
4830
5200
  }>>;
4831
5201
  delete: (sourceId: string) => Promise<void>;
4832
5202
  /** Send a sample envelope to the source's forward_url through the real
@@ -4841,20 +5211,38 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
4841
5211
  }>;
4842
5212
  };
4843
5213
  events: {
5214
+ /** Received events. Newest first, paged with `cursor`; or — with `after`
5215
+ * (your watermark, an event id) — OLDEST first from just after it, the
5216
+ * drain shape: store `next_after` and pass it back, `has_more` says
5217
+ * another page waits. The `after` drain answers only events received
5218
+ * at least 10 s ago (ids are not strictly arrival-ordered, so a fresher
5219
+ * event could otherwise commit below a stored watermark and be
5220
+ * skipped); `cursor` and `event_id` reads are not lagged. `event_id`
5221
+ * reads exactly one event. `cursor` and `after` are exclusive. */
4844
5222
  list: (q?: {
4845
5223
  source_id?: string;
4846
5224
  status?: string;
4847
5225
  cursor?: string;
4848
5226
  limit?: number;
5227
+ after?: string;
5228
+ event_id?: string;
4849
5229
  }) => Promise<{
4850
5230
  events: Array<{
4851
5231
  event_id: string;
5232
+ source_id: string;
5233
+ provider: string;
5234
+ provider_event_id: string | null;
4852
5235
  event_type: string | null;
4853
5236
  payload: Record<string, unknown>;
4854
5237
  sig_verified: boolean;
4855
5238
  status: string;
5239
+ received_at: string;
4856
5240
  }>;
4857
5241
  next_cursor: string | null;
5242
+ /** `after` pages only */
5243
+ next_after?: string | null;
5244
+ /** `after` pages only */
5245
+ has_more?: boolean;
4858
5246
  }>;
4859
5247
  replay: (eventId: string) => Promise<void>;
4860
5248
  /** The Svix message-attempt view: delivery state/attempts/DLQ flag for
@@ -5075,8 +5463,14 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
5075
5463
  * matching its TokenProvider type *structurally* (no import — a static
5076
5464
  * import would break the single-file served sdk.mjs) that re-mints via
5077
5465
  * POST /v1/realtime/tokens with `defaults` merged over the channel the
5078
- * client asks for. Server-side (Node) use only — it needs the API key;
5079
- * browsers must fetch tokens from YOUR backend instead. */
5466
+ * client asks for. Two safe ways to use it:
5467
+ * - on a server, with a server key (`user_id` names the subject); or
5468
+ * - in a browser / mobile app, with a PUBLIC `end_user_required` key and
5469
+ * the signed-in user's session (`endUserToken` on the client): the mint
5470
+ * runs in end-user mode and the token's subject is FORCED to the
5471
+ * verified user (any `user_id` you pass is overridden), so no
5472
+ * backend of your own is needed.
5473
+ * Never ship a server key to a browser. */
5080
5474
  tokenProvider: (defaults: {
5081
5475
  user_id: string;
5082
5476
  ttl_seconds?: number;
@@ -5107,16 +5501,27 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
5107
5501
  * long after the mint, whether or not `files.ttl` is enabled; `null` =
5108
5502
  * never. The answer's `expires_in` is the UPLOAD URL's life in seconds;
5109
5503
  * `expires_at` is when the object expires (null = never). `user_id` is
5110
- * required with a server key and omitted in end-user mode. */
5504
+ * required with a server key and omitted in end-user mode.
5505
+ * `checksum_sha256` (the SHA-256 of the bytes, 64 hex or 44 base64
5506
+ * characters) is signed into the URL (send the answer's `upload_headers`
5507
+ * on the PUT) and kept with the upload: `complete` compares it with the
5508
+ * SHA-256 the store reports for the bytes — 409 `checksum_mismatch` when
5509
+ * they differ, 409 `checksum_unverified` when the store reports none —
5510
+ * and on success answers the verified `checksum_sha256`. */
5111
5511
  createUploadUrl: (input: {
5112
5512
  user_id?: string;
5113
5513
  filename: string;
5114
5514
  content_type: string;
5115
5515
  size_bytes: number;
5116
5516
  expiresInSeconds?: number | null;
5517
+ checksum_sha256?: string;
5117
5518
  }) => Promise<FileUploadUrl>;
5118
5519
  /** Confirm the PUT (bytes verified, object → available). Answers the
5119
- * object's `expires_at` (null = never). */
5520
+ * object's `expires_at` (null = never) and `checksum_sha256` (hex; null
5521
+ * when the store reported none) — a repeated complete answers the same.
5522
+ * When the upload declared `checksum_sha256`: 409 `checksum_mismatch` if
5523
+ * the bytes hash differently, 409 `checksum_unverified` if the store
5524
+ * reports no checksum (the object stays pending either way). */
5120
5525
  complete: (objectId: string) => Promise<{
5121
5526
  object_id: string;
5122
5527
  status: string;
@@ -5641,6 +6046,9 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
5641
6046
  model?: string;
5642
6047
  user_id?: string;
5643
6048
  input?: Record<string, unknown>;
6049
+ /** Drop chunks whose cosine `similarity` to the query is below this
6050
+ * (-1..1) before grounding; overrides rag config `retrieval.minSimilarity`. */
6051
+ min_similarity?: number;
5644
6052
  }) => Promise<RagAnswer>;
5645
6053
  /** Streamed grounded answer: citations + the channel handle up front, tokens
5646
6054
  * over the realtime channel. */
@@ -5655,13 +6063,18 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
5655
6063
  model?: string;
5656
6064
  user_id?: string;
5657
6065
  input?: Record<string, unknown>;
6066
+ /** Drop chunks whose cosine `similarity` to the query is below this
6067
+ * (-1..1) before grounding; overrides rag config `retrieval.minSimilarity`. */
6068
+ min_similarity?: number;
5658
6069
  }) => Promise<RagStreamHandle>;
5659
6070
  /** Retrieval-only grounding preview (guide ch. 6, rag): the exact chunks `answer`
5660
6071
  * would ground on, with rerank + metadata boosts applied — no generation,
5661
6072
  * no token spend. `boosts`/`rerank`/`min_score` override the rag config.
5662
6073
  * `min_score` floors the EFFECTIVE `score`, which is a RANK value (about
5663
- * 0.016–0.033 at the top): a floor above ~0.033 drops every hit. Judge
5664
- * relevance by each hit's `similarity` (cosine, -1..1) instead. */
6074
+ * 0.016–0.033 at the top): a floor above ~0.033 drops every hit. For a
6075
+ * relevance threshold use `min_similarity`, which floors each hit's cosine
6076
+ * `similarity` (-1..1; overrides config `retrieval.minSimilarity`; a hit
6077
+ * with no measured similarity, e.g. keyword mode, is kept). */
5665
6078
  search: (input: {
5666
6079
  query: string;
5667
6080
  collection?: string;
@@ -5671,6 +6084,7 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
5671
6084
  rerank?: boolean;
5672
6085
  boosts?: Record<string, unknown>;
5673
6086
  min_score?: number;
6087
+ min_similarity?: number;
5674
6088
  }) => Promise<RagSearchResult>;
5675
6089
  /** Ingest into the backing vector-search collection (chunk → embed → index). */
5676
6090
  ingest: (collection: string, input: SearchIngestInput) => Promise<SearchIngestResult>;
@@ -5832,7 +6246,9 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
5832
6246
  * (no oversell). Pass `job_id` to make the debit PROVISIONAL (held, not yet
5833
6247
  * committed): a linked jobs run that terminally fails auto-refunds the hold,
5834
6248
  * a success settles it. Throws VxilError(402, 'INSUFFICIENT_CREDITS') when the
5835
- * available balance can't cover `amount`.
6249
+ * available balance can't cover `amount`. Throws VxilError(409,
6250
+ * 'job_released') when `job_id` names a generation run that already ended
6251
+ * (cancelled or failed) before this hold landed: nothing is held.
5836
6252
  *
5837
6253
  * Ordered credits: pass `credit_types` (1–8, in spend order — e.g.
5838
6254
  * `['free', 'subscription', 'topup']`) instead of `credit_type`; the WHOLE
@@ -6083,6 +6499,30 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
6083
6499
  user_id: string;
6084
6500
  tier?: string;
6085
6501
  }) => Promise<PaymentsSimulationResult>;
6502
+ /** SERVER-ONLY TEST CLOCK — move the end (`until`, past or future, within
6503
+ * 5 years of now) of a MANUAL grant or a purchase pass, to test expired /
6504
+ * export-window / renew-soon states on demand. Allowed only where no live
6505
+ * money can be involved: a `mock`-provider project, a project whose
6506
+ * provider runs in its sandbox (`paddle.sandbox`, `paypal.sandbox`, a
6507
+ * Stripe `sk_test_` key), or a dev/preview project — elsewhere 403
6508
+ * `test_clock_forbidden` (also for a pass linked to a non-sandbox charge).
6509
+ * 409 `provider_managed` for a provider subscription (use the provider's
6510
+ * own test clock); 409 `subscription_ended` for a revoked/refunded row.
6511
+ * The effect is a natural lapse's: the entitlements refold
6512
+ * (`payments.entitlement.changed`), a move into the past turns an active
6513
+ * row `lapsed` and emits `payments.subscription.lapsed` (`event_type:
6514
+ * 'test_clock'`, `environment: 'sandbox'`) once; a move back into the
6515
+ * future revives it (as `active` — a `trialing` row does not return to
6516
+ * `trialing`; a `past_due` row's status is left as it is). Only the end
6517
+ * moves: `since` (the period start) is never rewritten, so after a move
6518
+ * before it `since` > `until`. Every move emits
6519
+ * `payments.test_clock.moved`. Both events are emitted after the move
6520
+ * commits, best-effort (as the period-end sweep's are): if one fails to
6521
+ * record, the move still stands and is not re-announced on a retry, so
6522
+ * assert on the returned `status` / `lapsed` rather than only on the
6523
+ * event. Same-tier passes stacked behind the moved one are not
6524
+ * re-chained. No credits are granted or reversed. */
6525
+ moveSubscriptionUntil: (subscriptionId: string, until: string | Date) => Promise<PaymentsTestClockResult>;
6086
6526
  /** The last report-only reconciliation sweep result for this project
6087
6527
  * (`run: null` before the first daily tick). Finding kinds:
6088
6528
  * `balance_drift`, `entitlement_lag`, `null_tier`, `refund_pending`,