cassis-cli 3.1.1__tar.gz → 3.3.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cassis-cli
3
- Version: 3.1.1
3
+ Version: 3.3.0
4
4
  Summary: Validate, test and evaluate your Cassis context from your terminal, then publish it
5
5
  License: Apache-2.0
6
6
  License-File: LICENSE
@@ -24,7 +24,7 @@ Description-Content-Type: text/markdown
24
24
 
25
25
  Validate, test and evaluate your context from your terminal, then publish it. The same commands gate your pull requests in CI:
26
26
 
27
- - `cassis context check` validates the context files in your repository with the exact same checks as the Cassis GitHub PR check (YAML parsing, round-trip, import validation), so you can gate merges in any CI system, not just GitHub. It then prints advisory **context quality warnings** for a tree that parsed: tables not assigned to any domain, joins/metrics pointing at unknown tables or columns, missing table/column descriptions (the same findings `context test` reports, without the agent run). In a checkout bound to a project (`project.yml`, `--project`, or `CASSIS_PROJECT_ID`), it also cross-checks the tree against the project's source schema: references to tables or columns the warehouse doesn't have print as **warnings** too, advisory only (the object may simply not be built or synced yet). Warnings never fail the check.
27
+ - `cassis context check` validates the context files in your repository with the exact same checks as the Cassis GitHub PR check (YAML parsing, round-trip, import validation), so you can gate merges in any CI system, not just GitHub. It then prints advisory **context quality warnings** for a tree that parsed: tables not assigned to any domain, joins/metrics pointing at unknown tables or columns, missing table/column descriptions, `enum_values` the column's type can't store (the same findings `context test` reports, without the agent run). In a checkout bound to a project (`project.yml`, `--project`, or `CASSIS_PROJECT_ID`), it also cross-checks the tree against the project's source schema: references to tables or columns the warehouse doesn't have print as **warnings** too, advisory only (the object may simply not be built or synced yet). Warnings never fail the check.
28
28
  - `cassis schema pull` downloads the data source's full source schema (as Cassis last introspected it) into `<base-path>/.schema.json`, a **gitignored** local snapshot (the command maintains the ignore entry) with a `pulled_at` stamp. The warehouse stays authoritative; the snapshot is a cache for offline/bulk work, e.g. a coding agent grepping table and column names during a modeling pass instead of paging through the MCP `get_source_schema` tool. Re-run to refresh.
29
29
  - `cassis context fmt` rewrites the context files in canonical form (think `black`/`gofmt` for your context), so hand or agent edits pass the round-trip check.
30
30
  - `cassis context upload` uploads the context files to a Cassis project (full replace) and, by default, publishes them immediately as a new version, so a merge to your main branch can go live in one CI step. It runs from a git checkout whose context files are committed, and the published version records that commit, even when the upload changes nothing, so `cassis status` can tell whether a checkout matches what is live.
@@ -34,13 +34,15 @@ Validate, test and evaluate your context from your terminal, then publish it. Th
34
34
  - `cassis eval run` runs the project's eval suite against your local context files (scored in-memory, nothing is pushed to Cassis) and prints per-question results, so you can test the changes on your git branch before merging.
35
35
  - `cassis context test` runs individual questions through the text-to-SQL agent using your local context files, so you can check that a change actually works (e.g. a new column gets picked), where `eval run` only checks for regressions on existing eval cases.
36
36
  - `cassis eval add-case` adds a gold question/SQL case to the project's eval suite: after fixing a context issue, add the question users were failing on so `eval run` guards it from regressing.
37
- - `cassis eval list-cases` and `cassis eval delete-case` maintain the suite: list the current cases with their ids, and prune one that is stale or wrong (e.g. its gold SQL encodes a definition the context has since changed).
37
+ - `cassis eval list-cases`, `cassis eval history` and `cassis eval delete-case` maintain the suite: list the current cases with their ids (flagging those whose gold SQL reads tables the context lacks), see how one case has behaved across runs (pass rate, whether repeated runs under one configuration disagree, divergences and timings), and prune one that is stale or wrong (e.g. its gold SQL encodes a definition the context has since changed).
38
38
  - `cassis schema plan <ddl>` (or `--warehouse` on a project connected to a warehouse) previews what a schema update would change before anything is applied: the source schema diff, the context changes Cassis will make (every change on a table placed in the context, with everything a drop takes with it) and warnings, terraform-style. `cassis schema apply <ddl>` (or `--plan <id>`) writes the resulting context files into the local checkout, app untouched, for review with `git diff`. `cassis schema push <ddl> [--publish]` pushes the new schema and the local context to the app (`--yes` in CI). The file speaks only for the schemas it contains: pass `--complete` when it is the project's complete source schema so schemas absent from it are treated as dropped. With `--warehouse` the server introspects the connected warehouse instead of parsing a file; the plan is always whole-source. `cassis schema plan <ddl> --dry-run` is the prepare-ahead variant: the plan is computed synchronously and nothing is kept in Cassis (no plan to apply or resume, the current plan untouched), so the schema snapshot for a change still in a PR can be planned against safely; `--write-checkout` writes the context files it would produce into the checkout, to commit alongside the schema change.
39
39
  - `cassis projects list` lists the projects your API key can reach: id (what `--project` and `CASSIS_PROJECT_ID` take), name, published context version, and data-source dialect. A pipeline or agent can discover the project id from the terminal instead of fishing it out of a webapp URL.
40
40
  - DDL imports describe one schema snapshot/export file, including ordinary and materialized views; they do not replay incremental migrations. Extraction diagnostics include object names and statement locations. If extraction is incomplete, `schema plan` and `--dry-run` show the extracted inventory and exit 1; `apply`, `push`, and `--write-checkout` cannot save that result. Unknown column types are warnings when all output names are known. Plans also show object-kind, view-definition, comment, and constraint changes. `--json` preserves structured diagnostics and the server-capped inventory.
41
41
 
42
42
  - `cassis status` shows the project's published version (number, label, git commit), whether unpublished changes await publication, the git-sync binding, a schema plan waiting to be applied, and how your local git HEAD relates to the published commit (in sync / N commits ahead / diverged). `cassis status --watch` polls until the published commit matches your local HEAD (e.g. right after merging a PR whose CI publishes the context) instead of watching the GitHub Actions tab.
43
- - `cassis issues` triages the issues Cassis raised on the project (what it found wrong while answering questions: a context gap, missing data) without leaving the checkout: `issues list` (filterable by status, impact, cause and context domain, and showing each issue's domain so you can work through one domain at a time), `issues show <id>` for the diagnosis, suggested action and the occurrences behind it, `issues evidence <id> <occurrence-id>` for what the agent actually saw, and `issues resolve` / `dismiss` / `reopen` once you've acted on it. When the fix ships through a pull request, write the `PR mention:` line `issues show` prints (`Resolves <id>`) in the PR description instead: Cassis resolves the issue when the PR merges, and `issues show` then reports how it was closed and through which PR.
43
+ - `cassis issues` triages the issues Cassis raised on the project (what it found wrong while answering questions: a context gap, missing data) without leaving the checkout: `issues list` (filterable by status, impact, cause and context domain, and showing each issue's domain so you can work through one domain at a time; and each issue's reach, the chat answers that used its context tables in the last 30 days), `issues show <id>` for the diagnosis, suggested action, the occurrences behind it, its reach and evidence (removed from dbt, an AI review suggestion, user feedback, a failed eval, or the chat analysis), `issues evidence <id> <occurrence-id>` for what the agent actually saw, and `issues resolve` / `dismiss` / `reopen` once you've acted on it. When the fix ships through a pull request, write the `PR mention:` line `issues show` prints (`Resolves <id>`) in the PR description instead: Cassis resolves the issue when the PR merges, and `issues show` then reports how it was closed and through which PR.
44
+ - `cassis dbt push` sends your dbt project's artifacts (`target/manifest.json`, and `catalog.json` when present) to Cassis from the dbt project's CI, with the commit they were built from. Cassis checks the files, matches each dbt model, seed, snapshot and source to a table of the project's data source, and keeps the upload as the project's dbt baseline unless a newer one is already there. The command prints how many relations matched and how many tests they declare. Cassis compares the upload with the previous baseline and files each part of the context the merged change puts in doubt as an issue (impact "at risk"); the command lists them with links. Nothing the agent answers with changes.
45
+ - `cassis dbt check` runs the same comparison on a pull request, before the change merges: it uploads the PR's artifacts (after `dbt compile` on the production target), compares them with the baseline, and prints the context tables, columns, joins and metrics the change puts in doubt, with why it matters and the context change to make. Each finding also says its **reach**, how many chat answers used its context tables in the last 30 days, and within each section the most-reached objects print first, the order the Review page will show them in. Findings are advisory: nothing is filed, the baseline doesn't move, and the check exits 0 with or without findings.
44
46
  - `cassis verify` runs the full local gate in one verb (`context fmt --check`, `context check`, `eval run`), stopping at the first failure. One command in a checkout ("is this change safe to merge?"), one job in CI. `--no-eval` skips the eval suite.
45
47
 
46
48
  ## Install
@@ -55,7 +57,7 @@ The context tree under `<base-path>` (default `cassis/`) is:
55
57
 
56
58
  - **Project identity**: `project.yml`, the Cassis project id and format version. Written by `pull` and by server-side publish (the places that know the id); a local `fmt` won't create it.
57
59
  - **Domains**: Markdown files. Every domain is the `README.md` of its folder: `domains/README.md` for the root, `domains/<path>/README.md` for each sub-domain. Each has a small YAML frontmatter block (`type`, `title`, `description`) and a Markdown body carrying the domain's `context_md`; a generated section at the bottom links the domain's tables and metrics (kept current by `fmt`/`pull`: edit your prose above it, and the PR check fails if the links are stale, so re-run `fmt`). The layout is a Cassis profile inspired by [OKF](https://github.com/GoogleCloudPlatform/knowledge-catalog/tree/main/okf): the files render on GitHub and read in any Markdown editor, but Cassis validates them strictly (unknown keys are flagged, not preserved).
58
- - **Tables, joins, metrics**: YAML, unchanged: `tables/<schema>/<table>.yml`, `joins.yml`, `metrics/<name>.yml`.
60
+ - **Tables, joins, metrics**: YAML, unchanged: `tables/<schema>/<table>.yml`, `joins.yml`, `metrics/<name>.yml`. A column can list the values it stores as `enum_values` (items `{value, description?}`, values quoted exactly as stored); see `cassis/AGENTS.md`.
59
61
 
60
62
  **Migrating an existing repo** (domains were YAML `_project.yml` / `_domain.yml` before cassis-cli 1.1.0): upgrade and run `cassis context fmt` (or `cassis context pull` if you have no local edits). It rewrites the domain files to Markdown and removes the old ones. Review the diff and commit. Cassis reads the old YAML domain files too, so an un-migrated repo keeps working until you convert it. **Uploading requires cassis-cli ≥ 1.1.0**: the server rejects an older CLI (which would drop the Markdown domain files) with a clear upgrade error.
61
63
 
@@ -63,7 +65,7 @@ The context tree under `<base-path>` (default `cassis/`) is:
63
65
 
64
66
  1. Create an API key in Cassis under **Organization settings → API keys** (keys start with `sk-k6-`).
65
67
  2. Store it as a CI secret and expose it as `CASSIS_API_KEY`.
66
- 3. For `pull`, `upload`, `schema pull`, `eval run`, `context test`, the `eval` case commands (`add-case`, `list-cases`, `delete-case`), and the `issues` commands: the project ID (UUID) is taken from `<base-path>/project.yml` in the checkout (written by `pull` and by publishing), so once a repo is pulled you don't need to pass it. To override, or before the first pull, set `CASSIS_PROJECT_ID` or pass `--project` (find the UUID with `cassis projects list`, or in the project's URL). `context check` uses the same resolution but treats it as optional: unbound checkouts get the project-less validation (no schema reference warnings).
68
+ 3. For `pull`, `upload`, `schema pull`, `eval run`, `context test`, the `eval` case commands (`add-case`, `list-cases`, `history`, `delete-case`), and the `issues` commands: the project ID (UUID) is taken from `<base-path>/project.yml` in the checkout (written by `pull` and by publishing), so once a repo is pulled you don't need to pass it. To override, or before the first pull, set `CASSIS_PROJECT_ID` or pass `--project` (find the UUID with `cassis projects list`, or in the project's URL). `context check` uses the same resolution but treats it as optional: unbound checkouts get the project-less validation (no schema reference warnings).
67
69
 
68
70
  ## Usage
69
71
 
@@ -131,8 +133,12 @@ cassis eval add-case --project ... -q "How much was refunded last month?" \
131
133
  cassis eval add-case --project ... -q "How much was refunded last month?" \
132
134
  --gold-sql-file refunds.sql
133
135
 
134
- # List the suite's cases (id + question; --json adds the gold SQL), then prune one:
136
+ # List the suite's cases (id + question, a warning under any case whose gold SQL
137
+ # reads tables the context lacks; --json adds the gold SQL and the footprint;
138
+ # --branch <name> resolves against a context branch), see how one has behaved
139
+ # across runs, then prune one:
135
140
  cassis eval list-cases --project ...
141
+ cassis eval history 019f0000-0000-7000-8000-0000000000ca --project ...
136
142
  cassis eval delete-case 019f0000-0000-7000-8000-0000000000ca --project ...
137
143
 
138
144
  # Pull the source schema into <base-path>/.schema.json (gitignored local snapshot):
@@ -175,6 +181,16 @@ cassis issues reopen 019f0000-0000-7000-8000-0000000000e1
175
181
  cassis status
176
182
  cassis status --watch --timeout 600
177
183
 
184
+ # In the dbt project's CI, after a merge to the production branch: write the manifest and the
185
+ # catalog, then send them (findings are filed as issues). On GitHub Actions, expose GITHUB_TOKEN to
186
+ # the step so the push names the pull request that merged the commit:
187
+ dbt docs generate --target prod
188
+ cassis dbt push target --project ...
189
+
190
+ # On a dbt pull request: compile on the production target, then list what the change puts in doubt:
191
+ dbt compile --target prod
192
+ cassis dbt check target --project ...
193
+
178
194
  # The full local gate in one verb (fmt --check, check, eval run; stops at the first failure):
179
195
  cassis verify
180
196
  cassis verify --no-eval
@@ -187,7 +203,7 @@ Configuration (flags take precedence over env vars):
187
203
  | `--api-key` | `CASSIS_API_KEY` | none (required) |
188
204
  | `--api-url` | `CASSIS_API_URL` | `https://app.getcassis.com` |
189
205
  | `--base-path` | `CASSIS_BASE_PATH` | `cassis`, must match the project's git-sync "Path" setting |
190
- | `--project` (check, pull, upload, schema pull, eval run, eval add-case, eval list-cases, eval delete-case, test, issues) | `CASSIS_PROJECT_ID` | the id in `<base-path>/project.yml` (required before the first pull; `check` alone falls back to the project-less validation when unbound) |
206
+ | `--project` (check, pull, upload, schema pull, eval run, eval add-case, eval list-cases, eval history, eval delete-case, test, issues, dbt push, dbt check) | `CASSIS_PROJECT_ID` | the id in `<base-path>/project.yml` (required before the first pull; `check` alone falls back to the project-less validation when unbound) |
191
207
 
192
208
  `cassis eval run` also accepts `--case <id>` (repeatable; run only the named
193
209
  cases, ids from `eval list-cases` or `add-case`), `--label` (run label in the Evals page; defaults
@@ -216,10 +232,10 @@ cassis context fmt --check
216
232
 
217
233
  | Code | Meaning |
218
234
  | ---- | ------------------------------------------------------------------------------ |
219
- | 0 | Context is valid (check) / pulled (pull) / uploaded (upload) / eval run completed all-passed (eval run) / every probe completed (test, whatever its outcome; probes are informational, don't gate CI on them) |
220
- | 1 | Validation failed (check: findings printed; upload: nothing imported; eval run: invalid tree, failed cases, or failed/cancelled run; test: invalid tree or a probe failed; add-case: duplicate question or gold SQL that does not run; delete-case: no such case in the project; issues: no such issue or occurrence in the project; issues analyze: failed or cancelled analysis run; schema plan/apply: extraction is incomplete, the plan failed (unparseable or truncated DDL), is stale or expired, the apply failed, or the project won't accept it (a plan is being applied, a DDL was given for a warehouse-connected project, or --warehouse for a DDL-only one)) |
221
- | 2 | Usage error (missing API key or project, no context directory, unreadable file, tree over the size limits, `eval run --branch` naming a context branch the project does not have, `upload` or `schema push` outside a git checkout or with uncommitted context files) |
222
- | 3 | Transport/API error (unreachable API, invalid key, inaccessible project, unexpected response), another eval run or issue analysis already active, out of credits, or `--timeout` reached |
235
+ | 0 | Context is valid (check) / pulled (pull) / uploaded (upload) / eval run completed all-passed (eval run) / every probe completed (test, whatever its outcome; probes are informational, don't gate CI on them) / dbt upload accepted, or kept behind a newer one (dbt push) / dbt upload checked, with or without findings (dbt check) |
236
+ | 1 | Validation failed (check: findings printed; upload: nothing imported; eval run: invalid tree, failed cases, or failed/cancelled run; test: invalid tree or a probe failed; add-case: duplicate question or gold SQL that does not run; delete-case, history: no such case in the project; issues: no such issue or occurrence in the project; issues analyze: failed or cancelled analysis run; schema plan/apply: extraction is incomplete, the plan failed (unparseable or truncated DDL), is stale or expired, the apply failed, or the project won't accept it (a plan is being applied, a DDL was given for a warehouse-connected project, or --warehouse for a DDL-only one); dbt push: the upload was refused or rejected (a file is invalid, too large or from an unsupported dbt version, or no relation matches the project's data source); dbt check: the upload was refused or rejected (an invalid file, or no baseline yet: run `cassis dbt push` first)) |
237
+ | 2 | Usage error (missing API key or project, no context directory, unreadable file, tree over the size limits, `eval run --branch` naming a context branch the project does not have, `upload` or `schema push` outside a git checkout or with uncommitted context files, `dbt push` or `dbt check` without a manifest.json, outside a git checkout without `--commit`, or with an artifact over the size limits) |
238
+ | 3 | Transport/API error (unreachable API, invalid key, inaccessible project, unexpected response), another eval run or issue analysis already active, out of credits, `--timeout` reached, or Cassis failed on its side while processing a dbt upload (`dbt push` / `dbt check`: nothing to fix in the upload, push again) |
223
239
 
224
240
  Commands that send the local tree (`check`, `fmt`, `upload`, `eval run`, `test`) accept up to
225
241
  20,000 context files / 100 MB total (path + content bytes), sized for a context of
@@ -2,7 +2,7 @@
2
2
 
3
3
  Validate, test and evaluate your context from your terminal, then publish it. The same commands gate your pull requests in CI:
4
4
 
5
- - `cassis context check` validates the context files in your repository with the exact same checks as the Cassis GitHub PR check (YAML parsing, round-trip, import validation), so you can gate merges in any CI system, not just GitHub. It then prints advisory **context quality warnings** for a tree that parsed: tables not assigned to any domain, joins/metrics pointing at unknown tables or columns, missing table/column descriptions (the same findings `context test` reports, without the agent run). In a checkout bound to a project (`project.yml`, `--project`, or `CASSIS_PROJECT_ID`), it also cross-checks the tree against the project's source schema: references to tables or columns the warehouse doesn't have print as **warnings** too, advisory only (the object may simply not be built or synced yet). Warnings never fail the check.
5
+ - `cassis context check` validates the context files in your repository with the exact same checks as the Cassis GitHub PR check (YAML parsing, round-trip, import validation), so you can gate merges in any CI system, not just GitHub. It then prints advisory **context quality warnings** for a tree that parsed: tables not assigned to any domain, joins/metrics pointing at unknown tables or columns, missing table/column descriptions, `enum_values` the column's type can't store (the same findings `context test` reports, without the agent run). In a checkout bound to a project (`project.yml`, `--project`, or `CASSIS_PROJECT_ID`), it also cross-checks the tree against the project's source schema: references to tables or columns the warehouse doesn't have print as **warnings** too, advisory only (the object may simply not be built or synced yet). Warnings never fail the check.
6
6
  - `cassis schema pull` downloads the data source's full source schema (as Cassis last introspected it) into `<base-path>/.schema.json`, a **gitignored** local snapshot (the command maintains the ignore entry) with a `pulled_at` stamp. The warehouse stays authoritative; the snapshot is a cache for offline/bulk work, e.g. a coding agent grepping table and column names during a modeling pass instead of paging through the MCP `get_source_schema` tool. Re-run to refresh.
7
7
  - `cassis context fmt` rewrites the context files in canonical form (think `black`/`gofmt` for your context), so hand or agent edits pass the round-trip check.
8
8
  - `cassis context upload` uploads the context files to a Cassis project (full replace) and, by default, publishes them immediately as a new version, so a merge to your main branch can go live in one CI step. It runs from a git checkout whose context files are committed, and the published version records that commit, even when the upload changes nothing, so `cassis status` can tell whether a checkout matches what is live.
@@ -12,13 +12,15 @@ Validate, test and evaluate your context from your terminal, then publish it. Th
12
12
  - `cassis eval run` runs the project's eval suite against your local context files (scored in-memory, nothing is pushed to Cassis) and prints per-question results, so you can test the changes on your git branch before merging.
13
13
  - `cassis context test` runs individual questions through the text-to-SQL agent using your local context files, so you can check that a change actually works (e.g. a new column gets picked), where `eval run` only checks for regressions on existing eval cases.
14
14
  - `cassis eval add-case` adds a gold question/SQL case to the project's eval suite: after fixing a context issue, add the question users were failing on so `eval run` guards it from regressing.
15
- - `cassis eval list-cases` and `cassis eval delete-case` maintain the suite: list the current cases with their ids, and prune one that is stale or wrong (e.g. its gold SQL encodes a definition the context has since changed).
15
+ - `cassis eval list-cases`, `cassis eval history` and `cassis eval delete-case` maintain the suite: list the current cases with their ids (flagging those whose gold SQL reads tables the context lacks), see how one case has behaved across runs (pass rate, whether repeated runs under one configuration disagree, divergences and timings), and prune one that is stale or wrong (e.g. its gold SQL encodes a definition the context has since changed).
16
16
  - `cassis schema plan <ddl>` (or `--warehouse` on a project connected to a warehouse) previews what a schema update would change before anything is applied: the source schema diff, the context changes Cassis will make (every change on a table placed in the context, with everything a drop takes with it) and warnings, terraform-style. `cassis schema apply <ddl>` (or `--plan <id>`) writes the resulting context files into the local checkout, app untouched, for review with `git diff`. `cassis schema push <ddl> [--publish]` pushes the new schema and the local context to the app (`--yes` in CI). The file speaks only for the schemas it contains: pass `--complete` when it is the project's complete source schema so schemas absent from it are treated as dropped. With `--warehouse` the server introspects the connected warehouse instead of parsing a file; the plan is always whole-source. `cassis schema plan <ddl> --dry-run` is the prepare-ahead variant: the plan is computed synchronously and nothing is kept in Cassis (no plan to apply or resume, the current plan untouched), so the schema snapshot for a change still in a PR can be planned against safely; `--write-checkout` writes the context files it would produce into the checkout, to commit alongside the schema change.
17
17
  - `cassis projects list` lists the projects your API key can reach: id (what `--project` and `CASSIS_PROJECT_ID` take), name, published context version, and data-source dialect. A pipeline or agent can discover the project id from the terminal instead of fishing it out of a webapp URL.
18
18
  - DDL imports describe one schema snapshot/export file, including ordinary and materialized views; they do not replay incremental migrations. Extraction diagnostics include object names and statement locations. If extraction is incomplete, `schema plan` and `--dry-run` show the extracted inventory and exit 1; `apply`, `push`, and `--write-checkout` cannot save that result. Unknown column types are warnings when all output names are known. Plans also show object-kind, view-definition, comment, and constraint changes. `--json` preserves structured diagnostics and the server-capped inventory.
19
19
 
20
20
  - `cassis status` shows the project's published version (number, label, git commit), whether unpublished changes await publication, the git-sync binding, a schema plan waiting to be applied, and how your local git HEAD relates to the published commit (in sync / N commits ahead / diverged). `cassis status --watch` polls until the published commit matches your local HEAD (e.g. right after merging a PR whose CI publishes the context) instead of watching the GitHub Actions tab.
21
- - `cassis issues` triages the issues Cassis raised on the project (what it found wrong while answering questions: a context gap, missing data) without leaving the checkout: `issues list` (filterable by status, impact, cause and context domain, and showing each issue's domain so you can work through one domain at a time), `issues show <id>` for the diagnosis, suggested action and the occurrences behind it, `issues evidence <id> <occurrence-id>` for what the agent actually saw, and `issues resolve` / `dismiss` / `reopen` once you've acted on it. When the fix ships through a pull request, write the `PR mention:` line `issues show` prints (`Resolves <id>`) in the PR description instead: Cassis resolves the issue when the PR merges, and `issues show` then reports how it was closed and through which PR.
21
+ - `cassis issues` triages the issues Cassis raised on the project (what it found wrong while answering questions: a context gap, missing data) without leaving the checkout: `issues list` (filterable by status, impact, cause and context domain, and showing each issue's domain so you can work through one domain at a time; and each issue's reach, the chat answers that used its context tables in the last 30 days), `issues show <id>` for the diagnosis, suggested action, the occurrences behind it, its reach and evidence (removed from dbt, an AI review suggestion, user feedback, a failed eval, or the chat analysis), `issues evidence <id> <occurrence-id>` for what the agent actually saw, and `issues resolve` / `dismiss` / `reopen` once you've acted on it. When the fix ships through a pull request, write the `PR mention:` line `issues show` prints (`Resolves <id>`) in the PR description instead: Cassis resolves the issue when the PR merges, and `issues show` then reports how it was closed and through which PR.
22
+ - `cassis dbt push` sends your dbt project's artifacts (`target/manifest.json`, and `catalog.json` when present) to Cassis from the dbt project's CI, with the commit they were built from. Cassis checks the files, matches each dbt model, seed, snapshot and source to a table of the project's data source, and keeps the upload as the project's dbt baseline unless a newer one is already there. The command prints how many relations matched and how many tests they declare. Cassis compares the upload with the previous baseline and files each part of the context the merged change puts in doubt as an issue (impact "at risk"); the command lists them with links. Nothing the agent answers with changes.
23
+ - `cassis dbt check` runs the same comparison on a pull request, before the change merges: it uploads the PR's artifacts (after `dbt compile` on the production target), compares them with the baseline, and prints the context tables, columns, joins and metrics the change puts in doubt, with why it matters and the context change to make. Each finding also says its **reach**, how many chat answers used its context tables in the last 30 days, and within each section the most-reached objects print first, the order the Review page will show them in. Findings are advisory: nothing is filed, the baseline doesn't move, and the check exits 0 with or without findings.
22
24
  - `cassis verify` runs the full local gate in one verb (`context fmt --check`, `context check`, `eval run`), stopping at the first failure. One command in a checkout ("is this change safe to merge?"), one job in CI. `--no-eval` skips the eval suite.
23
25
 
24
26
  ## Install
@@ -33,7 +35,7 @@ The context tree under `<base-path>` (default `cassis/`) is:
33
35
 
34
36
  - **Project identity**: `project.yml`, the Cassis project id and format version. Written by `pull` and by server-side publish (the places that know the id); a local `fmt` won't create it.
35
37
  - **Domains**: Markdown files. Every domain is the `README.md` of its folder: `domains/README.md` for the root, `domains/<path>/README.md` for each sub-domain. Each has a small YAML frontmatter block (`type`, `title`, `description`) and a Markdown body carrying the domain's `context_md`; a generated section at the bottom links the domain's tables and metrics (kept current by `fmt`/`pull`: edit your prose above it, and the PR check fails if the links are stale, so re-run `fmt`). The layout is a Cassis profile inspired by [OKF](https://github.com/GoogleCloudPlatform/knowledge-catalog/tree/main/okf): the files render on GitHub and read in any Markdown editor, but Cassis validates them strictly (unknown keys are flagged, not preserved).
36
- - **Tables, joins, metrics**: YAML, unchanged: `tables/<schema>/<table>.yml`, `joins.yml`, `metrics/<name>.yml`.
38
+ - **Tables, joins, metrics**: YAML, unchanged: `tables/<schema>/<table>.yml`, `joins.yml`, `metrics/<name>.yml`. A column can list the values it stores as `enum_values` (items `{value, description?}`, values quoted exactly as stored); see `cassis/AGENTS.md`.
37
39
 
38
40
  **Migrating an existing repo** (domains were YAML `_project.yml` / `_domain.yml` before cassis-cli 1.1.0): upgrade and run `cassis context fmt` (or `cassis context pull` if you have no local edits). It rewrites the domain files to Markdown and removes the old ones. Review the diff and commit. Cassis reads the old YAML domain files too, so an un-migrated repo keeps working until you convert it. **Uploading requires cassis-cli ≥ 1.1.0**: the server rejects an older CLI (which would drop the Markdown domain files) with a clear upgrade error.
39
41
 
@@ -41,7 +43,7 @@ The context tree under `<base-path>` (default `cassis/`) is:
41
43
 
42
44
  1. Create an API key in Cassis under **Organization settings → API keys** (keys start with `sk-k6-`).
43
45
  2. Store it as a CI secret and expose it as `CASSIS_API_KEY`.
44
- 3. For `pull`, `upload`, `schema pull`, `eval run`, `context test`, the `eval` case commands (`add-case`, `list-cases`, `delete-case`), and the `issues` commands: the project ID (UUID) is taken from `<base-path>/project.yml` in the checkout (written by `pull` and by publishing), so once a repo is pulled you don't need to pass it. To override, or before the first pull, set `CASSIS_PROJECT_ID` or pass `--project` (find the UUID with `cassis projects list`, or in the project's URL). `context check` uses the same resolution but treats it as optional: unbound checkouts get the project-less validation (no schema reference warnings).
46
+ 3. For `pull`, `upload`, `schema pull`, `eval run`, `context test`, the `eval` case commands (`add-case`, `list-cases`, `history`, `delete-case`), and the `issues` commands: the project ID (UUID) is taken from `<base-path>/project.yml` in the checkout (written by `pull` and by publishing), so once a repo is pulled you don't need to pass it. To override, or before the first pull, set `CASSIS_PROJECT_ID` or pass `--project` (find the UUID with `cassis projects list`, or in the project's URL). `context check` uses the same resolution but treats it as optional: unbound checkouts get the project-less validation (no schema reference warnings).
45
47
 
46
48
  ## Usage
47
49
 
@@ -109,8 +111,12 @@ cassis eval add-case --project ... -q "How much was refunded last month?" \
109
111
  cassis eval add-case --project ... -q "How much was refunded last month?" \
110
112
  --gold-sql-file refunds.sql
111
113
 
112
- # List the suite's cases (id + question; --json adds the gold SQL), then prune one:
114
+ # List the suite's cases (id + question, a warning under any case whose gold SQL
115
+ # reads tables the context lacks; --json adds the gold SQL and the footprint;
116
+ # --branch <name> resolves against a context branch), see how one has behaved
117
+ # across runs, then prune one:
113
118
  cassis eval list-cases --project ...
119
+ cassis eval history 019f0000-0000-7000-8000-0000000000ca --project ...
114
120
  cassis eval delete-case 019f0000-0000-7000-8000-0000000000ca --project ...
115
121
 
116
122
  # Pull the source schema into <base-path>/.schema.json (gitignored local snapshot):
@@ -153,6 +159,16 @@ cassis issues reopen 019f0000-0000-7000-8000-0000000000e1
153
159
  cassis status
154
160
  cassis status --watch --timeout 600
155
161
 
162
+ # In the dbt project's CI, after a merge to the production branch: write the manifest and the
163
+ # catalog, then send them (findings are filed as issues). On GitHub Actions, expose GITHUB_TOKEN to
164
+ # the step so the push names the pull request that merged the commit:
165
+ dbt docs generate --target prod
166
+ cassis dbt push target --project ...
167
+
168
+ # On a dbt pull request: compile on the production target, then list what the change puts in doubt:
169
+ dbt compile --target prod
170
+ cassis dbt check target --project ...
171
+
156
172
  # The full local gate in one verb (fmt --check, check, eval run; stops at the first failure):
157
173
  cassis verify
158
174
  cassis verify --no-eval
@@ -165,7 +181,7 @@ Configuration (flags take precedence over env vars):
165
181
  | `--api-key` | `CASSIS_API_KEY` | none (required) |
166
182
  | `--api-url` | `CASSIS_API_URL` | `https://app.getcassis.com` |
167
183
  | `--base-path` | `CASSIS_BASE_PATH` | `cassis`, must match the project's git-sync "Path" setting |
168
- | `--project` (check, pull, upload, schema pull, eval run, eval add-case, eval list-cases, eval delete-case, test, issues) | `CASSIS_PROJECT_ID` | the id in `<base-path>/project.yml` (required before the first pull; `check` alone falls back to the project-less validation when unbound) |
184
+ | `--project` (check, pull, upload, schema pull, eval run, eval add-case, eval list-cases, eval history, eval delete-case, test, issues, dbt push, dbt check) | `CASSIS_PROJECT_ID` | the id in `<base-path>/project.yml` (required before the first pull; `check` alone falls back to the project-less validation when unbound) |
169
185
 
170
186
  `cassis eval run` also accepts `--case <id>` (repeatable; run only the named
171
187
  cases, ids from `eval list-cases` or `add-case`), `--label` (run label in the Evals page; defaults
@@ -194,10 +210,10 @@ cassis context fmt --check
194
210
 
195
211
  | Code | Meaning |
196
212
  | ---- | ------------------------------------------------------------------------------ |
197
- | 0 | Context is valid (check) / pulled (pull) / uploaded (upload) / eval run completed all-passed (eval run) / every probe completed (test, whatever its outcome; probes are informational, don't gate CI on them) |
198
- | 1 | Validation failed (check: findings printed; upload: nothing imported; eval run: invalid tree, failed cases, or failed/cancelled run; test: invalid tree or a probe failed; add-case: duplicate question or gold SQL that does not run; delete-case: no such case in the project; issues: no such issue or occurrence in the project; issues analyze: failed or cancelled analysis run; schema plan/apply: extraction is incomplete, the plan failed (unparseable or truncated DDL), is stale or expired, the apply failed, or the project won't accept it (a plan is being applied, a DDL was given for a warehouse-connected project, or --warehouse for a DDL-only one)) |
199
- | 2 | Usage error (missing API key or project, no context directory, unreadable file, tree over the size limits, `eval run --branch` naming a context branch the project does not have, `upload` or `schema push` outside a git checkout or with uncommitted context files) |
200
- | 3 | Transport/API error (unreachable API, invalid key, inaccessible project, unexpected response), another eval run or issue analysis already active, out of credits, or `--timeout` reached |
213
+ | 0 | Context is valid (check) / pulled (pull) / uploaded (upload) / eval run completed all-passed (eval run) / every probe completed (test, whatever its outcome; probes are informational, don't gate CI on them) / dbt upload accepted, or kept behind a newer one (dbt push) / dbt upload checked, with or without findings (dbt check) |
214
+ | 1 | Validation failed (check: findings printed; upload: nothing imported; eval run: invalid tree, failed cases, or failed/cancelled run; test: invalid tree or a probe failed; add-case: duplicate question or gold SQL that does not run; delete-case, history: no such case in the project; issues: no such issue or occurrence in the project; issues analyze: failed or cancelled analysis run; schema plan/apply: extraction is incomplete, the plan failed (unparseable or truncated DDL), is stale or expired, the apply failed, or the project won't accept it (a plan is being applied, a DDL was given for a warehouse-connected project, or --warehouse for a DDL-only one); dbt push: the upload was refused or rejected (a file is invalid, too large or from an unsupported dbt version, or no relation matches the project's data source); dbt check: the upload was refused or rejected (an invalid file, or no baseline yet: run `cassis dbt push` first)) |
215
+ | 2 | Usage error (missing API key or project, no context directory, unreadable file, tree over the size limits, `eval run --branch` naming a context branch the project does not have, `upload` or `schema push` outside a git checkout or with uncommitted context files, `dbt push` or `dbt check` without a manifest.json, outside a git checkout without `--commit`, or with an artifact over the size limits) |
216
+ | 3 | Transport/API error (unreachable API, invalid key, inaccessible project, unexpected response), another eval run or issue analysis already active, out of credits, `--timeout` reached, or Cassis failed on its side while processing a dbt upload (`dbt push` / `dbt check`: nothing to fix in the upload, push again) |
201
217
 
202
218
  Commands that send the local tree (`check`, `fmt`, `upload`, `eval run`, `test`) accept up to
203
219
  20,000 context files / 100 MB total (path + content bytes), sized for a context of
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import sys
6
6
  from typing import Any, Optional
7
+ from urllib.parse import urlencode
7
8
 
8
9
  import httpx
9
10
  from cassis_cli import __version__
@@ -692,16 +693,68 @@ def get_eval_cases(
692
693
  api_url: str,
693
694
  api_key: str,
694
695
  project_id: str,
696
+ branch: Optional[str] = None,
695
697
  transport: Optional[httpx.BaseTransport] = None,
696
698
  ) -> list[dict[str, Any]]:
697
- """GET /api/ci/projects/{project_id}/eval/cases and return the case list."""
699
+ """GET /api/ci/projects/{project_id}/eval/cases and return the case list.
700
+
701
+ With ``branch``, each case's footprint is resolved against that context
702
+ branch instead of main; a branch the project lacks raises
703
+ `EvalBranchNotFoundError` off the server's exact 404 detail.
704
+ """
698
705
  url = api_url.rstrip("/") + f"/api/ci/projects/{project_id}/eval/cases"
699
- result = _get_eval_json(url, api_key, transport)
706
+ if branch is not None:
707
+ url += "?" + urlencode({"branch": branch})
708
+ try:
709
+ with _client(transport=transport) as client:
710
+ response = client.get(url, headers={"Authorization": f"Bearer {api_key}"})
711
+ except httpx.HTTPError as exc:
712
+ raise ApiError(f"Could not reach the Cassis API at {url}: {exc}") from exc
713
+ if response.status_code == 401:
714
+ raise AuthError("The Cassis API rejected the API key (invalid or expired).")
715
+ if response.status_code == 404 and _is_branch_not_found(_detail_or_text(response)):
716
+ raise EvalBranchNotFoundError(_detail_or_text(response))
717
+ if response.status_code in (403, 404):
718
+ raise _project_scope_error(response)
719
+ if response.status_code >= 400:
720
+ raise ApiError(f"Cassis API returned HTTP {response.status_code}: {response.text[:500]}")
721
+ result = _parse_json_response(response, url)
700
722
  if not isinstance(result, list):
701
723
  raise ApiError(f"Unexpected response shape from the Cassis API at {url}.")
702
724
  return result
703
725
 
704
726
 
727
+ def get_eval_case_history(
728
+ *,
729
+ api_url: str,
730
+ api_key: str,
731
+ project_id: str,
732
+ case_id: str,
733
+ transport: Optional[httpx.BaseTransport] = None,
734
+ ) -> dict[str, Any]:
735
+ """GET /api/ci/projects/{project_id}/eval/cases/{case_id}/history: the case's health and results."""
736
+ url = api_url.rstrip("/") + f"/api/ci/projects/{project_id}/eval/cases/{case_id}/history"
737
+ try:
738
+ with _client(transport=transport) as client:
739
+ response = client.get(url, headers={"Authorization": f"Bearer {api_key}"})
740
+ except httpx.HTTPError as exc:
741
+ raise ApiError(f"Could not reach the Cassis API at {url}: {exc}") from exc
742
+ if response.status_code == 401:
743
+ raise AuthError("The Cassis API rejected the API key (invalid or expired).")
744
+ # Same exact-match wire contract as `delete_eval_case`: a missing case
745
+ # (exit 1) is not a project-scope 404 (exit 3).
746
+ if response.status_code == 404 and _detail_or_text(response) == "Eval case not found":
747
+ raise EvalCaseNotFoundError(f"No current eval case {case_id} in this project (already deleted, or wrong id?).")
748
+ if response.status_code in (403, 404):
749
+ raise _project_scope_error(response)
750
+ if response.status_code >= 400:
751
+ raise ApiError(f"Cassis API returned HTTP {response.status_code}: {response.text[:500]}")
752
+ result = _parse_json_response(response, url)
753
+ if not isinstance(result, dict) or not all(key in result for key in ("case_id", "health", "results")):
754
+ raise ApiError(f"Unexpected response shape from the Cassis API at {url}.")
755
+ return result
756
+
757
+
705
758
  def delete_eval_case(
706
759
  *,
707
760
  api_url: str,
@@ -1141,3 +1194,99 @@ def post_issue_analysis_cancel(
1141
1194
  raise _project_scope_error(response)
1142
1195
  if response.status_code >= 400:
1143
1196
  raise ApiError(f"Cassis API returned HTTP {response.status_code}: {response.text[:500]}")
1197
+
1198
+
1199
+ class DbtUploadRejectedError(ApiError):
1200
+ """The server refused the dbt upload request itself (400, 413, 422).
1201
+
1202
+ A file misnamed, too large or not gzip, an invalid field, or a project the
1203
+ upload can't go to (no data source): the message is the server's detail.
1204
+ """
1205
+
1206
+
1207
+ _DBT_UPLOAD_NOT_FOUND = "dbt upload not found"
1208
+
1209
+
1210
+ def _raise_for_dbt_status(response: httpx.Response) -> None:
1211
+ """Map the dbt routes' error statuses to the CLI's exceptions; return on 2xx."""
1212
+ if response.status_code == 401:
1213
+ raise AuthError("The Cassis API rejected the API key (invalid or expired).")
1214
+ if response.status_code in (400, 413, 422):
1215
+ raise DbtUploadRejectedError(_readable_detail(_detail_or_text(response)))
1216
+ if response.status_code == 404:
1217
+ detail = _detail_or_text(response)
1218
+ if detail == _DBT_UPLOAD_NOT_FOUND:
1219
+ raise ApiError("The dbt upload was not found in this project.")
1220
+ if detail == "Not Found":
1221
+ raise ServerTooOldError("This Cassis server does not accept dbt uploads yet: upgrade the server first.")
1222
+ raise _project_scope_error(response)
1223
+ if response.status_code == 403:
1224
+ raise _project_scope_error(response)
1225
+ if response.status_code >= 400:
1226
+ raise ApiError(f"Cassis API returned HTTP {response.status_code}: {response.text[:500]}")
1227
+
1228
+
1229
+ def _readable_detail(detail: Any) -> str:
1230
+ """A FastAPI validation error list as `field: message` pairs; any other detail as text."""
1231
+ if not isinstance(detail, list):
1232
+ return str(detail)
1233
+ parts = []
1234
+ for error in detail:
1235
+ if not isinstance(error, dict):
1236
+ parts.append(str(error))
1237
+ continue
1238
+ location = [str(part) for part in error.get("loc") or [] if part not in ("body", "query", "path")]
1239
+ message = str(error.get("msg") or "invalid")
1240
+ parts.append(f"{'.'.join(location)}: {message}" if location else message)
1241
+ return "; ".join(parts)
1242
+
1243
+
1244
+ def _dbt_observation_response(response: httpx.Response, url: str) -> dict[str, Any]:
1245
+ _raise_for_dbt_status(response)
1246
+ result = _parse_json_response(response, url)
1247
+ if not isinstance(result, dict) or "id" not in result or "status" not in result:
1248
+ raise ApiError(f"Unexpected response shape from the Cassis API at {url}.")
1249
+ return result
1250
+
1251
+
1252
+ def post_dbt_observation(
1253
+ *,
1254
+ api_url: str,
1255
+ api_key: str,
1256
+ project_id: str,
1257
+ files: "dict[str, bytes]",
1258
+ fields: "dict[str, str]",
1259
+ transport: Optional[httpx.BaseTransport] = None,
1260
+ ) -> dict[str, Any]:
1261
+ """POST gzip-compressed dbt artifacts to /api/ci/projects/{project_id}/dbt/observations.
1262
+
1263
+ ``files`` maps each artifact's file name (``manifest.json``, ...) to its
1264
+ gzip bytes; ``fields`` carries the git context. Returns the PROCESSING
1265
+ upload record.
1266
+ """
1267
+ url = api_url.rstrip("/") + f"/api/ci/projects/{project_id}/dbt/observations"
1268
+ multipart = {name[: -len(".json")]: (f"{name}.gz", content, "application/gzip") for name, content in files.items()}
1269
+ try:
1270
+ with _client(timeout=ONTOLOGY_TREE_TIMEOUT_SECONDS, transport=transport) as client:
1271
+ response = client.post(url, files=multipart, data=fields, headers={"Authorization": f"Bearer {api_key}"})
1272
+ except httpx.HTTPError as exc:
1273
+ raise ApiError(f"Could not reach the Cassis API at {url}: {exc}") from exc
1274
+ return _dbt_observation_response(response, url)
1275
+
1276
+
1277
+ def get_dbt_observation(
1278
+ *,
1279
+ api_url: str,
1280
+ api_key: str,
1281
+ project_id: str,
1282
+ observation_id: str,
1283
+ transport: Optional[httpx.BaseTransport] = None,
1284
+ ) -> dict[str, Any]:
1285
+ """GET /api/ci/projects/{project_id}/dbt/observations/{observation_id} and return the upload record."""
1286
+ url = api_url.rstrip("/") + f"/api/ci/projects/{project_id}/dbt/observations/{observation_id}"
1287
+ try:
1288
+ with _client(transport=transport) as client:
1289
+ response = client.get(url, headers={"Authorization": f"Bearer {api_key}"})
1290
+ except httpx.HTTPError as exc:
1291
+ raise ApiError(f"Could not reach the Cassis API at {url}: {exc}") from exc
1292
+ return _dbt_observation_response(response, url)