@graphit/cli 0.2.322 → 0.2.330
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/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/bin/graphit +1 -1
- package/bin/graphit.ps1 +1 -1
- package/dist/api/client.js +15 -0
- package/dist/api/client.js.map +1 -1
- package/dist/commands/ds-config.js +2 -2
- package/dist/commands/ds-config.js.map +1 -1
- package/dist/commands/ds.js +2 -2
- package/dist/commands/ds.js.map +1 -1
- package/dist/commands/kb.js +347 -15
- package/dist/commands/kb.js.map +1 -1
- package/dist/commands/query.js +7 -7
- package/dist/commands/query.js.map +1 -1
- package/dist/index.js +0 -8
- package/dist/index.js.map +1 -1
- package/dist/skill-guard.js +2 -1
- package/dist/skill-guard.js.map +1 -1
- package/package.json +1 -1
- package/scripts/verb-policy-source.json +31 -103
- package/skills/graphit/SKILL.md +32 -39
- package/skills/graphit/VERSION.json +1 -1
- package/skills/graphit/references/attached-docs.md +74 -0
- package/skills/graphit/references/data-source-refresh.md +38 -0
- package/skills/graphit/references/data-sources.md +39 -111
- package/skills/graphit/references/filters-advanced.md +2 -2
- package/skills/graphit/references/governance-explained.md +18 -29
- package/skills/graphit/references/governance.md +20 -83
- package/skills/graphit/references/kb-actions.md +28 -81
- package/skills/graphit/references/kb-discovery.md +35 -64
- package/skills/graphit/references/kb-scope.md +17 -15
- package/skills/graphit/references/kb-structure.md +28 -54
- package/skills/graphit/references/kb-traversal.md +24 -96
- package/skills/graphit/references/metric-families.md +19 -0
- package/skills/graphit/references/migration.md +2 -2
- package/skills/graphit/references/onboarding.md +5 -4
- package/skills/graphit/references/presentations.md +1 -1
- package/skills/graphit/references/runtime.md +6 -6
- package/skills/graphit/references/semantic-authoring.md +81 -0
- package/skills/graphit/references/sql-reference.md +19 -20
- package/dist/commands/kb-constraints.d.ts +0 -14
- package/dist/commands/kb-constraints.js +0 -53
- package/dist/commands/kb-constraints.js.map +0 -1
- package/dist/commands/kb-create.d.ts +0 -2
- package/dist/commands/kb-create.js +0 -296
- package/dist/commands/kb-create.js.map +0 -1
- package/dist/commands/kb-delete.d.ts +0 -2
- package/dist/commands/kb-delete.js +0 -37
- package/dist/commands/kb-delete.js.map +0 -1
- package/dist/commands/kb-read.d.ts +0 -2
- package/dist/commands/kb-read.js +0 -223
- package/dist/commands/kb-read.js.map +0 -1
- package/dist/commands/kb-shared.d.ts +0 -43
- package/dist/commands/kb-shared.js +0 -81
- package/dist/commands/kb-shared.js.map +0 -1
- package/dist/commands/kb-update.d.ts +0 -2
- package/dist/commands/kb-update.js +0 -240
- package/dist/commands/kb-update.js.map +0 -1
- package/dist/commands/sl/index.d.ts +0 -2
- package/dist/commands/sl/index.js +0 -263
- package/dist/commands/sl/index.js.map +0 -1
- package/skills/graphit/references/parameterized-metrics.md +0 -77
|
@@ -1,107 +1,35 @@
|
|
|
1
|
-
# KB Traversal
|
|
1
|
+
# KB Traversal
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Load when investigating semantic reach or presenting KB results.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Read roles
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
| Question | Command |
|
|
7
|
+
| Need | Read |
|
|
10
8
|
|---|---|
|
|
11
|
-
|
|
|
12
|
-
|
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
## Common Queries
|
|
21
|
-
|
|
22
|
-
### "Show me everything in the MARKETING domain"
|
|
23
|
-
`graphit kb explore domain MARKETING` - returns the domain's tables and the assets on them. This is the first call when scoping a build.
|
|
24
|
-
|
|
25
|
-
### "Find all revenue metrics"
|
|
26
|
-
`graphit kb explore topic REVENUE` returns the assets tagged with that concept across every domain. Fall back to `graphit kb search "revenue" --type metric` only when no topic captures the concept.
|
|
27
|
-
|
|
28
|
-
### "What depends on the ORDERS table?"
|
|
29
|
-
`graphit kb explore table ORDERS` - returns the table's column schema (names, types, descriptions) plus `metric_count`/`dimension_count`/`rule_count` and every bound metric (collapsed to templates, each with a `variant_count`), dimension, and rule whose SQL references ORDERS. The counts include child variants; the lists stay collapsed.
|
|
30
|
-
|
|
31
|
-
### "What columns does this table or data source have?" (read the schema)
|
|
32
|
-
`graphit kb explore table <NAME>` (or `graphit kb get table <NAME>`) returns the column schema - names, types, descriptions - straight from the knowledge base. This is how you read a table's schema; never `DESCRIBE` it (only read-only SELECT runs through `graphit query`, so DESCRIBE/SHOW/DDL are rejected). If a data source was just created and not yet scanned, the KB has no columns for it yet - read its shape with `graphit query "SELECT * FROM <NAME> LIMIT 0" --ds <NAME>`, or run `graphit ds verify <id>` to scan it into the KB.
|
|
33
|
-
|
|
34
|
-
### "What joins with MARKETING_UA_DS?"
|
|
35
|
-
`graphit kb explore table MARKETING_UA_DS`, then read the relationships in the response (the documented JOINs involving that table).
|
|
36
|
-
|
|
37
|
-
### "What topics does ARPU_D1 belong to?"
|
|
38
|
-
`graphit kb explore metric ARPU_D1` - the response includes its topics, along with its tables, dimensions, and home domain.
|
|
39
|
-
|
|
40
|
-
### "What domains do we have?" (enumerate names)
|
|
41
|
-
`graphit kb list domains` - returns every domain with its description and asset count. Report all of them, including empty ones. This enumerates domain NAMES; to see what is in one, explore it. Domain is not a searchable type, so `graphit kb search` will not surface domains.
|
|
42
|
-
|
|
43
|
-
## Presenting KB Results
|
|
44
|
-
|
|
45
|
-
A single `graphit kb explore` call returns one entity's full neighborhood, so it usually answers the question on its own - scan the response and run a second command only if the first does not contain what you need. The user cannot see raw CLI output; render every result with these per-command templates.
|
|
46
|
-
|
|
47
|
-
**After `graphit kb list <type>`** - count summary + table with bold names. The list is the **inventory**, and the response carries `total`/`truncated`: if the rows shown are fewer than `total`, the list was capped - raise `--limit` (a missing asset may be truncated, not absent). For metrics the list is **collapsed** - each row is a template or a flat metric, never a child variant; a template's `variant_count` says how many concrete variants it owns. Use `--include-variants` for the full flat set, or `graphit kb explore metric NAME` to enumerate one template's variants.
|
|
48
|
-
|
|
49
|
-
~~~
|
|
50
|
-
**12 metrics** (10 flat + 2 templates, 38 variants) across 3 tables:
|
|
51
|
-
|
|
52
|
-
| Metric | Table | Calculation | Params | Variants |
|
|
53
|
-
|---|---|---|---|---|
|
|
54
|
-
| **CPI** | **MARKETING_UA** | `SUM(spend)/SUM(installs)` | - | - |
|
|
55
|
-
| **ARPU** | **MARKETING_UA** | `SUM(revenue)/COUNT(...)` | DAY | 18 |
|
|
56
|
-
| **RETENTION** | **PLAYER_QUALITY** | `COUNT(CASE WHEN ...)` | DAY | 20 |
|
|
57
|
-
|
|
58
|
-
To use a variant, reference `{{metric:ARPU(DAY=7)}}`; to list them, explore the template.
|
|
59
|
-
~~~
|
|
60
|
-
|
|
61
|
-
Adapt columns per type: dimensions include semantic type, rules include constraint count, domains include asset count.
|
|
62
|
-
|
|
63
|
-
**After `graphit kb get <type> <name>`** - entity heading + key-value table:
|
|
64
|
-
|
|
65
|
-
~~~
|
|
66
|
-
### **CPI** (metric, verified)
|
|
67
|
-
|
|
68
|
-
*Cost per install*
|
|
69
|
-
|
|
70
|
-
| | |
|
|
71
|
-
|---:|---|
|
|
72
|
-
| **Table** | **MARKETING_UA** |
|
|
73
|
-
| **Calculation** | `SUM(spend) / SUM(installs)` |
|
|
74
|
-
| **Parameters** | none |
|
|
75
|
-
| **Topics** | ACQUISITION, SPEND |
|
|
76
|
-
| **Default dims** | MEDIA_SOURCE, CAMPAIGN_NAME |
|
|
77
|
-
~~~
|
|
9
|
+
| Inventory roots | list semantic-model, metric, group, or rule |
|
|
10
|
+
| Full root definition | get |
|
|
11
|
+
| Collapsed hierarchy | tree |
|
|
12
|
+
| Ranked discovery | search |
|
|
13
|
+
| Entity declarations | entity |
|
|
14
|
+
| Family expansion/axis resolution | family |
|
|
15
|
+
| Semantic neighborhood | explore semantic-model, metric, or group |
|
|
16
|
+
| Dashboard/rule impact | usage |
|
|
78
17
|
|
|
79
|
-
|
|
18
|
+
`list metric` is flat. Families collapse in tree/search/family views.
|
|
80
19
|
|
|
81
|
-
|
|
20
|
+
## Investigations
|
|
82
21
|
|
|
83
|
-
|
|
84
|
-
|
|
22
|
+
- **Everything in finance:** explore group `finance`; present models, metrics/families, nested counts, and rules.
|
|
23
|
+
- **How revenue is defined:** get metric `revenue`, then explore it for reached models and composition.
|
|
24
|
+
- **What a model owns:** get the model and present entities, dimensions, measures, group, binding, and rules.
|
|
25
|
+
- **What joins models:** inspect shared entities. Never look for a relationship asset.
|
|
26
|
+
- **Where a metric is shown:** use usage.
|
|
27
|
+
- **Physical columns:** use metadata discovery or model physical detail; do not explore a table noun.
|
|
85
28
|
|
|
86
|
-
|
|
87
|
-
|---|---|---|
|
|
88
|
-
| metric | **TOTAL_REVENUE** | Total revenue across all channels |
|
|
89
|
-
| dimension | **REVENUE_BUCKET** | Revenue range segmentation |
|
|
90
|
-
| synonym | GMV | Maps to **TOTAL_REVENUE** (metric) |
|
|
91
|
-
~~~
|
|
29
|
+
## Families
|
|
92
30
|
|
|
93
|
-
|
|
31
|
+
A family card is a view over concrete metrics. If axes match several members, show the open axes and ask. Never guess.
|
|
94
32
|
|
|
95
|
-
|
|
96
|
-
**CPI** (metric) on **MARKETING_UA**:
|
|
97
|
-
- **Calculation:** `SUM(spend) / SUM(installs)`
|
|
98
|
-
- **Tables:** **MARKETING_UA**, **MARKETING_UA_6MO** (secondary)
|
|
99
|
-
- **Related dimensions (8):**
|
|
100
|
-
- **MEDIA_SOURCE** - `media_source` (categorical)
|
|
101
|
-
- **CAMPAIGN_NAME** - `campaign_name` (categorical)
|
|
102
|
-
- **Rules:** **EXCLUDE_ORGANIC** (filters organic installs)
|
|
103
|
-
- **Domain:** MARKETING
|
|
104
|
-
- **Presented on:** **Marketing Command Center** (3 graphs) - offer to open or extend
|
|
105
|
-
~~~
|
|
33
|
+
## Presentation
|
|
106
34
|
|
|
107
|
-
For
|
|
35
|
+
For metrics show type, definition, group, reached models, rules, and usage. For models show nested components. For rules show body, constraints, targets, mode, and usage. Omit concealed neighbors without hinting they exist.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Metric Families
|
|
2
|
+
|
|
3
|
+
Load when the user asks for D7/D30, gross/net, payer/all, or another named metric axis.
|
|
4
|
+
|
|
5
|
+
A family is a UX grouping over concrete standard metrics. There is no parameterized template, generated child engine, `$` parameter token, or call-with-arguments query syntax.
|
|
6
|
+
|
|
7
|
+
Each member carries:
|
|
8
|
+
|
|
9
|
+
- its own lowercase metric name and complete definition
|
|
10
|
+
- `meta.graphit.family`
|
|
11
|
+
- axis key/value pairs in `meta.graphit.axes`
|
|
12
|
+
|
|
13
|
+
Create each concrete member with the family and repeatable axis options. Tree/search/family views collapse members; `list metric` remains flat.
|
|
14
|
+
|
|
15
|
+
Use family expansion to inspect members. Supply known axes to resolve. If several candidates remain, show open axes and ask—never guess a governed metric.
|
|
16
|
+
|
|
17
|
+
Reference the resolved concrete member normally with `{{ Metric('arppu_d7') }}`.
|
|
18
|
+
|
|
19
|
+
Before deleting a member or family population, inspect metric usage. Dependency guards do not prove every canvas reference is absent.
|
|
@@ -42,7 +42,7 @@ name the exact branch you could not compare. That is a successful outcome, not a
|
|
|
42
42
|
3. **Lift the complete template onto the owner.** The whole statement in `data-graphit-sql`, the data
|
|
43
43
|
source in `data-graphit-ds`. Complete means executable and parameterized:
|
|
44
44
|
- Keep every `:named` placeholder. Do not bake current filter values in.
|
|
45
|
-
- Keep `
|
|
45
|
+
- Keep final `Metric`, qualified `Dimension`, and `Measure` references intact.
|
|
46
46
|
- No ellipsis, no abbreviation, real table names, full WITH clause.
|
|
47
47
|
4. **Preserve the rest exactly.** `params`, `deps`, the `render` callback, any branch that picks
|
|
48
48
|
different SQL, and `sourceEntityId` / `targetEntityIds` attribution all stay as they were.
|
|
@@ -87,7 +87,7 @@ one statement. Everything above still applies - the hard rules, the four signals
|
|
|
87
87
|
correct permanent end state. Stop and say so.
|
|
88
88
|
2. **Lift the stabilized statement onto the entity** as `data-graphit-sql` / `data-graphit-ds`, keeping
|
|
89
89
|
`:name` placeholders for everything the filters supply - never the values one run happened to use -
|
|
90
|
-
and keeping every
|
|
90
|
+
and keeping every final semantic token exactly as written. Those tokens carry
|
|
91
91
|
lineage once the query lives on the entity; lift a raw-expression statement and step 4 strips the
|
|
92
92
|
closure off an entity that references nothing.
|
|
93
93
|
3. **Prove equivalence on the four signals above,** per filter state, exactly as for a legacy migration.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# First Run: From an Empty Workspace to a First Dashboard
|
|
2
2
|
|
|
3
|
-
Load this when the user is signed in but
|
|
3
|
+
Load this when the user is signed in but visible groups/models and `graphit ds list` are empty. Onboarding is the job, not a blocker: walk through it one step at a time and surface each result. Once a source and semantic assets exist, return to the normal loop.
|
|
4
4
|
|
|
5
5
|
## The arc
|
|
6
6
|
|
|
@@ -35,11 +35,12 @@ Before creating anything, ask what business question the user wants to answer. T
|
|
|
35
35
|
|
|
36
36
|
Explain that answering the question fast needs a cached data source over the connection, not repeated live-warehouse queries.
|
|
37
37
|
|
|
38
|
-
**
|
|
38
|
+
**Scope comes first.** A semantic group organizes assets, while data-source `--domain` takes the uppercase policy key returned by `graphit status` or a group's `domain_keys`. A brand-new workspace starts with org commons. Agree the audience and group before creating the source:
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
graphit kb list
|
|
42
|
-
graphit
|
|
41
|
+
graphit kb list group
|
|
42
|
+
graphit status --json
|
|
43
|
+
graphit kb create group --name marketing --description "Acquisition and spend"
|
|
43
44
|
```
|
|
44
45
|
|
|
45
46
|
**Read the table before you write its SQL.** You cannot author a source SELECT without knowing the columns, and guessing them wastes a round trip. Read them straight off the warehouse:
|
|
@@ -77,7 +77,7 @@ deck.slide({
|
|
|
77
77
|
html: `
|
|
78
78
|
<h2>Live Data</h2>
|
|
79
79
|
<div data-graphit-id="spend-chart" data-graphit-label="Ad Spend"
|
|
80
|
-
data-graphit-sql="SELECT {{
|
|
80
|
+
data-graphit-sql="SELECT {{ Dimension('campaign__media_source') }} AS source, {{ Metric('total_ad_spend') }} AS spend FROM MARKETING_UA_DS GROUP BY 1 ORDER BY spend DESC LIMIT 6"
|
|
81
81
|
data-graphit-ds="MARKETING_UA_DS">
|
|
82
82
|
<div id="chart1" class="gh-loading">
|
|
83
83
|
<div class="gh-loading-overlay"><svg class="gh-loading-spin" width="24" height="24" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="10" stroke="#e5e5e5" stroke-width="2.5"/><path d="M12 2a10 10 0 0 1 10 10" stroke="#4DB6AC" stroke-width="2.5" stroke-linecap="round"/></svg></div>
|
|
@@ -30,7 +30,7 @@ const result = await graphit.resolve({
|
|
|
30
30
|
|
|
31
31
|
MUST: every resolve feeding a rendered graph, KPI, or table carries attribution - `target` (the entity wrapper or an element inside it), or `sourceEntityId` plus `targetEntityIds` when one result feeds several graphs. Attribution records the live filtered query behind that entity's details panel; unattributed, the panel shows `data-graphit-sql` as an unrun example, so a user changing a filter sees the SQL never move. Saving a page with filters or params and zero attributed resolves returns an `unattributed_resolves` warning. Queries feeding page chrome need no attribution and no entity, but belong on the primitives - `graphit.cascade` for option lists, `graphit.dataBounds` for a column's min/max, `graphit.rank` for top-N (`filters-advanced.md`) - never hand-written entity-less SQL.
|
|
32
32
|
|
|
33
|
-
CRITICAL: use
|
|
33
|
+
CRITICAL: use `{{ Metric('name') }}`, `{{ Dimension('entity__name') }}`, or Graphit's `{{ Measure('name') }}` extension inside `data-graphit-sql` whenever the semantic asset exists. The server expands it into the governed tier. See `governance.md`.
|
|
34
34
|
|
|
35
35
|
Error handling: `graphit.resolve()` rejects on timeout (120s), bad SQL, or an invalid data source ID. Wrap calls in try/catch and show a user-visible error in the target element on failure. Verify the SQL returns data via the CLI before embedding it.
|
|
36
36
|
|
|
@@ -41,7 +41,7 @@ Every visible element - chart, KPI card, table, text section - must be wrapped s
|
|
|
41
41
|
```html
|
|
42
42
|
<div data-graphit-id="revenue-trend"
|
|
43
43
|
data-graphit-label="Revenue Trend"
|
|
44
|
-
data-graphit-sql="SELECT {{
|
|
44
|
+
data-graphit-sql="SELECT {{ Dimension('order__region') }} AS region, {{ Metric('revenue') }} AS revenue FROM ORDERS_DS GROUP BY region"
|
|
45
45
|
data-graphit-ds="ORDERS_DS">
|
|
46
46
|
<!-- chart, KPI, or table content here -->
|
|
47
47
|
</div>
|
|
@@ -55,7 +55,7 @@ Every visible element - chart, KPI card, table, text section - must be wrapped s
|
|
|
55
55
|
| `data-graphit-ds` | Data source name (same as the FROM table) or id | `"ORDERS_DS"` |
|
|
56
56
|
| `data-graphit-state` | Not an entity attribute - a SIBLING wrapper around a user-changeable control, naming the state key saved views capture (`filters.md`) | `"country"` |
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
Semantic references are derived automatically from the final grammar; the compiler resolves them and shows asset chips in the details panel. Missing any one attribute breaks the entity; missing the wrapper makes the element invisible to the platform.
|
|
59
59
|
|
|
60
60
|
**JavaScript may populate an entity, never create one.** Every entity - including on hidden tabs, collapsed panels, and lazily-shown views - exists as static markup in the HTML you save; JavaScript fills its chart host, wires listeners, and toggles visibility. Nothing server-side runs your JavaScript, so a card built at render time (`host.innerHTML = charts.map(...)`) does not exist for governance, lineage, the KB graph, `list-entities`, or a later `get-entity`. A save whose `data-graphit-id`s are reachable only by running your JavaScript is REFUSED and the error names them. A dynamic **query** is supported - the attribute carries the canonical template; a dynamic **entity** is not.
|
|
61
61
|
|
|
@@ -74,14 +74,14 @@ A filtered entity uses `graphit.bind(el, { params, deps, render })` (`filters.md
|
|
|
74
74
|
|
|
75
75
|
```html
|
|
76
76
|
<div data-graphit-id="explorer" data-graphit-label="Metric Explorer" data-graphit-ds="UA_DS"
|
|
77
|
-
data-graphit-sql="SELECT day, {{
|
|
78
|
-
data-graphit-vocab="metric:
|
|
77
|
+
data-graphit-sql="SELECT day, {{ Metric('roas') }} AS roas FROM UA_DS GROUP BY 1"
|
|
78
|
+
data-graphit-vocab="metric:revenue,metric:roas,dimension:campaign__country"></div>
|
|
79
79
|
```
|
|
80
80
|
```js
|
|
81
81
|
graphit.resolve({ sql: buildSql(picked), dataSourceId: "UA_DS", runtimeComposed: true, sourceEntityId: "explorer" });
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
Use comma-separated lowercase declarations such as `metric:revenue`, `dimension:order__region`, and `measure:order_total`. Every declared name must exist; unknown names and wildcards refuse. Undeclared inline SQL is invisible to lineage and governance. Existing runtime-composed queries are first-class, not debt; migrate query ownership only when asked (`migration.md`).
|
|
85
85
|
|
|
86
86
|
**Label equals the visible title.** `data-graphit-label` MUST match the card's visible heading exactly - users find their chart by that label in @ mention dropdowns and entity panels, and a mismatch means they cannot find it.
|
|
87
87
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Semantic Authoring
|
|
2
|
+
|
|
3
|
+
Load when creating or changing semantic models or metrics.
|
|
4
|
+
|
|
5
|
+
## Syntax boundary
|
|
6
|
+
|
|
7
|
+
Graphit accepts the MetricFlow 0.211 execution shape: semantic models contain
|
|
8
|
+
entities, dimensions, and measures; metrics are top-level objects with `type`
|
|
9
|
+
and `type_params`. Do not emit newer measureless/Fusion authoring syntax, dbt
|
|
10
|
+
project YAML, Jinja, `ref()` expressions, or source declarations on this
|
|
11
|
+
surface. Those belong to dbt project import/export, not Graphit authoring.
|
|
12
|
+
|
|
13
|
+
Public dbt/MetricFlow concepts:
|
|
14
|
+
|
|
15
|
+
- semantic model, entity, dimension, measure, metric, and group
|
|
16
|
+
- simple, ratio, and derived metric composition
|
|
17
|
+
- entity-qualified dimension paths
|
|
18
|
+
|
|
19
|
+
Graphit extensions and assets:
|
|
20
|
+
|
|
21
|
+
- `meta.graphit.family` and `axes` group concrete metric variants
|
|
22
|
+
- topics are curated Graphit metadata, deliberately not dbt `tags`
|
|
23
|
+
- rules are separate Graphit objects targeting semantic identities by name;
|
|
24
|
+
never embed them in dbt metadata
|
|
25
|
+
- verification attribution, provenance, and data-source bindings are
|
|
26
|
+
server-owned; use dedicated actions instead of hand-authoring them
|
|
27
|
+
|
|
28
|
+
Do not expose or depend on storage collection names, revision fields, feature
|
|
29
|
+
flags, cache keys, or compiler implementation details.
|
|
30
|
+
|
|
31
|
+
## Semantic model
|
|
32
|
+
|
|
33
|
+
A semantic model owns:
|
|
34
|
+
|
|
35
|
+
- lowercase name and physical model/binding
|
|
36
|
+
- group placement
|
|
37
|
+
- primary grain/entity
|
|
38
|
+
- entities for joins
|
|
39
|
+
- dimensions for grouping/filtering
|
|
40
|
+
- measures for aggregation input
|
|
41
|
+
- defaults such as aggregation time dimension
|
|
42
|
+
|
|
43
|
+
Measure-bearing models need a valid aggregation time dimension. Primary/unique entity claims require grain evidence; never guess uniqueness. Time-aware shapes require the platform time-spine prerequisite.
|
|
44
|
+
|
|
45
|
+
Nested lists replace whole lists on update. Read the model and preserve every sibling.
|
|
46
|
+
|
|
47
|
+
## Metrics
|
|
48
|
+
|
|
49
|
+
Only these shapes are served by the shipping fragment path:
|
|
50
|
+
|
|
51
|
+
- **simple:** one measure object
|
|
52
|
+
- **ratio:** numerator and denominator metric objects
|
|
53
|
+
- **derived:** expression over declared metric inputs or aliases
|
|
54
|
+
|
|
55
|
+
Metric-level and per-input filters must resolve through declared semantic identities. Bare strings where an input object is required are refused.
|
|
56
|
+
|
|
57
|
+
Cumulative, conversion, shifted inputs, time-spine joins, and null-fill are not yet supported. Use a supported decomposition or clearly labeled free SQL; do not create an unusable governed definition.
|
|
58
|
+
|
|
59
|
+
## Aggregation safety
|
|
60
|
+
|
|
61
|
+
| Class | Across dimensions | Across time | Examples |
|
|
62
|
+
|---|---|---|---|
|
|
63
|
+
| Fully additive | Sum | Sum | revenue, clicks, units |
|
|
64
|
+
| Semi-additive | Sum | Last/period-end snapshot | cash, MRR, headcount |
|
|
65
|
+
| Non-additive | Recompute | Recompute | rates, ratios, distinct counts |
|
|
66
|
+
|
|
67
|
+
Never sum or average a rate/ratio - recompute from additive components at the requested grain, guard zero denominators, and examine mix shift before interpreting rollups.
|
|
68
|
+
|
|
69
|
+
Measure `agg` accepts: sum, count, count_distinct, average, min, max, median, percentile, sum_boolean. A simple metric references a declared measure, never a raw column; a ratio references numerator/denominator metrics, never measures directly; every identifier in a derived expression must match an input name or alias exactly.
|
|
70
|
+
|
|
71
|
+
## Plan ordering
|
|
72
|
+
|
|
73
|
+
When authoring several definitions, sequence prerequisites first: group, then data sources, then semantic models with nested components, then simple metrics, then ratio/derived metrics that reference them, then rules after their targets exist. Execute one item at a time; do not start the next before the current receipt is terminal.
|
|
74
|
+
|
|
75
|
+
## Verification
|
|
76
|
+
|
|
77
|
+
Create defaults to verified on human-driven CLI paths; `--unverified` creates a draft. Promote or demote with dedicated verify/unverify actions. Never replace `meta` only to toggle verification.
|
|
78
|
+
|
|
79
|
+
## Final check
|
|
80
|
+
|
|
81
|
+
Re-read the root, verify nested completeness, inspect group/binding, and use the returned receipt. A degraded freshness result may mean the write landed; do not retry blindly.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# SQL Reference
|
|
2
2
|
|
|
3
|
-
Consult when writing queries. Data
|
|
3
|
+
Consult when writing queries. Data-source queries run DuckDB. Warehouse queries run the connected Snowflake or BigQuery dialect. Governance parses the same dialect. Read physical columns through metadata discovery or semantic-model physical detail; never use DDL through the SELECT-only query surface.
|
|
4
4
|
|
|
5
5
|
## DuckDB vs Snowflake Translation
|
|
6
6
|
|
|
@@ -49,6 +49,15 @@ NEVER use `->>` in Snowflake or `:field::STRING` in DuckDB.
|
|
|
49
49
|
- Snowflake does NOT support `FILTER (WHERE)` - use `CASE WHEN` instead
|
|
50
50
|
- `COUNT_IF` MUST receive a boolean expression, not a raw INT column. Use `COUNT_IF(is_active = 1)`, not `COUNT_IF(is_active)`
|
|
51
51
|
- String matching: prefer `ILIKE` (case-insensitive) over `LIKE`
|
|
52
|
+
- No `DISTINCT ON` (use `ROW_NUMBER()`); no negative array indices
|
|
53
|
+
- Geospatial: `ST_MAKEPOINT(lon, lat)` - lon FIRST; `ST_DISTANCE` returns meters
|
|
54
|
+
|
|
55
|
+
## Null / Join Safety
|
|
56
|
+
|
|
57
|
+
- `COUNT(column)` drops nulls; `COUNT(*)` does not
|
|
58
|
+
- Never `NOT IN` on nullable values (one null matches nothing); use `NOT EXISTS`
|
|
59
|
+
- One-to-many joins multiply measures unless the many side is pre-aggregated
|
|
60
|
+
- DuckDB JSON array check: `json_array_length(...)`, never `->0 IS NOT NULL`
|
|
52
61
|
|
|
53
62
|
## BigQuery Standard SQL Notes
|
|
54
63
|
|
|
@@ -138,22 +147,20 @@ The canvas `percent` format only appends `%` (it does not multiply by 100), so m
|
|
|
138
147
|
|
|
139
148
|
## Presenting Query Results
|
|
140
149
|
|
|
141
|
-
After every
|
|
142
|
-
|
|
143
|
-
**When using KB reference syntax** (`{{metric:X}}`, `{{dim:X}}`), show all five sections:
|
|
150
|
+
After every query, show all five sections below. Use `--verbose` to obtain the resolved SQL. Executable SQL uses semantic references; narration uses backticked exact asset names so Graphit can render clickable KB pills.
|
|
144
151
|
|
|
145
152
|
~~~
|
|
146
|
-
**KB Assets:** dimension
|
|
153
|
+
**KB Assets:** dimension `campaign__category`, metrics `total_installs` and `cpi`, semantic model `marketing_ua`
|
|
147
154
|
|
|
148
155
|
**Query:**
|
|
149
156
|
```sql
|
|
150
157
|
SELECT
|
|
151
|
-
{{
|
|
152
|
-
{{
|
|
153
|
-
{{
|
|
158
|
+
{{ Dimension('campaign__category') }} AS category,
|
|
159
|
+
{{ Metric('total_installs') }} AS installs,
|
|
160
|
+
{{ Metric('cpi') }} AS cpi
|
|
154
161
|
FROM MARKETING_UA_DS
|
|
155
162
|
WHERE ACTIVITY_TIME >= '2026-01-01'
|
|
156
|
-
GROUP BY {{
|
|
163
|
+
GROUP BY {{ Dimension('campaign__category') }}
|
|
157
164
|
ORDER BY installs DESC
|
|
158
165
|
```
|
|
159
166
|
|
|
@@ -179,17 +186,9 @@ ORDER BY installs DESC
|
|
|
179
186
|
| Retargeting | 12,300 | $1.24 |
|
|
180
187
|
| Connected TV | 3,100 | $2.80 |
|
|
181
188
|
|
|
182
|
-
**Governance:** governed - 3
|
|
183
|
-
~~~
|
|
184
|
-
|
|
185
|
-
**When using inline SQL** (no `{{metric:X}}`), present it the same way (query + results + governance), but the footer states the ad-hoc tier and offers the governed rewrite:
|
|
186
|
-
|
|
189
|
+
**Governance:** governed - 3 semantic refs; 2 rules enforced (`exclude_organic`, `min_spend`). Max rows: 1,000.
|
|
187
190
|
~~~
|
|
188
|
-
**Governance:** ad-hoc - 0 KB refs. Consider using `{{metric:TOTAL_SPEND}}` for governed tier.
|
|
189
|
-
~~~
|
|
190
|
-
|
|
191
|
-
Any ad-hoc query on the CLI is withheld until you justify it (full rules in governance.md): prefer a `{{metric:X}}` / `{{dim:X}}` rewrite, creating the metric or dimension first if it is missing, and pass `--adhoc-reason` only when nothing governed fits.
|
|
192
191
|
|
|
193
|
-
|
|
192
|
+
For inline SQL, use the same sections but state the ad-hoc tier and offer a governed rewrite, for example: **Governance:** ad-hoc - 0 semantic refs. Consider `{{ Metric('total_spend') }}`. Author a supported root definition first when needed; provide an ad-hoc reason only when nothing governed fits.
|
|
194
193
|
|
|
195
|
-
|
|
194
|
+
For zero rows, explain the checks performed and likely cause, such as date range, filter, or empty source.
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure parsers for `graphit kb create/update rule` --constraint / --apply-on flags.
|
|
3
|
-
*
|
|
4
|
-
* Extracted from kb.ts so they are unit-testable without Commander or the API
|
|
5
|
-
* client (mirrors query-timeout.ts). Issue #548: the required_where quote
|
|
6
|
-
* stripper used to remove ANY leading/trailing quote, so a predicate ending in
|
|
7
|
-
* a SQL string literal lost its closing quote
|
|
8
|
-
* (`MEDIA_SOURCE != 'organic'` -> `MEDIA_SOURCE != 'organic`), producing
|
|
9
|
-
* unparseable SQL that the backend rejected - the constraint never persisted.
|
|
10
|
-
*/
|
|
11
|
-
/** Strip wrapping quotes only when the value is wrapped in a matched pair. */
|
|
12
|
-
export declare function unwrapQuotes(value: string): string;
|
|
13
|
-
export declare function parseConstraintFlags(specs: string[]): Record<string, unknown>[];
|
|
14
|
-
export declare function parseApplyOnFlags(specs: string[]): Record<string, string>[];
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure parsers for `graphit kb create/update rule` --constraint / --apply-on flags.
|
|
3
|
-
*
|
|
4
|
-
* Extracted from kb.ts so they are unit-testable without Commander or the API
|
|
5
|
-
* client (mirrors query-timeout.ts). Issue #548: the required_where quote
|
|
6
|
-
* stripper used to remove ANY leading/trailing quote, so a predicate ending in
|
|
7
|
-
* a SQL string literal lost its closing quote
|
|
8
|
-
* (`MEDIA_SOURCE != 'organic'` -> `MEDIA_SOURCE != 'organic`), producing
|
|
9
|
-
* unparseable SQL that the backend rejected - the constraint never persisted.
|
|
10
|
-
*/
|
|
11
|
-
/** Strip wrapping quotes only when the value is wrapped in a matched pair. */
|
|
12
|
-
export function unwrapQuotes(value) {
|
|
13
|
-
const first = value[0];
|
|
14
|
-
if (value.length >= 2 &&
|
|
15
|
-
(first === '"' || first === "'") &&
|
|
16
|
-
value[value.length - 1] === first) {
|
|
17
|
-
return value.slice(1, -1);
|
|
18
|
-
}
|
|
19
|
-
return value;
|
|
20
|
-
}
|
|
21
|
-
export function parseConstraintFlags(specs) {
|
|
22
|
-
return specs.map((spec) => {
|
|
23
|
-
const colonIdx = spec.indexOf(":");
|
|
24
|
-
if (colonIdx < 0)
|
|
25
|
-
throw new Error(`Invalid constraint: "${spec}". Use type:value (e.g., forbidden_column:email)`);
|
|
26
|
-
const type = spec.slice(0, colonIdx);
|
|
27
|
-
const value = spec.slice(colonIdx + 1);
|
|
28
|
-
switch (type) {
|
|
29
|
-
case "required_where":
|
|
30
|
-
return { type, predicate: unwrapQuotes(value) };
|
|
31
|
-
case "forbidden_column":
|
|
32
|
-
case "required_filter":
|
|
33
|
-
case "required_aggregation":
|
|
34
|
-
return { type, column: value };
|
|
35
|
-
case "value_restriction": {
|
|
36
|
-
const [col, op, ...valueParts] = value.split(":");
|
|
37
|
-
const operator = op === "not_in" ? "not_in" : "in";
|
|
38
|
-
return { type, column: col, values: valueParts.join(":").split(","), operator };
|
|
39
|
-
}
|
|
40
|
-
default:
|
|
41
|
-
throw new Error(`Unknown constraint type: "${type}"`);
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
export function parseApplyOnFlags(specs) {
|
|
46
|
-
return specs.map((spec) => {
|
|
47
|
-
const colonIdx = spec.indexOf(":");
|
|
48
|
-
if (colonIdx < 0)
|
|
49
|
-
throw new Error(`Invalid apply-on: "${spec}". Use type:NAME (e.g., table:USERS)`);
|
|
50
|
-
return { type: spec.slice(0, colonIdx), name: spec.slice(colonIdx + 1) };
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
//# sourceMappingURL=kb-constraints.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"kb-constraints.js","sourceRoot":"","sources":["../../src/commands/kb-constraints.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,8EAA8E;AAC9E,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACvB,IACE,KAAK,CAAC,MAAM,IAAI,CAAC;QACjB,CAAC,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,GAAG,CAAC;QAChC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,KAAK,EACjC,CAAC;QACD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,KAAe;IAClD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACxB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,QAAQ,GAAG,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,IAAI,kDAAkD,CAAC,CAAC;QAClH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;QACrC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC;QACvC,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,gBAAgB;gBACnB,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;YAClD,KAAK,kBAAkB,CAAC;YACxB,KAAK,iBAAiB,CAAC;YACvB,KAAK,sBAAsB;gBACzB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;YACjC,KAAK,mBAAmB,CAAC,CAAC,CAAC;gBACzB,MAAM,CAAC,GAAG,EAAE,EAAE,EAAE,GAAG,UAAU,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;gBAClD,MAAM,QAAQ,GAAG,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;gBACnD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,CAAC;YAClF,CAAC;YACD;gBACE,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,GAAG,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAe;IAC/C,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACxB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,QAAQ,GAAG,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,IAAI,sCAAsC,CAAC,CAAC;QACpG,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,EAAE,CAAC;IAC3E,CAAC,CAAC,CAAC;AACL,CAAC"}
|