@hachej/boring-bi-dashboard 0.1.60
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/LICENSE +21 -0
- package/README.md +83 -0
- package/dist/front/index.d.ts +28 -0
- package/dist/front/index.js +831 -0
- package/dist/shared/index.d.ts +16 -0
- package/dist/shared/index.js +131 -0
- package/dist/types-BGZKL9Rs.d.ts +146 -0
- package/docs/issues/bi-dashboard-plugin/README.md +349 -0
- package/docs/issues/bi-dashboard-plugin/data-access-unification.md +638 -0
- package/docs/issues/bi-dashboard-plugin/data-bridge.md +425 -0
- package/example/.gitignore +16 -0
- package/example/.pi/extensions/bi-dashboard/front/index.ts +1 -0
- package/example/.pi/extensions/bi-dashboard/package.json +11 -0
- package/example/README.md +10 -0
- package/example/dashboards/people.dashboard.json +178 -0
- package/example/data/people.csv +13 -0
- package/example/eval/bi-dashboard.yaml +31 -0
- package/package.json +85 -0
- package/playground/README.md +25 -0
- package/playground/run-eval.ts +100 -0
- package/playground/smoke-dashboard.ts +101 -0
- package/skills/bi-dashboard-authoring/SKILL.md +78 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
# Data Bridge Plugin Plan
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Draft plan for a reusable `@hachej/boring-data-bridge` plugin built on top of
|
|
6
|
+
WorkspaceBridge RPC v1 from PR #71. This plugin should replace ad-hoc product
|
|
7
|
+
bridges such as Macro's custom workspace bridge over time, while keeping the
|
|
8
|
+
workspace package domain-neutral.
|
|
9
|
+
|
|
10
|
+
## Goal
|
|
11
|
+
|
|
12
|
+
Create a trusted, reusable data-access plugin that exposes stable semantic data
|
|
13
|
+
operations through WorkspaceBridge, implemented by provider adapters.
|
|
14
|
+
|
|
15
|
+
The bridge answers one question:
|
|
16
|
+
|
|
17
|
+
> Given a semantic data request, which installed adapter can satisfy it safely,
|
|
18
|
+
> and what portable result/artifact should the caller receive?
|
|
19
|
+
|
|
20
|
+
## Non-goals
|
|
21
|
+
|
|
22
|
+
- Do not add a new generic `/api/data-request/*` HTTP endpoint.
|
|
23
|
+
- Do not make `@hachej/boring-workspace` understand SQL, BSL, Macro, or
|
|
24
|
+
Perspective. Plain tabular file parsing should live in a separate server-only
|
|
25
|
+
`@hachej/file-data` package or a deliberately accepted workspace file-data
|
|
26
|
+
module, not inside data-bridge itself.
|
|
27
|
+
- Do not expose provider-bound operations such as `clickhouse.v1.query`,
|
|
28
|
+
`duckdb.v1.query`, or `bsl.v1.execute` as the BI/dashboard API.
|
|
29
|
+
- Do not let generated/user runtime plugins self-register host bridge handlers.
|
|
30
|
+
- Do not make agents generate raw Perspective, ECharts, DuckDB, or ClickHouse
|
|
31
|
+
configs.
|
|
32
|
+
|
|
33
|
+
## Dependencies
|
|
34
|
+
|
|
35
|
+
- PR #71 WorkspaceBridge RPC v1:
|
|
36
|
+
- `/api/v1/workspace-bridge/call`
|
|
37
|
+
- registered `workspaceBridgeHandlers`
|
|
38
|
+
- `WorkspaceBridgeClient.fromEnv()` for runtime callers
|
|
39
|
+
- runtime env: `BORING_WORKSPACE_BRIDGE_URL`, `BORING_WORKSPACE_BRIDGE_TOKEN`,
|
|
40
|
+
`BORING_WORKSPACE_ID`, `BORING_AGENT_SESSION_ID`
|
|
41
|
+
- caller classes, capabilities, schema limits, timeouts, idempotency
|
|
42
|
+
- BSL's existing LLM query mechanism:
|
|
43
|
+
- `BSLTools.query_model` accepts an Ibis-style BSL query string such as
|
|
44
|
+
`sm.group_by("origin").aggregate("flight_count")`
|
|
45
|
+
- this should be used by the BSL adapter rather than inventing a second JSON
|
|
46
|
+
DSL for BSL execution
|
|
47
|
+
- BSL Perspective artifact contract, used by the BSL adapter as an internal/nested payload rather than the top-level WorkspaceBridge response:
|
|
48
|
+
- `kind: bsl.perspective.dataset` for inline data
|
|
49
|
+
- `kind: bsl.perspective.artifact` with `data_ref` for external JSON/Arrow
|
|
50
|
+
|
|
51
|
+
## Package shape
|
|
52
|
+
|
|
53
|
+
```txt
|
|
54
|
+
plugins/data-bridge/
|
|
55
|
+
package.json
|
|
56
|
+
src/shared/
|
|
57
|
+
contracts.ts
|
|
58
|
+
adapters.ts
|
|
59
|
+
index.ts
|
|
60
|
+
src/server/
|
|
61
|
+
index.ts
|
|
62
|
+
handlers.ts
|
|
63
|
+
registry.ts
|
|
64
|
+
adapters/
|
|
65
|
+
staticFileAdapter.ts
|
|
66
|
+
bslAdapter.ts
|
|
67
|
+
duckdbAdapter.ts
|
|
68
|
+
perspectiveAdapter.ts
|
|
69
|
+
skills/data-bridge-usage/SKILL.md
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Package name:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
"name": "@hachej/boring-data-bridge"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The package is primarily a **trusted server plugin**. It may also export shared
|
|
79
|
+
client helpers, but the transport is WorkspaceBridge.
|
|
80
|
+
|
|
81
|
+
## Core contract
|
|
82
|
+
|
|
83
|
+
### Data bridge request identity
|
|
84
|
+
|
|
85
|
+
Every data request should be provider-neutral and adapter-routable:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
interface DataBridgeRequestBase {
|
|
89
|
+
requestId?: string
|
|
90
|
+
source?: string // optional adapter id or logical source id
|
|
91
|
+
workspaceId?: string // normally inferred by WorkspaceBridge context
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The caller may set `source` when the dashboard/model knows a logical source
|
|
96
|
+
(`bsl`, `playground`, `macro`). If omitted, the bridge asks adapters whether they
|
|
97
|
+
can satisfy the request.
|
|
98
|
+
|
|
99
|
+
### Portable tabular result
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
interface DataBridgeColumn {
|
|
103
|
+
name: string
|
|
104
|
+
type: "string" | "integer" | "float" | "boolean" | "date" | "datetime" | "json"
|
|
105
|
+
role?: "dimension" | "measure" | "time" | "unknown"
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
interface DataBridgeTableResult {
|
|
109
|
+
kind: "data-bridge.table"
|
|
110
|
+
version: 1
|
|
111
|
+
columns: DataBridgeColumn[]
|
|
112
|
+
rows: Record<string, unknown>[]
|
|
113
|
+
rowCount: number
|
|
114
|
+
truncated?: boolean
|
|
115
|
+
source?: string
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Semantic BSL query request
|
|
120
|
+
|
|
121
|
+
Prefer BSL's existing Python/Ibis-style query string mechanism:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
interface DataBridgeSemanticQuery {
|
|
125
|
+
language: "bsl-python"
|
|
126
|
+
model: string
|
|
127
|
+
query: string // e.g. 'sm.group_by("month").aggregate("revenue")'
|
|
128
|
+
parameters?: Record<string, unknown>
|
|
129
|
+
limit?: number
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Direct `language: "bsl-python"` is **not** the default browser/runtime dashboard
|
|
134
|
+
path. It is a code-like semantic query language and must be separately gated:
|
|
135
|
+
|
|
136
|
+
- allowed caller classes: `server` and trusted `runtime` only by default
|
|
137
|
+
- required capability: `data:bsl-query-string` in addition to `data:read`
|
|
138
|
+
- never accepted from generic browser dashboard rendering calls
|
|
139
|
+
- parsed/validated by the BSL adapter before execution; unsupported names,
|
|
140
|
+
imports, attributes outside the BSL query surface, multi-statements, and file/OS
|
|
141
|
+
access are rejected
|
|
142
|
+
|
|
143
|
+
Dashboard/browser usage should send `language: "bsl-dashboard"`; the trusted BSL
|
|
144
|
+
adapter compiles that safe structured query into the native BSL query-string path.
|
|
145
|
+
|
|
146
|
+
### Dashboard query request
|
|
147
|
+
|
|
148
|
+
For agent-authored dashboards, keep the high-level shape and compile in the
|
|
149
|
+
adapter/plugin layer:
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
interface DataBridgeFilterExpression {
|
|
153
|
+
field: string
|
|
154
|
+
op: "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "in" | "contains" | "between"
|
|
155
|
+
value: unknown
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
type DataRef =
|
|
159
|
+
| { kind: "workspace-file"; path: string; fileFormat?: "csv" | "json" | "ndjson" | "parquet" | "arrow"; limit?: number }
|
|
160
|
+
| { kind: "duckdb-file"; path: string; table?: string }
|
|
161
|
+
| { kind: "sqlite-file"; path: string; table?: string }
|
|
162
|
+
| { kind: "semantic-model"; model: string }
|
|
163
|
+
|
|
164
|
+
interface DataBridgeDashboardQuery {
|
|
165
|
+
language: "bsl-dashboard"
|
|
166
|
+
model: string
|
|
167
|
+
dataRef?: DataRef
|
|
168
|
+
groupBy?: string[]
|
|
169
|
+
measures?: string[]
|
|
170
|
+
dimensions?: string[]
|
|
171
|
+
filters?: DataBridgeFilterExpression[]
|
|
172
|
+
orderBy?: Array<[field: string, direction: "asc" | "desc"]>
|
|
173
|
+
limit?: number
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
The BSL adapter compiles this to the BSL query string form, for example:
|
|
178
|
+
|
|
179
|
+
```txt
|
|
180
|
+
sm.group_by("month", "region").aggregate("revenue", "order_count")
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
This keeps dashboard specs easy for agents while still using BSL's real query
|
|
184
|
+
mechanism under the hood. The `filters` and `orderBy` shapes intentionally match
|
|
185
|
+
`BslDashboardQuerySpec` so the BI dashboard compiler is lossless. Filter mapping
|
|
186
|
+
is explicit: `eq/neq/gt/gte/lt/lte` compile to scalar comparisons, `in` compiles
|
|
187
|
+
to membership, `contains` compiles to the adapter's supported string contains
|
|
188
|
+
operation, and `between` requires a two-value tuple/array and compiles to
|
|
189
|
+
`gte && lte`.
|
|
190
|
+
|
|
191
|
+
## WorkspaceBridge operations
|
|
192
|
+
|
|
193
|
+
Use stable `data.v1.*` operation names. These are not provider actions; they are
|
|
194
|
+
semantic data bridge capabilities implemented by adapters.
|
|
195
|
+
|
|
196
|
+
### `data.v1.catalog.search`
|
|
197
|
+
|
|
198
|
+
Search available datasets/models/series.
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
input: {
|
|
202
|
+
source?: string
|
|
203
|
+
query: string
|
|
204
|
+
limit?: number
|
|
205
|
+
filters?: Record<string, string | string[]>
|
|
206
|
+
}
|
|
207
|
+
output: {
|
|
208
|
+
items: Array<{ id: string; title: string; subtitle?: string; source?: string; tags?: string[] }>
|
|
209
|
+
total?: number
|
|
210
|
+
hasMore?: boolean
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### `data.v1.dataset.preview`
|
|
215
|
+
|
|
216
|
+
Preview a dataset by id/path/model without requiring a semantic query.
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
input: { source?: string; datasetId: string; limit?: number; offset?: number }
|
|
220
|
+
output: DataBridgeTableResult
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### `data.v1.query.run`
|
|
224
|
+
|
|
225
|
+
Run a semantic query and return tabular data.
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
interface DataBridgeSqlQuery {
|
|
229
|
+
language: "sql"
|
|
230
|
+
dialect: "duckdb" | "sqlite" | string
|
|
231
|
+
sql: string
|
|
232
|
+
dataRef?: Extract<DataRef, { kind: "duckdb-file" | "sqlite-file" }>
|
|
233
|
+
limit?: number
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
input: {
|
|
237
|
+
source?: string
|
|
238
|
+
query: DataBridgeDashboardQuery | DataBridgeSemanticQuery | DataBridgeSqlQuery
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
`DataBridgeSemanticQuery` and `DataBridgeSqlQuery` require handler-level caller/capability checks in addition to the operation definition. Because WorkspaceBridge capabilities are declared per operation, `data.v1.query.run` must still inspect `input.query.language` and reject `bsl-python` unless `context.callerClass` is trusted and `context.capabilities` includes `data:bsl-query-string`. Direct SQL requires a separate `data:sql-query` capability unless the adapter compiles from the safe `bsl-dashboard` shape. Browser dashboard callers are restricted to `DataBridgeDashboardQuery`.
|
|
242
|
+
output: DataBridgeTableResult
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### `data.v1.perspective.prepare`
|
|
246
|
+
|
|
247
|
+
Prepare a Perspective-compatible dataset or server table descriptor from a
|
|
248
|
+
semantic query. The consumer advertises acceptable transports; the server chooses
|
|
249
|
+
the safe concrete transport. Keep this descriptor shape aligned with
|
|
250
|
+
`plugins/bi-dashboard/docs/issues/bi-dashboard-plugin/data-access-unification.md`.
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
type DataTransport = "inline" | "artifact" | "websocket"
|
|
254
|
+
type PreferredDataTransport = "auto" | DataTransport
|
|
255
|
+
type PayloadFormat = "arrow" | "json"
|
|
256
|
+
|
|
257
|
+
interface PerspectiveViewerConfig {
|
|
258
|
+
plugin?: string
|
|
259
|
+
columns?: string[]
|
|
260
|
+
group_by?: string[]
|
|
261
|
+
split_by?: string[]
|
|
262
|
+
sort?: Array<[string, "asc" | "desc"]>
|
|
263
|
+
filter?: unknown[][]
|
|
264
|
+
aggregates?: Record<string, string>
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
input: {
|
|
268
|
+
source?: string
|
|
269
|
+
datasetId?: string
|
|
270
|
+
query: DataBridgeSemanticQuery | DataBridgeDashboardQuery | DataBridgeSqlQuery
|
|
271
|
+
viewer?: PerspectiveViewerConfig
|
|
272
|
+
transport?: {
|
|
273
|
+
preferred?: PreferredDataTransport
|
|
274
|
+
accepted: DataTransport[]
|
|
275
|
+
payloadFormat?: PayloadFormat
|
|
276
|
+
maxInlineBytes?: number
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
output: {
|
|
280
|
+
kind: "data-bridge.perspective"
|
|
281
|
+
version: 1
|
|
282
|
+
transport: DataTransport
|
|
283
|
+
payloadFormat: PayloadFormat
|
|
284
|
+
schema?: DataBridgeColumn[]
|
|
285
|
+
rowCount?: number
|
|
286
|
+
source?: string
|
|
287
|
+
viewer?: PerspectiveViewerConfig
|
|
288
|
+
inline?: { bytes?: number; data?: unknown; base64?: string }
|
|
289
|
+
artifact?: { url: string; contentType: string; expiresAt?: string; bytes?: number }
|
|
290
|
+
websocket?: { url: string; protocol: "perspective" | "data-bridge-arrow-delta"; sessionId: string }
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Start with inline JSON/Arrow for small results, add artifact Arrow for larger
|
|
295
|
+
static results, and add websocket when a Perspective server runtime is
|
|
296
|
+
available.
|
|
297
|
+
|
|
298
|
+
## Adapter API
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
interface DataBridgeAdapter {
|
|
302
|
+
id: string
|
|
303
|
+
label: string
|
|
304
|
+
capabilities: {
|
|
305
|
+
catalog?: boolean
|
|
306
|
+
datasetPreview?: boolean
|
|
307
|
+
semanticQuery?: boolean
|
|
308
|
+
perspective?: boolean
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
canHandle(input: unknown, context: DataBridgeContext): boolean | Promise<boolean>
|
|
312
|
+
searchCatalog?(input: CatalogSearchInput, context: DataBridgeContext): Promise<CatalogSearchOutput>
|
|
313
|
+
previewDataset?(input: DatasetPreviewInput, context: DataBridgeContext): Promise<DataBridgeTableResult>
|
|
314
|
+
runQuery?(input: QueryRunInput, context: DataBridgeContext): Promise<DataBridgeTableResult>
|
|
315
|
+
preparePerspective?(input: PerspectivePrepareInput, context: DataBridgeContext): Promise<PerspectivePrepareOutput>
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Adapters are explicitly installed by host/trusted plugins. Provider-specific
|
|
320
|
+
credentials stay inside adapters.
|
|
321
|
+
|
|
322
|
+
## Initial adapters
|
|
323
|
+
|
|
324
|
+
1. **Static/workspace file adapter**
|
|
325
|
+
- CSV/JSON/NDJSON preview and small dashboard aggregations delegate to the
|
|
326
|
+
shared `@hachej/file-data` implementation used by `/api/v1/files/records`.
|
|
327
|
+
- Never parse CSV/JSON independently and never read raw `workspaceRoot` paths
|
|
328
|
+
directly; use workspace adapter/file-data semantics.
|
|
329
|
+
- Useful for playground/evals and dashboards over local artifacts.
|
|
330
|
+
|
|
331
|
+
2. **DuckDB adapter**
|
|
332
|
+
- Read-only SQL over registered workspace datasets.
|
|
333
|
+
- This adapter may support `language: "sql"` internally, but BI dashboard
|
|
334
|
+
should prefer semantic/dashboard query inputs.
|
|
335
|
+
|
|
336
|
+
3. **BSL adapter**
|
|
337
|
+
- Executes BSL via existing `BSLTools`/query-string machinery.
|
|
338
|
+
- Accepts `language: "bsl-dashboard"` from normal dashboard/browser calls.
|
|
339
|
+
- Accepts direct `language: "bsl-python"` only after the handler checks
|
|
340
|
+
caller class and `data:bsl-query-string` capability.
|
|
341
|
+
- Compiles `language: "bsl-dashboard"` into BSL query strings.
|
|
342
|
+
- Can produce Perspective inline/artifact outputs using BSL's Perspective
|
|
343
|
+
helpers.
|
|
344
|
+
|
|
345
|
+
4. **Macro adapter**
|
|
346
|
+
- Wraps Macro's catalog/search/series services behind `data.v1.*` ops.
|
|
347
|
+
- Migration target for Macro's custom `/api/macro/workspace-bridge/call`.
|
|
348
|
+
|
|
349
|
+
5. **Perspective adapter/decorator**
|
|
350
|
+
- Converts `DataBridgeTableResult` or BSL result into
|
|
351
|
+
`bsl.perspective.dataset`/artifact.
|
|
352
|
+
- Later owns server-side Perspective table registry and websocket descriptor.
|
|
353
|
+
|
|
354
|
+
## Security and safety
|
|
355
|
+
|
|
356
|
+
- Register handlers via `workspaceBridgeHandlers` only from trusted server
|
|
357
|
+
plugins.
|
|
358
|
+
- Operation definitions set small defaults and explicit max output sizes.
|
|
359
|
+
- Runtime calls require `data:read` or narrower capabilities.
|
|
360
|
+
- Mutating or artifact-writing operations require idempotency keys.
|
|
361
|
+
- No raw provider credentials in bridge inputs/outputs/logs.
|
|
362
|
+
- No arbitrary filesystem paths from runtime callers; use workspace-relative ids
|
|
363
|
+
and existing file validation.
|
|
364
|
+
- SQL support, where present, is adapter-local and read-only; not part of the
|
|
365
|
+
BI dashboard contract.
|
|
366
|
+
|
|
367
|
+
## Implementation phases
|
|
368
|
+
|
|
369
|
+
### Phase 1 — Contracts and in-memory registry
|
|
370
|
+
|
|
371
|
+
- Add package scaffold.
|
|
372
|
+
- Define shared contracts and adapter API.
|
|
373
|
+
- Add server registry with deterministic adapter selection.
|
|
374
|
+
- Unit-test schema validation, adapter precedence, and error envelopes.
|
|
375
|
+
|
|
376
|
+
### Phase 2 — WorkspaceBridge handlers
|
|
377
|
+
|
|
378
|
+
- Register `data.v1.catalog.search`, `data.v1.dataset.preview`, and
|
|
379
|
+
`data.v1.query.run` through `workspaceBridgeHandlers`.
|
|
380
|
+
- Use PR #71 `defineTrustedDomainBridgeHandler` if available.
|
|
381
|
+
- Add an e2e smoke similar to PR #71 `bridge-e2e.ts`.
|
|
382
|
+
|
|
383
|
+
### Phase 3 — BSL adapter
|
|
384
|
+
|
|
385
|
+
- Add compiler from dashboard query shape to BSL query string.
|
|
386
|
+
- Add a BSL adapter that executes BSL's native tool/query mechanism.
|
|
387
|
+
- Gate direct `language: "bsl-python"` calls behind `data:bsl-query-string` and
|
|
388
|
+
trusted caller classes.
|
|
389
|
+
- Validate both direct and generated query strings against an allowlist-style
|
|
390
|
+
parser/builder; never concatenate unchecked user code.
|
|
391
|
+
|
|
392
|
+
### Phase 4 — Perspective prepare
|
|
393
|
+
|
|
394
|
+
- Add `data.v1.perspective.prepare`.
|
|
395
|
+
- Public output is the `data-bridge.perspective` descriptor. BSL
|
|
396
|
+
`bsl.perspective.dataset` / `bsl.perspective.artifact` objects may appear as
|
|
397
|
+
adapter-internal payloads inside `inline.data` or artifact metadata, but are
|
|
398
|
+
not the top-level WorkspaceBridge output.
|
|
399
|
+
- Return inline JSON/Arrow first for small results.
|
|
400
|
+
- Add artifact mode when file outputs are needed.
|
|
401
|
+
- Defer Perspective websocket server replication until the dataset path is
|
|
402
|
+
stable.
|
|
403
|
+
|
|
404
|
+
### Phase 5 — Macro/playground migrations
|
|
405
|
+
|
|
406
|
+
- Adapt playground CSV data and Macro services to data-bridge adapters.
|
|
407
|
+
- Keep old Macro routes temporarily as compatibility shims.
|
|
408
|
+
- Move dashboard/data-explorer consumers to `data.v1.*` calls.
|
|
409
|
+
|
|
410
|
+
## Validation
|
|
411
|
+
|
|
412
|
+
- Unit tests for contracts, adapter registry, and handlers.
|
|
413
|
+
- WorkspaceBridge e2e with browser and runtime caller classes.
|
|
414
|
+
- BSL adapter tests using a tiny semantic model and query string:
|
|
415
|
+
`sm.group_by("month").aggregate("revenue")`.
|
|
416
|
+
- Perspective prepare tests validating the public `data-bridge.perspective` descriptor and, for the BSL adapter, the nested BSL Perspective payload shape.
|
|
417
|
+
- Security tests: untrusted plugin cannot register handlers; runtime caller
|
|
418
|
+
without capability is rejected; oversize output is rejected.
|
|
419
|
+
|
|
420
|
+
## Open questions
|
|
421
|
+
|
|
422
|
+
- Should direct SQL be exposed to browser callers with `data:sql-query`, or restricted to runtime/server callers only?
|
|
423
|
+
- Should Perspective websocket mode live in data-bridge or a separate
|
|
424
|
+
`perspective-bridge` adapter package?
|
|
425
|
+
- How should BSL model/profile discovery be configured in child apps?
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
*
|
|
2
|
+
!.gitignore
|
|
3
|
+
!README.md
|
|
4
|
+
!dashboards/
|
|
5
|
+
!dashboards/**
|
|
6
|
+
!data/
|
|
7
|
+
!data/**
|
|
8
|
+
!eval/
|
|
9
|
+
!eval/**
|
|
10
|
+
!.pi/
|
|
11
|
+
!.pi/extensions/
|
|
12
|
+
!.pi/extensions/bi-dashboard/
|
|
13
|
+
!.pi/extensions/bi-dashboard/package.json
|
|
14
|
+
!.pi/extensions/bi-dashboard/front/
|
|
15
|
+
!.pi/extensions/bi-dashboard/front/index.ts
|
|
16
|
+
.pi/extensions/bi-dashboard/.boring-signature.json
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "@hachej/boring-bi-dashboard/front"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# BI dashboard demo workspace
|
|
2
|
+
|
|
3
|
+
This directory is a tiny workspace fixture for the BI dashboard plugin.
|
|
4
|
+
|
|
5
|
+
- `data/people.csv` — sample tabular data.
|
|
6
|
+
- `dashboards/people.dashboard.json` — dashboard spec that reads the CSV through `data.v1.query.run`.
|
|
7
|
+
- `eval/bi-dashboard.yaml` — authoring eval prompt for dashboard generation.
|
|
8
|
+
- `.pi/extensions/bi-dashboard` — tiny workspace-local front extension that re-exports `@hachej/boring-bi-dashboard/front` for manual browser testing with external plugins enabled.
|
|
9
|
+
|
|
10
|
+
For live data queries in browser testing, the host must also load the trusted `@hachej/boring-data-bridge` server plugin; the plugin-local eval runner does this automatically.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
{
|
|
2
|
+
"kind": "boring.generated-pane",
|
|
3
|
+
"profile": "bi-dashboard",
|
|
4
|
+
"version": 1,
|
|
5
|
+
"title": "People Operations Command Center",
|
|
6
|
+
"description": "An agent-authored BI dashboard rendered from safe JSON: controllers, metrics, charts, and Perspective-style tables.",
|
|
7
|
+
"queries": {
|
|
8
|
+
"total_revenue": {
|
|
9
|
+
"id": "total_revenue",
|
|
10
|
+
"source": "people-duckdb",
|
|
11
|
+
"sql": "SELECT sum(revenue) AS revenue FROM people"
|
|
12
|
+
},
|
|
13
|
+
"headcount": {
|
|
14
|
+
"id": "headcount",
|
|
15
|
+
"source": "people-duckdb",
|
|
16
|
+
"sql": "SELECT count(*) AS count FROM people"
|
|
17
|
+
},
|
|
18
|
+
"revenue_by_role": {
|
|
19
|
+
"id": "revenue_by_role",
|
|
20
|
+
"source": "people-duckdb",
|
|
21
|
+
"sql": "SELECT role, sum(revenue) AS revenue FROM people GROUP BY role ORDER BY revenue DESC"
|
|
22
|
+
},
|
|
23
|
+
"headcount_by_region": {
|
|
24
|
+
"id": "headcount_by_region",
|
|
25
|
+
"source": "people-duckdb",
|
|
26
|
+
"sql": "SELECT region, count(*) AS count FROM people GROUP BY region ORDER BY count DESC"
|
|
27
|
+
},
|
|
28
|
+
"revenue_by_region": {
|
|
29
|
+
"id": "revenue_by_region",
|
|
30
|
+
"source": "people-duckdb",
|
|
31
|
+
"sql": "SELECT region, sum(revenue) AS revenue FROM people GROUP BY region ORDER BY revenue DESC"
|
|
32
|
+
},
|
|
33
|
+
"people_detail": {
|
|
34
|
+
"id": "people_detail",
|
|
35
|
+
"source": "people-duckdb",
|
|
36
|
+
"sql": "SELECT role, region, sum(revenue) AS revenue FROM people GROUP BY role, region ORDER BY revenue DESC"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"root": "dashboard",
|
|
40
|
+
"elements": {
|
|
41
|
+
"dashboard": {
|
|
42
|
+
"type": "DashboardGrid",
|
|
43
|
+
"props": {
|
|
44
|
+
"columns": 12
|
|
45
|
+
},
|
|
46
|
+
"children": [
|
|
47
|
+
"summary",
|
|
48
|
+
"total-revenue",
|
|
49
|
+
"headcount",
|
|
50
|
+
"role-controller",
|
|
51
|
+
"region-controller",
|
|
52
|
+
"role-chart",
|
|
53
|
+
"region-chart",
|
|
54
|
+
"perspective-role",
|
|
55
|
+
"perspective-detail"
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
"summary": {
|
|
59
|
+
"type": "BSLText",
|
|
60
|
+
"props": {
|
|
61
|
+
"markdown": "Control-plane view for a people/revenue dataset. The JSON spec defines only catalog components and data queries; the plugin decides how to render charts and Perspective-style tables."
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"total-revenue": {
|
|
65
|
+
"type": "BSLMetric",
|
|
66
|
+
"props": {
|
|
67
|
+
"queryId": "total_revenue",
|
|
68
|
+
"valueField": "revenue",
|
|
69
|
+
"label": "Total revenue",
|
|
70
|
+
"format": "currency"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"headcount": {
|
|
74
|
+
"type": "BSLMetric",
|
|
75
|
+
"props": {
|
|
76
|
+
"queryId": "headcount",
|
|
77
|
+
"valueField": "count",
|
|
78
|
+
"label": "People",
|
|
79
|
+
"format": "number"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"role-controller": {
|
|
83
|
+
"type": "BSLFilter",
|
|
84
|
+
"props": {
|
|
85
|
+
"id": "role-filter",
|
|
86
|
+
"field": "role",
|
|
87
|
+
"label": "Role controller",
|
|
88
|
+
"controlType": "multiSelect",
|
|
89
|
+
"targetQueries": [
|
|
90
|
+
"revenue_by_role",
|
|
91
|
+
"people_detail"
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"region-controller": {
|
|
96
|
+
"type": "BSLFilter",
|
|
97
|
+
"props": {
|
|
98
|
+
"id": "region-filter",
|
|
99
|
+
"field": "region",
|
|
100
|
+
"label": "Region controller",
|
|
101
|
+
"controlType": "select",
|
|
102
|
+
"targetQueries": [
|
|
103
|
+
"headcount_by_region",
|
|
104
|
+
"revenue_by_region",
|
|
105
|
+
"people_detail"
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"role-chart": {
|
|
110
|
+
"type": "BSLChart",
|
|
111
|
+
"props": {
|
|
112
|
+
"queryId": "revenue_by_role",
|
|
113
|
+
"title": "Revenue by role",
|
|
114
|
+
"renderer": "echarts",
|
|
115
|
+
"chartType": "bar",
|
|
116
|
+
"x": "role",
|
|
117
|
+
"y": "revenue",
|
|
118
|
+
"controls": [
|
|
119
|
+
"role-filter"
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"region-chart": {
|
|
124
|
+
"type": "BSLChart",
|
|
125
|
+
"props": {
|
|
126
|
+
"queryId": "revenue_by_region",
|
|
127
|
+
"title": "Revenue by region",
|
|
128
|
+
"renderer": "echarts",
|
|
129
|
+
"chartType": "bar",
|
|
130
|
+
"x": "region",
|
|
131
|
+
"y": "revenue",
|
|
132
|
+
"controls": [
|
|
133
|
+
"region-filter"
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
"perspective-role": {
|
|
138
|
+
"type": "BSLPerspectiveViewer",
|
|
139
|
+
"props": {
|
|
140
|
+
"queryId": "headcount_by_region",
|
|
141
|
+
"title": "Perspective: headcount by region",
|
|
142
|
+
"plugin": "Y Bar",
|
|
143
|
+
"columns": [
|
|
144
|
+
"region",
|
|
145
|
+
"count"
|
|
146
|
+
],
|
|
147
|
+
"sort": [
|
|
148
|
+
[
|
|
149
|
+
"count",
|
|
150
|
+
"desc"
|
|
151
|
+
]
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"perspective-detail": {
|
|
156
|
+
"type": "BSLPerspectiveViewer",
|
|
157
|
+
"props": {
|
|
158
|
+
"queryId": "people_detail",
|
|
159
|
+
"title": "Perspective: role \u00d7 region revenue",
|
|
160
|
+
"plugin": "Datagrid",
|
|
161
|
+
"columns": [
|
|
162
|
+
"role",
|
|
163
|
+
"region",
|
|
164
|
+
"revenue"
|
|
165
|
+
],
|
|
166
|
+
"groupBy": [
|
|
167
|
+
"role"
|
|
168
|
+
],
|
|
169
|
+
"sort": [
|
|
170
|
+
[
|
|
171
|
+
"revenue",
|
|
172
|
+
"desc"
|
|
173
|
+
]
|
|
174
|
+
]
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
id,role,region,revenue,utilization,satisfaction
|
|
2
|
+
1,engineer,EMEA,120000,0.82,0.91
|
|
3
|
+
2,designer,AMER,80000,0.74,0.88
|
|
4
|
+
3,engineer,EMEA,140000,0.89,0.93
|
|
5
|
+
4,analyst,APAC,90000,0.69,0.84
|
|
6
|
+
5,engineer,AMER,155000,0.92,0.9
|
|
7
|
+
6,designer,EMEA,98000,0.77,0.86
|
|
8
|
+
7,analyst,AMER,112000,0.81,0.89
|
|
9
|
+
8,product,APAC,132000,0.73,0.87
|
|
10
|
+
9,product,EMEA,148000,0.86,0.92
|
|
11
|
+
10,engineer,APAC,126000,0.79,0.85
|
|
12
|
+
12,ops,AMER,76000,0.71,0.82
|
|
13
|
+
13,ops,EMEA,88000,0.76,0.86
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Eval suite: BSL BI dashboard authoring.
|
|
2
|
+
#
|
|
3
|
+
# Goal: verify the BI dashboard plugin skill gives the agent enough context to
|
|
4
|
+
# create dashboard artifacts from a plain BI dashboard request.
|
|
5
|
+
#
|
|
6
|
+
# Run from the repo root:
|
|
7
|
+
# pnpm --filter @hachej/boring-bi-dashboard playground:eval
|
|
8
|
+
|
|
9
|
+
model:
|
|
10
|
+
provider: openai-codex
|
|
11
|
+
id: gpt-5.5
|
|
12
|
+
|
|
13
|
+
defaults:
|
|
14
|
+
retries: 1
|
|
15
|
+
timeoutMs: 180000
|
|
16
|
+
|
|
17
|
+
prompts:
|
|
18
|
+
- prompt: Can you make me a dashboard for orders revenue that I can open in the workspace?
|
|
19
|
+
expect:
|
|
20
|
+
- tool: write
|
|
21
|
+
params:
|
|
22
|
+
path: !EvalRegex 'dashboards/.+\.dashboard\.json'
|
|
23
|
+
content: !EvalRegex '"kind"\s*:\s*"boring\.generated-pane"'
|
|
24
|
+
- tool: write
|
|
25
|
+
params:
|
|
26
|
+
path: !EvalRegex 'dashboards/.+\.dashboard\.json'
|
|
27
|
+
content: !EvalRegex '"profile"\s*:\s*"bi-dashboard"'
|
|
28
|
+
- tool: write
|
|
29
|
+
params:
|
|
30
|
+
path: !EvalRegex 'dashboards/.+\.dashboard\.json'
|
|
31
|
+
content: !EvalRegex '"elements"\s*:'
|