@xpert-ai/plugin-live-dashboard 0.1.4 → 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.
Files changed (49) hide show
  1. package/.xpertai-plugin/plugin.json +1 -1
  2. package/README.md +4 -4
  3. package/dist/lib/content-capability.d.ts +22 -0
  4. package/dist/lib/content-capability.js +33 -0
  5. package/dist/lib/content.provider.d.ts +23 -0
  6. package/dist/lib/content.provider.js +163 -0
  7. package/dist/lib/dashboard-navigation.d.ts +24 -0
  8. package/dist/lib/dashboard-navigation.js +1 -1
  9. package/dist/lib/dashboard-queries.d.ts +11 -0
  10. package/dist/lib/dashboard-queries.js +20 -0
  11. package/dist/lib/dashboard-search.d.ts +5 -0
  12. package/dist/lib/dashboard-search.js +12 -0
  13. package/dist/lib/dashboard.schema.js +4 -2
  14. package/dist/lib/dashboard.service.d.ts +8 -0
  15. package/dist/lib/dashboard.service.js +52 -15
  16. package/dist/lib/document-host.d.ts +4 -0
  17. package/dist/lib/document-host.js +13 -0
  18. package/dist/lib/document-network.d.ts +3 -0
  19. package/dist/lib/document-network.js +9 -0
  20. package/dist/lib/html-guidance.d.ts +1 -1
  21. package/dist/lib/html-guidance.js +1 -1
  22. package/dist/lib/middleware.js +47 -15
  23. package/dist/lib/plugin.js +3 -1
  24. package/dist/lib/presentation.provider.d.ts +5 -0
  25. package/dist/lib/presentation.provider.js +2 -2
  26. package/dist/lib/preview-retry.d.ts +7 -0
  27. package/dist/lib/preview-retry.js +52 -0
  28. package/dist/lib/renderer.js +5 -0
  29. package/dist/lib/save-evidence.d.ts +1 -1
  30. package/dist/lib/sharing.service.js +2 -0
  31. package/dist/lib/view.provider.d.ts +5 -0
  32. package/dist/lib/view.provider.js +4 -4
  33. package/dist/remote/app.css +1 -1
  34. package/dist/remote/app.js +36 -36
  35. package/dist/remote/build-manifest.json +34 -25
  36. package/dist/sandbox-actions/action.json +1 -1
  37. package/dist/sandbox-actions/bundle/host.js +2 -2
  38. package/dist/sandbox-actions/bundle/validate.mjs +24 -3
  39. package/dist/validation/host-entry.d.ts +3 -1
  40. package/dist/validation/host-entry.js +5 -2
  41. package/docs/drafts.mdx +1 -1
  42. package/docs/frontend-libraries.mdx +2 -2
  43. package/docs/html-mode.md +3 -3
  44. package/docs/team-sharing.mdx +1 -1
  45. package/docs/verification.mdx +3 -1
  46. package/package.json +6 -6
  47. package/skills/live-dashboard/SKILL.md +1 -1
  48. package/skills/live-dashboard/references/data-validation.md +4 -0
  49. package/skills/live-dashboard/references/html-runtime.md +1 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xpert-ai/plugin-live-dashboard",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "level": "system",
5
5
  "artifactNamespace": "live_dashboard",
6
6
  "sandboxActions": "./dist/sandbox-actions/action.json",
package/README.md CHANGED
@@ -31,7 +31,7 @@ The Workbench supplies its selected Artifact/version through the platform contex
31
31
  update preserves identity and omitted fields, requires fresh preview receipts for replacement bindings, and uses the existing validation/CAS path.
32
32
  Successful update receipts open the saved working draft; only human publication creates a formal version. See [continuous editing](docs/editing.mdx).
33
33
 
34
- Tools are `dashboard_list_sources`, `dashboard_discover`, `dashboard_preview`, `dashboard_preview_binding`, `dashboard_create`, `dashboard_open`, `dashboard_update` and `dashboard_create_html`. File authoring is supplied by the platform SandboxFile middleware. The canonical preview tool accepts complete `bindingJson` returned by an adapter registration tool. Both preview tools use the same discovery and receipt checks. The middleware adds business-semantic guidance while preserving the host system message. It requires discovery and a successful preview of the exact binding/defaults before creation via run-local `previewId` receipts; save revalidates current source access. Evidence is local to the run and actor, and repeated creation of the same manifest rechecks authorization before reusing its Artifact.
34
+ Tools are `dashboard_list_sources`, `dashboard_discover`, `dashboard_preview`, `dashboard_preview_binding`, `dashboard_create`, `dashboard_open`, `dashboard_update` and `dashboard_create_html`. File authoring is supplied by the platform SandboxFile middleware. The canonical preview tool accepts complete `bindingJson` returned by an adapter registration tool. Both preview tools automatically discover the query contract with an empty request when it is absent in this run. Contracts are cached by exact source (including version) and provider; concurrent previews share discovery, and discovery failures propagate without executing the query and can be retried. Explicit discovery remains available for business metadata inspection. Source selection, payload validation and runtime authorization still apply. The middleware adds business-semantic guidance while preserving the host system message. Creation requires a successful preview of the exact binding/defaults via run-local `previewId` receipts; save revalidates current source access. Evidence is local to the run and actor, and repeated creation of the same manifest rechecks authorization before reusing its Artifact.
35
35
 
36
36
  Specialized Assistants may configure `allowedFinalProviders` on the middleware, for example `["workspace_script.query"]`. Upstream discovery and preview still work with other providers, but Agent save rejects final bindings outside this list. Omit it for the existing unrestricted provider workflow. This is an Agent workflow policy, not a substitute for the server's source/Artifact authorization.
37
37
 
@@ -39,7 +39,7 @@ For a dedicated Dashboard Assistant, mark its connected `LiveDashboard` middlewa
39
39
 
40
40
  Only the LLM boundary uses `requestJson` / `payloadJson` strings, because recursive JSON unions are unreliable in model function schemas. The plugin parses these into the unchanged canonical JSON protocol before calling the platform. Protocol version and operation come from the discovered provider contract; parameter name also supplies the Agent-created control ID. Saved manifests retain the full generic Binding protocol. Preview uses Binding parameter defaults, avoiding a second control mapping for the model to repeat. Provider syntax is learned from discovery, not embedded in platform prompts.
41
41
 
42
- The model gets at most 20 preview rows/32 KB, with original completeness and explicit omitted-row counts. It has 24 model tool rounds; after that it must report saved receipts or a blocker. Authored definitions, units, date range and aggregation grain still require business verification: structural validation alone does not prove metric correctness.
42
+ The model gets at most 20 preview rows/32 KB, with original completeness and explicit omitted-row counts. Failed preview/filter-test inputs execute at most twice per run; repeated identical failures are blocked before querying. Six consecutive preview failures stop tool use and request a truthful blocker summary; a model that still emits tool calls receives a terminal diagnostic instead. Schema failures count too. Successful previews reset the consecutive-failure count, and corrected inputs can be tried before the limit. The model also has 24 model tool rounds; after that it must report saved receipts or a blocker. Authored definitions, units, date range and aggregation grain still require business verification: structural validation alone does not prove metric correctness.
43
43
 
44
44
  Categorical charts must encode every Dataset grain dimension in `x`/`color`. The renderer does not aggregate: totals need a separate previewed binding. Unsupported business metrics require clarification, not a proxy dashboard without agreement.
45
45
 
@@ -63,7 +63,7 @@ See [HTML mode and acceptance](docs/html-mode.md). Agent tools write a complete
63
63
 
64
64
  The platform browser entry `@xpert-ai/plugin-sdk/artifact-document` owns the sandbox, CSP, MessageChannel, request validation and latest-response policy. The plugin owns lifecycle and supplies a narrow callback to `query-binding`. Its server loads the exact saved Artifact version, finds the declared Binding, validates controls and rechecks source access. The endpoint rejects full Binding, Provider, SQL/payload and alternate source fields.
65
65
 
66
- Pages call `window.xpertLiveArtifact.ready()`, `subscribe(bindingId, render)`, `query(bindingId, controls)` and `refresh(bindingId)`. Register subscriptions before the first query so the toolbar can refresh without resetting the document. Use inline DOM/SVG/Canvas; network/CDN imports and eval are unavailable. Script processing remains in the separate Workspace Script provider.
66
+ Pages call `window.xpertLiveArtifact.ready()`, `subscribe(bindingId, render)`, `query(bindingId, controls)` and `refresh(bindingId)`. Register subscriptions before the first query so the toolbar can refresh without resetting the document. Use inline DOM/SVG/Canvas or online scripts/styles/fonts from the HTTPS origins returned by `dashboard_libraries.online`. Tailwind CDN, jsDelivr, cdnjs, unpkg and Google Fonts are supported according to their resource-type allowlist. Preflight and the displayed page use the same policy; failed loads prevent saving. Pin online versions where possible. Unlike packaged libraries, online bytes are not part of the immutable Artifact snapshot. Direct data requests, external images, eval, workers and frames remain unavailable. Script processing remains in the separate Workspace Script provider.
67
67
 
68
68
  HTML is capped at 300 KB; the whole saved definition remains capped at 600 KB. HTML pages own their chart layout, so their charts/tables arrays are empty. The standard renderer remains an independent option. Sharing, data permissions and source limits are unchanged.
69
69
 
@@ -75,7 +75,7 @@ The renderer runs in a script-only sandbox with no-network CSP. A small in-memor
75
75
 
76
76
  ## Build and validation
77
77
 
78
- Use the xpertai workspace's declared Corepack/pnpm. Development dependencies pin published SDK/contracts 3.26.1 and the Data Analytics renderer 0.1.35; no local platform or community links are required.
78
+ Use the xpertai workspace's declared Corepack/pnpm. Development dependencies pin published SDK/contracts 3.27.0 and the Data Analytics renderer 0.1.35; no local platform or community links are required. SDK 3.27.0 includes the host-owned online resource policy, so no SDK patch is required. Rebuild both the viewer and HTML preflight assets after changing the SDK; `verify:dist` checks their source fingerprints.
79
79
 
80
80
  ```sh
81
81
  corepack pnpm install --frozen-lockfile
@@ -0,0 +1,22 @@
1
+ import { ModelFeature, type TXpertTeamDraft } from '@xpert-ai/contracts';
2
+ import { type IAssistantCapabilityProvider, type AssistantCapabilityApplyContext } from '@xpert-ai/plugin-sdk';
3
+ /** Optional tool composition; installing a View never silently enables these tools. */
4
+ export declare class DashboardCapability implements IAssistantCapabilityProvider {
5
+ readonly key = "live-dashboard";
6
+ readonly availableForBlankAssistant = true;
7
+ readonly supportsQuickCreation = true;
8
+ readonly requiredModelFeatures: ModelFeature[];
9
+ readonly label: {
10
+ en_US: string;
11
+ zh_Hans: string;
12
+ };
13
+ readonly description: {
14
+ en_US: string;
15
+ zh_Hans: string;
16
+ };
17
+ isEnabled(draft: TXpertTeamDraft): boolean;
18
+ check(): Promise<{
19
+ available: boolean;
20
+ }>;
21
+ apply({ draft }: AssistantCapabilityApplyContext): Promise<void>;
22
+ }
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.DashboardCapability = void 0;
10
+ const common_1 = require("@nestjs/common");
11
+ const contracts_1 = require("@xpert-ai/contracts");
12
+ const plugin_sdk_1 = require("@xpert-ai/plugin-sdk");
13
+ /** Optional tool composition; installing a View never silently enables these tools. */
14
+ let DashboardCapability = class DashboardCapability {
15
+ constructor() {
16
+ this.key = 'live-dashboard';
17
+ this.availableForBlankAssistant = true;
18
+ this.supportsQuickCreation = true;
19
+ this.requiredModelFeatures = [contracts_1.ModelFeature.TOOL_CALL];
20
+ this.label = { en_US: 'Live dashboards', zh_Hans: '实时看板' };
21
+ this.description = { en_US: 'Create and maintain live dashboards with this Assistant in a Project.', zh_Hans: '为 Assistant 增加实时看板的创建与维护工具,沿用项目权限。' };
22
+ }
23
+ isEnabled(draft) { return (0, plugin_sdk_1.hasAssistantMiddleware)(draft, 'LiveDashboard'); }
24
+ async check() { return { available: true }; }
25
+ async apply({ draft }) {
26
+ (0, plugin_sdk_1.applyAssistantMiddleware)(draft, { provider: 'LiveDashboard', key: 'Capability_DashboardCapability', title: 'Live dashboards', instructions: 'Maintain live dashboard definitions in the current Project using Live Dashboard tools and registered Analytics providers. Discover and validate current data bindings before saving. Read the current draft revision and handle conflicts without overwriting another editor. Query with the current actor permissions; never copy credentials or query rows into the shared definition. Publishing a definition is separate from saving and refreshing data.' });
27
+ }
28
+ };
29
+ exports.DashboardCapability = DashboardCapability;
30
+ exports.DashboardCapability = DashboardCapability = __decorate([
31
+ (0, common_1.Injectable)(),
32
+ (0, plugin_sdk_1.AssistantCapabilityProvider)('live-dashboard')
33
+ ], DashboardCapability);
@@ -0,0 +1,23 @@
1
+ import type { ResourceContentEditorRegistration } from '@xpert-ai/contracts';
2
+ import { type RuntimeCapabilityRegistry, type ContentCatalogProvider, type ContentResourceRequest, type IContentResourceProvider } from '@xpert-ai/plugin-sdk';
3
+ import { DashboardService } from './dashboard.service';
4
+ export declare const DASHBOARD_CONTENT_PROVIDER = "live-dashboard.content";
5
+ export declare class DashboardContentEditor {
6
+ readonly descriptor: ResourceContentEditorRegistration;
7
+ }
8
+ export declare class DashboardContentProvider implements IContentResourceProvider {
9
+ private readonly service;
10
+ private readonly runtime;
11
+ readonly title = "Live Dashboard";
12
+ constructor(service: DashboardService, runtime: RuntimeCapabilityRegistry);
13
+ readonly catalog: ContentCatalogProvider;
14
+ authorize(input: ContentResourceRequest): Promise<{
15
+ providerKey: string;
16
+ resourceId: string;
17
+ title: string;
18
+ role: "owner" | "manager" | "editor" | "member";
19
+ canWrite: boolean;
20
+ canManage: boolean;
21
+ }>;
22
+ private describe;
23
+ }
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.DashboardContentProvider = exports.DashboardContentEditor = exports.DASHBOARD_CONTENT_PROVIDER = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const plugin_sdk_1 = require("@xpert-ai/plugin-sdk");
18
+ const dashboard_service_1 = require("./dashboard.service");
19
+ const dashboard_search_1 = require("./dashboard-search");
20
+ exports.DASHBOARD_CONTENT_PROVIDER = 'live-dashboard.content';
21
+ const MODEL = { typeId: 'xpert.live-dashboard', modelId: 'live-dashboard.manifest', schemaVersion: '1', editProtocol: { id: 'artifact-draft.cas', version: '1' } };
22
+ const EDITOR = 'live-dashboard.editor';
23
+ const context = (scope) => ({ ...scope.actor, hostType: 'project', hostId: scope.projectId, slots: [] });
24
+ let DashboardContentEditor = class DashboardContentEditor {
25
+ constructor() {
26
+ this.descriptor = { kind: 'resource', protocolVersion: 2, key: EDITOR, title: 'Live Dashboard',
27
+ view: { providerKey: 'live-dashboard', viewKey: 'live-dashboard', entry: 'live-dashboard' }, supports: [{ ...MODEL, access: ['view', 'edit'] }] };
28
+ }
29
+ };
30
+ exports.DashboardContentEditor = DashboardContentEditor;
31
+ exports.DashboardContentEditor = DashboardContentEditor = __decorate([
32
+ (0, common_1.Injectable)(),
33
+ (0, plugin_sdk_1.ContentEditorProvider)(EDITOR)
34
+ ], DashboardContentEditor);
35
+ let DashboardContentProvider = class DashboardContentProvider {
36
+ constructor(service, runtime) {
37
+ this.service = service;
38
+ this.runtime = runtime;
39
+ this.title = 'Live Dashboard';
40
+ this.catalog = {
41
+ types: [{ ...MODEL, title: { en_US: 'Live dashboards', zh_Hans: '实时看板' }, implementationKey: 'live-dashboard',
42
+ implementationTitle: { en_US: 'Live Dashboard', zh_Hans: '实时看板' }, defaultEditorKey: EDITOR }],
43
+ discover: async (scope) => {
44
+ await this.service.authorizeProject(context(scope));
45
+ const page = Number(scope.cursor ?? 1);
46
+ if (!Number.isSafeInteger(page) || page < 1)
47
+ throw new common_1.BadRequestException('DASHBOARD_INVALID_CURSOR');
48
+ const result = await this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability).listArtifacts({ pluginName: dashboard_service_1.PLUGIN_NAME,
49
+ resourceType: 'live-dashboard', resourceIdPrefix: `project/${scope.projectId}/`, page, pageSize: 100, status: 'all' });
50
+ return { resourceIds: result.items.filter(item => !item.userId && item.projectId === scope.projectId && item.status !== 'deleted').map(item => item.id),
51
+ ...(result.items.length === 100 ? { nextCursor: String(page + 1) } : {}) };
52
+ },
53
+ describe: input => this.describe(input),
54
+ extract: async (input) => {
55
+ const saved = await this.service.open(context(input), input.sourceResourceId);
56
+ return (0, dashboard_search_1.dashboardSearchProjection)(saved.manifest, String(saved.draftRevision ?? 0));
57
+ },
58
+ create: async (input) => {
59
+ if (input.templateId)
60
+ throw new common_1.BadRequestException('DASHBOARD_TEMPLATE_UNAVAILABLE');
61
+ const ctx = context(input);
62
+ await this.service.authorizeProject(ctx, 'write');
63
+ const source = { pluginName: dashboard_service_1.PLUGIN_NAME, resourceType: 'live-dashboard', resourceId: `project/${input.projectId}/${input.requestId}` };
64
+ const existing = await this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability).findArtifactBySource(source);
65
+ if (existing && await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).get(existing.id))
66
+ return { providerKey: exports.DASHBOARD_CONTENT_PROVIDER, resourceId: existing.id };
67
+ try {
68
+ const created = await this.service.save(ctx, { schema: 'xpert.live-dashboard', id: input.requestId, title: input.title,
69
+ bindings: [], controls: [], charts: [], tables: [] }, null, 0);
70
+ return { providerKey: exports.DASHBOARD_CONTENT_PROVIDER, resourceId: created.artifactId };
71
+ }
72
+ catch (error) {
73
+ // Another request with this create-only identity may have saved the first
74
+ // draft. Reuse it; never retry a create by overwriting the current draft.
75
+ if (!(error instanceof common_1.ConflictException))
76
+ throw error;
77
+ const winner = await this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability).findArtifactBySource(source);
78
+ if (!winner || !await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).get(winner.id))
79
+ throw error;
80
+ await this.service.open(ctx, winner.id);
81
+ return { providerKey: exports.DASHBOARD_CONTENT_PROVIDER, resourceId: winner.id };
82
+ }
83
+ },
84
+ mutate: async (input) => {
85
+ const ctx = context(input), drafts = this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability);
86
+ if (input.change.action === 'trash' || input.change.action === 'restore') {
87
+ await this.service.authorizeProject(ctx, 'manage');
88
+ await drafts.setRecycled({ artifactId: input.sourceResourceId, expectedRevision: Number(input.expectedRevision), recycled: input.change.action === 'trash' });
89
+ }
90
+ else {
91
+ const saved = await this.service.open(ctx, input.sourceResourceId);
92
+ if (String(saved.draftRevision ?? 0) !== input.expectedRevision)
93
+ throw new common_1.ConflictException('DASHBOARD_DRAFT_CONFLICT');
94
+ if (input.change.action === 'duplicate') {
95
+ await this.service.authorizeProject(ctx, 'write');
96
+ const artifacts = this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability);
97
+ const source = { pluginName: dashboard_service_1.PLUGIN_NAME, resourceType: 'live-dashboard', resourceId: `project/${input.projectId}/${input.change.requestId}` };
98
+ const existing = await artifacts.findArtifactBySource(source);
99
+ if (existing && await drafts.get(existing.id))
100
+ return { providerKey: exports.DASHBOARD_CONTENT_PROVIDER, resourceId: existing.id };
101
+ try {
102
+ // Only the definition is copied. Data is queried again as the acting user;
103
+ // published versions, share tokens and query results are never cloned.
104
+ const copy = await this.service.save(ctx, { ...saved.manifest, id: input.change.requestId, title: input.change.title }, null, 0);
105
+ return { providerKey: exports.DASHBOARD_CONTENT_PROVIDER, resourceId: copy.artifactId };
106
+ }
107
+ catch (error) {
108
+ if (!(error instanceof common_1.ConflictException))
109
+ throw error;
110
+ const winner = await artifacts.findArtifactBySource(source);
111
+ if (!winner || !await drafts.get(winner.id))
112
+ throw error;
113
+ return { providerKey: exports.DASHBOARD_CONTENT_PROVIDER, resourceId: winner.id };
114
+ }
115
+ }
116
+ if (input.change.action !== 'rename')
117
+ throw new common_1.BadRequestException('DASHBOARD_ACTION_UNAVAILABLE');
118
+ await this.service.update(ctx, input.sourceResourceId, Number(input.expectedRevision), { title: input.change.title }, saved.baseVersionId ?? saved.versionId ?? null);
119
+ }
120
+ return { providerKey: exports.DASHBOARD_CONTENT_PROVIDER, resourceId: input.sourceResourceId };
121
+ }
122
+ };
123
+ }
124
+ async authorize(input) {
125
+ const artifact = await this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability).getArtifact(input.resourceId);
126
+ if (!artifact.projectId || artifact.userId || artifact.pluginName !== dashboard_service_1.PLUGIN_NAME || artifact.resourceType !== 'live-dashboard')
127
+ throw new common_1.ForbiddenException('DASHBOARD_ACCESS_DENIED');
128
+ const ctx = context({ actor: input.actor, projectId: artifact.projectId });
129
+ await this.service.authorizeProject(ctx, input.operation === 'read' ? 'read' : input.operation === 'manage' ? 'manage' : 'write');
130
+ const access = (await this.runtime.require(plugin_sdk_1.ProjectAccessRuntimeCapability).listReadable({ actor: input.actor, projectIds: [artifact.projectId] }))[0];
131
+ if (!access)
132
+ throw new common_1.ForbiddenException('DASHBOARD_ACCESS_DENIED');
133
+ return { providerKey: exports.DASHBOARD_CONTENT_PROVIDER, resourceId: input.resourceId, title: artifact.title ?? '', role: access.role,
134
+ canWrite: !access.archived && ['owner', 'manager', 'editor'].includes(access.role), canManage: access.canManage };
135
+ }
136
+ async describe(input) {
137
+ await this.service.authorizeProject(context(input));
138
+ const access = (await this.runtime.require(plugin_sdk_1.ProjectAccessRuntimeCapability).listReadable({ actor: input.actor, projectIds: [input.projectId] }))[0];
139
+ if (!access)
140
+ throw new common_1.ForbiddenException('DASHBOARD_ACCESS_DENIED');
141
+ const artifact = await this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability).getArtifact(input.sourceResourceId);
142
+ if (artifact.projectId !== input.projectId || artifact.status === 'deleted')
143
+ throw new common_1.ForbiddenException('DASHBOARD_ACCESS_DENIED');
144
+ const draft = await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).get(input.sourceResourceId, { includeArchived: true });
145
+ if (!draft)
146
+ throw new common_1.BadRequestException('DASHBOARD_VERSION_UNAVAILABLE');
147
+ const deleted = artifact.status === 'archived';
148
+ const saved = !deleted ? await this.service.open(context(input), input.sourceResourceId) : null;
149
+ return { ...MODEL, offlineAccess: 'project', projectId: input.projectId, title: draft.title ?? artifact.title ?? '', revision: String(draft.revision),
150
+ updatedAt: new Date(draft.updatedAt ?? artifact.updatedAt ?? 0).toISOString(), deleted, defaultEditorKey: EDITOR,
151
+ allowedActions: [...(!deleted ? ['rename', 'duplicate'] : []), ...(access.canManage ? [deleted ? 'restore' : 'trash'] : [])],
152
+ ...(saved ? { preview: { kind: 'outline', revision: String(draft.revision), lines: [saved.manifest.title, ...saved.manifest.charts.map(chart => chart.title), ...saved.manifest.tables.map(table => table.title)].slice(0, 6).map(line => line.slice(0, 160)) } } : {}),
153
+ sourceSync: { status: 'synced', revision: String(draft.revision) },
154
+ source: draft.workspaceFileRef?.workspacePath ? { kind: 'file', path: draft.workspaceFileRef.workspacePath } : { kind: 'managed' } };
155
+ }
156
+ };
157
+ exports.DashboardContentProvider = DashboardContentProvider;
158
+ exports.DashboardContentProvider = DashboardContentProvider = __decorate([
159
+ (0, common_1.Injectable)(),
160
+ (0, plugin_sdk_1.ContentResourceProvider)(exports.DASHBOARD_CONTENT_PROVIDER),
161
+ __param(1, (0, common_1.Inject)(plugin_sdk_1.XPERT_RUNTIME_CAPABILITIES_TOKEN)),
162
+ __metadata("design:paramtypes", [dashboard_service_1.DashboardService, Object])
163
+ ], DashboardContentProvider);
@@ -29,26 +29,46 @@ export declare function dashboardNavigationTarget(context: XpertResolvedViewHost
29
29
  export declare const dashboardNavigationSchema: z.ZodEffects<z.ZodObject<{
30
30
  selectionId: z.ZodOptional<z.ZodString>;
31
31
  parameters: z.ZodOptional<z.ZodObject<{
32
+ contentProvider: z.ZodOptional<z.ZodString>;
33
+ contentId: z.ZodOptional<z.ZodString>;
34
+ contentTitle: z.ZodOptional<z.ZodString>;
35
+ contentEditor: z.ZodOptional<z.ZodString>;
32
36
  artifactId: z.ZodOptional<z.ZodString>;
33
37
  versionId: z.ZodOptional<z.ZodString>;
34
38
  }, "strict", z.ZodTypeAny, {
35
39
  artifactId?: string | undefined;
36
40
  versionId?: string | undefined;
41
+ contentProvider?: string | undefined;
42
+ contentId?: string | undefined;
43
+ contentTitle?: string | undefined;
44
+ contentEditor?: string | undefined;
37
45
  }, {
38
46
  artifactId?: string | undefined;
39
47
  versionId?: string | undefined;
48
+ contentProvider?: string | undefined;
49
+ contentId?: string | undefined;
50
+ contentTitle?: string | undefined;
51
+ contentEditor?: string | undefined;
40
52
  }>>;
41
53
  }, "strip", z.ZodTypeAny, {
42
54
  selectionId?: string | undefined;
43
55
  parameters?: {
44
56
  artifactId?: string | undefined;
45
57
  versionId?: string | undefined;
58
+ contentProvider?: string | undefined;
59
+ contentId?: string | undefined;
60
+ contentTitle?: string | undefined;
61
+ contentEditor?: string | undefined;
46
62
  } | undefined;
47
63
  }, {
48
64
  selectionId?: string | undefined;
49
65
  parameters?: {
50
66
  artifactId?: string | undefined;
51
67
  versionId?: string | undefined;
68
+ contentProvider?: string | undefined;
69
+ contentId?: string | undefined;
70
+ contentTitle?: string | undefined;
71
+ contentEditor?: string | undefined;
52
72
  } | undefined;
53
73
  }>, {
54
74
  artifactId: string | undefined;
@@ -58,6 +78,10 @@ export declare const dashboardNavigationSchema: z.ZodEffects<z.ZodObject<{
58
78
  parameters?: {
59
79
  artifactId?: string | undefined;
60
80
  versionId?: string | undefined;
81
+ contentProvider?: string | undefined;
82
+ contentId?: string | undefined;
83
+ contentTitle?: string | undefined;
84
+ contentEditor?: string | undefined;
61
85
  } | undefined;
62
86
  }>;
63
87
  export type DashboardNavigation = z.infer<typeof dashboardNavigationSchema>;
@@ -20,7 +20,7 @@ function dashboardNavigationTarget(context) {
20
20
  }
21
21
  exports.dashboardNavigationSchema = v3_1.z.object({
22
22
  selectionId: v3_1.z.string().uuid().optional(),
23
- parameters: v3_1.z.object({ artifactId: v3_1.z.string().uuid().optional(), versionId: v3_1.z.string().uuid().optional() }).strict().optional()
23
+ parameters: v3_1.z.object({ contentProvider: v3_1.z.string().optional(), contentId: v3_1.z.string().uuid().optional(), contentTitle: v3_1.z.string().optional(), contentEditor: v3_1.z.string().optional(), artifactId: v3_1.z.string().uuid().optional(), versionId: v3_1.z.string().uuid().optional() }).strict().optional()
24
24
  }).transform((query, ctx) => {
25
25
  const artifactId = query.selectionId ?? query.parameters?.artifactId;
26
26
  const versionId = query.parameters?.versionId;
@@ -6,3 +6,14 @@ export declare function validateControls(manifest: DashboardManifest, input: Rec
6
6
  };
7
7
  export declare function queryDashboardBinding(runtime: RuntimeCapabilityRegistry, actor: DataSourceActor, manifest: DashboardManifest, bindingId: string, input: Record<string, AnalyticsValue>): Promise<AnalyticalDataset>;
8
8
  export declare function queryDashboard(runtime: RuntimeCapabilityRegistry, actor: DataSourceActor, manifest: DashboardManifest, input: Record<string, AnalyticsValue>): Promise<AnalyticalDataset[]>;
9
+ /** Read-only rendering can show independent successful bindings. Saving/publishing still
10
+ * uses queryDashboard and requires every binding to validate. Never return native error
11
+ * details, which may contain private source configuration or query text. */
12
+ export declare function queryDashboardPartial(runtime: RuntimeCapabilityRegistry, actor: DataSourceActor, manifest: DashboardManifest, input: Record<string, AnalyticsValue>): Promise<{
13
+ datasets: AnalyticalDataset[];
14
+ failures: {
15
+ bindingId: string;
16
+ datasetId: string;
17
+ reason: "unavailable";
18
+ }[];
19
+ }>;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validateControls = validateControls;
4
4
  exports.queryDashboardBinding = queryDashboardBinding;
5
5
  exports.queryDashboard = queryDashboard;
6
+ exports.queryDashboardPartial = queryDashboardPartial;
6
7
  const common_1 = require("@nestjs/common");
7
8
  const plugin_sdk_1 = require("@xpert-ai/plugin-sdk");
8
9
  function validateControls(manifest, input) {
@@ -37,3 +38,22 @@ async function queryDashboard(runtime, actor, manifest, input) {
37
38
  throw new common_1.BadRequestException('DASHBOARD_RESULT_TOO_LARGE');
38
39
  return datasets;
39
40
  }
41
+ /** Read-only rendering can show independent successful bindings. Saving/publishing still
42
+ * uses queryDashboard and requires every binding to validate. Never return native error
43
+ * details, which may contain private source configuration or query text. */
44
+ async function queryDashboardPartial(runtime, actor, manifest, input) {
45
+ validateControls(manifest, input);
46
+ const datasets = [];
47
+ const failures = [];
48
+ for (const binding of manifest.bindings) {
49
+ try {
50
+ datasets.push(await queryDashboardBinding(runtime, actor, manifest, binding.id, input));
51
+ }
52
+ catch {
53
+ failures.push({ bindingId: binding.id, datasetId: binding.output.datasetId, reason: 'unavailable' });
54
+ }
55
+ }
56
+ if (Buffer.byteLength(JSON.stringify(datasets)) > 3_000_000)
57
+ throw new common_1.BadRequestException('DASHBOARD_RESULT_TOO_LARGE');
58
+ return { datasets, failures };
59
+ }
@@ -0,0 +1,5 @@
1
+ import type { ContentSearchProjection } from '@xpert-ai/contracts';
2
+ import type { DashboardManifest } from './dashboard.schema';
3
+ /** Explicit allowlist: no Binding source, parameter values, HTML/script, credentials
4
+ * or queried rows cross the shared knowledge boundary. Data access stays viewer-specific. */
5
+ export declare function dashboardSearchProjection(manifest: DashboardManifest, revision: string): ContentSearchProjection;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dashboardSearchProjection = dashboardSearchProjection;
4
+ /** Explicit allowlist: no Binding source, parameter values, HTML/script, credentials
5
+ * or queried rows cross the shared knowledge boundary. Data access stays viewer-specific. */
6
+ function dashboardSearchProjection(manifest, revision) {
7
+ return { revision, sections: [
8
+ { anchor: { kind: 'document', id: manifest.id }, text: manifest.title },
9
+ ...[...manifest.charts, ...manifest.tables].map(view => ({ anchor: { kind: 'widget', id: view.id }, text: view.title })),
10
+ ...manifest.controls.map(control => ({ anchor: { kind: 'control', id: control.controlId }, text: control.label }))
11
+ ] };
12
+ }
@@ -7,7 +7,7 @@ const artifact_document_1 = require("@xpert-ai/plugin-sdk/artifact-document");
7
7
  const id = v3_1.z.string().regex(/^[A-Za-z][A-Za-z0-9_.-]{0,79}$/);
8
8
  exports.dashboardSchema = v3_1.z.object({
9
9
  schema: v3_1.z.literal('xpert.live-dashboard'), id: v3_1.z.string().uuid(), title: v3_1.z.string().min(1).max(160),
10
- bindings: v3_1.z.array(analytics_1.analyticsBindingSchema).min(1).max(8),
10
+ bindings: v3_1.z.array(analytics_1.analyticsBindingSchema).max(8),
11
11
  presentation: v3_1.z.object({ type: v3_1.z.literal('html'), html: artifact_document_1.artifactDocumentHtml, dependencies: artifact_document_1.artifactDocumentDependencyLocks.optional() }).strict().optional(),
12
12
  controls: v3_1.z.array(v3_1.z.object({ controlId: id, type: v3_1.z.enum(['string', 'number', 'boolean', 'date']),
13
13
  defaultValue: v3_1.z.union([v3_1.z.string(), v3_1.z.number(), v3_1.z.boolean(), v3_1.z.null()]).optional(),
@@ -21,11 +21,13 @@ exports.dashboardSchema = v3_1.z.object({
21
21
  if (new Set(values).size !== values.length)
22
22
  ctx.addIssue({ code: 'custom', message: `Duplicate ${name}` });
23
23
  };
24
+ if (!manifest.bindings.length && (manifest.presentation || manifest.controls.length || manifest.charts.length || manifest.tables.length))
25
+ ctx.addIssue({ code: 'custom', message: 'An unconfigured draft must contain no views or controls' });
24
26
  unique(manifest.bindings.map((binding) => binding.id), 'binding');
25
27
  unique(manifest.bindings.map((binding) => binding.output.datasetId), 'dataset');
26
28
  unique(manifest.controls.map((control) => control.controlId), 'control');
27
29
  unique([...manifest.charts, ...manifest.tables].map((view) => view.id), 'view');
28
- if (!manifest.presentation && !manifest.charts.length && !manifest.tables.length)
30
+ if (manifest.bindings.length && !manifest.presentation && !manifest.charts.length && !manifest.tables.length)
29
31
  ctx.addIssue({ code: 'custom', message: 'At least one chart or table is required' });
30
32
  if (manifest.presentation && (manifest.charts.length || manifest.tables.length))
31
33
  ctx.addIssue({ code: 'custom', message: 'HTML presentation owns its charts; use empty charts/tables arrays' });
@@ -194,6 +194,11 @@ export declare class DashboardService {
194
194
  };
195
195
  } | undefined;
196
196
  datasets: import("@xpert-ai/contracts").AnalyticalDataset[];
197
+ failures: {
198
+ bindingId: string;
199
+ datasetId: string;
200
+ reason: "unavailable";
201
+ }[];
197
202
  manifest: {
198
203
  schema: "xpert.live-dashboard";
199
204
  id: string;
@@ -238,6 +243,9 @@ export declare class DashboardService {
238
243
  versionNumber: number | undefined;
239
244
  }>;
240
245
  queryBinding(context: XpertResolvedViewHostContext, artifactId: string, versionId: string | undefined, bindingId: string, input: Record<string, AnalyticsValue>, draftRevision?: number): Promise<import("@xpert-ai/contracts").AnalyticalDataset>;
246
+ /** Every operation uses the current human's Project role; data queries still use actor(context). */
247
+ authorizeProject(context: XpertResolvedViewHostContext, operation?: 'read' | 'write' | 'manage'): Promise<void>;
241
248
  private execute;
242
249
  private probeControls;
243
250
  }
251
+ export declare function projectId(context: XpertResolvedViewHostContext): string | null | undefined;