Decisions
Spec labels like D21 are shorthand for locked product rules. This page states
what each one means in plain language. Full rationale:
planning/01-decisions.md.
| Id | In one sentence |
|---|---|
D17 |
Agents use Query IR and catalog SQL — not GraphQL as the qLLM API. |
D18 |
Demo Compose/fixtures never become silent defaults for a real deploy. |
D19 |
Redis/Kafka (and similar) are read-only and non-destructive. |
D20 |
fromFilter rebuilds a REST key the API omitted from the body. |
D21 |
Row scope comes from the credential, not from a field the model can rewrite. |
D22 |
json cells are parsed objects/arrays; shape is a hint only. |
Agent surface is Query IR + catalog SQL
HTTP exposes Query IR and catalog SQL. MCP tools run catalog SQL. There is no GraphQL
agent API, tool, or IR↔GraphQL translation. An optional experimental
type: graphql source talks to an upstream GraphQL HTTP endpoint as data —
that is unrelated to how agents call qLLM.
Harness world stays separate
Compose, seeds, and fixtures/ prove the runtime. The binary only sees the
YAML you point at with --config-dir (or preset/catalog/project/CWD). No
valid project files → CONFIG_ERROR, never a silent fallback to demo hosts or
entities.
Key/stream sources are read-only
Experimental redis and kafka only use non-mutating reads (no
delete/set/pop, no consumer-group commit, no produce). Queries without the required
key/partition access path return UNSUPPORTED. Brokers/ACLs remain the real
safety boundary; the runtime is a second line.
Separately: Oracle, BigQuery, Snowflake, Elasticsearch, and S3-as-table are simply not source types today — that is a connector matrix gap, not D19.
REST omitted field → fromFilter
When a REST API accepts a filter (for example warehouse_id) but does not
echo it in the JSON body, mark the catalog field fromFilter: true. qLLM
copies the value from a top-level eq so joins and GROUP BY
still see the column. No matching eq → INVALID_IR. Body value
disagrees with the filter → SOURCE_ERROR.
Scope on the credential
qllm.access.yaml lists app types, not end users. Row
identity lives in a derived Bearer key (or static scope), and the catalog
entities[].scope forces an equality filter on that column. The model cannot
swap user_id in the query to see another tenant’s rows.
Parsed json cells + typed columns
A catalog field with type: json returns a parsed object or array in the
response envelope (not a JSON string). Optional shape is free text for the
LLM ({aisle, bin}, string[]); the runtime does not validate it.
Column types in SQL results follow the source/DuckDB type. Integers above 253
should be catalogued as string because number is DOUBLE.
Key-conditioned sources (DynamoDB, Cassandra, ksql, Redis, Kafka) still need the
documented accessPath equalities or they return UNSUPPORTED —
see Connectors. That requirement is separate from the json
cell rule, even though both show up when you design catalogs carefully.