cassis-cli 3.2.0__tar.gz → 3.4.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.2.0
3
+ Version: 3.4.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
@@ -33,14 +33,17 @@ Validate, test and evaluate your context from your terminal, then publish it. Th
33
33
  - The CLI identifies itself to the API (`User-Agent: cassis-cli/<version>`), and successful API responses advertise the newest published version. When you are behind, commands print a one-line upgrade notice on stderr (purely informational; output and exit codes are unchanged).
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
- - `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).
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. Cassis then runs the new case three times in the background, and `eval list-cases` flags it if the attempts fail or disagree.
37
+ - `cassis eval run --repeat N` runs every case N times (2 to 5) on the same context and model and compares the attempts per case: which cases flipped, and whether a flip came from the agent writing different SQL or the judge ruling differently on the same SQL. `cassis eval compare <run-id>` prints that comparison again. A repeat run measures noise: it opens no issue, is not the run shown before publishing, and exits 1 only when a case failed every attempt.
38
+ - `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
39
  - `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
40
  - `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
41
  - 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
42
 
42
43
  - `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.
44
+ - `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.
45
+ - `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.
46
+ - `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
47
  - `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
48
 
46
49
  ## Install
@@ -63,7 +66,7 @@ The context tree under `<base-path>` (default `cassis/`) is:
63
66
 
64
67
  1. Create an API key in Cassis under **Organization settings → API keys** (keys start with `sk-k6-`).
65
68
  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).
69
+ 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
70
 
68
71
  ## Usage
69
72
 
@@ -131,10 +134,18 @@ cassis eval add-case --project ... -q "How much was refunded last month?" \
131
134
  cassis eval add-case --project ... -q "How much was refunded last month?" \
132
135
  --gold-sql-file refunds.sql
133
136
 
134
- # List the suite's cases (id + question; --json adds the gold SQL), then prune one:
137
+ # List the suite's cases (id + question, a warning under any case whose gold SQL
138
+ # reads tables the context lacks; --json adds the gold SQL and the footprint;
139
+ # --branch <name> resolves against a context branch), see how one has behaved
140
+ # across runs, then prune one:
135
141
  cassis eval list-cases --project ...
142
+ cassis eval history 019f0000-0000-7000-8000-0000000000ca --project ...
136
143
  cassis eval delete-case 019f0000-0000-7000-8000-0000000000ca --project ...
137
144
 
145
+ # Run the suite 3 times to see which cases flip, and why; print it again later:
146
+ cassis eval run --repeat 3
147
+ cassis eval compare 019f0000-0000-7000-8000-00000000aaaa --project ...
148
+
138
149
  # Pull the source schema into <base-path>/.schema.json (gitignored local snapshot):
139
150
  cassis schema pull
140
151
 
@@ -175,6 +186,16 @@ cassis issues reopen 019f0000-0000-7000-8000-0000000000e1
175
186
  cassis status
176
187
  cassis status --watch --timeout 600
177
188
 
189
+ # In the dbt project's CI, after a merge to the production branch: write the manifest and the
190
+ # catalog, then send them (findings are filed as issues). On GitHub Actions, expose GITHUB_TOKEN to
191
+ # the step so the push names the pull request that merged the commit:
192
+ dbt docs generate --target prod
193
+ cassis dbt push target --project ...
194
+
195
+ # On a dbt pull request: compile on the production target, then list what the change puts in doubt:
196
+ dbt compile --target prod
197
+ cassis dbt check target --project ...
198
+
178
199
  # The full local gate in one verb (fmt --check, check, eval run; stops at the first failure):
179
200
  cassis verify
180
201
  cassis verify --no-eval
@@ -187,7 +208,7 @@ Configuration (flags take precedence over env vars):
187
208
  | `--api-key` | `CASSIS_API_KEY` | none (required) |
188
209
  | `--api-url` | `CASSIS_API_URL` | `https://app.getcassis.com` |
189
210
  | `--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) |
211
+ | `--project` (check, pull, upload, schema pull, eval run, eval add-case, eval list-cases, eval history, eval compare, 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
212
 
192
213
  `cassis eval run` also accepts `--case <id>` (repeatable; run only the named
193
214
  cases, ids from `eval list-cases` or `add-case`), `--label` (run label in the Evals page; defaults
@@ -216,10 +237,10 @@ cassis context fmt --check
216
237
 
217
238
  | Code | Meaning |
218
239
  | ---- | ------------------------------------------------------------------------------ |
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 |
240
+ | 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) |
241
+ | 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)) |
242
+ | 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) |
243
+ | 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
244
 
224
245
  Commands that send the local tree (`check`, `fmt`, `upload`, `eval run`, `test`) accept up to
225
246
  20,000 context files / 100 MB total (path + content bytes), sized for a context of
@@ -11,14 +11,17 @@ Validate, test and evaluate your context from your terminal, then publish it. Th
11
11
  - The CLI identifies itself to the API (`User-Agent: cassis-cli/<version>`), and successful API responses advertise the newest published version. When you are behind, commands print a one-line upgrade notice on stderr (purely informational; output and exit codes are unchanged).
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
- - `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).
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. Cassis then runs the new case three times in the background, and `eval list-cases` flags it if the attempts fail or disagree.
15
+ - `cassis eval run --repeat N` runs every case N times (2 to 5) on the same context and model and compares the attempts per case: which cases flipped, and whether a flip came from the agent writing different SQL or the judge ruling differently on the same SQL. `cassis eval compare <run-id>` prints that comparison again. A repeat run measures noise: it opens no issue, is not the run shown before publishing, and exits 1 only when a case failed every attempt.
16
+ - `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
17
  - `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
18
  - `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
19
  - 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
20
 
20
21
  - `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.
22
+ - `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.
23
+ - `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.
24
+ - `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
25
  - `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
26
 
24
27
  ## Install
@@ -41,7 +44,7 @@ The context tree under `<base-path>` (default `cassis/`) is:
41
44
 
42
45
  1. Create an API key in Cassis under **Organization settings → API keys** (keys start with `sk-k6-`).
43
46
  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).
47
+ 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
48
 
46
49
  ## Usage
47
50
 
@@ -109,10 +112,18 @@ cassis eval add-case --project ... -q "How much was refunded last month?" \
109
112
  cassis eval add-case --project ... -q "How much was refunded last month?" \
110
113
  --gold-sql-file refunds.sql
111
114
 
112
- # List the suite's cases (id + question; --json adds the gold SQL), then prune one:
115
+ # List the suite's cases (id + question, a warning under any case whose gold SQL
116
+ # reads tables the context lacks; --json adds the gold SQL and the footprint;
117
+ # --branch <name> resolves against a context branch), see how one has behaved
118
+ # across runs, then prune one:
113
119
  cassis eval list-cases --project ...
120
+ cassis eval history 019f0000-0000-7000-8000-0000000000ca --project ...
114
121
  cassis eval delete-case 019f0000-0000-7000-8000-0000000000ca --project ...
115
122
 
123
+ # Run the suite 3 times to see which cases flip, and why; print it again later:
124
+ cassis eval run --repeat 3
125
+ cassis eval compare 019f0000-0000-7000-8000-00000000aaaa --project ...
126
+
116
127
  # Pull the source schema into <base-path>/.schema.json (gitignored local snapshot):
117
128
  cassis schema pull
118
129
 
@@ -153,6 +164,16 @@ cassis issues reopen 019f0000-0000-7000-8000-0000000000e1
153
164
  cassis status
154
165
  cassis status --watch --timeout 600
155
166
 
167
+ # In the dbt project's CI, after a merge to the production branch: write the manifest and the
168
+ # catalog, then send them (findings are filed as issues). On GitHub Actions, expose GITHUB_TOKEN to
169
+ # the step so the push names the pull request that merged the commit:
170
+ dbt docs generate --target prod
171
+ cassis dbt push target --project ...
172
+
173
+ # On a dbt pull request: compile on the production target, then list what the change puts in doubt:
174
+ dbt compile --target prod
175
+ cassis dbt check target --project ...
176
+
156
177
  # The full local gate in one verb (fmt --check, check, eval run; stops at the first failure):
157
178
  cassis verify
158
179
  cassis verify --no-eval
@@ -165,7 +186,7 @@ Configuration (flags take precedence over env vars):
165
186
  | `--api-key` | `CASSIS_API_KEY` | none (required) |
166
187
  | `--api-url` | `CASSIS_API_URL` | `https://app.getcassis.com` |
167
188
  | `--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) |
189
+ | `--project` (check, pull, upload, schema pull, eval run, eval add-case, eval list-cases, eval history, eval compare, 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
190
 
170
191
  `cassis eval run` also accepts `--case <id>` (repeatable; run only the named
171
192
  cases, ids from `eval list-cases` or `add-case`), `--label` (run label in the Evals page; defaults
@@ -194,10 +215,10 @@ cassis context fmt --check
194
215
 
195
216
  | Code | Meaning |
196
217
  | ---- | ------------------------------------------------------------------------------ |
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 |
218
+ | 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) |
219
+ | 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)) |
220
+ | 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) |
221
+ | 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
222
 
202
223
  Commands that send the local tree (`check`, `fmt`, `upload`, `eval run`, `test`) accept up to
203
224
  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__
@@ -593,6 +594,7 @@ def post_eval_run_start(
593
594
  branch: Optional[str] = None,
594
595
  label: Optional[str] = None,
595
596
  case_ids: Optional[list[str]] = None,
597
+ repeat_count: Optional[int] = None,
596
598
  transport: Optional[httpx.BaseTransport] = None,
597
599
  ) -> dict[str, Any]:
598
600
  """POST to /api/ci/projects/{project_id}/eval/runs and return the run record."""
@@ -606,6 +608,8 @@ def post_eval_run_start(
606
608
  body["label"] = label
607
609
  if case_ids is not None:
608
610
  body["test_case_ids"] = case_ids
611
+ if repeat_count is not None:
612
+ body["repeat_count"] = repeat_count
609
613
  try:
610
614
  # May carry the whole ontology tree in `files` — same budget as the
611
615
  # other whole-tree endpoints.
@@ -692,16 +696,68 @@ def get_eval_cases(
692
696
  api_url: str,
693
697
  api_key: str,
694
698
  project_id: str,
699
+ branch: Optional[str] = None,
695
700
  transport: Optional[httpx.BaseTransport] = None,
696
701
  ) -> list[dict[str, Any]]:
697
- """GET /api/ci/projects/{project_id}/eval/cases and return the case list."""
702
+ """GET /api/ci/projects/{project_id}/eval/cases and return the case list.
703
+
704
+ With ``branch``, each case's footprint is resolved against that context
705
+ branch instead of main; a branch the project lacks raises
706
+ `EvalBranchNotFoundError` off the server's exact 404 detail.
707
+ """
698
708
  url = api_url.rstrip("/") + f"/api/ci/projects/{project_id}/eval/cases"
699
- result = _get_eval_json(url, api_key, transport)
709
+ if branch is not None:
710
+ url += "?" + urlencode({"branch": branch})
711
+ try:
712
+ with _client(transport=transport) as client:
713
+ response = client.get(url, headers={"Authorization": f"Bearer {api_key}"})
714
+ except httpx.HTTPError as exc:
715
+ raise ApiError(f"Could not reach the Cassis API at {url}: {exc}") from exc
716
+ if response.status_code == 401:
717
+ raise AuthError("The Cassis API rejected the API key (invalid or expired).")
718
+ if response.status_code == 404 and _is_branch_not_found(_detail_or_text(response)):
719
+ raise EvalBranchNotFoundError(_detail_or_text(response))
720
+ if response.status_code in (403, 404):
721
+ raise _project_scope_error(response)
722
+ if response.status_code >= 400:
723
+ raise ApiError(f"Cassis API returned HTTP {response.status_code}: {response.text[:500]}")
724
+ result = _parse_json_response(response, url)
700
725
  if not isinstance(result, list):
701
726
  raise ApiError(f"Unexpected response shape from the Cassis API at {url}.")
702
727
  return result
703
728
 
704
729
 
730
+ def get_eval_case_history(
731
+ *,
732
+ api_url: str,
733
+ api_key: str,
734
+ project_id: str,
735
+ case_id: str,
736
+ transport: Optional[httpx.BaseTransport] = None,
737
+ ) -> dict[str, Any]:
738
+ """GET /api/ci/projects/{project_id}/eval/cases/{case_id}/history: the case's health and results."""
739
+ url = api_url.rstrip("/") + f"/api/ci/projects/{project_id}/eval/cases/{case_id}/history"
740
+ try:
741
+ with _client(transport=transport) as client:
742
+ response = client.get(url, headers={"Authorization": f"Bearer {api_key}"})
743
+ except httpx.HTTPError as exc:
744
+ raise ApiError(f"Could not reach the Cassis API at {url}: {exc}") from exc
745
+ if response.status_code == 401:
746
+ raise AuthError("The Cassis API rejected the API key (invalid or expired).")
747
+ # Same exact-match wire contract as `delete_eval_case`: a missing case
748
+ # (exit 1) is not a project-scope 404 (exit 3).
749
+ if response.status_code == 404 and _detail_or_text(response) == "Eval case not found":
750
+ raise EvalCaseNotFoundError(f"No current eval case {case_id} in this project (already deleted, or wrong id?).")
751
+ if response.status_code in (403, 404):
752
+ raise _project_scope_error(response)
753
+ if response.status_code >= 400:
754
+ raise ApiError(f"Cassis API returned HTTP {response.status_code}: {response.text[:500]}")
755
+ result = _parse_json_response(response, url)
756
+ if not isinstance(result, dict) or not all(key in result for key in ("case_id", "health", "results")):
757
+ raise ApiError(f"Unexpected response shape from the Cassis API at {url}.")
758
+ return result
759
+
760
+
705
761
  def delete_eval_case(
706
762
  *,
707
763
  api_url: str,
@@ -780,6 +836,41 @@ def get_eval_run_results(
780
836
  return result
781
837
 
782
838
 
839
+ class EvalCompareUnsupportedError(ApiError):
840
+ """The server predates run comparisons."""
841
+
842
+
843
+ def get_eval_run_comparison(
844
+ *,
845
+ api_url: str,
846
+ api_key: str,
847
+ project_id: str,
848
+ run_id: str,
849
+ transport: Optional[httpx.BaseTransport] = None,
850
+ ) -> dict[str, Any]:
851
+ """GET /api/ci/projects/{project_id}/eval/runs/{run_id}/comparison: the run's attempts compared per case."""
852
+ url = api_url.rstrip("/") + f"/api/ci/projects/{project_id}/eval/runs/{run_id}/comparison"
853
+ try:
854
+ with _client(transport=transport) as client:
855
+ response = client.get(url, headers={"Authorization": f"Bearer {api_key}"})
856
+ except httpx.HTTPError as exc:
857
+ raise ApiError(f"Could not reach the Cassis API at {url}: {exc}") from exc
858
+ if response.status_code == 401:
859
+ raise AuthError("The Cassis API rejected the API key (invalid or expired).")
860
+ # A server without the route answers the framework's own "Not Found",
861
+ # distinct from the "Eval run not found" a real lookup returns.
862
+ if response.status_code == 404 and _detail_or_text(response) == "Not Found":
863
+ raise EvalCompareUnsupportedError("This Cassis server does not compare runs yet; it needs a newer server.")
864
+ if response.status_code in (403, 404):
865
+ raise _project_scope_error(response)
866
+ if response.status_code >= 400:
867
+ raise ApiError(f"Cassis API returned HTTP {response.status_code}: {response.text[:500]}")
868
+ result = _parse_json_response(response, url)
869
+ if not isinstance(result, dict) or not all(key in result for key in ("run_id", "summary", "cases")):
870
+ raise ApiError(f"Unexpected response shape from the Cassis API at {url}.")
871
+ return result
872
+
873
+
783
874
  def post_eval_run_cancel(
784
875
  *,
785
876
  api_url: str,
@@ -1141,3 +1232,99 @@ def post_issue_analysis_cancel(
1141
1232
  raise _project_scope_error(response)
1142
1233
  if response.status_code >= 400:
1143
1234
  raise ApiError(f"Cassis API returned HTTP {response.status_code}: {response.text[:500]}")
1235
+
1236
+
1237
+ class DbtUploadRejectedError(ApiError):
1238
+ """The server refused the dbt upload request itself (400, 413, 422).
1239
+
1240
+ A file misnamed, too large or not gzip, an invalid field, or a project the
1241
+ upload can't go to (no data source): the message is the server's detail.
1242
+ """
1243
+
1244
+
1245
+ _DBT_UPLOAD_NOT_FOUND = "dbt upload not found"
1246
+
1247
+
1248
+ def _raise_for_dbt_status(response: httpx.Response) -> None:
1249
+ """Map the dbt routes' error statuses to the CLI's exceptions; return on 2xx."""
1250
+ if response.status_code == 401:
1251
+ raise AuthError("The Cassis API rejected the API key (invalid or expired).")
1252
+ if response.status_code in (400, 413, 422):
1253
+ raise DbtUploadRejectedError(_readable_detail(_detail_or_text(response)))
1254
+ if response.status_code == 404:
1255
+ detail = _detail_or_text(response)
1256
+ if detail == _DBT_UPLOAD_NOT_FOUND:
1257
+ raise ApiError("The dbt upload was not found in this project.")
1258
+ if detail == "Not Found":
1259
+ raise ServerTooOldError("This Cassis server does not accept dbt uploads yet: upgrade the server first.")
1260
+ raise _project_scope_error(response)
1261
+ if response.status_code == 403:
1262
+ raise _project_scope_error(response)
1263
+ if response.status_code >= 400:
1264
+ raise ApiError(f"Cassis API returned HTTP {response.status_code}: {response.text[:500]}")
1265
+
1266
+
1267
+ def _readable_detail(detail: Any) -> str:
1268
+ """A FastAPI validation error list as `field: message` pairs; any other detail as text."""
1269
+ if not isinstance(detail, list):
1270
+ return str(detail)
1271
+ parts = []
1272
+ for error in detail:
1273
+ if not isinstance(error, dict):
1274
+ parts.append(str(error))
1275
+ continue
1276
+ location = [str(part) for part in error.get("loc") or [] if part not in ("body", "query", "path")]
1277
+ message = str(error.get("msg") or "invalid")
1278
+ parts.append(f"{'.'.join(location)}: {message}" if location else message)
1279
+ return "; ".join(parts)
1280
+
1281
+
1282
+ def _dbt_observation_response(response: httpx.Response, url: str) -> dict[str, Any]:
1283
+ _raise_for_dbt_status(response)
1284
+ result = _parse_json_response(response, url)
1285
+ if not isinstance(result, dict) or "id" not in result or "status" not in result:
1286
+ raise ApiError(f"Unexpected response shape from the Cassis API at {url}.")
1287
+ return result
1288
+
1289
+
1290
+ def post_dbt_observation(
1291
+ *,
1292
+ api_url: str,
1293
+ api_key: str,
1294
+ project_id: str,
1295
+ files: "dict[str, bytes]",
1296
+ fields: "dict[str, str]",
1297
+ transport: Optional[httpx.BaseTransport] = None,
1298
+ ) -> dict[str, Any]:
1299
+ """POST gzip-compressed dbt artifacts to /api/ci/projects/{project_id}/dbt/observations.
1300
+
1301
+ ``files`` maps each artifact's file name (``manifest.json``, ...) to its
1302
+ gzip bytes; ``fields`` carries the git context. Returns the PROCESSING
1303
+ upload record.
1304
+ """
1305
+ url = api_url.rstrip("/") + f"/api/ci/projects/{project_id}/dbt/observations"
1306
+ multipart = {name[: -len(".json")]: (f"{name}.gz", content, "application/gzip") for name, content in files.items()}
1307
+ try:
1308
+ with _client(timeout=ONTOLOGY_TREE_TIMEOUT_SECONDS, transport=transport) as client:
1309
+ response = client.post(url, files=multipart, data=fields, headers={"Authorization": f"Bearer {api_key}"})
1310
+ except httpx.HTTPError as exc:
1311
+ raise ApiError(f"Could not reach the Cassis API at {url}: {exc}") from exc
1312
+ return _dbt_observation_response(response, url)
1313
+
1314
+
1315
+ def get_dbt_observation(
1316
+ *,
1317
+ api_url: str,
1318
+ api_key: str,
1319
+ project_id: str,
1320
+ observation_id: str,
1321
+ transport: Optional[httpx.BaseTransport] = None,
1322
+ ) -> dict[str, Any]:
1323
+ """GET /api/ci/projects/{project_id}/dbt/observations/{observation_id} and return the upload record."""
1324
+ url = api_url.rstrip("/") + f"/api/ci/projects/{project_id}/dbt/observations/{observation_id}"
1325
+ try:
1326
+ with _client(transport=transport) as client:
1327
+ response = client.get(url, headers={"Authorization": f"Bearer {api_key}"})
1328
+ except httpx.HTTPError as exc:
1329
+ raise ApiError(f"Could not reach the Cassis API at {url}: {exc}") from exc
1330
+ return _dbt_observation_response(response, url)