@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.
Files changed (63) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/bin/graphit +1 -1
  5. package/bin/graphit.ps1 +1 -1
  6. package/dist/api/client.js +15 -0
  7. package/dist/api/client.js.map +1 -1
  8. package/dist/commands/ds-config.js +2 -2
  9. package/dist/commands/ds-config.js.map +1 -1
  10. package/dist/commands/ds.js +2 -2
  11. package/dist/commands/ds.js.map +1 -1
  12. package/dist/commands/kb.js +347 -15
  13. package/dist/commands/kb.js.map +1 -1
  14. package/dist/commands/query.js +7 -7
  15. package/dist/commands/query.js.map +1 -1
  16. package/dist/index.js +0 -8
  17. package/dist/index.js.map +1 -1
  18. package/dist/skill-guard.js +2 -1
  19. package/dist/skill-guard.js.map +1 -1
  20. package/package.json +1 -1
  21. package/scripts/verb-policy-source.json +31 -103
  22. package/skills/graphit/SKILL.md +32 -39
  23. package/skills/graphit/VERSION.json +1 -1
  24. package/skills/graphit/references/attached-docs.md +74 -0
  25. package/skills/graphit/references/data-source-refresh.md +38 -0
  26. package/skills/graphit/references/data-sources.md +39 -111
  27. package/skills/graphit/references/filters-advanced.md +2 -2
  28. package/skills/graphit/references/governance-explained.md +18 -29
  29. package/skills/graphit/references/governance.md +20 -83
  30. package/skills/graphit/references/kb-actions.md +28 -81
  31. package/skills/graphit/references/kb-discovery.md +35 -64
  32. package/skills/graphit/references/kb-scope.md +17 -15
  33. package/skills/graphit/references/kb-structure.md +28 -54
  34. package/skills/graphit/references/kb-traversal.md +24 -96
  35. package/skills/graphit/references/metric-families.md +19 -0
  36. package/skills/graphit/references/migration.md +2 -2
  37. package/skills/graphit/references/onboarding.md +5 -4
  38. package/skills/graphit/references/presentations.md +1 -1
  39. package/skills/graphit/references/runtime.md +6 -6
  40. package/skills/graphit/references/semantic-authoring.md +81 -0
  41. package/skills/graphit/references/sql-reference.md +19 -20
  42. package/dist/commands/kb-constraints.d.ts +0 -14
  43. package/dist/commands/kb-constraints.js +0 -53
  44. package/dist/commands/kb-constraints.js.map +0 -1
  45. package/dist/commands/kb-create.d.ts +0 -2
  46. package/dist/commands/kb-create.js +0 -296
  47. package/dist/commands/kb-create.js.map +0 -1
  48. package/dist/commands/kb-delete.d.ts +0 -2
  49. package/dist/commands/kb-delete.js +0 -37
  50. package/dist/commands/kb-delete.js.map +0 -1
  51. package/dist/commands/kb-read.d.ts +0 -2
  52. package/dist/commands/kb-read.js +0 -223
  53. package/dist/commands/kb-read.js.map +0 -1
  54. package/dist/commands/kb-shared.d.ts +0 -43
  55. package/dist/commands/kb-shared.js +0 -81
  56. package/dist/commands/kb-shared.js.map +0 -1
  57. package/dist/commands/kb-update.d.ts +0 -2
  58. package/dist/commands/kb-update.js +0 -240
  59. package/dist/commands/kb-update.js.map +0 -1
  60. package/dist/commands/sl/index.d.ts +0 -2
  61. package/dist/commands/sl/index.js +0 -263
  62. package/dist/commands/sl/index.js.map +0 -1
  63. package/skills/graphit/references/parameterized-metrics.md +0 -77
@@ -1,107 +1,35 @@
1
- # KB Traversal - Worked Examples
1
+ # KB Traversal
2
2
 
3
- Contents: Which Command (the read-command picker) - Common Queries (worked examples) - Reading the Results - Presenting KB Results (the per-command output templates).
3
+ Load when investigating semantic reach or presenting KB results.
4
4
 
5
- Which `graphit kb` read command answers each question, and how to present the result. Each verb owns one role: `graphit kb list <type>` is the **inventory** (how many, and which, assets of a type exist), `graphit kb get <type> NAME` is one asset's **full definition**, and `graphit kb explore <type> NAME` is the **relationships** call - it walks the graph around one entity and returns its whole neighborhood (and, for a template, its concrete variants) in one call. `graphit kb search "<query>"` is the fallback for semantic discovery when you cannot name the entity. Explore is the primary discovery call.
5
+ ## Read roles
6
6
 
7
- ## Which Command
8
-
9
- | Question | Command |
7
+ | Need | Read |
10
8
  |---|---|
11
- | How many / which assets of a type exist (inventory) | `graphit kb list <type>` (metrics collapse to templates; `--include-variants` for the flat set) |
12
- | What is inside a domain or topic (the build entry point) | `graphit kb explore domain NAME` / `graphit kb explore topic NAME` |
13
- | What connects to X (dependencies, joins, topics, domain) | `graphit kb explore <type> NAME` |
14
- | A template's concrete variants | `graphit kb explore metric NAME` |
15
- | Get one asset's full details | `graphit kb get metric NAME` |
16
- | Find things like X when you cannot name it (semantic) | `graphit kb search "X"` (add `--type metric` to narrow) |
17
-
18
- `graphit kb explore` returns the whole neighborhood in one call and has no edge-type or depth flags - the response already carries dependents, joins, topics, and domain together, so read the part you need instead of chaining calls.
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
- Adapt fields per type. Rules: content, constraints, apply-on, plus `enforced` (Enforced = has constraints / applied to every query; Suggested = body-only / guides the AI) and `governs` (the tables it covers whole + any narrowed metric/dimension targets). Metrics and dimensions carry `governed_by` - the rules governing that asset, each tagged `enforced` and a `scope_tag` (`whole table` vs `this metric`/`this dimension`) - so reading a metric also shows what rules apply to it. Dimensions also: expression, semantic type, output type.
18
+ `list metric` is flat. Families collapse in tree/search/family views.
80
19
 
81
- **After `graphit kb search`** - result count + table with type column:
20
+ ## Investigations
82
21
 
83
- ~~~
84
- **5 results** for "revenue":
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
- | Type | Name | Description |
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
- **After `graphit kb explore`** - tree with bold names, indented by level:
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 domain exploration, show Domain > Table > Asset hierarchy as a tree, then the `presented_on` dashboards. For table exploration, list the table's columns first (`NAME - type - description`), then the metrics, dimensions, and rules defined on it.
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 `{{metric:NAME}}` / `{{dim:NAME}}` references as they are.
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 `{{metric:}}` / `{{dim:}}` token exactly as written. Those tokens are what carry
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 the workspace is empty - `graphit kb list domains` (and `graphit ds list`) come back with nothing. That means no data is connected yet. Onboarding IS the job here, not a blocker: walk the user through it one step at a time, surfacing each result. This flow self-limits - once a data source and KB assets exist, `kb list domains` is no longer empty and you land in the normal loop instead.
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
- **A domain comes first.** Every data source is created inside a KB domain - it is what makes the source findable and grantable, and there is no uncategorized fallback. A brand-new workspace has only the org-wide commons, so check with `graphit kb list domains` and, if the user's work deserves its own area, agree a name and create it before the source:
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 domains
42
- graphit kb create domain --name MARKETING --description "Acquisition and spend"
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 {{dim:MEDIA_SOURCE_DIMENSION}} AS source, {{metric:TOTAL_AD_SPEND}} AS spend FROM MARKETING_UA_DS GROUP BY 1 ORDER BY spend DESC LIMIT 6"
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 KB reference syntax (`{{metric:NAME}}`, `{{dim:NAME}}`) inside the entity's `data-graphit-sql` whenever a KB asset exists - the server expands it at query time, producing the governed trust tier. Syntax and trust tiers: `governance.md`.
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 {{dim:REGION}} AS region, {{metric:REVENUE}} AS revenue FROM ORDERS_DS GROUP BY region"
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
- KB asset references are derived automatically from `{{metric:X}}` / `{{dim:X}}` in the SQL; the governance compiler resolves these and shows KB asset chips in the details panel. Missing any one attribute breaks the entity; missing the wrapper makes the element invisible to the platform.
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, {{metric:ROAS}} AS roas FROM UA_DS GROUP BY 1"
78
- data-graphit-vocab="metric:REV*,metric:ROAS,dim:COUNTRY"></div>
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
- `metric:NAME` / `dim:NAME`, UPPER_SNAKE_CASE, one optional trailing `*` per family; a space instead of a comma is one malformed entry. Declare the family, not today's SQL - but every name must exist (an unknown name, a wildcard matching nothing, or a bare `metric:*` refuses), and wildcards alone add no lineage. Undeclared inline SQL is invisible to lineage and governance; a save that ADDS one is refused, and existing ones are not a defect to fix - move a query onto its entity only when asked (`migration.md`).
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 source queries (`graphit query --ds`) always run DuckDB. Warehouse queries (`graphit query --warehouse`) run the connected warehouse - Snowflake or BigQuery - and the dialect follows the connection type; governance parses your SQL in that dialect. You MUST use the correct dialect. To read a table's columns, use `graphit kb explore table <NAME>` - never `DESCRIBE`/DDL (`graphit query` runs SELECT only).
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 `graphit query`, present results grounded in the KB. Always show which KB assets were used - this is what makes governed queries valuable.
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 **CAMPAIGN_CATEGORY**, metric **TOTAL_INSTALLS**, metric **CPI**, table **MARKETING_UA_DS**
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
- {{dim:CAMPAIGN_CATEGORY}} AS category,
152
- {{metric:TOTAL_INSTALLS}} AS installs,
153
- {{metric:CPI}} AS cpi
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 {{dim:CAMPAIGN_CATEGORY}}
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 KB refs, 2 rules enforced (**EXCLUDE_ORGANIC**, **MIN_SPEND**). Max rows: 1,000.
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
- **Always use `--verbose`** to get the resolved SQL; if the user didn't pass it, re-run with it.
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
- Zero rows: explain what you checked and hypothesize why (wrong date range, filter too strict, table empty).
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"}
@@ -1,2 +0,0 @@
1
- import { Command } from "commander";
2
- export declare function addKBCreateCommands(kb: Command): void;