Skip to content
Merged
43 changes: 43 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,49 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.12.9] - 2026-11-04

**Claude Code and other coding agents can call altimate-code's data tools directly.** `altimate-code mcp serve` and the new model-free `altimate-code tool` CLI run the same tools without a second model: reads by default, writes only with `--allow-write` (over MCP, `warehouse_add` can save a connection without it). In projects linked to a workspace, what people teach the agent can become a team rule: they confirm it on a card, the workspace owner reviews it, and approved rules reach every teammate's sessions. `dbt_lineage` and `impact_analysis` now return structured lineage, and column impact traces each downstream model's compiled SQL. Also in this release: `auth logout` removes the saved Altimate gateway key, the analyst stays read-only whatever config grants, and the home screen suggests the Claude Code and Codex plugins.

**Heads-up for support:**
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Resolved
- **MCP and `tool run` requests are read-only unless `--allow-write`.** The database enforces read-only on PostgreSQL, SQLite and DuckDB; on other warehouses it rests on SQL classification, so stored functions with side effects are not blocked. `DROP DATABASE`, `DROP SCHEMA` and `TRUNCATE` are refused even with `--allow-write`, but that block also reads the SQL text and is best effort. Use a read-only database user for a hard guarantee. `--allow-write` is all or nothing, and statements such as `SET`, `ATTACH`, `COPY ... TO`, `INSTALL` and DuckDB `SUMMARIZE` count as writes.
- **`mcp serve` allows `warehouse_add` without `--allow-write`**, so a calling agent can save a connection; `tool run` needs `--allow-write` for it. Tool results, including query rows and sample values, go to the calling agent's model provider. Large results are spilled to private local files.
- **`tool run` starts a per-project background process** that keeps connections warm and exits after 10 idle minutes. `--no-daemon` runs a call in its own process, and `ALTIMATE_TOOL_DAEMON=0` turns the background process off.
- **Workspace skill syncs can write copies into `<project>/.claude/skills/`** when `ALTIMATE_CLAUDE_SKILL_SYNC=1` is set, which the Claude Code plugin does. Skills you wrote with the same name are never touched; unlinking or signing out removes the copies.
- **`schema_inspect` without a table** lists schemas, or up to 500 tables with catalog row estimates, instead of scanning.
- **`learn status` prints `Learning from sessions: on|off (source)`** where it printed `Capture:`, and learn messages say "rule" where they said "lesson". The JSON output only gains a `rules` block.
- **In a linked project, `training_save` with `kind: "rule"` shows a proposal card** instead of writing training, and `learn bootstrap` / `learn import-reviews` send what they find for review.
- **Telemetry carries the signed-in user ID and tenant** when the machine is signed in to Altimate, so those events are not anonymous; no email address, hashed or not, is sent. One `GET /users/me` lookup per credential finds them, and none is made while telemetry is off. Rows from machines that are not signed in keep the machine id. Turning telemetry off sends one final `telemetry_opted_out` event (machine id and opt-out source only), then nothing.
- **The analyst's built-in write tools stay off whatever config grants**, including `"*": "allow"`: file edits, SQL writes, memory saves, shell and delegation. Configs that relied on a grant to give the analyst more power will see those tools denied. `task` from the analyst is limited to `data-explorer`; MCP and custom tools still follow config.
- **A symlink inside an allowed external folder that points elsewhere now asks** for the folder it points to, for every agent.
- **`auth logout` removes the saved gateway key**, `~/.altimate/altimate.json`, MCP server entries that carry that key (add the datamate again after signing in), and the cached telemetry identity.
- **Ctrl+C during browser sign-in exits with status 130**, not 0.
- **The Altimate LLM Gateway free-token allowance is 1M tokens.**
- **Read-only `dbt_lineage` calls never start the dbt adapter**, which runs `dbt deps`, writes `target/` and can run macro SQL: MCP and `tool run` calls without `--allow-write`, and plan-mode calls, answer from the manifest. With writes allowed it asks the adapter when the installed dbt integration has `getModelLineage`, and otherwise answers from the manifest and says why.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This support-facing bullet doesn't say that the manifest answer ignores column. On read-only or no-integration calls, dbt_lineage returns the whole model's lineage, not a column trace. Add that the manifest answer covers the model's own compiled SQL only and ignores column, so support can explain it.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At CHANGELOG.md, line 26:

<comment>This support-facing bullet doesn't say that the manifest answer ignores `column`. On read-only or no-integration calls, `dbt_lineage` returns the whole model's lineage, not a column trace. Add that the manifest answer covers the model's own compiled SQL only and ignores `column`, so support can explain it.</comment>

<file context>
@@ -23,6 +23,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - **`auth logout` removes the saved gateway key**, `~/.altimate/altimate.json`, MCP server entries that carry that key (add the datamate again after signing in), and the cached telemetry identity.
 - **Ctrl+C during browser sign-in exits with status 130**, not 0.
 - **The Altimate LLM Gateway free-token allowance is 1M tokens.**
+- **Read-only `dbt_lineage` calls never start the dbt adapter**, which runs `dbt deps`, writes `target/` and can run macro SQL: MCP and `tool run` calls without `--allow-write`, and plan-mode calls, answer from the manifest. With writes allowed it asks the adapter when the installed dbt integration has `getModelLineage`, and otherwise answers from the manifest and says why.
+- **`impact_analysis` with a column can report models as `uncertain` or `untraced`** instead of a confident answer, for example a `select *` from a parent whose schema.yml doesn't list the column, a NATURAL JOIN, or a model with no compiled SQL. A parent's whole row passed as a value (`to_json(o)`) is not caught yet.
 
</file context>
Suggested change
- **Read-only `dbt_lineage` calls never start the dbt adapter**, which runs `dbt deps`, writes `target/` and can run macro SQL: MCP and `tool run` calls without `--allow-write`, and plan-mode calls, answer from the manifest. With writes allowed it asks the adapter when the installed dbt integration has `getModelLineage`, and otherwise answers from the manifest and says why.
- **Read-only `dbt_lineage` calls never start the dbt adapter**, which runs `dbt deps`, writes `target/` and can run macro SQL: MCP and `tool run` calls without `--allow-write`, and plan-mode calls, answer from the manifest. With writes allowed it asks the adapter when the installed dbt integration has `getModelLineage`, and otherwise answers from the manifest and says why. The manifest answer covers only the model's own compiled SQL, so a `column` argument returns the whole model's lineage there.

- **`impact_analysis` with a column can report models as `uncertain` or `untraced`** instead of a confident answer, for example a `select *` from a parent whose schema.yml doesn't list the column, a NATURAL JOIN, or a model with no compiled SQL. A parent's whole row passed as a value (`to_json(o)`) is not caught yet.

### Added

- **`altimate-code tool` CLI and expanded `mcp serve`.** `tool list`, `schema`, `run`, `context` and `daemon` call the same tools as `mcp serve` through one adapter, one allow-list and one permission layer. Default MCP tools now include `impact_analysis`, `dbt_lineage`, `altimate_core_column_lineage`, `schema_detect_pii`, `data_diff`, `altimate_core_equivalence` and `sql_translate`; `--all-tools` exposes the full list. Workspace tools are on for `tool` commands in linked projects (`--no-workspace-tools` turns them off) and opt-in for `mcp serve` (`--workspace-tools`). `link --claude-md` writes routing guidance into `CLAUDE.md`. (#1609)
- **Workspace rules.** A proposal card in the TUI (`ctrl+x p`), owner review with approve, edit and "Not a rule", `/rules` and a sidebar `rules:` line, `learn sync`, and approved rules delivered into every session with their `applies_to` paths. See "Workspace rules" in the learn docs. (#1605)
- **Plugin tips on the home screen** for Claude Code and Codex, and a README section on using altimate-code from them. (#1625)
- **Workspace telemetry** for linking, usage, the engine and the starter. The telemetry reference lists every new event. (#1513)
- **Signed-in identity on telemetry and a final opt-out event.** (#1501)
- **Structured `dbt_lineage`.** It returns the model's nodes, columns and column edges (3 hops each way) as structured content when the dbt integration answers, and its own lineage fields from the manifest otherwise. It finds a model by name, unique_id or project-relative SQL path. `include_graph` adds the whole graph, up to 500 nodes each side; over MCP the graph is left out. (#1637, #1635)
- **Column reach in `impact_analysis`.** With a column, it traces each downstream model's and snapshot's compiled SQL in dependency order, following renames, and returns `column_reach`: certain readers with the output columns they feed, `uncertain` and `untraced` models, and the exposures on each. It also returns `downstream` and `exposures`. Over MCP both tools declare an output schema and return structured content capped at 60 KB. (#1635)

### Changed

- **The analyst stays read-only.** After config, it denies again every built-in tool that changes state, so a wildcard or explicit grant cannot turn them back on, and it can read the files that ship with global skills. (#1597)
- **External-directory checks follow symlinks**, so a link in an allowed folder cannot reach a folder that is not allowed. (#1597)
- **The analyst asks for high reasoning effort** on adaptive-thinking models unless the user or agent config sets one. (#1609)

### Fixed

- **`auth logout` removes the saved Altimate gateway key** and the MCP entries that carry it. Sign-out now also removes the cached telemetry identity, as the telemetry reference says. (#1606)
- **Ctrl+C during browser sign-in exits 130** instead of reporting success. (#1624)
- **Docs and the TUI show the 1M free-token allowance.** (#1611)

## [0.12.8] - 2026-10-23

**Claude Code can work through your Altimate workspace.** With the Altimate Code plugin installed and the project linked, tasks Claude hands to altimate-code run with the workspace's skills, knowledge and shared memory, and a headless `run` no longer hangs waiting on a stdin nobody writes to. Also in this release: the curl and PowerShell installers add an `altimate-code` command, the analyst can read workspace memory, `learn status` shows whether a lesson keeps being corrected, `schema_inspect` shows what column values look like, and SQL policy checks fail closed on statements they cannot analyze.
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/configure/mcp-servers.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Add this entry to your MCP client's configuration (for example, Claude Code's `.
}
```

By default the server exposes `warehouse_list`, `warehouse_add`, `sql_execute`, `sql_materialize` and `answer_commit`, plus `impact_analysis`, `dbt_lineage`, `altimate_core_column_lineage`, `schema_detect_pii`, `data_diff`, `altimate_core_equivalence` and `sql_translate`. Pass `tables` to `sql_execute` to get those tables' columns and value profiles in the same call as its queries. Start with `--all-tools` to expose every data tool, the same set `altimate-code tool list` shows. Without `--connections`, the server reads the usual connection sources and re-reads them while none are configured, so a connections file written mid-session, or a connection added with `warehouse_add`, is picked up without a restart. A connections file is a JSON object keyed by connection name:
By default the server exposes `warehouse_list`, `warehouse_add`, `sql_execute`, `sql_materialize` and `answer_commit`, plus `impact_analysis`, `dbt_lineage`, `altimate_core_column_lineage`, `schema_detect_pii`, `data_diff`, `altimate_core_equivalence` and `sql_translate`. Pass `tables` to `sql_execute` to get those tables' columns and value profiles in the same call as its queries. Start with `--all-tools` to expose every data tool, the same set `altimate-code tool list` shows. In a project linked to a workspace, `--workspace-tools` adds `workspace_context`, `workspace_skill` and `workspace_execute`; in an unlinked project `workspace_context` reports what setup is missing. Without `--connections`, the server reads the usual connection sources and re-reads them while none are configured, so a connections file written mid-session, or a connection added with `warehouse_add`, is picked up without a restart. A connections file is a JSON object keyed by connection name:

```json
{
Expand All @@ -37,7 +37,7 @@ At startup the server lists each connection's name and type in its instructions,

<!-- altimate_change start — MCP read-only limits and private output lifecycle -->

Source writes are refused by default. Without `--allow-write`, source queries request database-enforced read-only execution on PostgreSQL, SQLite and DuckDB. For other connection types, read-only is enforced by SQL classification only; stored functions with side effects are not blocked — use a read-only database user. The server prints this caveat on stderr at startup and includes it in `warehouse_list` for each affected connection. Add `--allow-write` to enable SQL write permissions; existing hard safety blocks still apply (`DROP DATABASE`, `DROP SCHEMA` and `TRUNCATE` stay blocked). `warehouse_add` may write local connection configuration (the connections file and the operating system credential store) even in read-only mode. `sql_materialize` always requires a read-only source query and writes only into the server session's temporary `scratch` warehouse. Scratch tables disappear when the server exits. Large results may include a local spill file path that your client can open with any file reader. Spill files use a private directory and are deleted when the server exits. Local `tool_output.max_lines` and `tool_output.max_bytes` settings control output limits; remote agent configuration is not loaded.
Source writes are refused by default. Without `--allow-write`, source queries request database-enforced read-only execution on PostgreSQL, SQLite and DuckDB. For other connection types, read-only is enforced by SQL classification only; stored functions with side effects are not blocked — use a read-only database user. The server prints this caveat on stderr at startup and includes it in `warehouse_list` for each affected connection. Add `--allow-write` to enable SQL write permissions; `DROP DATABASE`, `DROP SCHEMA` and `TRUNCATE` are still refused. That block reads the SQL text, so it is best effort; use a database user without those privileges for a hard guarantee. `warehouse_add` may write local connection configuration (the connections file and the operating system credential store) even in read-only mode. `sql_materialize` always requires a read-only source query and writes only into the server session's temporary `scratch` warehouse. Scratch tables disappear when the server exits. Large results may include a local spill file path that your client can open with any file reader. Spill files use a private directory and are deleted when the server exits. Local `tool_output.max_lines` and `tool_output.max_bytes` settings control output limits; remote agent configuration is not loaded.

<!-- altimate_change end -->

Expand Down
8 changes: 4 additions & 4 deletions docs/docs/reference/security-faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export ALTIMATE_CLI_MODELS_PATH=/path/to/models.json

## What telemetry is collected?

Anonymous usage telemetry, including event names, token counts, timing, and error types. **Never** code, queries, credentials, file paths, or prompt content. See the full [Telemetry reference](telemetry.md) for the complete event list.
Usage telemetry, including event names, token counts, timing, and error types. **Never** code, queries, credentials, file paths, or prompt content. See the full [Telemetry reference](telemetry.md) for the complete event list.

Disable telemetry entirely:

Expand All @@ -164,8 +164,8 @@ export ALTIMATE_TELEMETRY_DISABLED=true

### How does Altimate Code identify users for analytics?

- **Logged-in users:** Your email is SHA-256 hashed before sending. We never see your raw email.
- **Anonymous users:** A random UUID (`crypto.randomUUID()`) is generated on first run and stored at `~/.altimate/machine-id`. This is NOT tied to your hardware, OS, or identity — it's purely random.
- **Signed-in users:** Events carry your Altimate user ID and tenant, so they are not anonymous. No email address, hashed or not, is sent. See [User identification](telemetry.md) for how the ID is looked up and cached.
- **Users who are not signed in:** A random UUID (`crypto.randomUUID()`) is generated on first run and stored at `~/.altimate/machine-id`. This is NOT tied to your hardware, OS, or identity — it's purely random.
- **Both identifiers** are only sent when telemetry is enabled. Disable with `ALTIMATE_TELEMETRY_DISABLED=true` or the `telemetry.disabled` config option.
- **No fingerprinting:** We do not use browser fingerprinting, hardware IDs, MAC addresses, or IP-based tracking.
- **CLI auth flow:** When you sign in via `altimate auth login`, the anonymous machine ID is included in the authorization URL and associated with your account in product analytics for funnel analysis. This is suppressed when you disable telemetry — via `ALTIMATE_TELEMETRY_DISABLED=true` or the `telemetry.disabled` config option — and the machine ID is omitted from the URL entirely.
Expand All @@ -184,7 +184,7 @@ No code, queries, file paths, or personal information is included. This event he
The install scripts (`altimate.sh/install`, `install.ps1`), the npm postinstall, and the VS Code extension's installer send nothing themselves and contact no telemetry endpoint. They only record the version and installer name to a local file that the CLI reads on its next run, so the opt-out above decides whether anything is ever transmitted.

!!! warning "One caveat on the config-file opt-out"
The environment variables (`ALTIMATE_TELEMETRY_DISABLED`, `OPENCODE_DISABLE_TELEMETRY`) are always honoured. The `telemetry.disabled` **config key** is read during telemetry startup, which can run before the CLI's config is resolvable — and in that case startup currently proceeds with telemetry enabled. A user who has opted out via the config key alone may therefore still have this event transmitted. Use an environment variable if you need a guarantee.
The environment variables (`ALTIMATE_TELEMETRY_DISABLED`, `OPENCODE_DISABLE_TELEMETRY`) are always honoured. The `telemetry.disabled` **config key** is read from the config files at telemetry startup. If a config file cannot be read or parsed at that point, startup proceeds with telemetry enabled, so this event may still be transmitted. Use an environment variable if you need a guarantee.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This caveat now describes telemetry staying on when the config file is unreadable as the settled behavior. The team guidance for telemetry.disabled is to fail closed, so either keep the wording marked as a known gap or fix startup to fail closed and update this text to match.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At docs/docs/reference/security-faq.md, line 187:

<comment>This caveat now describes telemetry staying on when the config file is unreadable as the settled behavior. The team guidance for `telemetry.disabled` is to fail closed, so either keep the wording marked as a known gap or fix startup to fail closed and update this text to match.</comment>

<file context>
@@ -184,7 +184,7 @@ No code, queries, file paths, or personal information is included. This event he
 
 !!! warning "One caveat on the config-file opt-out"
-    The environment variables (`ALTIMATE_TELEMETRY_DISABLED`, `OPENCODE_DISABLE_TELEMETRY`) are always honoured. The `telemetry.disabled` **config key** is read during telemetry startup, which can run before the CLI's config is resolvable — and in that case startup currently proceeds with telemetry enabled. A user who has opted out via the config key alone may therefore still have this event transmitted. Use an environment variable if you need a guarantee.
+    The environment variables (`ALTIMATE_TELEMETRY_DISABLED`, `OPENCODE_DISABLE_TELEMETRY`) are always honoured. The `telemetry.disabled` **config key** is read from the config files at telemetry startup. If a config file cannot be read or parsed at that point, startup proceeds with telemetry enabled, so this event may still be transmitted. Use an environment variable if you need a guarantee.
 
 ## What happens when I authenticate via a well-known URL?
</file context>


## What happens when I authenticate via a well-known URL?

Expand Down
4 changes: 3 additions & 1 deletion docs/docs/usage/tool.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ Only the data tools are exposed. Coding tools (`bash`, `read`, `edit`, ...), `db

Runs are read-only by default. Reads such as `sql_execute` on a SELECT and `schema_inspect` are allowed; anything else is refused with `Permission denied: <kind>. Re-run with --allow-write to enable writes.` Tools that change local configuration (`warehouse_add`, `warehouse_remove`, `warehouse_install_driver`) also need `--allow-write`.

`DROP DATABASE`, `DROP SCHEMA` and `TRUNCATE` stay blocked even with `--allow-write`.
`DROP DATABASE`, `DROP SCHEMA` and `TRUNCATE` are refused even with `--allow-write`. The block reads the SQL text, so it is best effort: statements built at run time, for example by a stored procedure, are not seen. For a hard guarantee, connect with a database user that lacks `DROP` and `TRUNCATE`.

Tool results go to the model of the agent that called the tool. `sql_execute` returns result rows, `warehouse_list` shows one sample row per table of a local database, and `schema_inspect` and `sql_execute` with `tables` return value profiles per column; set `ALTIMATE_DISABLE_SCHEMA_VALUES=1` to leave the value profiles out. Keep passwords and keys out of `warehouse_add` arguments, which end up in the calling agent's transcript: reference credentials from a connections file or `ALTIMATE_CODE_CONN_*` variables instead.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The value-profile sentence leaves out what a profile contains. State that columns with at most 20 distinct values list them all, text columns above that show frequent values (and shapes for short text), and other columns show only distinct count and null share.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At docs/docs/usage/tool.md, line 56:

<comment>The value-profile sentence leaves out what a profile contains. State that columns with at most 20 distinct values list them all, text columns above that show frequent values (and shapes for short text), and other columns show only distinct count and null share.</comment>

<file context>
@@ -51,7 +51,9 @@ Only the data tools are exposed. Coding tools (`bash`, `read`, `edit`, ...), `db
-`DROP DATABASE`, `DROP SCHEMA` and `TRUNCATE` stay blocked even with `--allow-write`.
+`DROP DATABASE`, `DROP SCHEMA` and `TRUNCATE` are refused even with `--allow-write`. The block reads the SQL text, so it is best effort: statements built at run time, for example by a stored procedure, are not seen. For a hard guarantee, connect with a database user that lacks `DROP` and `TRUNCATE`.
+
+Tool results go to the model of the agent that called the tool. `sql_execute` returns result rows, `warehouse_list` shows one sample row per table of a local database, and `schema_inspect` and `sql_execute` with `tables` return value profiles per column; set `ALTIMATE_DISABLE_SCHEMA_VALUES=1` to leave the value profiles out. Keep passwords and keys out of `warehouse_add` arguments, which end up in the calling agent's transcript: reference credentials from a connections file or `ALTIMATE_CODE_CONN_*` variables instead.
 
 ## Daemon
</file context>


## Daemon

Expand Down
Loading
Loading