@oxy-hq/sdk 2.6.0 → 2.9.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.cts CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- import { A as UseSemanticQueryOpts, B as CustomAppErrorReport, C as UseProcedureRunInput, D as UseQueryOpts, E as UseQueryInput, F as useProcedureRun, G as OxyAppFunctionManifest, H as apiErrorFromResponse, I as useQuery, J as _resetCustomAppManifestCacheForTest, K as OxyAppManifest, L as useResolvedManifest, M as useAgentRun, N as useFunction, O as UseQueryResult, P as useOxyApp, R as useSemanticQuery, S as UseFunctionResult, T as UseProcedureRunResult, U as interpretCustomAppError, V as OxyApiError, W as LoadManifestOptions, Y as loadCustomAppManifest, _ as SemanticFilter, a as AppFetcher, b as UseAgentRunInput, c as OxyAppProvider, d as OxyChatProps, f as ProcedureProgress, g as SemanticDateRangeOp, h as SemanticArrayOp, i as AgentSqlArtifact, j as UseSemanticQueryResult, k as UseSemanticQueryInput, l as OxyAppProviderProps, m as ProcedureRunState, n as AgentRunEvent, o as OxyAnswer, p as ProcedureResult, q as ResolvedCustomAppManifest, r as AgentRunState, s as OxyAnswerProps, t as AgentArtifact, u as OxyChat, v as SemanticScalarOp, w as UseProcedureRunOpts, x as UseAgentRunResult, y as SemanticTimeDimension, z as useTrackEvent } from "./react-DnBdQ8dG.cjs";
2
+ import { A as UseSemanticQueryOpts, B as CustomAppErrorReport, C as UseProcedureRunInput, D as UseQueryOpts, E as UseQueryInput, F as useProcedureRun, G as OxyAppFunctionManifest, H as apiErrorFromResponse, I as useQuery, J as _resetCustomAppManifestCacheForTest, K as OxyAppManifest, L as useResolvedManifest, M as useAgentRun, N as useFunction, O as UseQueryResult, P as useOxyApp, R as useSemanticQuery, S as UseFunctionResult, T as UseProcedureRunResult, U as interpretCustomAppError, V as OxyApiError, W as LoadManifestOptions, Y as loadCustomAppManifest, _ as SemanticFilter, a as AppFetcher, b as UseAgentRunInput, c as OxyAppProvider, d as OxyChatProps, f as ProcedureProgress, g as SemanticDateRangeOp, h as SemanticArrayOp, i as AgentSqlArtifact, j as UseSemanticQueryResult, k as UseSemanticQueryInput, l as OxyAppProviderProps, m as ProcedureRunState, n as AgentRunEvent, o as OxyAnswer, p as ProcedureResult, q as ResolvedCustomAppManifest, r as AgentRunState, s as OxyAnswerProps, t as AgentArtifact, u as OxyChat, v as SemanticScalarOp, w as UseProcedureRunOpts, x as UseAgentRunResult, y as SemanticTimeDimension, z as useTrackEvent } from "./react-DBG6Pfp_.cjs";
3
3
  //#region src/config.d.ts
4
4
  /**
5
5
  * Configuration for the Oxy SDK
@@ -141,15 +141,38 @@ interface ExplainNode {
141
141
  dimension_count?: number;
142
142
  children?: ExplainNode[];
143
143
  }
144
+ /** Whether a driver's observed move pushes the target the way it actually
145
+ * moved (`contributing`) or against it (`counteracting` — it offset part of
146
+ * the move rather than causing it). `unknown` when no signed claim is
147
+ * available: `direction: unknown` with no coefficient, or a flat
148
+ * driver/target. */
149
+ type DriverContribution = "contributing" | "counteracting" | "unknown";
150
+ /** A driver's move split into the part its base forced and the part its own
151
+ * ratio contributed. Emitted only when the driver genuinely tracks a sibling
152
+ * rather than moving on its own — presence is the claim.
153
+ * `base_driven_delta + ratio_driven_delta === driver_delta`. */
154
+ interface PassthroughSplit {
155
+ base_measure: string;
156
+ ratio_previous: number;
157
+ ratio_current: number;
158
+ base_driven_delta: number;
159
+ ratio_driven_delta: number;
160
+ }
144
161
  interface DriverAttribution {
145
162
  driver_measure: string;
146
163
  driver_previous: number;
147
164
  driver_current: number;
148
165
  driver_delta: number;
166
+ /** Both optional: an `explain_cache` row written before these fields shipped
167
+ * is served verbatim, so absent means unclassified — not a default. */
168
+ direction?: DriverDirection;
169
+ contribution?: DriverContribution;
149
170
  coefficient?: number;
150
171
  form: DriverForm;
172
+ /** Absent for a purely qualitative driver (no coefficient). */
151
173
  estimated_target_impact?: number;
152
174
  description?: string;
175
+ passthrough?: PassthroughSplit;
153
176
  }
154
177
  type ExplainWarning = {
155
178
  type: "simpsons_paradox";
@@ -225,12 +248,31 @@ interface OpportunityResult {
225
248
  target: string;
226
249
  period: [string, string];
227
250
  overall_value: number;
228
- /** "value_share" (additive) or "equal" (ratios). */
251
+ /**
252
+ * "rows" (rate-based additive sizing — the only basis that yields a sized
253
+ * upside figure), "value_share" (additive) or "equal" (ratios).
254
+ */
229
255
  weight_basis: string;
230
256
  dimensions: DimensionOpportunity[];
231
257
  skipped_dimensions: SkippedDimension[];
232
258
  downstream: PredictImpact[];
233
259
  }
260
+ /**
261
+ * Single-period structural decomposition. The server auto-derives the
262
+ * baseline as the equal-length window immediately before `period`, then
263
+ * returns an {@link ExplainResult}-shaped payload (so the same renderers
264
+ * work). Ignore the delta fields when rendering a pure distribution.
265
+ */
266
+ interface DistributionRequest {
267
+ target: string;
268
+ time_dimension: string;
269
+ /** `[start, end]` inclusive date strings. */
270
+ period: [string, string];
271
+ }
272
+ interface TimeDimensionsResponse {
273
+ /** view name → fully-qualified time-dimension ids (`view.dim`). */
274
+ by_view: Record<string, string[]>;
275
+ }
234
276
  /**
235
277
  * Shape of the inner request helper exposed by `OxyClient`. The metric-tree
236
278
  * client reuses it to inherit auth headers, timeout, baseUrl, and project
@@ -334,6 +376,13 @@ declare class MetricTreeClient {
334
376
  //#region src/anomalies.d.ts
335
377
  type AnomalyStatus = "new" | "acknowledged" | "dismissed";
336
378
  type AnomalySeverity = "low" | "medium" | "high";
379
+ /** One filter pinning an anomaly (or a failed monitor) to a segment. */
380
+ interface AnomalyFilter {
381
+ /** Fully-qualified dimension id, e.g. `"sales_daily.restaurant_id"`. */
382
+ member: string;
383
+ /** Matched values (OR within a filter). */
384
+ values: string[];
385
+ }
337
386
  /**
338
387
  * One row in the anomaly inbox. Detected by `oxy-metric-monitoring` per
339
388
  * `.monitor.yml` entry; upserted by repeat scans so unresolved anomalies
@@ -355,6 +404,26 @@ interface Anomaly {
355
404
  severity: AnomalySeverity | string;
356
405
  status: AnomalyStatus | string;
357
406
  label?: string | null;
407
+ /**
408
+ * Stable key derived from the monitor's filters (e.g.
409
+ * `"sales_daily.restaurant_id=loc-abc"`). Empty for chain-wide monitors.
410
+ */
411
+ dimension_key: string;
412
+ /**
413
+ * Raw filters identifying the segment; `null` for chain-wide monitors.
414
+ * Always present on the wire (the server serializes it unconditionally),
415
+ * hence required-nullable rather than optional — same shape as
416
+ * {@link ScanFailure.filters}.
417
+ */
418
+ filters: AnomalyFilter[] | null;
419
+ /**
420
+ * Groups consecutive flagged buckets of one segment into a single event, so a
421
+ * surge spanning Mon/Wed/Thu reads as one problem rather than three. `null`
422
+ * for rows detected before events existed. This is what
423
+ * {@link AnomaliesClient.updateStatusBulk} wants as `eventIds` — a status
424
+ * action applies to the whole event.
425
+ */
426
+ event_id?: string | null;
358
427
  /** Cached ExplainResult — populated by `POST /anomalies/:id/explain`. */
359
428
  explain_cache?: ExplainResult | null;
360
429
  explain_cached_at?: string | null;
@@ -363,20 +432,135 @@ interface Anomaly {
363
432
  }
364
433
  interface ListAnomaliesOptions {
365
434
  status?: AnomalyStatus | string;
366
- /** Max rows (server caps at 500, defaults to 100). */
435
+ /**
436
+ * Max **events** (server caps at 500, defaults to 100). Every bucket of a
437
+ * returned event comes back, so the row count is `limit × buckets-per-event`.
438
+ * With `order: "recent"` it is a plain row limit instead.
439
+ */
367
440
  limit?: number;
441
+ /**
442
+ * How many **events** to skip (rows, with `order: "recent"`) — same unit as
443
+ * `limit`, so page `n` is `offset: (n - 1) * limit`. Defaults to 0.
444
+ *
445
+ * Bounded: past the server's maximum depth the request is refused with a 400
446
+ * rather than served a repeat of the last reachable page, so a runaway
447
+ * `offset += limit` loop ends loudly instead of spinning. Every response
448
+ * echoes that depth as `max_offset`, so a loop can stop before reaching it.
449
+ */
450
+ offset?: number;
451
+ /**
452
+ * `"recent"` returns latest-first (`detected_at DESC`). Omit for the default
453
+ * worst-first ranking by event severity (active events before dismissed).
454
+ */
455
+ order?: "recent";
368
456
  }
369
457
  interface ListAnomaliesResponse {
370
458
  anomalies: Anomaly[];
459
+ /**
460
+ * Total matching the filter across every page — **events** under the default
461
+ * ranking, rows under `order: "recent"`. Same unit as `limit`/`offset`, so
462
+ * `Math.ceil(total / limit)` is the page count. Note it will not equal
463
+ * `anomalies.length` under the default ranking even on a single page: each
464
+ * event returns all of its buckets.
465
+ *
466
+ * **Absent** in two cases, and a client that pages has to handle both. Send
467
+ * neither `limit` nor `offset` and you have asked for "the top N", so there
468
+ * is no total behind the answer — the field is omitted rather than filled
469
+ * with the page's own length. Pass a `limit` (with `offset: 0` for the first
470
+ * page) to get a real total to loop against.
471
+ *
472
+ * It is also dropped when the count query itself fails: the page rows are
473
+ * already in hand, and the server serves them without their denominator
474
+ * rather than failing a request it could answer. So a page you asked for
475
+ * with `limit` can still come back untotalled — page off `anomalies.length`
476
+ * and `max_offset` in that case rather than treating it as zero.
477
+ */
478
+ total?: number;
479
+ /**
480
+ * The page actually served. `limit` is clamped to 1..=500, so it can come
481
+ * back smaller than you asked for and every page number you compute must
482
+ * divide by this rather than by what you sent. `offset` is *not* clamped —
483
+ * too deep a request is refused with a 400 (see `max_offset`), so this echoes
484
+ * the offset you sent whenever there is a response at all.
485
+ *
486
+ * Optional because a replica still running a pre-paging build emits neither,
487
+ * which is a live shape during a rolling deploy. Fall back to what you asked
488
+ * for rather than doing arithmetic on `undefined`.
489
+ */
490
+ limit?: number;
491
+ offset?: number;
492
+ /**
493
+ * The deepest `offset` the server will serve — past it a request is refused
494
+ * with a 400. Read it rather than hardcoding a copy: a paging loop bounded by
495
+ * this stops cleanly instead of ending on an error.
496
+ */
497
+ max_offset?: number;
498
+ /**
499
+ * Event keys whose buckets were trimmed to the server's per-event cap (50) —
500
+ * an `event_id`, or `ungrouped:<row id>` for a row detected before events
501
+ * existed. For those events `anomalies` holds the worst buckets, not all of
502
+ * them, so a status write should name the event through `updateStatusBulk`'s
503
+ * `eventIds` rather than enumerating the buckets you received.
504
+ *
505
+ * Only meaningful under the default ranking, which pages *events* and
506
+ * returns each whole — there, an absence means complete. With
507
+ * `order: "recent"` the page is row-limited, so an event can straddle its
508
+ * boundary instead; this list stays empty and every event should be treated
509
+ * as possibly partial.
510
+ */
511
+ truncated_events?: string[];
512
+ }
513
+ interface BulkUpdateStatusResponse {
514
+ /** Buckets actually written. Lower than what you sent when a row was
515
+ * deleted, moved out of `onlyStatus`, or belongs to another workspace. */
516
+ updated: number;
517
+ /** Distinct anomalies behind those buckets — events, plus standalone
518
+ * pre-event rows. The unit a UI counts in, and one only the server can
519
+ * compute: naming an event never told you how many buckets it held.
520
+ *
521
+ * An anomaly counts as updated once *any* of its buckets is written. Name
522
+ * events through `eventIds` and that is the whole anomaly; name one bucket
523
+ * of a long chain through `ids` and this still reports `1` while the rest
524
+ * keep their old status. `ids` is for pre-event rows, which hold one bucket
525
+ * each — using it for anything else buys a partial write. */
526
+ events_updated: number;
371
527
  }
372
528
  interface ScanOptions {
373
529
  /** Override the reference "now" date (YYYY-MM-DD) — useful for demos. */
374
530
  as_of?: string;
375
531
  }
532
+ /** One `.monitor.yml` entry that errored during a scan. */
533
+ interface ScanFailure {
534
+ measure: string;
535
+ time_dimension: string;
536
+ granularity: string;
537
+ label: string | null;
538
+ /** Segment key for a `group_by`/filtered monitor; empty for chain-wide. */
539
+ dimension_key: string;
540
+ /** Raw filters identifying the segment; null for chain-wide monitors. */
541
+ filters: AnomalyFilter[] | null;
542
+ error: string;
543
+ }
376
544
  interface ScanResponse {
377
545
  monitors_scanned: number;
378
546
  monitors_failed: number;
379
547
  anomalies_persisted: number;
548
+ /**
549
+ * True when the scan is still running server-side (it exceeded the 55 s
550
+ * synchronous window, or a scan started within the last 60 s and this call
551
+ * was debounced). The counts are all `0` in that case — they are NOT a
552
+ * "nothing found" result. Refetch with `list()` after a short delay.
553
+ */
554
+ pending: boolean;
555
+ /**
556
+ * Per-monitor failures. Empty array (never absent) on a clean scan and on
557
+ * the `pending` path, where failures aren't known yet.
558
+ */
559
+ failures: ScanFailure[];
560
+ }
561
+ interface ExplainOptions {
562
+ /** Recompute even when the row already has a cached result. */
563
+ refresh?: boolean;
380
564
  }
381
565
  type RequestFn = <T>(endpoint: string, options?: RequestInit) => Promise<T>;
382
566
  /**
@@ -399,12 +583,17 @@ declare class AnomaliesClient {
399
583
  private path;
400
584
  private buildQuery;
401
585
  /**
402
- * List anomalies in the inbox, newest first.
586
+ * List anomalies in the inbox, ranked worst-first by event severity (active
587
+ * events before dismissed). Pass `order: "recent"` for latest-first.
403
588
  *
404
589
  * @example
405
590
  * ```typescript
406
591
  * // Open / unresolved anomalies only
407
592
  * const { anomalies } = await client.anomalies.list({ status: "new" });
593
+ *
594
+ * // Second page of 25 events
595
+ * const page2 = await client.anomalies.list({ limit: 25, offset: 25 });
596
+ * console.log(`${(page2.offset ?? 25) + 1}+ of ${page2.total ?? "?"}`);
408
597
  * ```
409
598
  */
410
599
  list(options?: ListAnomaliesOptions): Promise<ListAnomaliesResponse>;
@@ -413,11 +602,20 @@ declare class AnomaliesClient {
413
602
  * workspace, runs the detector, and upserts matching rows into the
414
603
  * inbox. Returns counts of scanned / failed / persisted.
415
604
  *
605
+ * Long-running: the server waits up to 55 s, then returns
606
+ * `pending: true` with zeroed counts while the scan finishes in the
607
+ * background. Always check `pending` before treating `0` as "nothing
608
+ * found", and refetch with {@link list} shortly after.
609
+ *
416
610
  * @example
417
611
  * ```typescript
418
612
  * // Scan against a known-good reference date (matches the seed dataset)
419
613
  * const result = await client.anomalies.scan({ as_of: "2025-12-15" });
420
- * console.log(`${result.anomalies_persisted} anomalies detected`);
614
+ * if (result.pending) {
615
+ * console.log("scan still running — refetch shortly");
616
+ * } else {
617
+ * console.log(`${result.anomalies_persisted} anomalies detected`);
618
+ * }
421
619
  * ```
422
620
  */
423
621
  scan(options?: ScanOptions): Promise<ScanResponse>;
@@ -425,13 +623,84 @@ declare class AnomaliesClient {
425
623
  * Update an anomaly's status (acknowledge / dismiss / re-open).
426
624
  */
427
625
  updateStatus(anomalyId: string, status: AnomalyStatus): Promise<Anomaly>;
626
+ /**
627
+ * Update many anomalies in one request — the batch form of
628
+ * {@link updateStatus}. Identifiers outside the workspace are skipped rather
629
+ * than erroring, so `updated` (rows written) can be lower than what you sent.
630
+ * At most 2000 identifiers across both lists.
631
+ *
632
+ * **Prefer `eventIds`.** Inbox actions are per *event*, and a list response
633
+ * caps how many buckets it returns per event — so acking the bucket ids you
634
+ * received can leave the tail of a long chain behind, `new`, under a clean
635
+ * success. Naming the event lets the server write all of it. `ids` is for
636
+ * rows with no `event_id` (detected before events existed), which can only
637
+ * be named individually.
638
+ *
639
+ * `onlyStatuses` says which of an event's buckets may move. An event can span
640
+ * statuses, so an unbounded write resurrects buckets that were dismissed on
641
+ * purpose — which is why omitting it takes a scope rather than no bound at
642
+ * all: the live statuses (`["new", "acknowledged"]`) for an ack or dismiss,
643
+ * and all three for `status: "new"`, since reopening is how a dismissed
644
+ * anomaly comes back. The server applies that same default, so the safe
645
+ * behaviour does not depend on going through this client. Pass `[]` to opt
646
+ * out of the bound entirely.
647
+ *
648
+ * @example
649
+ * ```typescript
650
+ * const { anomalies } = await client.anomalies.list({ status: "new", limit: 50, offset: 0 });
651
+ * // Both lists: events by id, and pre-event rows (no `event_id`) by their own.
652
+ * const eventIds = [...new Set(anomalies.flatMap((a) => (a.event_id ? [a.event_id] : [])))];
653
+ * const ids = anomalies.filter((a) => !a.event_id).map((a) => a.id);
654
+ * const { updated } = await client.anomalies.updateStatusBulk(
655
+ * { ids, eventIds, onlyStatuses: ["new", "acknowledged"] },
656
+ * "acknowledged"
657
+ * );
658
+ * ```
659
+ */
660
+ updateStatusBulk(target: {
661
+ ids?: string[];
662
+ eventIds?: string[];
663
+ onlyStatuses?: AnomalyStatus[];
664
+ }, status: AnomalyStatus): Promise<BulkUpdateStatusResponse>;
428
665
  /**
429
666
  * Run the metric-tree `explain` for an anomaly and cache the result on
430
- * the row. Subsequent calls return the cached `ExplainResult` instantly.
667
+ * the row. Subsequent calls return the cached `ExplainResult` instantly;
668
+ * pass `{ refresh: true }` to bust the cache and recompute.
669
+ *
670
+ * The uncached path runs a 20-30 s recursive driver search — budget for it
671
+ * (or read `explain_cache` off the row from {@link list} when it's already
672
+ * populated).
431
673
  */
432
- explain(anomalyId: string): Promise<ExplainResult>;
674
+ explain(anomalyId: string, options?: ExplainOptions): Promise<ExplainResult>;
433
675
  }
434
676
  //#endregion
677
+ //#region src/custom-app/base64.d.ts
678
+ /**
679
+ * Encode bytes as standard (padded) base64.
680
+ *
681
+ * ```ts
682
+ * const pdf = new Uint8Array(await renderReport());
683
+ * await ctx.email.send({
684
+ * to: ctx.user.email,
685
+ * subject: "Report",
686
+ * text: "attached",
687
+ * attachments: [{ filename: "report.pdf", content: bytesToBase64(pdf) }]
688
+ * });
689
+ * ```
690
+ *
691
+ * For **text** you generated, skip this entirely and pass the string with
692
+ * `encoding: "utf8"` — it needs no encoder and stays byte-exact for non-ASCII.
693
+ */
694
+ declare function bytesToBase64(input: Uint8Array | ArrayBuffer | ArrayBufferView): string;
695
+ /**
696
+ * Decode standard base64 to bytes — e.g. the body from
697
+ * `ctx.storage.get(key, { encoding: "base64" })`.
698
+ *
699
+ * Throws on malformed input rather than returning a short buffer: a truncated
700
+ * decode that reports success is a corrupt file nobody notices.
701
+ */
702
+ declare function base64ToBytes(base64: string): Uint8Array;
703
+ //#endregion
435
704
  //#region src/custom-app/debug.d.ts
436
705
  /** Untyped at the boundary — keep it loose so server-side schema
437
706
  * additions don't break older bundles. Stable enough for inspection
@@ -481,11 +750,60 @@ interface OxyFunctionRequest {
481
750
  }
482
751
  /** A single row from a `ctx.query` / `ctx.queryStream` result. */
483
752
  type OxyFunctionRow = Record<string, unknown>;
484
- /** Identity of the invoking user (route) or the system identity (schedule/airway). */
753
+ /** One org team the caller belongs to, as reported by {@link OxyFunctionUser.teams}. */
754
+ interface OxyOrgTeam {
755
+ id: string;
756
+ name: string;
757
+ }
758
+ /**
759
+ * Who — or what — invoked this function.
760
+ *
761
+ * `"system"` means **no caller to attribute this to** — not necessarily "no
762
+ * human caused it". A schedule tick, an Airway transform step, and an operator's
763
+ * manual *Run now* all take this path: they run under the org owner's `id` (the
764
+ * invocation record needs a real user FK) with every caller field absent. So on
765
+ * a manual run a person really did click, and there is still no way to reach
766
+ * them; the platform does not carry the triggering operator through the job
767
+ * queue.
768
+ *
769
+ * Any branch that emails "the person who clicked" or renders a personal view
770
+ * must check this rather than sniff the synthetic `email` — and must have a
771
+ * sensible answer for the case where there is nobody to send to.
772
+ */
773
+ type OxyIdentityKind = "user" | "system";
774
+ /**
775
+ * Identity of the invoking user (route) or the system identity (schedule,
776
+ * Airway step, or a manual job run).
777
+ *
778
+ * Assembled server-side on every invocation from the authenticated session —
779
+ * **nothing on it is client-supplied**, which is the entire reason to read
780
+ * identity here instead of from the request body. See
781
+ * `internal-docs/custom-apps-user-identity.md` for the full contract, including
782
+ * what the client-side `useShellContext()` can and cannot be trusted for.
783
+ */
485
784
  interface OxyFunctionUser {
785
+ /**
786
+ * `users.id`. On a `"system"` invocation this is the org owner's id and not a
787
+ * caller — check {@link kind} before attributing anything to it.
788
+ */
486
789
  id: string;
790
+ /** Their email, or `schedule+<fn>@system.oxy` when {@link kind} is `"system"`. */
487
791
  email: string;
792
+ /**
793
+ * The org that owns this app — the tenant boundary for anything the function
794
+ * reads or writes.
795
+ *
796
+ * Servers before 2026-08-21 mistakenly sent this as `org_id`, so `orgId` read
797
+ * `undefined` there; both keys are populated now. If your function filters SQL
798
+ * on it, that is exactly the bug to re-check.
799
+ */
488
800
  orgId: string;
801
+ /** Display name. Absent on a `"system"` invocation. User-controlled free text —
802
+ * fine for a greeting or an audit row, never a key, and escape it before it
803
+ * reaches HTML or SQL. */
804
+ name?: string;
805
+ /** Avatar URL. Absent when unset or on a `"system"` invocation. */
806
+ picture?: string;
489
807
  /**
490
808
  * The caller's role **within this app**, derived server-side from app
491
809
  * membership (with org-owner / Oxy-staff break-glass). Absent when they hold
@@ -502,20 +820,104 @@ interface OxyFunctionUser {
502
820
  *
503
821
  * Note it is deliberately NOT the org role: an app admin administers one app
504
822
  * without holding org-Admin (which also carries billing and member management).
823
+ *
824
+ * A `"system"` invocation runs under the org owner, so this reads `"admin"`
825
+ * there — a schedule carries owner authority by construction. Add a
826
+ * {@link kind} check when a surface must be human-only.
505
827
  */
506
828
  appRole?: "admin" | "member";
829
+ /**
830
+ * The caller's role in the owning **org**. Absent when they reach the app
831
+ * without an org membership (Oxy staff on break-glass) or on a `"system"`
832
+ * invocation.
833
+ *
834
+ * Informational, not a gate — org standing and app standing are separate
835
+ * rings. Use it to explain ("ask your org admin to connect a warehouse"), to
836
+ * label, or to route; gate on {@link appRole}.
837
+ */
838
+ orgRole?: "owner" | "admin" | "member";
839
+ /**
840
+ * The org teams the caller belongs to, name-sorted, and scoped to this app's
841
+ * org — teams they hold in other orgs are never reported. Empty when they
842
+ * belong to none.
843
+ *
844
+ * Optional because a server older than 2026-08-21 does not send it: use
845
+ * `ctx.user.teams?.some(...)`, never `ctx.user.teams.some(...)`, or the
846
+ * function throws on that server rather than degrading.
847
+ *
848
+ * Useful for *shaping* a view (default the Finance team to the finance tab).
849
+ * Not a permission: a team only grants anything on an app through an app team
850
+ * grant, which is already folded into {@link appRole}. Gating on a team name
851
+ * invents a permission the platform cannot revoke.
852
+ */
853
+ teams?: OxyOrgTeam[];
854
+ /**
855
+ * Whether there is a caller to attribute this invocation to.
856
+ *
857
+ * On a current server this is exact — `ctx.user.kind === "system"` is the
858
+ * check.
859
+ *
860
+ * Optional for the same reason as {@link teams}: a server older than
861
+ * 2026-08-21 does not send it. Note there is no safe *inference* to fall back
862
+ * on, in either direction — `=== "system"` reads `false` for a cron tick, and
863
+ * `!== "user"` reads `true` for a real person. An older server genuinely
864
+ * cannot tell you.
865
+ *
866
+ * So if you must support one, don't infer: a schedule invokes the function
867
+ * with the `input` you configured on it, which is yours to mark.
868
+ *
869
+ * ```ts
870
+ * const body = JSON.parse(req.body || "{}");
871
+ * const isSystem = ctx.user.kind ? ctx.user.kind === "system" : body._trigger === "schedule";
872
+ * ```
873
+ */
874
+ kind?: OxyIdentityKind;
507
875
  }
508
876
  /** Result of a `ctx.fetch` call. */
509
877
  interface OxyFetchResult {
510
878
  status: number;
879
+ /** Response body, decoded per the requested {@link OxyFetchInit.encoding}. */
511
880
  body: string;
881
+ /** Echoes how `body` was encoded (`"utf8"` unless base64 was requested). */
882
+ encoding?: "utf8" | "base64";
512
883
  }
884
+ /**
885
+ * `init` for `ctx.fetch` — the standard `RequestInit` fields the host honours
886
+ * (`method`, `headers`, `body`) plus how to decode the response.
887
+ */
888
+ type OxyFetchInit = RequestInit & {
889
+ /**
890
+ * How to decode the response body. `"utf8"` (default) is **lossy for
891
+ * binary** — every non-UTF-8 byte becomes U+FFFD, so a fetched PDF/PNG comes
892
+ * back corrupt. Pass `"base64"` for any binary response, e.g. to hand it
893
+ * straight to an email attachment.
894
+ */
895
+ encoding?: "utf8" | "base64";
896
+ };
513
897
  /** `ctx.warehouse.*` — writes to one of the app's configured destination databases. */
514
898
  interface OxyWarehouseApi {
515
899
  insert(database: string, table: string, rows: OxyFunctionRow[]): Promise<unknown>;
516
900
  exec(database: string, sql: string): Promise<unknown>;
517
901
  upsert(database: string, table: string, rows: OxyFunctionRow[], conflictColumns: string[]): Promise<unknown>;
518
902
  }
903
+ /**
904
+ * The handle `ctx.tx` passes to your callback — a pinned connection with an
905
+ * open transaction.
906
+ *
907
+ * Both methods take **bound parameters** (`$1`, `$2`, …). Never build SQL by
908
+ * concatenating request data: `ctx.warehouse.exec` takes a bare string, but a
909
+ * transaction exists for surfaces that accept end-user input, and placeholders
910
+ * are the only thing that makes that safe.
911
+ *
912
+ * The handle is live only for the duration of the callback. Using it after the
913
+ * callback returns throws — it is not a connection you can stash.
914
+ */
915
+ interface OxyTransaction {
916
+ /** Run a row-returning statement (including `INSERT … RETURNING`). */
917
+ query(sql: string, params?: unknown[]): Promise<OxyFunctionRow[]>;
918
+ /** Run a statement for its effect; resolves to the number of rows affected. */
919
+ exec(sql: string, params?: unknown[]): Promise<number>;
920
+ }
519
921
  /** `ctx.secrets` — write app-scoped secrets (gated by the `secrets.write` capability). */
520
922
  interface OxySecretsApi {
521
923
  set(key: string, value: string): Promise<void>;
@@ -561,6 +963,9 @@ interface EmailSendInput {
561
963
  * Files to attach. Max 20 per send, and **10 MiB decoded in total** — SES
562
964
  * caps a whole message near 40 MB, so for anything larger store the file with
563
965
  * {@link OxyStorageApi} and email a presigned link instead of inlining it.
966
+ *
967
+ * `content` is base64 by default; for generated text set
968
+ * `encoding: "utf8"` and attach the string as-is.
564
969
  */
565
970
  attachments?: EmailAttachment[];
566
971
  }
@@ -568,8 +973,23 @@ interface EmailSendInput {
568
973
  interface EmailAttachment {
569
974
  /** Filename shown to the recipient. Required; path separators are stripped. */
570
975
  filename: string;
571
- /** File bytes, **base64-encoded** (the only way binary crosses the isolate boundary). */
976
+ /**
977
+ * File contents, interpreted per {@link EmailAttachment.encoding} — base64 by
978
+ * default, which is the only way binary crosses the isolate boundary.
979
+ */
572
980
  content: string;
981
+ /**
982
+ * How `content` is encoded. Defaults to `"base64"`.
983
+ *
984
+ * Use `"utf8"` to attach text the function just generated (CSV, JSON, HTML)
985
+ * — it needs no encoder and is byte-exact for non-ASCII. `btoa` is the wrong
986
+ * tool there: it encodes U+0080..U+00FF as *Latin1*, so accented text comes
987
+ * out as mojibake rather than as an error. For binary, take base64 straight
988
+ * from the source — `ctx.storage.get(key, { encoding: "base64" })` or
989
+ * `ctx.fetch(url, { encoding: "base64" })` — or {@link bytesToBase64} for a
990
+ * `Uint8Array` you built yourself.
991
+ */
992
+ encoding?: "base64" | "utf8";
573
993
  /** MIME type; defaults to `application/octet-stream`. */
574
994
  contentType?: string;
575
995
  /** Render inline (e.g. an image referenced as `cid:<contentId>`) instead of as a download. */
@@ -619,6 +1039,30 @@ interface StorageUploadUrl {
619
1039
  key: string;
620
1040
  /** ISO-8601 expiry of the presigned URL. */
621
1041
  expiresAt: string;
1042
+ /**
1043
+ * Retention tag for this key, present only when your app declares a matching
1044
+ * `storage.retention` rule in `oxy-app.json` (e.g. `"oxy-ttl=30d"`).
1045
+ *
1046
+ * **When present, the upload MUST send it as the `x-amz-tagging` header** — it
1047
+ * is bound into the signature, so omitting it fails the PUT with a signature
1048
+ * mismatch rather than storing an untagged object:
1049
+ *
1050
+ * ```ts
1051
+ * const { url, tagging } = await ctx.storage.getUploadUrl({ ... });
1052
+ * await fetch(url, {
1053
+ * method: "PUT",
1054
+ * body: file,
1055
+ * headers: {
1056
+ * "Content-Type": file.type,
1057
+ * ...(tagging ? { "x-amz-tagging": tagging } : {}),
1058
+ * },
1059
+ * });
1060
+ * ```
1061
+ *
1062
+ * Signing it is deliberate: a browser that could drop the header could opt any
1063
+ * upload out of the app's own retention policy.
1064
+ */
1065
+ tagging?: string;
622
1066
  }
623
1067
  /** A minted presigned download. */
624
1068
  interface StorageDownloadUrl {
@@ -762,9 +1206,45 @@ interface OxyFunctionContext {
762
1206
  queryStream(sql: string, opts?: {
763
1207
  batchSize?: number;
764
1208
  }): AsyncGenerator<OxyFunctionRow[], void, unknown>;
765
- /** SSRF-allowlisted outbound HTTP with a response-size cap. */
766
- fetch(url: string, init?: RequestInit): Promise<OxyFetchResult>;
1209
+ /**
1210
+ * SSRF-allowlisted outbound HTTP with a response-size cap. Pass
1211
+ * `{ encoding: "base64" }` for a binary response — the default UTF-8 decode
1212
+ * corrupts it.
1213
+ */
1214
+ fetch(url: string, init?: OxyFetchInit): Promise<OxyFetchResult>;
767
1215
  warehouse: OxyWarehouseApi;
1216
+ /**
1217
+ * Run several statements atomically on one connection: commits when your
1218
+ * callback resolves, rolls back when it throws, and rethrows your error
1219
+ * either way. Resolves to whatever the callback returns.
1220
+ *
1221
+ * `database` must be in this function's manifest `destinations` — a
1222
+ * transaction is a write, and the same fail-closed allowlist applies. Postgres
1223
+ * only; other backends reject `ctx.tx` rather than faking it.
1224
+ *
1225
+ * **Do not catch a failed statement and return normally.** A statement the
1226
+ * server rejects aborts the whole transaction, and `COMMIT` on an aborted
1227
+ * transaction does not fail — Postgres applies nothing and reports success —
1228
+ * so `ctx.tx` refuses to commit and throws instead, naming the statement that
1229
+ * poisoned it. Let the error propagate.
1230
+ *
1231
+ * ```ts
1232
+ * const orderId = await ctx.tx("appdb", async (tx) => {
1233
+ * const [{ id }] = await tx.query(
1234
+ * "INSERT INTO orders (table_no) VALUES ($1) RETURNING id",
1235
+ * [tableNo],
1236
+ * );
1237
+ * for (const it of items) {
1238
+ * await tx.exec(
1239
+ * "INSERT INTO order_items (order_id, sku, qty) VALUES ($1, $2, $3)",
1240
+ * [id, it.sku, it.qty],
1241
+ * );
1242
+ * }
1243
+ * return id;
1244
+ * });
1245
+ * ```
1246
+ */
1247
+ tx<T>(database: string, fn: (tx: OxyTransaction) => Promise<T> | T): Promise<T>;
768
1248
  secrets: OxySecretsApi;
769
1249
  semantic: OxySemanticApi;
770
1250
  airway: OxyAirwayApi;
@@ -813,5 +1293,333 @@ declare function setOxyAppLogger(logger: OxyAppLogger | null): void;
813
1293
  /** Used by the SDK internals; not part of the public surface. */
814
1294
  declare function getOxyAppLogger(): OxyAppLogger;
815
1295
  //#endregion
816
- export { type AgentArtifact, type AgentRunEvent, type AgentRunState, type AgentSqlArtifact, AnomaliesClient, type Anomaly, type AnomalySeverity, type AnomalyStatus, type AppFetcher, type CustomAppDebugSnapshot, type CustomAppErrorReport, type DimensionOpportunity, type DriverAttribution, type DriverConfidence, type DriverDirection, type DriverForm, type DriverStrength, type EdgeKind, type EmailAttachment, type EmailSendInput, type EmailSendResult, type ExplainConfigOverride, type ExplainNode, type ExplainRequest, type ExplainResult, type ExplainSibling, type ExplainWarning, type ListAnomaliesOptions, type ListAnomaliesResponse, type LoadManifestOptions, type MetricEdge, type MetricNode, type MetricTree, MetricTreeClient, type OpportunityRequest, type OpportunityResult, type OxyAirwayApi, OxyAnswer, type OxyAnswerProps, OxyApiError, type OxyAppFunctionManifest, type OxyAppLogLevel, type OxyAppLogger, type OxyAppManifest, OxyAppProvider, type OxyAppProviderProps, OxyChat, type OxyChatProps, type OxyEmailApi, type OxyFetchResult, type OxyFunctionContext, type OxyFunctionHandler, type OxyFunctionRequest, type OxyFunctionRow, type OxyFunctionUser, type OxyInjectedAppConfig, type OxySecretsApi, type OxySemanticApi, type OxyStorageApi, type OxyWarehouseApi, type PredictChange, type PredictImpact, type PredictResult, type ProcedureProgress, type ProcedureResult, type ProcedureRunState, type ResolvedCustomAppManifest, type ScanOptions, type ScanResponse, type SegmentOpportunity, type SemanticArrayOp, type SemanticDateRangeOp, type SemanticFilter, type SemanticScalarOp, type SemanticTimeDimension, type SensitivityDriver, type SensitivityResult, type SkippedDimension, type SplitKind, type StorageDownloadUrl, type StorageListPage, type StorageObject, type StoragePutOptions, type StoragePutResult, type StorageUploadUrl, type StorageUploadUrlInput, type UseAgentRunInput, type UseAgentRunResult, type UseFunctionResult, type UseProcedureRunInput, type UseProcedureRunOpts, type UseProcedureRunResult, type UseQueryInput, type UseQueryOpts, type UseQueryResult, type UseSemanticQueryInput, type UseSemanticQueryOpts, type UseSemanticQueryResult, _resetCustomAppManifestCacheForTest, apiErrorFromResponse, getCustomAppDebug, getOxyAppLogger, interpretCustomAppError, loadCustomAppManifest, readInjectedAppConfig, setOxyAppLogger, useAgentRun, useFunction, useOxyApp, useProcedureRun, useQuery, useResolvedManifest, useSemanticQuery, useTrackEvent };
1296
+ //#region src/custom-app/metric-tree-hooks.d.ts
1297
+ /** Shared result envelope for every metric-tree hook. */
1298
+ interface MetricTreeHookResult<Data> {
1299
+ data: Data | null;
1300
+ loading: boolean;
1301
+ error: Error | null;
1302
+ /** Force a re-run, bypassing nothing — the server honors `?refresh`. */
1303
+ refetch: () => void;
1304
+ }
1305
+ interface EndpointOpts {
1306
+ /** Set false to skip the request (e.g. waiting on a user selection). */
1307
+ enabled?: boolean;
1308
+ }
1309
+ interface UseMetricTreeOpts extends EndpointOpts {
1310
+ /** Optional measure id to root the returned subtree at. */
1311
+ root?: string;
1312
+ }
1313
+ /**
1314
+ * The project's metric tree — measures (nodes) and their component /
1315
+ * driver relationships (edges) — or the subtree rooted at `opts.root`.
1316
+ * The structural backbone every other metric-tree analysis reads against.
1317
+ */
1318
+ declare function useMetricTree(opts?: UseMetricTreeOpts): MetricTreeHookResult<MetricTree>;
1319
+ /**
1320
+ * Ranked drivers of `measureId`, by influence — the "what moves this
1321
+ * measure" question. Pass `null` to stay idle until a measure is chosen.
1322
+ */
1323
+ declare function useSensitivity(measureId: string | null, opts?: EndpointOpts): MetricTreeHookResult<SensitivityResult>;
1324
+ /**
1325
+ * Propagate hypothetical `(measure, delta)` changes upward through the
1326
+ * tree and return the estimated impact on every downstream measure — a
1327
+ * pure metric-tree walk, no warehouse query. Pass `null` to stay idle.
1328
+ */
1329
+ declare function usePredict(changes: PredictChange[] | null, opts?: EndpointOpts): MetricTreeHookResult<PredictResult>;
1330
+ /**
1331
+ * Period-over-period root-cause decomposition: recursively splits the
1332
+ * target measure by components and dimensions until the move concentrates.
1333
+ * This is the heavy one — it can fire many warehouse queries and the
1334
+ * server caps it at 45s. Pass `null` to defer until periods are chosen.
1335
+ */
1336
+ declare function useExplain(request: ExplainRequest | null, opts?: EndpointOpts): MetricTreeHookResult<ExplainResult>;
1337
+ /**
1338
+ * Single-period distribution of a measure — an {@link ExplainResult}
1339
+ * against an auto-derived immediately-prior baseline. Same renderers as
1340
+ * `useExplain`; ignore the delta fields for a pure distribution view.
1341
+ */
1342
+ declare function useDistribution(request: DistributionRequest | null, opts?: EndpointOpts): MetricTreeHookResult<ExplainResult>;
1343
+ /**
1344
+ * Segment opportunity sizing for a measure over a period: finds
1345
+ * underperforming segments and sizes the addressable upside of closing
1346
+ * each rate gap against a benchmark peer. Pass `null` to stay idle until
1347
+ * a target + period are chosen.
1348
+ */
1349
+ declare function useOpportunity(request: OpportunityRequest | null, opts?: EndpointOpts): MetricTreeHookResult<OpportunityResult>;
1350
+ /**
1351
+ * The queryable time dimensions per view (`view.dim` ids) — what a
1352
+ * bundle offers as the period axis for `explain` / `opportunity` /
1353
+ * `distribution` instead of hardcoding a curated map.
1354
+ */
1355
+ declare function useTimeDimensions(opts?: EndpointOpts): MetricTreeHookResult<TimeDimensionsResponse>;
1356
+ //#endregion
1357
+ //#region src/custom-app/sse.d.ts
1358
+ /**
1359
+ * Read a `text/event-stream` response, invoking `onEvent` with each parsed
1360
+ * JSON frame. Frames that fail to parse are skipped (a malformed frame must
1361
+ * not tear down the whole stream). Resolves when the body closes.
1362
+ */
1363
+ declare function readJsonSseStream<E>(resp: Response, onEvent: (event: E) => void): Promise<void>;
1364
+ //#endregion
1365
+ //#region src/worldModel.d.ts
1366
+ /** How a measure aggregates across the entity hierarchy. */
1367
+ type AdditivityClass = "additive" | "non_additive" | "passthrough";
1368
+ interface WorldModelMeasure {
1369
+ name: string;
1370
+ measure_type: string;
1371
+ additivity: AdditivityClass;
1372
+ description?: string | null;
1373
+ expr?: string | null;
1374
+ label?: string | null;
1375
+ /** True when the measure decomposes into a metric-tree driver breakdown. */
1376
+ has_breakdown?: boolean;
1377
+ }
1378
+ /** A measure promoted onto this entity from a descendant view. */
1379
+ interface WorldModelInducedMeasure extends WorldModelMeasure {
1380
+ /** View the measure is actually declared on. */
1381
+ promoted_from: string;
1382
+ /** Promotion path from the declaring view up to this entity. */
1383
+ path: string[];
1384
+ }
1385
+ interface WorldModelDimension {
1386
+ name: string;
1387
+ dim_type: string;
1388
+ description?: string | null;
1389
+ label?: string | null;
1390
+ }
1391
+ interface WorldModelEntity {
1392
+ id: string;
1393
+ label: string;
1394
+ view: string;
1395
+ description?: string | null;
1396
+ depth: number;
1397
+ dimensions: WorldModelDimension[];
1398
+ own_measures: WorldModelMeasure[];
1399
+ induced_measures: WorldModelInducedMeasure[];
1400
+ display_field?: string | null;
1401
+ }
1402
+ /** A promotion edge: measures on `from` promote up to `to`. */
1403
+ interface WorldModelEdge {
1404
+ from: string;
1405
+ to: string;
1406
+ functional: boolean;
1407
+ }
1408
+ interface WorldModel {
1409
+ entities: WorldModelEntity[];
1410
+ edges: WorldModelEdge[];
1411
+ }
1412
+ interface WmInstance {
1413
+ key: string;
1414
+ display: string;
1415
+ }
1416
+ interface WmInstancesResponse {
1417
+ total: number;
1418
+ has_more: boolean;
1419
+ items: WmInstance[];
1420
+ }
1421
+ interface WmEntityCount {
1422
+ matched: number;
1423
+ total: number;
1424
+ /** Sample of reachable descendant rows at this grain (display strings). */
1425
+ sample?: string[];
1426
+ /** Navigation keys aligned with `sample`. */
1427
+ sample_keys?: string[];
1428
+ }
1429
+ interface WmFilterCountsResponse {
1430
+ counts: Record<string, WmEntityCount>;
1431
+ }
1432
+ interface WmBreakdownNode {
1433
+ /** Metric node id `view.measure`. */
1434
+ id: string;
1435
+ view: string;
1436
+ measure: string;
1437
+ label: string;
1438
+ measure_type: string;
1439
+ is_composite: boolean;
1440
+ is_root: boolean;
1441
+ expr?: string | null;
1442
+ /** Filled by `value` frames; null while pending. */
1443
+ value: string | null;
1444
+ unvalued_reason: string | null;
1445
+ }
1446
+ interface WmBreakdownEdge {
1447
+ from: string;
1448
+ to: string;
1449
+ operator: "add" | "sub" | "mul" | "div";
1450
+ sign: number;
1451
+ }
1452
+ /** One frame of the measure-breakdown stream. The `init` frame carries the
1453
+ * graph shape; `value` frames fill node values in as they resolve. */
1454
+ type WmMeasureBreakdownEvent = {
1455
+ kind: "init";
1456
+ root: string;
1457
+ nodes: Omit<WmBreakdownNode, "value" | "unvalued_reason">[];
1458
+ edges: WmBreakdownEdge[];
1459
+ } | {
1460
+ kind: "value";
1461
+ node_id: string;
1462
+ value: string | null;
1463
+ unvalued_reason: string | null;
1464
+ } | {
1465
+ kind: "done";
1466
+ };
1467
+ /** Accumulated breakdown — the shape `useMeasureBreakdown` folds the
1468
+ * stream into. */
1469
+ interface WmMeasureBreakdown {
1470
+ root: string;
1471
+ nodes: WmBreakdownNode[];
1472
+ edges: WmBreakdownEdge[];
1473
+ }
1474
+ //#endregion
1475
+ //#region src/custom-app/world-model-hooks.d.ts
1476
+ interface UseWorldModelGraphResult {
1477
+ data: WorldModel | null;
1478
+ loading: boolean;
1479
+ error: Error | null;
1480
+ refetch: () => void;
1481
+ }
1482
+ /**
1483
+ * The world-model graph — entities (nodes), their measures/dimensions, and
1484
+ * how measures promote across the entity hierarchy (edges). Applies the
1485
+ * project's `.world-model.yml` display config server-side.
1486
+ *
1487
+ * @remarks
1488
+ * This returns the raw semantic-layer entity graph. For the higher-level
1489
+ * node-paradigm interface (`world.metric(id)` speaking `expand` / `explain` /
1490
+ * `size`), use {@link useWorldModel} from `./world-node` instead.
1491
+ */
1492
+ declare function useWorldModelGraph(opts?: {
1493
+ enabled?: boolean;
1494
+ }): UseWorldModelGraphResult;
1495
+ interface UseWorldModelInstancesOpts {
1496
+ /** Substring/prefix search over the entity's display field. */
1497
+ search?: string;
1498
+ /** Max rows to return (default 50 server-side). */
1499
+ limit?: number;
1500
+ enabled?: boolean;
1501
+ }
1502
+ interface UseWorldModelInstancesResult {
1503
+ data: WmInstancesResponse | null;
1504
+ loading: boolean;
1505
+ error: Error | null;
1506
+ refetch: () => void;
1507
+ }
1508
+ /**
1509
+ * List the instances (rows) of `entityId` — a bounded, searchable picker
1510
+ * over the entity's primary keys + display label. Pass `null` for `entityId`
1511
+ * to stay idle until an entity is chosen.
1512
+ */
1513
+ declare function useWorldModelInstances(entityId: string | null, opts?: UseWorldModelInstancesOpts): UseWorldModelInstancesResult;
1514
+ interface UseMeasureBreakdownResult {
1515
+ /** Accumulated breakdown graph; null until the `init` frame. */
1516
+ breakdown: WmMeasureBreakdown | null;
1517
+ loading: boolean;
1518
+ done: boolean;
1519
+ error: Error | null;
1520
+ }
1521
+ /**
1522
+ * Stream the driver-tree breakdown of one instance's measure — the metric
1523
+ * decomposition (add/sub/mul/div component graph) with each node's value
1524
+ * filling in as it resolves. This is the per-instance RCA view. Pass `null`
1525
+ * for `measure` to stay idle.
1526
+ */
1527
+ declare function useMeasureBreakdown(entityId: string | null, keyValue: string | null, measure: string | null): UseMeasureBreakdownResult;
1528
+ //#endregion
1529
+ //#region src/custom-app/world-node.d.ts
1530
+ /** A `dimension → value` scope narrowed onto a node via {@link MetricHandle.drill}. */
1531
+ type MetricScope = Readonly<Record<string, string>>;
1532
+ /** Options for {@link MetricHandle.explain} — an {@link ExplainRequest} minus
1533
+ * the `target`, which the handle supplies from its own id. */
1534
+ type ExplainOpts = Omit<ExplainRequest, "target">;
1535
+ /** Options for {@link MetricHandle.size} — an {@link OpportunityRequest} minus
1536
+ * the `target`. */
1537
+ type SizeOpts = Omit<OpportunityRequest, "target">;
1538
+ /** One child revealed by {@link MetricHandle.expand}: the child measure's
1539
+ * node, the edge that connects it to the parent, and a handle to recurse. */
1540
+ interface ExpandedNode {
1541
+ /** The child measure (a component or a driver of the parent). */
1542
+ node: MetricNode;
1543
+ /** The parent → child edge — `kind`, `direction`, `strength`, `form`, … */
1544
+ edge: MetricEdge;
1545
+ /** A live handle on the child, carrying the parent's scope. */
1546
+ handle: MetricHandle;
1547
+ }
1548
+ /**
1549
+ * A live handle on one metric node. Carry it around and call a verb; every
1550
+ * verb returns either more nodes (`expand`), a scoped handle (`drill`), or an
1551
+ * analysis result (`explain` / `size` / `drivers`).
1552
+ */
1553
+ interface MetricHandle {
1554
+ /** Fully-qualified measure id (`view.measure`). */
1555
+ readonly id: string;
1556
+ /** The scope narrowed onto this handle by `drill` (empty for a root handle). */
1557
+ readonly scope: MetricScope;
1558
+ /** The measure's own tree node (label, expr, is_composite). */
1559
+ node(signal?: AbortSignal): Promise<MetricNode>;
1560
+ /** One hop of relationships — the metric's components and drivers as child nodes. */
1561
+ expand(signal?: AbortSignal): Promise<ExpandedNode[]>;
1562
+ /** The declared drivers of this measure, ranked by influence (sensitivity). */
1563
+ drivers(signal?: AbortSignal): Promise<SensitivityResult>;
1564
+ /** Root-cause a period-over-period move: why it dropped or climbed. */
1565
+ explain(opts: ExplainOpts, signal?: AbortSignal): Promise<ExplainResult>;
1566
+ /** Compare this node to its peers across each dimension and size the gap. */
1567
+ size(opts: SizeOpts, signal?: AbortSignal): Promise<OpportunityResult>;
1568
+ /** Narrow into a segment or entity instance — returns a scoped handle. */
1569
+ drill(scope: Record<string, string>): MetricHandle;
1570
+ }
1571
+ /**
1572
+ * The World Model interface, scoped to one project. The whole surface hangs
1573
+ * off this: grab a {@link MetricHandle} with `metric(id)` and the handle
1574
+ * speaks the verbs, or pull the whole graph with `tree(root?)`.
1575
+ */
1576
+ interface WorldModelApi {
1577
+ /** The active project id, or `null` before `<OxyAppProvider>` resolves one. */
1578
+ readonly projectId: string | null;
1579
+ /** The metric tree, rooted anywhere you like (default: the whole tree). */
1580
+ tree(root?: string, signal?: AbortSignal): Promise<MetricTree>;
1581
+ /** A live handle on one measure node. */
1582
+ metric(id: string): MetricHandle;
1583
+ }
1584
+ /**
1585
+ * Thrown by the value verbs (`explain` / `size`) when called on a handle that
1586
+ * has been `drill`ed. The metric-tree backend cannot yet scope these analyses
1587
+ * to a segment, so failing loud beats returning population numbers for a
1588
+ * question that asked about one segment.
1589
+ */
1590
+ declare class WorldModelScopeUnsupportedError extends Error {
1591
+ readonly code = "world_model_scope_unsupported";
1592
+ readonly scope: MetricScope;
1593
+ constructor(verb: string, scope: MetricScope);
1594
+ }
1595
+ /**
1596
+ * Build a {@link WorldModelApi} over a project id and fetcher. Framework-
1597
+ * agnostic — `useWorldModel()` wraps this for React, but it is directly
1598
+ * unit-testable with a mock fetcher.
1599
+ */
1600
+ declare function createWorldModel(projectId: string | null, fetcher: AppFetcher): WorldModelApi;
1601
+ /**
1602
+ * The World Model node interface, scoped to the active `<OxyAppProvider>`
1603
+ * project. Returns a stable {@link WorldModelApi} — grab a node with
1604
+ * `world.metric(id)` and let it speak the verbs.
1605
+ *
1606
+ * @example
1607
+ * ```tsx
1608
+ * const world = useWorldModel();
1609
+ * const revenue = world.metric("orders.net_revenue");
1610
+ * const children = await revenue.expand(); // components + drivers
1611
+ * const rca = await revenue.explain({
1612
+ * time_dimension: "orders.order_date",
1613
+ * current_period: ["2026-06-01", "2026-06-30"],
1614
+ * previous_period: ["2026-05-01", "2026-05-31"],
1615
+ * });
1616
+ * ```
1617
+ *
1618
+ * @remarks
1619
+ * This is the node-paradigm hook. For the raw semantic-layer entity/measure
1620
+ * graph, use {@link useWorldModelGraph} instead.
1621
+ */
1622
+ declare function useWorldModel(): WorldModelApi;
1623
+ //#endregion
1624
+ export { type AdditivityClass, type AgentArtifact, type AgentRunEvent, type AgentRunState, type AgentSqlArtifact, AnomaliesClient, type Anomaly, type AnomalyFilter, type AnomalySeverity, type AnomalyStatus, type AppFetcher, type BulkUpdateStatusResponse, type CustomAppDebugSnapshot, type CustomAppErrorReport, type DimensionOpportunity, type DistributionRequest, type DriverAttribution, type DriverConfidence, type DriverDirection, type DriverForm, type DriverStrength, type EdgeKind, type EmailAttachment, type EmailSendInput, type EmailSendResult, type ExpandedNode, type ExplainConfigOverride, type ExplainNode, type ExplainOptions, type ExplainOpts, type ExplainRequest, type ExplainResult, type ExplainSibling, type ExplainWarning, type ListAnomaliesOptions, type ListAnomaliesResponse, type LoadManifestOptions, type MetricEdge, type MetricHandle, type MetricNode, type MetricScope, type MetricTree, MetricTreeClient, type MetricTreeHookResult, type OpportunityRequest, type OpportunityResult, type OxyAirwayApi, OxyAnswer, type OxyAnswerProps, OxyApiError, type OxyAppFunctionManifest, type OxyAppLogLevel, type OxyAppLogger, type OxyAppManifest, OxyAppProvider, type OxyAppProviderProps, OxyChat, type OxyChatProps, type OxyEmailApi, type OxyFetchResult, type OxyFunctionContext, type OxyFunctionHandler, type OxyFunctionRequest, type OxyFunctionRow, type OxyFunctionUser, type OxyIdentityKind, type OxyInjectedAppConfig, type OxyOrgTeam, type OxySecretsApi, type OxySemanticApi, type OxyStorageApi, type OxyTransaction, type OxyWarehouseApi, type PredictChange, type PredictImpact, type PredictResult, type ProcedureProgress, type ProcedureResult, type ProcedureRunState, type ResolvedCustomAppManifest, type ScanFailure, type ScanOptions, type ScanResponse, type SegmentOpportunity, type SemanticArrayOp, type SemanticDateRangeOp, type SemanticFilter, type SemanticScalarOp, type SemanticTimeDimension, type SensitivityDriver, type SensitivityResult, type SizeOpts, type SkippedDimension, type SplitKind, type StorageDownloadUrl, type StorageListPage, type StorageObject, type StoragePutOptions, type StoragePutResult, type StorageUploadUrl, type StorageUploadUrlInput, type TimeDimensionsResponse, type UseAgentRunInput, type UseAgentRunResult, type UseFunctionResult, type UseMeasureBreakdownResult, type UseMetricTreeOpts, type UseProcedureRunInput, type UseProcedureRunOpts, type UseProcedureRunResult, type UseQueryInput, type UseQueryOpts, type UseQueryResult, type UseSemanticQueryInput, type UseSemanticQueryOpts, type UseSemanticQueryResult, type UseWorldModelGraphResult, type UseWorldModelInstancesOpts, type UseWorldModelInstancesResult, type WmBreakdownEdge, type WmBreakdownNode, type WmEntityCount, type WmFilterCountsResponse, type WmInstance, type WmInstancesResponse, type WmMeasureBreakdown, type WmMeasureBreakdownEvent, type WorldModel, type WorldModelApi, type WorldModelDimension, type WorldModelEdge, type WorldModelEntity, type WorldModelInducedMeasure, type WorldModelMeasure, WorldModelScopeUnsupportedError, _resetCustomAppManifestCacheForTest, apiErrorFromResponse, base64ToBytes, bytesToBase64, createWorldModel, getCustomAppDebug, getOxyAppLogger, interpretCustomAppError, loadCustomAppManifest, readInjectedAppConfig, readJsonSseStream, setOxyAppLogger, useAgentRun, useDistribution, useExplain, useFunction, useMeasureBreakdown, useMetricTree, useOpportunity, useOxyApp, usePredict, useProcedureRun, useQuery, useResolvedManifest, useSemanticQuery, useSensitivity, useTimeDimensions, useTrackEvent, useWorldModel, useWorldModelGraph, useWorldModelInstances };
817
1625
  //# sourceMappingURL=index.d.cts.map