Ariel Baselines: Upstream-Only Writes, Allowlisted Reads
traceo intermediate 6 min read
ELI5
A baseline is a photograph of a set of documents, taken at a moment you want to prove later. Traceo used to let anyone take, edit and freeze photographs. Now the photographs are taken upstream, in a separate tool called ariel. Traceo only shows them, and only to customers on an approved list. One operation still crosses the line: an admin can copy a frozen photograph’s contents into their workspace as requirements.
Technical Deep Dive
Source: traceo-cat/traceo_mcp_server/traceo_mcp_server/ariel_integration/policy.py:1-130, ariel_integration/mcp_tools.py:36-430, ariel_integration/repositories/factory.py:27-53, migrations/008_reconcile_schema.sql:128-131 and :668-745; decision 0104 (accepted 2026-10-10) and finding 20261010-1536; traceo-cat origin/main 7d647ab, read 2026-10-25
Decision 0104 in one table
| Operation | Through REST or MCP | Rule |
|---|---|---|
| create, add-CI, freeze, build | refused for every caller, owner included | BASELINE_WRITE_DISABLED. Baselines enter only through ariel (I-1) |
| verify, diff, list, trace, validate | allowed only if the caller is authenticated and their tenant is in BASELINE_READ_TENANT_IDS | an unset or empty list means nobody reads, and a refused read looks exactly like a missing baseline |
ariel_sync baseline→traceo | admin/owner only, never anonymous, never a machine identity | checked from the principal, independently of ENABLE_RBAC |
ariel_sync traceo→baseline | refused | that direction would be a baseline write |
The operator chose “operator tenant only” for reads on 2026-10-10. The tenant comes from the workspace.tenant_id that AuthMiddleware resolves for each request (policy.py:89-128).
Why: the store was shared
Finding 20261010-1536 recorded that production traceo:mcp left TRACEO_STORAGE_BACKEND and ARIEL_BASELINES_PATH unset (read from the Railway project on 2026-10-10, not re-read for this card). With both unset, repositories/factory.py returns the default YAML-file BaselineManager over ./baselines (/app/baselines in the container). It is one shared directory with no owner field, so every tenant saw and could write every baseline. Signup had gone public that same day.
The Postgres ariel_* tables from 008 (ariel_baselines, ariel_configuration_items, ariel_traceo_references, ariel_sync_history, all with ENABLE + FORCE RLS) exist. They serve baselines only when TRACEO_STORAGE_BACKEND is postgres or postgres_yaml. The v1.0 card described those tables as the live store.
flowchart TD ENV{"TRACEO_STORAGE_BACKEND"} -->|"unset (prod, per 0104)"| YAML["BaselineManager: YAML files under /app/baselines, shared by all tenants"] ENV -->|"postgres / postgres_yaml"| PGR["PostgresBaselineRepository: ariel_* tables with RLS"] YAML --> POL["policy.py: writes refused, reads allowlisted"] PGR --> POL ARIEL["ariel upstream (pinned dependency)"] -->|"authors and freezes"| YAMLThe lifecycle still exists, upstream
baseline_status is draft, frozen, superseded, archived. The transitions happen in ariel, not through Traceo’s tools.
stateDiagram-v2 [*] --> draft : authored in ariel draft --> frozen : frozen in ariel frozen --> superseded : newer baseline names it as parent frozen --> archived draft --> archived superseded --> archived archived --> [*] frozen --> frozen : ariel_sync baseline_to_traceo (copies into workspace, admin/owner)Postgres shape (when that backend is selected)
erDiagram WORKSPACE ||--o{ ARIEL_BASELINES : owns ARIEL_BASELINES ||--o{ ARIEL_CONFIGURATION_ITEMS : contains ARIEL_BASELINES ||--o{ ARIEL_TRACEO_REFERENCES : cites ARIEL_BASELINES ||--o{ ARIEL_SYNC_HISTORY : logsKey Terms
- I-1 → Traceo’s invariant that baselines are authored upstream in ariel and arrive only through the pinned dependency.
BASELINE_READ_TENANT_IDS→ the backend env allowlist of tenants that may read baselines. Unset means nobody.BaselineManager→ ariel’s file-backed baseline store, and the default when no storage backend is configured.ariel_sync→ copies a frozen baseline’s items into a workspace as requirements. It is the only baseline-adjacent write left, restricted to admin/owner.
Q&A
Q: Where is a baseline authored and frozen?
A: Upstream, in ariel. Traceo’s create, add-CI, freeze and build tools return BASELINE_WRITE_DISABLED for everyone (decision 0104).
Q: Which store serves baselines in production by default, and why is that a tenancy problem?
A: With TRACEO_STORAGE_BACKEND unset, it is the YAML-file BaselineManager over one shared directory with no owner field, which every tenant saw. That is why reads are now allowlisted per tenant and writes are refused outright.
Q: Who may read a baseline through Traceo?
A: Authenticated callers whose tenant is listed in BASELINE_READ_TENANT_IDS. Anyone else gets an empty list or a 404, as if the baseline did not exist. A GHA OIDC machine principal can only reach GET /api/ariel/baselines/<id> and still faces the same allowlist.
Q: What does ariel_sync do, and who may run it? A: It pushes a frozen baseline’s contents into a workspace as requirements (baseline→traceo). Only authenticated admin or owner principals may run it, never a machine identity. The reverse direction is refused.
Examples
An owner on a tenant outside the allowlist calls ariel_baseline_list and gets an empty list. They call ariel_baseline_freeze("BL-3") and get BASELINE_WRITE_DISABLED. The operator’s tenant is on the list, so the operator can ariel_baseline_diff("BL-2","BL-3") and then ariel_sync BL-3 into their workspace to seed requirements from it.
neighbors on the map
- FastMCP Tool Surface (31 tools, HTTP-deployed) registering a new MCP tool
- JWT Auth: BetterAuth ES256 via JWKS, the GHA OIDC Machine Issuer, and RBAC adding a new permission to a tool