@xpert-ai/plugin-analytics-data-xpert 0.1.2 → 0.2.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/README.md CHANGED
@@ -18,4 +18,28 @@ Run `corepack pnpm exec nx run @xpert-ai/plugin-analytics-data-xpert:test` with
18
18
 
19
19
  Discovery preserves each adapter's `definition` (identity, version, native metadata and instructions) and each Action's input schema. Semantic Model, OData and future registered read-only adapters share this transport without sharing query syntax.
20
20
 
21
+ Before constructing a dashboard binding, call `dashboard_discover` with the selected source/provider and `requestJson` encoding:
22
+
23
+ ```json
24
+ {"kind":"query-schema","payload":{"resourceId":"<discovered-resource>","actionTypeCode":"<discovered-read-action>","target":{"entityRef":"<canonical-target>"},"params":{},"parameterPaths":{}},"parameters":{}}
25
+ ```
26
+
27
+ Omit `datasetId` to let the provider generate a valid local output key from the native payload, including its projection. An optional `datasetId` accepts a nonempty string of up to 512 characters: valid output keys (a letter followed by up to 79 letters, digits, `_`, `.` or `-`) are preserved; native references or other aliases are replaced with a generated key. Native resource IDs and targets are unchanged. Copy the returned `output.datasetId` as well as its fields.
28
+
29
+ Use the Action's actual bounded query arguments in `params`. This plugin-owned discovery request executes the same authorized `/agent-workbench/analytics/query` path as preview, with the current actor, host row limit and cancellation signal. It does not require a new DataXpert discovery endpoint. Copy `items[0].metadata.output` into the preview binding and use the same native payload and parameter values. The response includes no business rows and is not a preview receipt; a successful `dashboard_preview` is still required before saving.
30
+
31
+ Output fields retain exact native keys and declared types, roles and nullability, including empty results. Decimal/int64 cells remain exact strings. Stable field IDs are derived from native keys; duplicate semantic selectors use exact keys instead. Grain is left empty until query semantics establish uniqueness. Missing declarations fail with `DATAX_RESULT_SCHEMA_UNAVAILABLE`; duplicate native keys fail with `DATAX_RESULT_SCHEMA_AMBIGUOUS`. Legacy authored bindings can still execute without these optional declarations. Rediscover after changing the projection; existing incorrect saved bindings must be corrected and previewed before saving.
32
+
33
+ Release compatibility: this path uses existing exports from the published SDK/contracts 3.26.1 and does not require a new host capability. The companion SDK change only adds optional column metadata to its TypeScript contract. Merge and publish this plugin's Changeset, then update the installed plugin; merging source alone does not update an existing deployment. Schema authoring requires DataXpert to supply declared column metadata (including the decimal fix in data-xpert PR #108). SAP query errors, authentication, and model-generated malformed tool calls remain separate acceptance checks.
34
+
35
+ After building this plugin and Live Dashboard, run `node plugin-dev-harness/verify-datax-dashboard-schema.mjs` from the repository root. It invokes the real discovery and preview tools, provider and HTTP parser with published SDKs. Its local HTTP source and host-service fixtures do not establish customer SSO, SAP or browser acceptance.
36
+
21
37
  See [architecture and Binding usage](docs/index.mdx) and [compatibility and verification](docs/verification.mdx).
38
+ # Public query diagnostics
39
+
40
+ Non-success HTTP responses may contain the shared `xpert.analytics.diagnostic` v1 envelope.
41
+ Only validated public causes and opaque audit references cross into `AnalyticsProviderError`.
42
+ Malformed, sensitive, oversized or unfinished bodies use the existing status-only fallback.
43
+ Parameter mapping failures use provider-authored repair guidance without echoing values.
44
+
45
+ Structured diagnostics require `@xpert-ai/contracts` and `@xpert-ai/plugin-sdk` 3.32.0 or a compatible later 3.x release.
@@ -1,61 +1,14 @@
1
- import { z } from 'zod/v3';
2
1
  import type { AnalyticsBinding, AnalyticsValue, JSONValue, AnalyticsDiscoveryResult } from '@xpert-ai/contracts';
2
+ export { dataXpertPayloadSchema } from './payload';
3
3
  import { type AnalyticsProviderContext, type AnalyticsProviderResult, type RuntimeCapabilityRegistry } from '@xpert-ai/plugin-sdk';
4
4
  export type DataXpertClientContext = Pick<AnalyticsProviderContext, 'actor' | 'options' | 'signal' | 'deadlineAt' | 'maxRows'>;
5
- /** Native Action arguments stay plugin-owned; the host only interprets Dataset/Binding. */
6
- export declare const dataXpertPayloadSchema: z.ZodObject<{
7
- resourceId: z.ZodString;
8
- actionTypeCode: z.ZodString;
9
- target: z.ZodEffects<z.ZodObject<{
10
- entityId: z.ZodOptional<z.ZodString>;
11
- entityTypeCode: z.ZodOptional<z.ZodString>;
12
- entityRef: z.ZodOptional<z.ZodString>;
13
- }, "strict", z.ZodTypeAny, {
14
- entityTypeCode?: string | undefined;
15
- entityRef?: string | undefined;
16
- entityId?: string | undefined;
17
- }, {
18
- entityTypeCode?: string | undefined;
19
- entityRef?: string | undefined;
20
- entityId?: string | undefined;
21
- }>, {
22
- entityTypeCode?: string | undefined;
23
- entityRef?: string | undefined;
24
- entityId?: string | undefined;
25
- }, {
26
- entityTypeCode?: string | undefined;
27
- entityRef?: string | undefined;
28
- entityId?: string | undefined;
29
- }>;
30
- params: z.ZodRecord<z.ZodString, z.ZodType<JSONValue, z.ZodTypeDef, JSONValue>>;
31
- parameterPaths: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString, "many">>>;
32
- }, "strict", z.ZodTypeAny, {
33
- params: Record<string, JSONValue>;
34
- resourceId: string;
35
- target: {
36
- entityTypeCode?: string | undefined;
37
- entityRef?: string | undefined;
38
- entityId?: string | undefined;
39
- };
40
- actionTypeCode: string;
41
- parameterPaths: Record<string, string[]>;
42
- }, {
43
- params: Record<string, JSONValue>;
44
- resourceId: string;
45
- target: {
46
- entityTypeCode?: string | undefined;
47
- entityRef?: string | undefined;
48
- entityId?: string | undefined;
49
- };
50
- actionTypeCode: string;
51
- parameterPaths?: Record<string, string[]> | undefined;
52
- }>;
53
5
  export declare class DataXpertAnalyticsClient {
54
6
  private readonly runtime;
55
7
  constructor(runtime: RuntimeCapabilityRegistry);
56
8
  discover(request: JSONValue, context: DataXpertClientContext): Promise<AnalyticsDiscoveryResult>;
57
9
  private request;
58
10
  execute(binding: AnalyticsBinding, parameters: Record<string, AnalyticsValue>, context: DataXpertClientContext): Promise<AnalyticsProviderResult>;
11
+ private query;
59
12
  }
60
13
  /** Typed paths replace whole parameter values, never splice text into SQL/MDX/OData. */
61
14
  export declare function bindParameters(params: Record<string, JSONValue>, paths: Record<string, string[]>, parameters: Record<string, AnalyticsValue>): Record<string, JSONValue>;
@@ -17,19 +17,18 @@ exports.bindParameters = bindParameters;
17
17
  const common_1 = require("@nestjs/common");
18
18
  const v3_1 = require("zod/v3");
19
19
  const discovery_1 = require("./discovery");
20
+ const diagnostic_1 = require("./diagnostic");
21
+ const payload_1 = require("./payload");
22
+ var payload_2 = require("./payload");
23
+ Object.defineProperty(exports, "dataXpertPayloadSchema", { enumerable: true, get: function () { return payload_2.dataXpertPayloadSchema; } });
24
+ const import_targets_1 = require("./import-targets");
25
+ const result_schema_1 = require("./result-schema");
20
26
  const plugin_sdk_1 = require("@xpert-ai/plugin-sdk");
21
27
  const scalar = v3_1.z.union([v3_1.z.string(), v3_1.z.number().finite(), v3_1.z.boolean(), v3_1.z.null()]);
22
28
  const json = v3_1.z.lazy(() => v3_1.z.union([scalar, v3_1.z.array(json), v3_1.z.record(json)]));
23
- /** Native Action arguments stay plugin-owned; the host only interprets Dataset/Binding. */
24
- exports.dataXpertPayloadSchema = v3_1.z.object({
25
- resourceId: v3_1.z.string().min(1).max(160), actionTypeCode: v3_1.z.string().min(1).max(160),
26
- target: v3_1.z.object({
27
- entityId: v3_1.z.string().trim().min(1).max(256).optional(),
28
- entityTypeCode: v3_1.z.string().trim().min(1).max(256).optional(),
29
- entityRef: v3_1.z.string().trim().min(1).max(512).optional()
30
- }).strict().refine(target => Boolean(target.entityId || target.entityRef), 'Target reference is required'),
31
- params: v3_1.z.record(json),
32
- parameterPaths: v3_1.z.record(v3_1.z.array(v3_1.z.string().min(1)).min(1).max(8)).default({})
29
+ const querySchemaRequestSchema = v3_1.z.object({
30
+ kind: v3_1.z.literal('query-schema'), datasetId: v3_1.z.string().min(1).max(512).optional(),
31
+ payload: payload_1.dataXpertPayloadSchema, parameters: v3_1.z.record(scalar).default({})
33
32
  }).strict();
34
33
  /** Trusted DataSource configuration; never merge it with payloads, controls or discovery requests. */
35
34
  const optionsSchema = v3_1.z.object({
@@ -38,7 +37,7 @@ const optionsSchema = v3_1.z.object({
38
37
  }).strict();
39
38
  const responseSchema = v3_1.z.object({
40
39
  effect: v3_1.z.object({ version: v3_1.z.literal(2), kind: v3_1.z.enum(['rows', 'series', 'entity']),
41
- columns: v3_1.z.array(v3_1.z.object({ key: v3_1.z.string(), semanticRef: v3_1.z.string().optional() })),
40
+ columns: v3_1.z.array(result_schema_1.dataXpertResultColumnSchema),
42
41
  rows: v3_1.z.array(v3_1.z.record(scalar)),
43
42
  summary: v3_1.z.object({ rowCount: v3_1.z.number().int().nonnegative(), returnedRowCount: v3_1.z.number().int().nonnegative(), truncated: v3_1.z.boolean() }) }),
44
43
  fetchedAt: v3_1.z.string().datetime(), executionId: v3_1.z.string(), auditRef: v3_1.z.string(), fromCache: v3_1.z.literal(false)
@@ -48,6 +47,19 @@ let DataXpertAnalyticsClient = class DataXpertAnalyticsClient {
48
47
  this.runtime = runtime;
49
48
  }
50
49
  async discover(request, context) {
50
+ const importing = import_targets_1.importTargetRequest.safeParse(request);
51
+ if (importing.success)
52
+ return (0, import_targets_1.discoverImportTargets)(importing.data, browse => this.discover(browse, context));
53
+ if (request && typeof request === 'object' && !Array.isArray(request) && request.kind === 'query-schema') {
54
+ const parsed = querySchemaRequestSchema.parse(request);
55
+ const datasetId = (0, result_schema_1.createDataXpertDatasetId)(parsed.payload, parsed.datasetId);
56
+ const result = await this.query(datasetId, parsed.payload, parsed.parameters, context);
57
+ return plugin_sdk_1.analyticsDiscoveryResultSchema.parse({
58
+ items: [{ id: datasetId, name: datasetId, kind: 'dataset',
59
+ metadata: { output: (0, result_schema_1.createDataXpertOutput)(datasetId, result.effect.columns) } }],
60
+ queryContract: discovery_1.dataXpertQueryContract
61
+ });
62
+ }
51
63
  const empty = request && typeof request === 'object' && !Array.isArray(request) && Object.keys(request).length === 0;
52
64
  const parsed = discovery_1.dataXpertDiscoveryRequestSchema.parse(empty ? { kind: 'resources' } : request);
53
65
  const response = await this.request('discover', context, { request: parsed });
@@ -69,25 +81,28 @@ let DataXpertAnalyticsClient = class DataXpertAnalyticsClient {
69
81
  body: JSON.stringify({ assistant: { code: options.assistantCode, xpertId: options.assistantXpertId, workspaceId: options.workspaceId }, ...body })
70
82
  });
71
83
  if (!response.ok) {
72
- // Do not retain an unconsumed upstream error stream or expose its potentially sensitive body.
73
- await response.body?.cancel().catch(() => undefined);
74
- throw new Error(`DATAX_QUERY_HTTP_${response.status}`);
84
+ const error = await (0, diagnostic_1.readAnalyticsHttpError)(response, context.signal, context.deadlineAt);
85
+ context.signal.throwIfAborted();
86
+ throw error ?? new Error(`DATAX_QUERY_HTTP_${response.status}`);
75
87
  }
76
88
  return response;
77
89
  }
78
90
  async execute(binding, parameters, context) {
79
- const payload = exports.dataXpertPayloadSchema.parse(binding.payload);
80
- const response = await this.request('query', context, {
81
- binding: { id: binding.id, resourceId: payload.resourceId, actionTypeCode: payload.actionTypeCode, target: payload.target,
82
- params: bindParameters(payload.params, payload.parameterPaths, parameters) }, maxRows: context.maxRows
83
- });
84
- const result = responseSchema.parse(await boundedJson(response));
91
+ const payload = payload_1.dataXpertPayloadSchema.parse(binding.payload);
92
+ const result = await this.query(binding.id, payload, parameters, context);
85
93
  return { columns: result.effect.columns, rows: result.effect.rows,
86
94
  completeness: { returnedRows: result.effect.summary.returnedRowCount, totalRows: result.effect.summary.rowCount,
87
95
  totalAccuracy: result.effect.summary.truncated ? 'unknown' : 'exact', scope: result.effect.summary.truncated ? 'page' : 'full_query_result',
88
96
  truncated: result.effect.summary.truncated }, sourceFetchedAt: result.fetchedAt,
89
97
  upstreamExecutionId: result.executionId, auditRef: result.auditRef };
90
98
  }
99
+ async query(id, payload, parameters, context) {
100
+ const response = await this.request('query', context, {
101
+ binding: { id, resourceId: payload.resourceId, actionTypeCode: payload.actionTypeCode, target: payload.target,
102
+ params: bindParameters(payload.params, payload.parameterPaths, parameters) }, maxRows: context.maxRows
103
+ });
104
+ return responseSchema.parse(await boundedJson(response));
105
+ }
91
106
  };
92
107
  exports.DataXpertAnalyticsClient = DataXpertAnalyticsClient;
93
108
  exports.DataXpertAnalyticsClient = DataXpertAnalyticsClient = __decorate([
@@ -99,21 +114,21 @@ exports.DataXpertAnalyticsClient = DataXpertAnalyticsClient = __decorate([
99
114
  function bindParameters(params, paths, parameters) {
100
115
  const result = structuredClone(params);
101
116
  if (Object.keys(parameters).some((name) => !Object.hasOwn(paths, name)) || Object.keys(paths).some((name) => !Object.hasOwn(parameters, name)))
102
- throw new Error('DATAX_PARAMETER_MAPPING_INVALID');
117
+ throw (0, diagnostic_1.dataXpertParameterError)('DATAX_PARAMETER_MAPPING_INVALID');
103
118
  if (new Set(Object.values(paths).map((path) => JSON.stringify(path))).size !== Object.keys(paths).length)
104
- throw new Error('DATAX_PARAMETER_MAPPING_INVALID');
119
+ throw (0, diagnostic_1.dataXpertParameterError)('DATAX_PARAMETER_MAPPING_INVALID');
105
120
  for (const [name, path] of Object.entries(paths)) {
106
121
  let current = result;
107
122
  for (const [index, key] of path.entries()) {
108
123
  if (['__proto__', 'prototype', 'constructor'].includes(key) || !current || typeof current !== 'object' || !Object.hasOwn(current, key))
109
- throw new common_1.BadRequestException('DATAX_PARAMETER_PATH_INVALID: parameterPaths are relative to params. Omit the leading params segment and target an existing scalar, for example ["slicers","1","value"].');
124
+ throw (0, diagnostic_1.dataXpertParameterError)('DATAX_PARAMETER_PATH_INVALID');
110
125
  if (Array.isArray(current)) {
111
126
  if (!/^(0|[1-9]\d*)$/.test(key) || Number(key) >= current.length)
112
- throw new Error('DATAX_PARAMETER_PATH_INVALID');
127
+ throw (0, diagnostic_1.dataXpertParameterError)('DATAX_PARAMETER_PATH_INVALID');
113
128
  const position = Number(key);
114
129
  if (index === path.length - 1) {
115
130
  if (current[position] !== null && typeof current[position] === 'object')
116
- throw new Error('DATAX_PARAMETER_PATH_NOT_SCALAR');
131
+ throw (0, diagnostic_1.dataXpertParameterError)('DATAX_PARAMETER_PATH_NOT_SCALAR');
117
132
  current[position] = parameters[name];
118
133
  }
119
134
  else
@@ -121,7 +136,7 @@ function bindParameters(params, paths, parameters) {
121
136
  }
122
137
  else if (index === path.length - 1) {
123
138
  if (current[key] !== null && typeof current[key] === 'object')
124
- throw new Error('DATAX_PARAMETER_PATH_NOT_SCALAR');
139
+ throw (0, diagnostic_1.dataXpertParameterError)('DATAX_PARAMETER_PATH_NOT_SCALAR');
125
140
  current[key] = parameters[name];
126
141
  }
127
142
  else
@@ -0,0 +1,10 @@
1
+ import { AnalyticsProviderError } from '@xpert-ai/plugin-sdk';
2
+ declare const parameterMessages: {
3
+ DATAX_PARAMETER_MAPPING_INVALID: string;
4
+ DATAX_PARAMETER_PATH_INVALID: string;
5
+ DATAX_PARAMETER_PATH_NOT_SCALAR: string;
6
+ };
7
+ export declare function dataXpertParameterError(code: keyof typeof parameterMessages): AnalyticsProviderError;
8
+ /** Error responses get a smaller byte/time budget than results. Never retain or echo an unknown body. */
9
+ export declare function readAnalyticsHttpError(response: Response, signal: AbortSignal, deadlineAt?: number): Promise<AnalyticsProviderError | undefined>;
10
+ export {};
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dataXpertParameterError = dataXpertParameterError;
4
+ exports.readAnalyticsHttpError = readAnalyticsHttpError;
5
+ const contracts_1 = require("@xpert-ai/contracts");
6
+ const plugin_sdk_1 = require("@xpert-ai/plugin-sdk");
7
+ const parameterMessages = {
8
+ DATAX_PARAMETER_MAPPING_INVALID: 'Parameter names must match parameterPaths exactly, with a distinct target path for each parameter.',
9
+ DATAX_PARAMETER_PATH_INVALID: 'parameterPaths are relative to params. Omit the leading params segment and target an existing scalar, for example ["slicers","1","value"].',
10
+ DATAX_PARAMETER_PATH_NOT_SCALAR: 'A parameter path must target a scalar value, not an entire object or array. Select the existing filter value field.'
11
+ };
12
+ function dataXpertParameterError(code) {
13
+ return new plugin_sdk_1.AnalyticsProviderError({ schema: 'xpert.analytics.diagnostic', version: 1,
14
+ code, stage: 'validation', retryable: false, message: parameterMessages[code] });
15
+ }
16
+ /** Error responses get a smaller byte/time budget than results. Never retain or echo an unknown body. */
17
+ async function readAnalyticsHttpError(response, signal, deadlineAt) {
18
+ if (!/^application\/json(?:\s*;|$)/i.test(response.headers.get('content-type') ?? '') || !response.body) {
19
+ await response.body?.cancel().catch(() => undefined);
20
+ return undefined;
21
+ }
22
+ const reader = response.body.getReader();
23
+ const budget = Math.min(1000, Math.max(0, (deadlineAt ?? Infinity) - Date.now()));
24
+ let timer;
25
+ let abort = () => undefined;
26
+ const cancelled = new Promise(resolve => {
27
+ abort = () => resolve(undefined);
28
+ timer = setTimeout(abort, budget);
29
+ signal.addEventListener('abort', abort, { once: true });
30
+ });
31
+ const read = async () => {
32
+ const chunks = [];
33
+ let size = 0;
34
+ for (;;) {
35
+ const chunk = await reader.read();
36
+ if (chunk.done)
37
+ break;
38
+ size += chunk.value.byteLength;
39
+ if (size > 16_384)
40
+ return undefined;
41
+ chunks.push(chunk.value);
42
+ }
43
+ const diagnostic = (0, contracts_1.parseAnalyticsDiagnostic)(JSON.parse(Buffer.concat(chunks).toString('utf8')));
44
+ return diagnostic && new plugin_sdk_1.AnalyticsProviderError(diagnostic);
45
+ };
46
+ try {
47
+ signal.throwIfAborted();
48
+ return await Promise.race([read(), cancelled]);
49
+ }
50
+ catch {
51
+ return undefined;
52
+ }
53
+ finally {
54
+ clearTimeout(timer);
55
+ signal.removeEventListener('abort', abort);
56
+ await reader.cancel().catch(() => undefined);
57
+ }
58
+ }
@@ -22,6 +22,7 @@ exports.dataXpertDiscoveryRequestSchema = v3_1.z.discriminatedUnion('kind', [
22
22
  exports.dataXpertQueryContract = {
23
23
  operation: 'action', contractVersion: 1,
24
24
  payloadSchema: {
25
+ 'x-xpert-import': { version: 1, request: { kind: 'import-targets' } },
25
26
  type: 'object', additionalProperties: false,
26
27
  required: ['resourceId', 'actionTypeCode', 'target', 'params', 'parameterPaths'],
27
28
  properties: {
@@ -47,6 +48,7 @@ exports.dataXpertQueryContract = {
47
48
  'Entity metadata.definition contains adapterId, contractVersion, metadata and instructions. Follow that adapter-specific definition and the discovered Action inputSchema/inputHint. Preserve canonical targets, resource IDs and exact field names. Do not infer adapter types from names or apply one adapter\'s query, filter or time rules to another.',
48
49
  'Use only published metadata and authorized previews to establish business metrics, time axes, supported filters and complete aggregation. Missing metadata does not authorize guessing; obtain sufficient evidence before generating a time filter.',
49
50
  'Use parameterPaths relative to params to replace an existing scalar value, for example {region:["filters","0","value"]}. Paths must be unique. Never interpolate values into query text. Controls supply scalar values; arrays remain static unless the native Action explicitly supports another declared scalar mapping. Null handling and filter operators are defined by the Action schema and adapter instructions.',
50
- 'Only read-only Actions allowed by the current viewer policy are returned. Map output sourceField to returned column keys (or an explicit semanticRef). Numeric JSON cells require float64; decimal/int64 require exact strings. Preview to verify types, grain and completeness before binding charts. A partial page is not a complete business aggregate.'
51
+ 'Before authoring a binding, use dashboard_discover with the same source/providerId and requestJson encoding {kind:"query-schema",payload:<native Action envelope>,parameters:<mapped scalar values, or {}>}. This runs that bounded read-only Action under the current viewer policy; respect the native Action limits and filters. It returns items[0].metadata.output without business rows. Omit datasetId to let the provider generate a valid output key. Copy this output into the dashboard_preview binding, preserving datasetId, field IDs, sourceField, logicalType, role and nullable. Decimal/int64 remain exact strings; do not infer types from sample cells or names. If declarations are missing, obtain source metadata instead of guessing.',
52
+ 'The discovered output has empty grain because column roles alone do not prove uniqueness. Declare grain only when supported by query semantics. Always run dashboard_preview with the same payload and parameter values to validate the binding and obtain a preview receipt before saving; schema discovery is not a preview receipt. Re-discover after changing the query projection. A partial page is not a complete business aggregate.'
51
53
  ].join(' ')
52
54
  };
@@ -0,0 +1,164 @@
1
+ import { z } from 'zod/v3';
2
+ import type { AnalyticsDiscoveryResult, JSONValue } from '@xpert-ai/contracts';
3
+ /** Provider-owned reference migration. Native Action arguments and output contracts remain unchanged. */
4
+ export declare const importTargetRequest: z.ZodObject<{
5
+ kind: z.ZodLiteral<"import-targets">;
6
+ payload: z.ZodObject<{
7
+ resourceId: z.ZodString;
8
+ actionTypeCode: z.ZodString;
9
+ target: z.ZodEffects<z.ZodObject<{
10
+ entityId: z.ZodOptional<z.ZodString>;
11
+ entityTypeCode: z.ZodOptional<z.ZodString>;
12
+ entityRef: z.ZodOptional<z.ZodString>;
13
+ }, "strict", z.ZodTypeAny, {
14
+ entityTypeCode?: string | undefined;
15
+ entityRef?: string | undefined;
16
+ entityId?: string | undefined;
17
+ }, {
18
+ entityTypeCode?: string | undefined;
19
+ entityRef?: string | undefined;
20
+ entityId?: string | undefined;
21
+ }>, {
22
+ entityTypeCode?: string | undefined;
23
+ entityRef?: string | undefined;
24
+ entityId?: string | undefined;
25
+ }, {
26
+ entityTypeCode?: string | undefined;
27
+ entityRef?: string | undefined;
28
+ entityId?: string | undefined;
29
+ }>;
30
+ params: z.ZodRecord<z.ZodString, z.ZodType<JSONValue, z.ZodTypeDef, JSONValue>>;
31
+ parameterPaths: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString, "many">>>;
32
+ }, "strict", z.ZodTypeAny, {
33
+ params: Record<string, JSONValue>;
34
+ resourceId: string;
35
+ target: {
36
+ entityTypeCode?: string | undefined;
37
+ entityRef?: string | undefined;
38
+ entityId?: string | undefined;
39
+ };
40
+ actionTypeCode: string;
41
+ parameterPaths: Record<string, string[]>;
42
+ }, {
43
+ params: Record<string, JSONValue>;
44
+ resourceId: string;
45
+ target: {
46
+ entityTypeCode?: string | undefined;
47
+ entityRef?: string | undefined;
48
+ entityId?: string | undefined;
49
+ };
50
+ actionTypeCode: string;
51
+ parameterPaths?: Record<string, string[]> | undefined;
52
+ }>;
53
+ browse: z.ZodDefault<z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
54
+ kind: z.ZodLiteral<"resources">;
55
+ after: z.ZodOptional<z.ZodString>;
56
+ }, "strict", z.ZodTypeAny, {
57
+ kind: "resources";
58
+ after?: string | undefined;
59
+ }, {
60
+ kind: "resources";
61
+ after?: string | undefined;
62
+ }>, z.ZodObject<{
63
+ kind: z.ZodLiteral<"entities">;
64
+ resourceId: z.ZodString;
65
+ entityTypeCode: z.ZodOptional<z.ZodString>;
66
+ query: z.ZodOptional<z.ZodString>;
67
+ }, "strict", z.ZodTypeAny, {
68
+ kind: "entities";
69
+ resourceId: string;
70
+ entityTypeCode?: string | undefined;
71
+ query?: string | undefined;
72
+ }, {
73
+ kind: "entities";
74
+ resourceId: string;
75
+ entityTypeCode?: string | undefined;
76
+ query?: string | undefined;
77
+ }>, z.ZodObject<{
78
+ kind: z.ZodLiteral<"actions">;
79
+ resourceId: z.ZodString;
80
+ target: z.ZodObject<{
81
+ entityTypeCode: z.ZodString;
82
+ entityRef: z.ZodString;
83
+ }, "strict", z.ZodTypeAny, {
84
+ entityTypeCode: string;
85
+ entityRef: string;
86
+ }, {
87
+ entityTypeCode: string;
88
+ entityRef: string;
89
+ }>;
90
+ }, "strict", z.ZodTypeAny, {
91
+ kind: "actions";
92
+ resourceId: string;
93
+ target: {
94
+ entityTypeCode: string;
95
+ entityRef: string;
96
+ };
97
+ }, {
98
+ kind: "actions";
99
+ resourceId: string;
100
+ target: {
101
+ entityTypeCode: string;
102
+ entityRef: string;
103
+ };
104
+ }>]>>;
105
+ }, "strict", z.ZodTypeAny, {
106
+ kind: "import-targets";
107
+ payload: {
108
+ params: Record<string, JSONValue>;
109
+ resourceId: string;
110
+ target: {
111
+ entityTypeCode?: string | undefined;
112
+ entityRef?: string | undefined;
113
+ entityId?: string | undefined;
114
+ };
115
+ actionTypeCode: string;
116
+ parameterPaths: Record<string, string[]>;
117
+ };
118
+ browse: {
119
+ kind: "resources";
120
+ after?: string | undefined;
121
+ } | {
122
+ kind: "entities";
123
+ resourceId: string;
124
+ entityTypeCode?: string | undefined;
125
+ query?: string | undefined;
126
+ } | {
127
+ kind: "actions";
128
+ resourceId: string;
129
+ target: {
130
+ entityTypeCode: string;
131
+ entityRef: string;
132
+ };
133
+ };
134
+ }, {
135
+ kind: "import-targets";
136
+ payload: {
137
+ params: Record<string, JSONValue>;
138
+ resourceId: string;
139
+ target: {
140
+ entityTypeCode?: string | undefined;
141
+ entityRef?: string | undefined;
142
+ entityId?: string | undefined;
143
+ };
144
+ actionTypeCode: string;
145
+ parameterPaths?: Record<string, string[]> | undefined;
146
+ };
147
+ browse?: {
148
+ kind: "resources";
149
+ after?: string | undefined;
150
+ } | {
151
+ kind: "entities";
152
+ resourceId: string;
153
+ entityTypeCode?: string | undefined;
154
+ query?: string | undefined;
155
+ } | {
156
+ kind: "actions";
157
+ resourceId: string;
158
+ target: {
159
+ entityTypeCode: string;
160
+ entityRef: string;
161
+ };
162
+ } | undefined;
163
+ }>;
164
+ export declare function discoverImportTargets(input: z.infer<typeof importTargetRequest>, discover: (request: JSONValue) => Promise<AnalyticsDiscoveryResult>): Promise<AnalyticsDiscoveryResult>;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.importTargetRequest = void 0;
4
+ exports.discoverImportTargets = discoverImportTargets;
5
+ const v3_1 = require("zod/v3");
6
+ const payload_1 = require("./payload");
7
+ const discovery_1 = require("./discovery");
8
+ /** Provider-owned reference migration. Native Action arguments and output contracts remain unchanged. */
9
+ exports.importTargetRequest = v3_1.z.object({
10
+ kind: v3_1.z.literal('import-targets'), payload: payload_1.dataXpertPayloadSchema,
11
+ browse: discovery_1.dataXpertDiscoveryRequestSchema.default({ kind: 'resources' })
12
+ }).strict();
13
+ async function discoverImportTargets(input, discover) {
14
+ const result = await discover(input.browse);
15
+ const wrap = (browse) => ({ kind: 'import-targets', payload: { ...input.payload, params: {}, parameterPaths: {} }, browse });
16
+ return { ...result,
17
+ items: result.items.flatMap(item => {
18
+ const next = discovery_1.dataXpertDiscoveryRequestSchema.safeParse(item.inspectRequest);
19
+ if (next.success && next.data.kind === 'actions') {
20
+ const target = next.data.target;
21
+ if (input.payload.target.entityTypeCode && target.entityTypeCode !== input.payload.target.entityTypeCode)
22
+ return [];
23
+ return [{ id: item.id, name: item.name, kind: 'dataset', description: item.description,
24
+ metadata: { schema: 'xpert.analytics.import-target', version: 1,
25
+ payloadPatch: { resourceId: next.data.resourceId, target } } }];
26
+ }
27
+ return item.inspectRequest ? [{ ...item, inspectRequest: wrap(item.inspectRequest) }] : [];
28
+ }),
29
+ ...(result.nextRequest ? { nextRequest: wrap(result.nextRequest) } : {}) };
30
+ }
@@ -0,0 +1,50 @@
1
+ import { z } from 'zod/v3';
2
+ import type { JSONValue } from '@xpert-ai/contracts';
3
+ /** Native Action arguments stay plugin-owned; the host only interprets Dataset/Binding. */
4
+ export declare const dataXpertPayloadSchema: z.ZodObject<{
5
+ resourceId: z.ZodString;
6
+ actionTypeCode: z.ZodString;
7
+ target: z.ZodEffects<z.ZodObject<{
8
+ entityId: z.ZodOptional<z.ZodString>;
9
+ entityTypeCode: z.ZodOptional<z.ZodString>;
10
+ entityRef: z.ZodOptional<z.ZodString>;
11
+ }, "strict", z.ZodTypeAny, {
12
+ entityTypeCode?: string | undefined;
13
+ entityRef?: string | undefined;
14
+ entityId?: string | undefined;
15
+ }, {
16
+ entityTypeCode?: string | undefined;
17
+ entityRef?: string | undefined;
18
+ entityId?: string | undefined;
19
+ }>, {
20
+ entityTypeCode?: string | undefined;
21
+ entityRef?: string | undefined;
22
+ entityId?: string | undefined;
23
+ }, {
24
+ entityTypeCode?: string | undefined;
25
+ entityRef?: string | undefined;
26
+ entityId?: string | undefined;
27
+ }>;
28
+ params: z.ZodRecord<z.ZodString, z.ZodType<JSONValue, z.ZodTypeDef, JSONValue>>;
29
+ parameterPaths: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString, "many">>>;
30
+ }, "strict", z.ZodTypeAny, {
31
+ params: Record<string, JSONValue>;
32
+ resourceId: string;
33
+ target: {
34
+ entityTypeCode?: string | undefined;
35
+ entityRef?: string | undefined;
36
+ entityId?: string | undefined;
37
+ };
38
+ actionTypeCode: string;
39
+ parameterPaths: Record<string, string[]>;
40
+ }, {
41
+ params: Record<string, JSONValue>;
42
+ resourceId: string;
43
+ target: {
44
+ entityTypeCode?: string | undefined;
45
+ entityRef?: string | undefined;
46
+ entityId?: string | undefined;
47
+ };
48
+ actionTypeCode: string;
49
+ parameterPaths?: Record<string, string[]> | undefined;
50
+ }>;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dataXpertPayloadSchema = void 0;
4
+ const v3_1 = require("zod/v3");
5
+ const scalar = v3_1.z.union([v3_1.z.string(), v3_1.z.number().finite(), v3_1.z.boolean(), v3_1.z.null()]);
6
+ const json = v3_1.z.lazy(() => v3_1.z.union([scalar, v3_1.z.array(json), v3_1.z.record(json)]));
7
+ /** Native Action arguments stay plugin-owned; the host only interprets Dataset/Binding. */
8
+ exports.dataXpertPayloadSchema = v3_1.z.object({
9
+ resourceId: v3_1.z.string().min(1).max(160), actionTypeCode: v3_1.z.string().min(1).max(160),
10
+ target: v3_1.z.object({
11
+ entityId: v3_1.z.string().trim().min(1).max(256).optional(),
12
+ entityTypeCode: v3_1.z.string().trim().min(1).max(256).optional(),
13
+ entityRef: v3_1.z.string().trim().min(1).max(512).optional()
14
+ }).strict().refine(target => Boolean(target.entityId || target.entityRef), 'Target reference is required'),
15
+ params: v3_1.z.record(json),
16
+ parameterPaths: v3_1.z.record(v3_1.z.array(v3_1.z.string().min(1)).min(1).max(8)).default({})
17
+ }).strict();
@@ -0,0 +1,101 @@
1
+ import { z } from 'zod/v3';
2
+ import type { JSONValue } from '@xpert-ai/contracts';
3
+ export declare const dataXpertResultColumnSchema: z.ZodObject<{
4
+ label: z.ZodOptional<z.ZodString>;
5
+ logicalType: z.ZodOptional<z.ZodEnum<["string", "boolean", "int64", "float64", "decimal", "date", "timestamp"]>>;
6
+ nullable: z.ZodOptional<z.ZodBoolean>;
7
+ role: z.ZodOptional<z.ZodEnum<["dimension", "measure", "time", "identifier"]>>;
8
+ semanticRef: z.ZodOptional<z.ZodOptional<z.ZodString>>;
9
+ unit: z.ZodOptional<z.ZodOptional<z.ZodString>>;
10
+ } & {
11
+ key: z.ZodString;
12
+ }, "strip", z.ZodTypeAny, {
13
+ key: string;
14
+ label?: string | undefined;
15
+ logicalType?: "string" | "boolean" | "date" | "int64" | "float64" | "decimal" | "timestamp" | undefined;
16
+ nullable?: boolean | undefined;
17
+ role?: "dimension" | "measure" | "time" | "identifier" | undefined;
18
+ semanticRef?: string | undefined;
19
+ unit?: string | undefined;
20
+ }, {
21
+ key: string;
22
+ label?: string | undefined;
23
+ logicalType?: "string" | "boolean" | "date" | "int64" | "float64" | "decimal" | "timestamp" | undefined;
24
+ nullable?: boolean | undefined;
25
+ role?: "dimension" | "measure" | "time" | "identifier" | undefined;
26
+ semanticRef?: string | undefined;
27
+ unit?: string | undefined;
28
+ }>;
29
+ export declare const dataXpertOutputSchema: z.ZodObject<{
30
+ datasetId: z.ZodString;
31
+ fields: z.ZodArray<z.ZodObject<{
32
+ id: z.ZodString;
33
+ sourceField: z.ZodString;
34
+ label: z.ZodString;
35
+ logicalType: z.ZodEnum<["string", "boolean", "int64", "float64", "decimal", "date", "timestamp"]>;
36
+ nullable: z.ZodBoolean;
37
+ role: z.ZodEnum<["dimension", "measure", "time", "identifier"]>;
38
+ semanticRef: z.ZodOptional<z.ZodString>;
39
+ unit: z.ZodOptional<z.ZodString>;
40
+ }, "strict", z.ZodTypeAny, {
41
+ id?: string;
42
+ sourceField?: string;
43
+ label?: string;
44
+ logicalType?: "string" | "boolean" | "date" | "int64" | "float64" | "decimal" | "timestamp";
45
+ nullable?: boolean;
46
+ role?: "dimension" | "measure" | "time" | "identifier";
47
+ semanticRef?: string;
48
+ unit?: string;
49
+ }, {
50
+ id?: string;
51
+ sourceField?: string;
52
+ label?: string;
53
+ logicalType?: "string" | "boolean" | "date" | "int64" | "float64" | "decimal" | "timestamp";
54
+ nullable?: boolean;
55
+ role?: "dimension" | "measure" | "time" | "identifier";
56
+ semanticRef?: string;
57
+ unit?: string;
58
+ }>, "many">;
59
+ grain: z.ZodArray<z.ZodString, "many">;
60
+ }, "strict", z.ZodTypeAny, {
61
+ datasetId?: string;
62
+ fields?: {
63
+ id?: string;
64
+ sourceField?: string;
65
+ label?: string;
66
+ logicalType?: "string" | "boolean" | "date" | "int64" | "float64" | "decimal" | "timestamp";
67
+ nullable?: boolean;
68
+ role?: "dimension" | "measure" | "time" | "identifier";
69
+ semanticRef?: string;
70
+ unit?: string;
71
+ }[];
72
+ grain?: string[];
73
+ }, {
74
+ datasetId?: string;
75
+ fields?: {
76
+ id?: string;
77
+ sourceField?: string;
78
+ label?: string;
79
+ logicalType?: "string" | "boolean" | "date" | "int64" | "float64" | "decimal" | "timestamp";
80
+ nullable?: boolean;
81
+ role?: "dimension" | "measure" | "time" | "identifier";
82
+ semanticRef?: string;
83
+ unit?: string;
84
+ }[];
85
+ grain?: string[];
86
+ }>;
87
+ export declare function createDataXpertDatasetId(payload: JSONValue, requestedId?: string): string;
88
+ export declare function createDataXpertOutput(datasetId: string, columns: z.output<typeof dataXpertResultColumnSchema>[]): {
89
+ datasetId?: string;
90
+ fields?: {
91
+ id?: string;
92
+ sourceField?: string;
93
+ label?: string;
94
+ logicalType?: "string" | "boolean" | "date" | "int64" | "float64" | "decimal" | "timestamp";
95
+ nullable?: boolean;
96
+ role?: "dimension" | "measure" | "time" | "identifier";
97
+ semanticRef?: string;
98
+ unit?: string;
99
+ }[];
100
+ grain?: string[];
101
+ };
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dataXpertOutputSchema = exports.dataXpertResultColumnSchema = void 0;
4
+ exports.createDataXpertDatasetId = createDataXpertDatasetId;
5
+ exports.createDataXpertOutput = createDataXpertOutput;
6
+ const node_crypto_1 = require("node:crypto");
7
+ const v3_1 = require("zod/v3");
8
+ const analytics_1 = require("@xpert-ai/plugin-sdk/analytics");
9
+ // Optional on legacy responses; schema authoring requires explicit declarations.
10
+ exports.dataXpertResultColumnSchema = analytics_1.analyticsFieldSchema
11
+ .omit({ id: true, sourceField: true })
12
+ .partial()
13
+ .extend({ key: v3_1.z.string() })
14
+ .strip();
15
+ exports.dataXpertOutputSchema = analytics_1.analyticsBindingObjectSchema.shape.output;
16
+ function createDataXpertDatasetId(payload, requestedId) {
17
+ const parsed = exports.dataXpertOutputSchema.shape.datasetId.safeParse(requestedId);
18
+ if (parsed.success)
19
+ return parsed.data;
20
+ // Native references are not output keys. Include the query projection to separate bindings.
21
+ return `dataset_${(0, node_crypto_1.createHash)('sha256').update(JSON.stringify(payload)).digest('hex')}`;
22
+ }
23
+ function createDataXpertOutput(datasetId, columns) {
24
+ if (columns.length === 0 || columns.length > 80)
25
+ throw new Error('DATAX_RESULT_SCHEMA_UNAVAILABLE: expected 1 to 80 declared columns');
26
+ if (new Set(columns.map(column => column.key)).size !== columns.length)
27
+ throw new Error('DATAX_RESULT_SCHEMA_AMBIGUOUS');
28
+ const fields = columns.map(column => {
29
+ if (column.logicalType === undefined || column.role === undefined || column.nullable === undefined)
30
+ throw new Error('DATAX_RESULT_SCHEMA_UNAVAILABLE');
31
+ // Repeated semantic selectors cannot identify a column; its unique native key can.
32
+ const semanticRef = columns.filter(item => item.semanticRef === column.semanticRef).length === 1
33
+ ? column.semanticRef : undefined;
34
+ return {
35
+ id: `field_${(0, node_crypto_1.createHash)('sha256').update(column.key).digest('hex')}`,
36
+ sourceField: column.key, label: column.label ?? column.key.slice(0, 160),
37
+ logicalType: column.logicalType, role: column.role, nullable: column.nullable,
38
+ ...(semanticRef === undefined ? {} : { semanticRef }),
39
+ ...(column.unit === undefined ? {} : { unit: column.unit })
40
+ };
41
+ });
42
+ // Column roles do not establish row uniqueness or business aggregation grain.
43
+ return exports.dataXpertOutputSchema.parse({ datasetId, fields, grain: [] });
44
+ }
@@ -5,9 +5,10 @@ description: "Separate transport-contract tests from real SSO and end-to-end das
5
5
 
6
6
  ## Compatibility
7
7
 
8
- Development dependencies pin published SDK/contracts 3.26.1, which export AnalyticsProvider,
9
- AnalyticsQueryProvider, ActorTokenRuntimeFactoryCapability and the shared Dataset/Binding
10
- contracts. The host must provide the corresponding runtime capabilities.
8
+ Development dependencies pin `@xpert-ai/contracts` and `@xpert-ai/plugin-sdk` to 3.32.0.
9
+ Both peer dependencies require `^3.32.0`. The plugin validates upstream error responses with
10
+ `parseAnalyticsDiagnostic` and returns accepted diagnostics through `AnalyticsProviderError`.
11
+ The host must provide the Analytics and Actor Token runtime capabilities.
11
12
 
12
13
  Data-xpert must expose `POST /agent-workbench/analytics/discover` and
13
14
  `POST /agent-workbench/analytics/query` under the configured API base. Discovery returns
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xpert-ai/plugin-analytics-data-xpert",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Governed data-xpert ontology analytics provider",
5
5
  "main": "./index.cjs",
6
6
  "types": "./dist/index.d.ts",
@@ -27,16 +27,16 @@
27
27
  }
28
28
  },
29
29
  "peerDependencies": {
30
- "@xpert-ai/plugin-sdk": "^3.26.1",
31
- "@xpert-ai/contracts": "^3.26.1",
30
+ "@xpert-ai/plugin-sdk": "^3.32.0",
31
+ "@xpert-ai/contracts": "^3.32.0",
32
32
  "@nestjs/common": "^11.1.6",
33
33
  "@nestjs/core": "^11.1.6",
34
34
  "zod": "3.25.67",
35
35
  "@nestjs/cqrs": "^11.0.3"
36
36
  },
37
37
  "devDependencies": {
38
- "@xpert-ai/plugin-sdk": "3.26.1",
39
- "@xpert-ai/contracts": "3.26.1",
38
+ "@xpert-ai/plugin-sdk": "3.32.0",
39
+ "@xpert-ai/contracts": "3.32.0",
40
40
  "@nestjs/common": "^11.1.6",
41
41
  "@nestjs/core": "^11.1.6",
42
42
  "zod": "3.25.67",