dataspring-cli 0.4.2__py3-none-any.whl → 0.4.3__py3-none-any.whl
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.
- cli/_skills/dataspring-author/SKILL.md +139 -18
- cli/_skills/dataspring-consume/SKILL.md +104 -22
- cli/_skills/dataspring-correct/SKILL.md +4 -3
- cli/bundled_manifest.py +1 -1
- cli/contract.py +62 -13
- cli/generated.py +66 -48
- cli/main.py +291 -28
- {dataspring_cli-0.4.2.dist-info → dataspring_cli-0.4.3.dist-info}/METADATA +1 -1
- dataspring_cli-0.4.3.dist-info/RECORD +21 -0
- dataspring_cli-0.4.2.dist-info/RECORD +0 -21
- {dataspring_cli-0.4.2.dist-info → dataspring_cli-0.4.3.dist-info}/WHEEL +0 -0
- {dataspring_cli-0.4.2.dist-info → dataspring_cli-0.4.3.dist-info}/entry_points.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dataspring-author
|
|
3
|
-
description:
|
|
3
|
+
description: Building and documenting an org's data in DataSpring - the datacore loop (pipelines, connections, dbt models, metrics; pull, push, check, deploy, build, run, backfills), dbt YAML descriptions and the business_context doc. Use when editing a datacore workspace or dbt/models/**.yml, writing metrics, loading or backfilling a pipeline, or any file that feeds DataSpring's semantic layer or org context.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Authoring DataSpring's data-layer context
|
|
@@ -9,6 +9,28 @@ You are editing files that an analytical agent will read at query time.
|
|
|
9
9
|
Lazy descriptions silently degrade every query that touches the
|
|
10
10
|
affected entity. This skill exists so the conventions stay tight.
|
|
11
11
|
|
|
12
|
+
## Before you start: sign in and name the org
|
|
13
|
+
|
|
14
|
+
Every command and connection acts in ONE org, named each time; nothing
|
|
15
|
+
switches it for later. CLI: `dataspring auth login` prints a URL and a code
|
|
16
|
+
at once (run it in the background and hand both to the person), then
|
|
17
|
+
`--org <org>`, `DATASPRING_ORG`, or `{"org": "<org>"}` in a
|
|
18
|
+
`.forspring.json` at the workspace root, which is the natural place:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
echo '{"org": "acme"}' > .forspring.json && dataspring whoami --json
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
MCP: one connection per org, `https://dataspring.app/api/mcp/?org=<org>`, or
|
|
25
|
+
`Authorization: Bearer ds_...` for a headless agent (the key's own org).
|
|
26
|
+
|
|
27
|
+
A refusal leads with a reason and ends with its fix; match the reason. The
|
|
28
|
+
ones you meet while building: `role_too_low` (connections, pipelines,
|
|
29
|
+
deploy, build, run, cancel and reset need admin; a member can edit models
|
|
30
|
+
and check), `org_required` (ask which org, never guess), `not_signed_in`
|
|
31
|
+
(log in again). The full table, with the CLI exit codes, is in
|
|
32
|
+
`dataspring-consume`.
|
|
33
|
+
|
|
12
34
|
## 1. The contract
|
|
13
35
|
|
|
14
36
|
The descriptions on metrics, dimensions, and semantic models are
|
|
@@ -206,7 +228,44 @@ version produced each issue. Edit the board and the next issue follows.
|
|
|
206
228
|
and is what stops later history columns moving. Rendering and checking
|
|
207
229
|
prose is the consumer's job and is covered in `dataspring-consume`.
|
|
208
230
|
|
|
209
|
-
## 8.2
|
|
231
|
+
## 8.2 The datacore loop (managed tenants)
|
|
232
|
+
|
|
233
|
+
A managed org's pipelines, connections and dbt project are one workspace of
|
|
234
|
+
files. Edit, `check`, `deploy`; nothing an edit or a check does reaches
|
|
235
|
+
production.
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
dataspring datacore pull ./ws # deployed files, each exactly as pushed
|
|
239
|
+
dataspring datacore push ./ws # changed files only, into a new draft
|
|
240
|
+
dataspring datacore check # waits; exits 1 unless it passes
|
|
241
|
+
dataspring datacore deploy --then-build # admin; rebuilds changed models too
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
- **A new org** pulls its starting workspace (`datacore.yaml`, empty
|
|
245
|
+
`pipelines/`, `connections/`, `models/`); `datacore
|
|
246
|
+
status` says `deployed none yet` until the first deploy.
|
|
247
|
+
- **A check** compiles every pipeline, builds the changed models and their
|
|
248
|
+
tests into `<org>_dev`, and dry-runs their SQL. It runs as an identity
|
|
249
|
+
that can write `<org>_dev` only: a project whose `generate_schema_name`
|
|
250
|
+
puts a model in another dataset is refused, naming the models. A
|
|
251
|
+
workspace with no `models/dbt_project.yml` yet passes with dbt `skipped`,
|
|
252
|
+
so land raw data first and add models after.
|
|
253
|
+
- **The semantic manifest is checked per item**, each finding as `FAIL metric
|
|
254
|
+
<name>: <message>` (`check.dbt.failures` on the run). It is also
|
|
255
|
+
dry-imported as a load would import it, so what passes the check imports.
|
|
256
|
+
A ratio metric's `numerator` and `denominator` are metrics, not measures
|
|
257
|
+
(MetricFlow's definition), as are a derived metric's inputs:
|
|
258
|
+
|
|
259
|
+
```yaml
|
|
260
|
+
- name: gross_margin_pct
|
|
261
|
+
type: ratio
|
|
262
|
+
type_params: {numerator: gross_margin, denominator: revenue} # two metrics
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
- **Deploy** needs a green check of that exact draft (any edit makes a new
|
|
266
|
+
draft) and starts a run of each windowed pipeline it changed.
|
|
267
|
+
|
|
268
|
+
## 8.3 Iterating on a dbt model (managed tenants)
|
|
210
269
|
|
|
211
270
|
A model change does not need a load. The loop is `datacore_edit` (kind
|
|
212
271
|
`model`), `datacore_run` `check`, `deploy`, then `build`: a run that loads
|
|
@@ -218,6 +277,68 @@ under `models/`. Do not use `run` to see a model change: it reloads every
|
|
|
218
277
|
resource of the pipeline first. CLI: `dataspring datacore build --select
|
|
219
278
|
"marts.finance+" --wait`.
|
|
220
279
|
|
|
280
|
+
**Look before you write, and verify before you deploy.** `run_sql`
|
|
281
|
+
(`dataspring sql`) runs as the org's read identity, which reads the org's
|
|
282
|
+
own `<org>_raw` (what the loads wrote), `<org>_staging` (the staging views
|
|
283
|
+
over raw), `<org>_core`, `<org>_marts` and `<org>_dev` (what the last
|
|
284
|
+
`check` built), and nothing else; `<org>` is the org id with `-` as `_`.
|
|
285
|
+
|
|
286
|
+
- Before writing a staging model, list the raw columns that actually
|
|
287
|
+
exist: `dataspring datacore schema --table <t>` (or
|
|
288
|
+
`dataspring://datacore/schema`). A field that was null on every row
|
|
289
|
+
loaded has no column at all, so a model written against the source API's
|
|
290
|
+
documentation can fail with `Unrecognized name`; select a typed null for
|
|
291
|
+
it instead. `dataspring datacore freshness` gives row counts and the run
|
|
292
|
+
that wrote each table; `dataspring datacore sample <t>` some rows.
|
|
293
|
+
- After a `check`, the models it built (the changed ones and everything
|
|
294
|
+
downstream) are tables and views in `<org>_dev` for 7 days: compare their
|
|
295
|
+
numbers with the source before `deploy` puts them where dashboards read,
|
|
296
|
+
e.g. `dataspring sql "SELECT * FROM <org>_dev.fct_project LIMIT 20"` or
|
|
297
|
+
`dataspring datacore schema --dataset dev`.
|
|
298
|
+
- A script of several statements runs as one job and answers the last
|
|
299
|
+
one's rows; `CREATE TEMP TABLE` in it is scratch space for an analysis
|
|
300
|
+
that ends with the script.
|
|
301
|
+
|
|
302
|
+
## 8.4 Workspace files keep the customer's text
|
|
303
|
+
|
|
304
|
+
The customer's comments in a pipeline, connection or dbt YAML file are
|
|
305
|
+
its documentation. To replace a connection or pipeline whole, send
|
|
306
|
+
`datacore_edit` `apply` the YAML text as `content`: it is validated like a
|
|
307
|
+
`document` and stored byte for byte, so `dataspring datacore pull` returns
|
|
308
|
+
it as written. A `document` (an object) is stored as a fresh dump with no
|
|
309
|
+
comments; use it only for a file you are creating. `patch`, and a
|
|
310
|
+
`metric_edit` / `semantic_model_edit` update or delete in a shared dbt
|
|
311
|
+
file, replace only the lines of the keys or list entries that changed;
|
|
312
|
+
the rest of the file, mixed indentation included, stays byte for byte.
|
|
313
|
+
When that cannot be done (a flow-style document, say) the answer carries
|
|
314
|
+
`reformatted`: the whole file was rewritten in one indentation style.
|
|
315
|
+
Tell the user the diff is large for that reason. `dataspring datacore
|
|
316
|
+
push` sends each changed file as its text the same way, and skips a file
|
|
317
|
+
whose bytes equal the stored one's.
|
|
318
|
+
|
|
319
|
+
## 8.5 Loads, windows and backfills
|
|
320
|
+
|
|
321
|
+
- **Windows are whole inclusive days**, the run's and every chunk's. When an
|
|
322
|
+
API reads its end date as exclusive (`to=2025-03-31` misses 31 March), the
|
|
323
|
+
resource says so and is sent the day after each window's last day:
|
|
324
|
+
|
|
325
|
+
```yaml
|
|
326
|
+
window: { lookback: 7d, param_start: createdFrom, param_end: createdTo, end: exclusive }
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Test an API's end bound with a one-day window before a backfill: a wrong
|
|
330
|
+
guess silently drops the last day of every chunk.
|
|
331
|
+
- **A backfill** is `dataspring datacore run <pipeline> --start ... --end ...
|
|
332
|
+
--chunk 1M` (or `3M`, `7d`, `2w`): one child run per chunk, each started as
|
|
333
|
+
the previous one ends. A chunk before the last loads only the windowed
|
|
334
|
+
resources (and those they read rows from) and skips dbt; the last chunk
|
|
335
|
+
loads everything and builds the models once. A failed child fails the
|
|
336
|
+
backfill. One backfill per pipeline at a time.
|
|
337
|
+
- **Stop** a queued or running run, or a whole backfill, with `dataspring
|
|
338
|
+
datacore cancel <run_id>` (admin); the schedule fires again at its next slot.
|
|
339
|
+
- **`reset`** drops a pipeline's raw tables and load state. It is
|
|
340
|
+
irreversible: ask first.
|
|
341
|
+
|
|
221
342
|
## 9. Parameter reference
|
|
222
343
|
|
|
223
344
|
<!-- dispatch-params: semantic_model_edit metric_edit board_edit briefing_edit import_manifest business_context_edit warehouse_edit datacore_edit datacore_run -->
|
|
@@ -227,14 +348,14 @@ _Generated from the dispatch registry (`GET /api/dispatch`); do not edit by hand
|
|
|
227
348
|
|
|
228
349
|
MCP tool `semantic_model_edit`; `POST /api/dispatch/semantic_model_edit`. Edit semantic models in the org manifest. Admin/owner only for writes. The body is `{"action": {"action": "<verb>", ...}}`, one verb per table below.
|
|
229
350
|
|
|
230
|
-
**`create`** —
|
|
351
|
+
**`create`** — Create a semantic model (admin/owner).
|
|
231
352
|
|
|
232
353
|
| Parameter | Type | Required | Description |
|
|
233
354
|
|---|---|---|---|
|
|
234
355
|
| `model_data` | object | yes | Full semantic-model definition (name, measures, dimensions, entities) |
|
|
235
356
|
| `expect_version` | string | no | Managed tenants only: the datacore version (draft snapshot id) this edit was written against, from the previous edit's `version` or dataspring://datacore/files; refused with the current one when stale |
|
|
236
357
|
|
|
237
|
-
**`update`** —
|
|
358
|
+
**`update`** — Change a semantic model (admin/owner). On an imported one a change to how it computes answers a dbt YAML proposal and writes nothing; descriptions write at once.
|
|
238
359
|
|
|
239
360
|
| Parameter | Type | Required | Description |
|
|
240
361
|
|---|---|---|---|
|
|
@@ -243,7 +364,7 @@ MCP tool `semantic_model_edit`; `POST /api/dispatch/semantic_model_edit`. Edit s
|
|
|
243
364
|
| `confirmed` | boolean | no, default `False` | set true only after the user confirmed a redefinition of a NATIVE entity whose impact you showed them; has no effect on imported entities |
|
|
244
365
|
| `expect_version` | string | no | Managed tenants only: the datacore version (draft snapshot id) this edit was written against, from the previous edit's `version` or dataspring://datacore/files; refused with the current one when stale |
|
|
245
366
|
|
|
246
|
-
**`delete`** —
|
|
367
|
+
**`delete`** — Delete a semantic model (admin/owner).
|
|
247
368
|
|
|
248
369
|
| Parameter | Type | Required | Description |
|
|
249
370
|
|---|---|---|---|
|
|
@@ -260,14 +381,14 @@ MCP tool `semantic_model_edit`; `POST /api/dispatch/semantic_model_edit`. Edit s
|
|
|
260
381
|
|
|
261
382
|
MCP tool `metric_edit`; `POST /api/dispatch/metric_edit`. Edit metrics in the org manifest. Admin/owner only for writes. The body is `{"action": {"action": "<verb>", ...}}`, one verb per table below.
|
|
262
383
|
|
|
263
|
-
**`create`** —
|
|
384
|
+
**`create`** — Create a metric, native to DataSpring (admin/owner). A ratio's numerator and denominator, a derived metric's inputs and a cumulative or conversion metric's base are metrics, not measures.
|
|
264
385
|
|
|
265
386
|
| Parameter | Type | Required | Description |
|
|
266
387
|
|---|---|---|---|
|
|
267
388
|
| `metric_data` | object | yes | Metric definition (name, type, type_params) |
|
|
268
389
|
| `expect_version` | string | no | Managed tenants only: the datacore version (draft snapshot id) this edit was written against, from the previous edit's `version` or dataspring://datacore/files; refused with the current one when stale |
|
|
269
390
|
|
|
270
|
-
**`update`** —
|
|
391
|
+
**`update`** — Change a metric (admin/owner). On an imported one a change to how it computes answers a dbt YAML proposal and writes nothing; descriptions write at once.
|
|
271
392
|
|
|
272
393
|
| Parameter | Type | Required | Description |
|
|
273
394
|
|---|---|---|---|
|
|
@@ -276,14 +397,14 @@ MCP tool `metric_edit`; `POST /api/dispatch/metric_edit`. Edit metrics in the or
|
|
|
276
397
|
| `confirmed` | boolean | no, default `False` | set true only after the user confirmed a redefinition of a NATIVE entity whose impact you showed them; has no effect on imported entities |
|
|
277
398
|
| `expect_version` | string | no | Managed tenants only: the datacore version (draft snapshot id) this edit was written against, from the previous edit's `version` or dataspring://datacore/files; refused with the current one when stale |
|
|
278
399
|
|
|
279
|
-
**`delete`** —
|
|
400
|
+
**`delete`** — Delete a metric (admin/owner; irreversible).
|
|
280
401
|
|
|
281
402
|
| Parameter | Type | Required | Description |
|
|
282
403
|
|---|---|---|---|
|
|
283
404
|
| `name` | string | yes | |
|
|
284
405
|
| `expect_version` | string | no | Managed tenants only: the datacore version (draft snapshot id) this edit was written against, from the previous edit's `version` or dataspring://datacore/files; refused with the current one when stale |
|
|
285
406
|
|
|
286
|
-
**`preview`** —
|
|
407
|
+
**`preview`** — Test a metric before saving (any role; writes nothing): sample query results or validation errors, plus the `dbt_yaml` that would be committed for it.
|
|
287
408
|
|
|
288
409
|
| Parameter | Type | Required | Description |
|
|
289
410
|
|---|---|---|---|
|
|
@@ -430,17 +551,17 @@ MCP tool `warehouse_edit`; `POST /api/dispatch/warehouse_edit`. Manage the org's
|
|
|
430
551
|
|
|
431
552
|
MCP tool `datacore_edit`; `POST /api/dispatch/datacore_edit`. Edit the datacore workspace: connections, pipelines and the dbt project of a managed tenant, as files. ``kind`` picks the family: The body is `{"action": {"action": "<verb>", ...}}`, one verb per table below.
|
|
432
553
|
|
|
433
|
-
**`apply`** — Write one workspace file whole (create or replace): a connection or pipeline as a ``document`` (validated against its schema and the catalog
|
|
554
|
+
**`apply`** — Write one workspace file whole (create or replace): a dbt project file as ``content``; a connection or pipeline as ``content`` (its YAML text, stored exactly as sent, comments and layout kept) or as a ``document`` (an object, stored as a fresh dump), validated against its schema and the catalog either way.
|
|
434
555
|
|
|
435
556
|
| Parameter | Type | Required | Description |
|
|
436
557
|
|---|---|---|---|
|
|
437
558
|
| `kind` | one of `connection`, `pipeline`, `model` | yes | connection (admin), pipeline (admin) or model (member) |
|
|
438
559
|
| `name` | string | yes | For kind connection or pipeline: the name, i.e. the file name without .yaml (connections/<name>.yaml, pipelines/<name>.yaml). For kind model: the path under models/, the dbt project (models/models/marts/orders.sql, models/models/staging/schema.yml, models/seeds/x.csv, models/dbt_project.yml); no traversal, .sql .yml .yaml .csv .md .txt .json, never target/ or dbt_packages/ |
|
|
439
560
|
| `document` | object | no | kind connection: the whole document (connection.schema.json: kind, base_url, auth with secret NAMES). kind pipeline: the whole document (pipeline.schema.json: a catalog `connector` with config, secrets, overrides; or a `connection` reference, an inline `source` or named `sources`, with `resources`; schedule, checks, then). Secrets are named, never valued; every name must exist |
|
|
440
|
-
| `content` | string | no | kind model: the
|
|
561
|
+
| `content` | string | no | The whole file as text. kind model: required. kind connection or pipeline: the YAML text, validated like a document and stored byte for byte (comments and layout kept); give content or document, not both |
|
|
441
562
|
| `expect_version` | string | no | The datacore version this edit was written against: the `version` the previous edit returned, or dataspring://datacore/files. An edit against a stale version is refused with the current one. Omit to skip the check. |
|
|
442
563
|
|
|
443
|
-
**`patch`** — Apply a JSON merge patch (RFC 7386: a key set to null is removed) to an existing YAML file: a connection, a pipeline (a schedule, a window, a check), or a YAML file under models/ (not SQL).
|
|
564
|
+
**`patch`** — Apply a JSON merge patch (RFC 7386: a key set to null is removed) to an existing YAML file: a connection, a pipeline (a schedule, a window, a check), or a YAML file under models/ (not SQL). Only the lines of the keys the patch changes are rewritten; every other line, comments and indentation included, stays as written (an answer with ``reformatted`` says the file had to be rewritten whole).
|
|
444
565
|
|
|
445
566
|
| Parameter | Type | Required | Description |
|
|
446
567
|
|---|---|---|---|
|
|
@@ -461,13 +582,13 @@ MCP tool `datacore_edit`; `POST /api/dispatch/datacore_edit`. Edit the datacore
|
|
|
461
582
|
|
|
462
583
|
MCP tool `datacore_run`; `POST /api/dispatch/datacore_run`. The datacore's check-then-deploy loop and its operations. The body is `{"action": {"action": "<verb>", ...}}`, one verb per table below.
|
|
463
584
|
|
|
464
|
-
**`check`** — Check the current draft
|
|
585
|
+
**`check`** — Check the current draft (member): compile every pipeline, build the changed dbt models into <org>_dev with their tests, and validate the semantic manifest per metric. Nothing reaches production.
|
|
465
586
|
|
|
466
587
|
| Parameter | Type | Required | Description |
|
|
467
588
|
|---|---|---|---|
|
|
468
589
|
| `note` | string | no | Optional note, recorded on the check run |
|
|
469
590
|
|
|
470
|
-
**`deploy`** —
|
|
591
|
+
**`deploy`** — Promote the checked draft to deployed (admin), or roll back to an earlier deployed snapshot; then_build also rebuilds changed models.
|
|
471
592
|
|
|
472
593
|
| Parameter | Type | Required | Description |
|
|
473
594
|
|---|---|---|---|
|
|
@@ -476,14 +597,14 @@ MCP tool `datacore_run`; `POST /api/dispatch/datacore_run`. The datacore's check
|
|
|
476
597
|
| `then_build` | boolean | no, default `False` | When the deploy changed any file under models/, also queue a models-only build (the `build` action) of the newly deployed snapshot on the raw tables already loaded; build_select narrows it. Without it, a deploy that changed models answers with the command that does it |
|
|
477
598
|
| `build_select` | string | no | With then_build: the dbt selector to rebuild (default: every model, `fqn:*`) |
|
|
478
599
|
|
|
479
|
-
**`run`** —
|
|
600
|
+
**`run`** — Load a pipeline now (admin): its own window, a given window, or a chunked backfill whose chunks run back to back.
|
|
480
601
|
|
|
481
602
|
| Parameter | Type | Required | Description |
|
|
482
603
|
|---|---|---|---|
|
|
483
604
|
| `pipeline` | string | yes | Pipeline name (a deployed pipelines/<name>.yaml) |
|
|
484
605
|
| `window` | RunWindowParams | no | A date window to load instead of the pipeline's own; wider than backfill_chunk becomes a chunked backfill. Both ends are inclusive days, and so is every chunk; a resource whose API reads its end bound as exclusive declares `window.end: exclusive` in the pipeline and is sent the day after each chunk's last day |
|
|
485
606
|
| `cursor` | RunCursorParams | no | {from: <value>}: a cursor to re-pull from |
|
|
486
|
-
| `backfill_chunk` | string | no | Chunk size for a windowed run: 7d, 2w or 1M (calendar months, the default) |
|
|
607
|
+
| `backfill_chunk` | string | no | Chunk size for a windowed run: 7d, 2w or 1M (calendar months, the default). Every chunk before the last loads only the windowed resources (and the ones they read rows from) and skips its dbt step, each skip recorded with its reason on the child run; the last chunk loads every resource and builds the models once |
|
|
487
608
|
|
|
488
609
|
**`build`** — Rebuild the deployed dbt models on the raw tables already loaded: no resource is loaded.
|
|
489
610
|
|
|
@@ -492,14 +613,14 @@ MCP tool `datacore_run`; `POST /api/dispatch/datacore_run`. The datacore's check
|
|
|
492
613
|
| `pipeline` | string | no | A deployed pipeline whose `then` block names what to build (its dbt_select, dbt_exclude and tests). Omit to build every model of the deployed project |
|
|
493
614
|
| `select` | string | no | A dbt selector (`dbt build --select`), e.g. `marts.finance+` or `+fct_postings`; replaces the pipeline's dbt_select and dbt_exclude. Default: the pipeline's then.dbt_select, or every model (`fqn:*`) when no pipeline is named |
|
|
494
615
|
|
|
495
|
-
**`reset`** —
|
|
616
|
+
**`reset`** — Delete a pipeline's dlt state and drop its raw tables (admin; irreversible).
|
|
496
617
|
|
|
497
618
|
| Parameter | Type | Required | Description |
|
|
498
619
|
|---|---|---|---|
|
|
499
620
|
| `pipeline` | string | yes | The pipeline whose dlt state and raw tables to remove |
|
|
500
621
|
| `approve` | boolean | no, default `False` | Must be true: the reset is irreversible. Ask the user first |
|
|
501
622
|
|
|
502
|
-
**`cancel`** —
|
|
623
|
+
**`cancel`** — Stop a queued or running run, or a whole backfill (admin); the schedule fires again at its next slot.
|
|
503
624
|
|
|
504
625
|
| Parameter | Type | Required | Description |
|
|
505
626
|
|---|---|---|---|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dataspring-consume
|
|
3
|
-
description: How to query DataSpring correctly to answer analytical questions. Use when the user is asking analytical/business questions, when an agent is connected to a DataSpring MCP server, when running `dataspring query ...` from the terminal, or when scripting against DataSpring's REST API. Covers the three surfaces (MCP, CLI, REST) — same principles, different invocations.
|
|
3
|
+
description: How to query DataSpring correctly to answer analytical questions. Use when the user is asking analytical/business questions, when an agent is connected to a DataSpring MCP server, when running `dataspring query ...` from the terminal, or when scripting against DataSpring's REST API. Covers the three surfaces (MCP, CLI, REST) — same principles, different invocations — and signing in, naming the org, and what to do with a refusal.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Querying DataSpring to answer analytical questions
|
|
@@ -24,10 +24,10 @@ traces — departing from them produces misleading numbers.
|
|
|
24
24
|
| List dimensions | `list_dimensions()` | `dataspring dimensions list` | `GET /api/semantic-layer/dimensions` |
|
|
25
25
|
| Explain one metric | `explain_metric(metric_name)` | `dataspring metrics show <name>` | `GET /api/semantic-layer/metrics/<name>` |
|
|
26
26
|
| Query | `query_metrics(metrics, dimensions?, grain?, start_date?, end_date?, where?, ...)` | `dataspring query -m <name> [-d ...] [-g month] ...` | `POST /api/dispatch/query_metrics` with the same fields |
|
|
27
|
-
| Explain what was computed | `explain_query(metrics, ...)` (same fields as `query_metrics`) |
|
|
27
|
+
| Explain what was computed | `explain_query(metrics, ...)` (same fields as `query_metrics`) | `dataspring explain_query --metrics <name> ...` | `POST /api/dispatch/explain_query` |
|
|
28
28
|
| What DataSpring learned / undo | `learned_edit list` / `undo` | `dataspring learned list` | `GET /api/learned` |
|
|
29
|
-
| Verified queries | `verified_query_edit list` / `record` |
|
|
30
|
-
| Read business_context | `
|
|
29
|
+
| Verified queries | `verified_query_edit list` / `record` | `dataspring verified_query_edit list` | `POST /api/dispatch/verified_query_edit` |
|
|
30
|
+
| Read business_context | `business_context_edit get` (also in the connection's instructions) | `dataspring business-context get` | `GET /api/context` |
|
|
31
31
|
| Check business_context size | `business_context_edit show_size` | `dataspring business-context size` | (no dedicated route — use `GET /api/context` and measure) |
|
|
32
32
|
| Org's published skill | (this skill, served via SKILL.md) | `dataspring skill instructions consume` | `GET /api/v1/skill/consume/instructions` |
|
|
33
33
|
| Be woken when a number moves | `watch_edit apply` | `dataspring watch_edit apply --document-json @watch.yaml` | `POST /api/dispatch/watch_edit` |
|
|
@@ -51,15 +51,44 @@ reads is the one this deployment was built with:
|
|
|
51
51
|
|
|
52
52
|
```bash
|
|
53
53
|
curl -fsSL https://dataspring.app/install.sh | sh # Windows: irm https://dataspring.app/install.ps1 | iex
|
|
54
|
-
dataspring login
|
|
54
|
+
dataspring auth login
|
|
55
55
|
dataspring skills install --all # into ~/.claude/skills/<name>/, stamped
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
Run `dataspring skills install` again after `dataspring upgrade` (the CLI
|
|
59
|
-
upgrades itself in place
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
`skills`
|
|
59
|
+
upgrades itself in place through uv tool, pipx or pip, and prints the
|
|
60
|
+
commands to run when it cannot tell which installed it; `dataspring
|
|
61
|
+
--version` says when the server has operations this CLI does not know).
|
|
62
|
+
`dataspring skills diff` shows what drifted between the installed copy and
|
|
63
|
+
the served one; `--offline` on any `skills` command uses the copy bundled in
|
|
64
|
+
the CLI instead of the server.
|
|
65
|
+
|
|
66
|
+
## Signing in and naming the org
|
|
67
|
+
|
|
68
|
+
People sign in with their Forspring account; who is in which org, with
|
|
69
|
+
which role, is the account's answer on every request. DataSpring has no
|
|
70
|
+
"current org": every command and every connection names one.
|
|
71
|
+
|
|
72
|
+
- **CLI.** `dataspring auth login` prints a URL and a code at once and waits
|
|
73
|
+
until a person approves it in a browser (the machine running the CLI needs
|
|
74
|
+
none). Run it in the background, hand the person the URL and the code, and
|
|
75
|
+
wait for it to exit. It stores a `ds_` key for the org they approve
|
|
76
|
+
(`--all-orgs`: one per org). With one key stored, commands act in that org.
|
|
77
|
+
With several, name the org on every command, in this order of precedence:
|
|
78
|
+
`--org <org>`, `DATASPRING_ORG` (then `FORSPRING_ORG`), or a
|
|
79
|
+
`.forspring.json` holding `{"org": "<org>"}` in the working directory or a
|
|
80
|
+
parent. `DATASPRING_API_KEY=ds_...` (CI) acts in the key's own org.
|
|
81
|
+
`dataspring whoami --json` says which org a command here acts in and why
|
|
82
|
+
(`org_from`). There is no command that switches org for later commands.
|
|
83
|
+
- **MCP.** One connection per org: `https://dataspring.app/api/mcp/?org=<org>`
|
|
84
|
+
(the slash before `?` is required), signed in with the Forspring account.
|
|
85
|
+
To work in two orgs, add two connections; no tool moves a connection. A
|
|
86
|
+
headless agent connects to the same URL with `Authorization: Bearer ds_...`;
|
|
87
|
+
the key acts in its own org, and `?org=` may only repeat it.
|
|
88
|
+
- **REST.** `Authorization: Bearer ds_...`; the key is the org.
|
|
89
|
+
|
|
90
|
+
Never pick an org for the person. When the org is ambiguous you are refused
|
|
91
|
+
with `org_required` and the list of their orgs (§11): ask which one.
|
|
63
92
|
|
|
64
93
|
## 1. Knowledge layers — consult before querying
|
|
65
94
|
|
|
@@ -271,6 +300,31 @@ two queries (before/after) and calling out the methodology change rather
|
|
|
271
300
|
than silently quoting one misleading trend. Same rule regardless of
|
|
272
301
|
surface — same warehouse, same boundaries.
|
|
273
302
|
|
|
303
|
+
## 6.1 Say when a period is partial
|
|
304
|
+
|
|
305
|
+
A grouped query is clamped to the window you asked for, so its first and
|
|
306
|
+
last buckets can hold only part of their period. The answer then carries
|
|
307
|
+
`effective_window`: `covered` (the days the numbers cover, always the window
|
|
308
|
+
asked for), `partial_periods` (each cut-short bucket with the days it holds)
|
|
309
|
+
and a `note`. It is absent when every bucket is whole. Quarter to date at
|
|
310
|
+
quarter grain, or January to September at year grain, comes back as ONE
|
|
311
|
+
row labelled with the whole period:
|
|
312
|
+
|
|
313
|
+
```json
|
|
314
|
+
"effective_window": {"covered": {"start": "2025-01-01", "end": "2025-09-30"},
|
|
315
|
+
"partial_periods": [{"bucket": "2025-01-01", "start": "2025-01-01", "end": "2025-09-30"}]}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Label every partial bucket with the days it holds, beside its number, and
|
|
319
|
+
never compare it with a whole period as if it were one:
|
|
320
|
+
|
|
321
|
+
> 2025 to date (1 January to 30 September): 4.2M DKK, against 3.9M DKK for
|
|
322
|
+
> the same nine months of 2024.
|
|
323
|
+
|
|
324
|
+
For a like-for-like comparison send `compare_to: same_period_last_year` (or
|
|
325
|
+
`previous_period`), which compares the same days. `explain_query` reports the
|
|
326
|
+
same `effective_window` before you run anything.
|
|
327
|
+
|
|
274
328
|
## 7. Insights, not just data
|
|
275
329
|
|
|
276
330
|
Don't just return raw query results. Interpret what the number
|
|
@@ -488,8 +542,8 @@ Examples of honest refusals:
|
|
|
488
542
|
|
|
489
543
|
## 11. Tool / response error recovery
|
|
490
544
|
|
|
491
|
-
|
|
492
|
-
|
|
545
|
+
A failed query comes back with an `error`, a `code` and a `suggestion`
|
|
546
|
+
(`hint` is an alias older clients read). Follow the suggestion.
|
|
493
547
|
|
|
494
548
|
Codes you'll see:
|
|
495
549
|
|
|
@@ -523,10 +577,34 @@ replaced the sentence the reader actually needed. If you could not
|
|
|
523
577
|
recover, say what you could not find out and what you would need — that
|
|
524
578
|
is about THEM. If you did recover, just answer.
|
|
525
579
|
|
|
526
|
-
Surface shape: **MCP** returns `{error, code, suggestion}`
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
`{"detail":
|
|
580
|
+
Surface shape: **MCP** returns `{error, code, suggestion}` as the tool
|
|
581
|
+
result; **REST** (`POST /api/dispatch/<tool>`) answers the same body with
|
|
582
|
+
HTTP 200, and a body the tool's schema refuses (or an operation that fails
|
|
583
|
+
outright) with 400 `{"detail": ...}`; **CLI** prints `✗ Error: …` with a `Hint:` line and exits 1.
|
|
584
|
+
|
|
585
|
+
### Refusals: who you are, which org, what your role allows
|
|
586
|
+
|
|
587
|
+
A refusal is not a query error. It has a stable `reason`, a `note` saying
|
|
588
|
+
what happened and a `fix` saying what fixes it. **REST** answers it with the
|
|
589
|
+
reason's HTTP status and `{"ok": false, "reason", "note", "fix"}` (plus
|
|
590
|
+
`org`, and `contacts` and `fix_url` when the account named who can fix it);
|
|
591
|
+
**MCP** raises a tool error whose text is `<reason>: <note> Fix: <fix>`;
|
|
592
|
+
the **CLI** prints the same line and exits with the reason's code. Match on
|
|
593
|
+
the reason, never on the prose, and pass the fix on verbatim.
|
|
594
|
+
|
|
595
|
+
| Reason | CLI exit | What you do |
|
|
596
|
+
|---|---|---|
|
|
597
|
+
| `not_signed_in` | 4 | CLI: run `dataspring auth login` and hand the person its URL and code. MCP: reconnect and sign in. Never ask for a password or a key in chat. |
|
|
598
|
+
| `org_required` | 2 | Several orgs, none named. Ask the person which one (the refusal lists them); then `--org`, `DATASPRING_ORG` or `.forspring.json`. Never guess. |
|
|
599
|
+
| `not_a_member` | 3 | Check the org id's spelling against the list. Otherwise only an owner or admin of that org can add the person, at the Forspring account; say so and stop. |
|
|
600
|
+
| `product_off` | 4 | DataSpring is not switched on for this person in that org. Tell them who can switch it on (`contacts`, `fix_url`); do not retry. |
|
|
601
|
+
| `org_suspended` | 4 | Billing stopped DataSpring for the whole org. Tell them an owner can restart it on the billing page; do not retry. |
|
|
602
|
+
| `role_too_low` | 4 | Their role cannot do this. Offer what it can do (a personal preference or quick metric instead of an org-wide change), or name the role needed. |
|
|
603
|
+
| `org_mismatch` | 4 | The key or connection is for another org than the one named. Use that org's key or URL, or drop the conflicting `--org` / `?org=`. |
|
|
604
|
+
| `account_unavailable` | 1 | Forspring's account did not answer, so access could not be checked. Retry once after a few seconds; nothing changed. |
|
|
605
|
+
|
|
606
|
+
Other CLI exits: 0 success, 1 any other failure, 2 a usage error, 64 a
|
|
607
|
+
removed command (it prints what replaced it).
|
|
530
608
|
|
|
531
609
|
## 12. Multi-turn / multi-call efficiency
|
|
532
610
|
|
|
@@ -606,9 +684,13 @@ How it reaches you:
|
|
|
606
684
|
`dataspring business-context size` shows the byte budget.
|
|
607
685
|
- **REST**: `GET /api/context` returns `{"content": "..."}`.
|
|
608
686
|
|
|
687
|
+
- **MCP**: the connection's instructions carry it from the first turn;
|
|
688
|
+
`business_context_edit` `get` reads it again. (`dataspring://context` is
|
|
689
|
+
the user's own query preferences, not this document.)
|
|
690
|
+
|
|
609
691
|
Treat `business_context` as part of the same authoritative layer
|
|
610
|
-
as metric descriptions.
|
|
611
|
-
|
|
692
|
+
as metric descriptions: the same trust level. It is edited in DataSpring
|
|
693
|
+
(`business_context_edit`), and an edit is live at once.
|
|
612
694
|
|
|
613
695
|
That's the working agreement. Same principles, three surfaces —
|
|
614
696
|
do this and the answers will be right.
|
|
@@ -699,11 +781,11 @@ MCP tool `explain_query`; `POST /api/dispatch/explain_query`. Show what a query
|
|
|
699
781
|
|
|
700
782
|
#### `run_sql`
|
|
701
783
|
|
|
702
|
-
MCP tool `run_sql`; `POST /api/dispatch/run_sql`. Run
|
|
784
|
+
MCP tool `run_sql`; `POST /api/dispatch/run_sql`. Run read-only SQL (one statement, or a script whose last statement answers) against the org's warehouse and get the rows back, ungoverned.
|
|
703
785
|
|
|
704
786
|
| Parameter | Type | Required | Description |
|
|
705
787
|
|---|---|---|---|
|
|
706
|
-
| `sql` | string | yes | One SQL statement (BigQuery Standard SQL on a managed warehouse). Name tables as <org>_marts.<table>, <org>_core.<table> or <org>
|
|
788
|
+
| `sql` | string | yes | One SQL statement (BigQuery Standard SQL on a managed warehouse), or a script of several separated by ';' whose last statement's rows are returned (CREATE TEMP TABLE ... ; SELECT ... works: temp tables are the scratch space, gone when the script ends). Name tables as <org>_marts.<table>, <org>_core.<table>, <org>_staging.<table>, <org>_raw.<table> (what the loads wrote) or <org>_dev.<table> (what the last datacore check built; expire after 7 days), <org> being the organization id with every hyphen as an underscore (acme-dk is acme_dk_marts); the identity it runs as can read those five datasets of its own org and nothing else, so a write to a dataset or a reach outside them is refused by the warehouse. |
|
|
707
789
|
| `warehouse` | string | no | One of the org's warehouses (see dataspring://warehouses); omit for the active one. |
|
|
708
790
|
| `max_rows` | integer | no, default `1000` | Rows to return at most (default 1000, cap 10000). Aggregate in SQL rather than paging. |
|
|
709
791
|
| `format` | one of `json`, `csv`, `markdown` | no, default `json` | 'json' answers rows in `data`; 'csv' or 'markdown' answer one document in `content`. |
|
|
@@ -766,7 +848,7 @@ MCP tool `update_context`; `POST /api/dispatch/update_context`. Update user pref
|
|
|
766
848
|
|
|
767
849
|
MCP tool `quick_metric_edit`; `POST /api/dispatch/quick_metric_edit`. Edit user-defined quick metrics (arithmetic expressions over existing metrics). The body is `{"action": {"action": "<verb>", ...}}`, one verb per table below.
|
|
768
850
|
|
|
769
|
-
**`create`** —
|
|
851
|
+
**`create`** — Save a personal quick metric: arithmetic over existing metrics, queried as `qm:<name>`.
|
|
770
852
|
|
|
771
853
|
| Parameter | Type | Required | Description |
|
|
772
854
|
|---|---|---|---|
|
|
@@ -774,7 +856,7 @@ MCP tool `quick_metric_edit`; `POST /api/dispatch/quick_metric_edit`. Edit user-
|
|
|
774
856
|
| `expression` | string | yes | Arithmetic expression over existing metrics (e.g., "total_revenue / order_count") |
|
|
775
857
|
| `description` | string | no | |
|
|
776
858
|
|
|
777
|
-
**`update`** —
|
|
859
|
+
**`update`** — Change one of your quick metrics.
|
|
778
860
|
|
|
779
861
|
| Parameter | Type | Required | Description |
|
|
780
862
|
|---|---|---|---|
|
|
@@ -783,7 +865,7 @@ MCP tool `quick_metric_edit`; `POST /api/dispatch/quick_metric_edit`. Edit user-
|
|
|
783
865
|
| `expression` | string | no | |
|
|
784
866
|
| `description` | string | no | |
|
|
785
867
|
|
|
786
|
-
**`delete`** —
|
|
868
|
+
**`delete`** — Delete one of your quick metrics.
|
|
787
869
|
|
|
788
870
|
| Parameter | Type | Required | Description |
|
|
789
871
|
|---|---|---|---|
|
|
@@ -12,8 +12,9 @@ readers see it, and be exact about what changed.
|
|
|
12
12
|
|
|
13
13
|
Before agreeing anything is wrong, call `explain_query`; with no
|
|
14
14
|
`params` it explains this session's last query. Quote back the metric it
|
|
15
|
-
resolved, the filters
|
|
16
|
-
|
|
15
|
+
resolved, the filters (standing preferences included), the window, and
|
|
16
|
+
briefly the SQL. A bucket in `effective_window.partial_periods` (a year to
|
|
17
|
+
date labelled as the year) is often the whole dispute: name its days. Often the number is right and the disagreement
|
|
17
18
|
is about which metric was meant — ambiguity, not a correction. Never
|
|
18
19
|
apologise for a number you have not re-read.
|
|
19
20
|
|
|
@@ -48,7 +49,7 @@ Read the WHAT YOU MAY CHANGE block — this user's targets, nothing else. An
|
|
|
48
49
|
admin gets the systemic fix; a member or viewer a personal preference,
|
|
49
50
|
verified query or quick metric, which fixes their experience immediately.
|
|
50
51
|
Both are complete answers: never route a user elsewhere, never offer what
|
|
51
|
-
ends in a
|
|
52
|
+
ends in a `role_too_low` refusal. §7 is the same split for a coverage gap.
|
|
52
53
|
|
|
53
54
|
## 4. Ownership and ceremony
|
|
54
55
|
|
cli/bundled_manifest.py
CHANGED
|
@@ -6,7 +6,7 @@ the server's to say whether the server has operations this CLI does not
|
|
|
6
6
|
know. tests/test_skills_surface.py asserts it is current.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
MANIFEST_HASH = "
|
|
9
|
+
MANIFEST_HASH = "e61e4a47f3c193beb2f6d731efde82839f5e62788bfa18560277459487bb26a7"
|
|
10
10
|
TOOL_KEYS = (
|
|
11
11
|
"business_context_edit",
|
|
12
12
|
"semantic_model_edit",
|