rfc: 0032 title: Query-schema and cost-model resource for the MCP surface (RFC 0027 amendment) status: green author: Jens Holdgaard Pedersen jens@holdgaard.org drafting-assistance: Claude created: 2026-07-13 supersedes: — superseded-by: —
RFC 0032 — Query-schema and cost-model resource for the MCP surface
1. Summary
Add a second MCP resource — ourios://query-schema — beside the
RFC 0027 grammar resource, carrying the stored-log field
vocabulary and a query-class cost model: the fixed OTLP log
columns (including the OTel 1–24 severity scale the DSL’s severity
names compile onto), the promoted attribute columns of this
deployment (the running PromotedAttributes set, RFC 0022), and a
structural classification of predicate kinds into cost tiers
(index-backed / pruned / scan). RFC 0027 is accepted (terminal), so
this lands as a new RFC amending it, à la RFC 0022/0023/0024:
read-only, additive, no new tool — the existing
list_resources/read_resource hook gains one resource. The grammar
resource already teaches an agent how to write a query;
list_templates gives the body vocabulary; this completes the set
with the field vocabulary and with which query shapes the backend
answers cheaply.
2. Motivation
- Agents guess field names; the deployment knows them. An agent
can learn the DSL from
ourios://dsl-grammar, but the grammar’sresource.<key>/attr.<key>productions are open-ended — which keys exist as typed, prunable columns is per-deployment configuration (storage.promoted_attributes) that no client can guess. OTel field practice supports the claim: an OTel-native vendor doing Drain-style mining reports that handing an agent semantic-convention + resource-attribute context makes it “way better at writing correct queries on the first try” (OTel Night Berlin 2025, sig-end-user transcript). Issue #465 is the scoping record. - The severity scale is a first-try failure mode.
severity >= erroronly works if the client knows severity is the numeric OTelSeverityNumberand the names are four-wide bands (error→ 17..=20). That mapping is Ourios’s documented choice (RFC 0002 §6.1,SeverityName::floor/ceil); it belongs in the protocol, not in each consumer’s prompt. - The cost model is structural, and agents can exploit it. The
RFC 0031 comparative program (§9.13, epic #498) demonstrated the
durable shape of Ourios query cost: exact-id lookups and promoted
equality prune to a handful of row groups, time windows prune by
statistics, body-substring browses scan. That tiering is true by
construction — it follows from which columns the writer
bloom-filters and page-indexes — so it can be published as
structure without ever publishing benchmark numbers (which rot).
A consumer that knows
trace_id == …is cheap andcontains(body, …)is a scan writes better queries at zero per-query cost to the backend, and is steered onto the backend’s structural strengths (pillar #1/#2,CLAUDE.md§2). - Cheap by construction. The
list_resources/read_resourcehook, the config plumbing (storage.promoted_attributesis already resolved into aPromotedAttributesat startup,ourios-server/src/main.rs), and the tier facts (which columns are bloomed,ourios-parquet/src/writer.rs) all exist. The new work is one JSON document and threading the promoted set into the querier role’s MCP handler.
3. Proposed design
3.1 Placement
- A second
Resourcein the RFC 0027 module (crates/ourios-server/src/mcp.rs), URIourios://query-schema, MIMEapplication/json.list_resourcesreturns both resources;read_resourcedispatches on URI. - The querier role today does not receive the resolved
PromotedAttributes(only the receiver’s write path does). The server threadsconfig.promotedintomcp_router→OuriosMcp, and the resource document is built once at role startup from that set — configuration is startup-static (RFC 0020), so the document is immutable for the process lifetime, like the grammar section. - Read-only contract untouched: no new tool, no write, no tenant-scoped data. The document derives exclusively from static configuration and compiled-in schema facts — never from ingested telemetry, so it is the one MCP payload that carries no untrusted-content caveat.
3.2 The document
The resource body is one versioned JSON object (format_version
evolution hook per the RFC 0033 precedent: consumers treat an unknown
version as “fetch nothing, fall back to the grammar + docs”):
{
"format_version": 1,
"fields": [
{ "name": "ts", "type": "timestamp" },
{ "name": "observed_ts", "type": "timestamp" },
{ "name": "severity", "type": "integer" },
{ "name": "body", "type": "string" },
{ "name": "trace_id", "type": "hex_string" },
{ "name": "span_id", "type": "hex_string" },
{ "name": "scope", "type": "string" },
{ "name": "flags", "type": "integer" },
{ "name": "service", "type": "string" },
{ "name": "template_id", "type": "integer" },
{ "name": "confidence", "type": "float" },
{ "name": "lossy", "type": "boolean" }
],
"severity": {
"comparison": "numeric, OTel SeverityNumber 1-24",
"names": [
{ "name": "trace", "floor": 1, "ceil": 4 },
{ "name": "debug", "floor": 5, "ceil": 8 },
{ "name": "info", "floor": 9, "ceil": 12 },
{ "name": "warn", "floor": 13, "ceil": 16 },
{ "name": "error", "floor": 17, "ceil": 20 },
{ "name": "fatal", "floor": 21, "ceil": 24 }
]
},
"promoted_attributes": {
"resource": ["service.name", "k8s.namespace.name"],
"log": ["http.route"]
},
"cost_model": {
"tiers": ["index_backed", "pruned", "scan"],
"classification": [
{ "kind": "exact_equality", "fields": ["trace_id", "span_id", "template_id"],
"tier": "index_backed", "mechanism": "bloom" },
{ "kind": "ordering_or_equality", "fields": ["severity"],
"tier": "index_backed", "mechanism": "statistics" },
{ "kind": "promoted_attribute_equality",
"fields": ["service", "resource.<promoted key>", "attr.<promoted key>"],
"tier": "index_backed", "mechanism": "bloom" },
{ "kind": "time_window", "fields": ["ts", "observed_ts"],
"tier": "pruned", "mechanism": "statistics" },
{ "kind": "non_promoted_attribute_predicate",
"fields": ["resource.<other key>", "attr.<other key>"],
"tier": "scan" },
{ "kind": "body_substring_or_regex", "fields": ["body"],
"tier": "scan" },
{ "kind": "unscoped_browse", "fields": [],
"tier": "scan" }
]
}
}
Normative content rules:
fields— exactly the RFC 0002 §7fieldproduction (the DSL surface, not the raw Parquet schema; hazard §4.6 — the resource must not leak storage columns the DSL does not expose). Each entry MAY carry a shortdescriptionstring; the shape above is the minimum.severity— the six names with theirfloor/ceilbands MUST equal the DSL’sSeverityName::floor/ceilmapping (crates/ourios-querier/src/dsl/ir.rs): ordering comparisons use the floor, equality tests the band. This is the resource’s answer to “how do I writeseverity >= ERROR”.promoted_attributes— the effective running set from the threadedPromotedAttributes(resource_keys()/log_keys()):service.namealways present and first, configured keys after, in the deduplicated config order. This is the per-deployment half an agent cannot guess, and it is what makes thecost_modeldeployment-specific:promoted_attribute_equalityis index-backed for exactly these keys, in this instance; the same predicate on any other key isnon_promoted_attribute_predicate(the RFC 0022 §3.3 JSON-LIKEfallback — correct, unpruned).cost_model— structure only, never numbers: no latencies, no byte counts, no ratios. The tier facts are true by construction of the writer:bloommechanism entries correspond one-to-one to the columnswriter.rsactually bloom-filters today:template_id(RFC 0005 §3.6),trace_id/span_id(the RFC 0031 L3 fix), and every promoted attribute column (RFC 0022 §3.1).severitycarries no bloom filter — its predicates prune through min/max page statistics (ordinal data, where statistics are the right index); the resource saysstatistics, notbloom, because claiming index-backing that the writer does not provide is exactly the drift RFC0032.4 gates against.time_windowis therange(t1, t2)stage pruning on the time columns’ statistics;unscoped_browse(norangestage beyond the default look-back) and body substring/regex predicates are scans — expensive, still correct.
3.3 Tool-description placement rule
Each of the three RFC 0027 tool descriptions gains one advisory
sentence pointing at the resource, e.g. for query_logs: “Read
the ourios://query-schema resource first for the queryable fields,
the severity scale, and which predicates are index-backed.” The full
tiering lives only in the machine-readable resource — tool
descriptions are prompt real estate in every client context, and the
tiers would otherwise be paraphrased into prose that drifts. One
pointer, one source of truth.
3.4 What this RFC does not change
No Parquet schema change, no DSL change, no new tool, no new crate, no change to any RFC 0027 tool’s arguments or output. The RFC 0027 §5 suite must pass after this lands, with only the §3.1 two-resource amendment applied (RFC0032.6 pins the exact contract).
4. Alternatives considered
- Static-only resource (fixed columns + severity scale, no
config plumbing — issue #465’s first fork). Trivial to ship, but
it omits exactly the half an agent cannot guess: which
resource.<key>/attr.<key>predicates are typed, prunable columns here. Without the promoted set the cost model cannot be stated honestly either (promoted equality and non-promoted fallback land in different tiers). Rejected; the plumbing is one threaded value. - Put the schema in the tool descriptions. Descriptions ship
into every client’s context on
tools/list; a schema + cost table there is paid on every session and invites clients to treat prose as data. A resource is fetched on demand and machine-readable. Rejected — this RFC pins the one-advisory-sentence rule instead (§3.3). - Extend the grammar resource instead of adding a second one. The grammar resource’s contract is byte-identity with RFC 0002 §7 (RFC0027.6) — appending deployment-specific JSON would break that invariant and mix a static doc with dynamic config. Rejected.
- A
describe_schematool. Tools imply arguments and per-call work; this content is constant per process and tenant-independent. MCP resources exist precisely for this. Rejected (also keeps the RFC 0027 deny-list — “exactly the §3.2 three tools” — intact). - Serve
ourios-semconvnames. Wrong vocabulary: that crate holds Ourios’s own emitted-telemetry names (how the backend describes itself), not the stored-log query surface (issue #465 notes this explicitly). Rejected. - Include benchmark-derived cost numbers. The RFC 0031 numbers are corpus- and channel-dependent and rot with every writer change; the tier structure is what is durable. Rejected — the cost model is structural by rule (§3.2).
5. Acceptance criteria
Scenario ids RFC0032.<m>, referenced from test code.
Scenario RFC0032.1 — listed and readable. Given
querier.mcp.enabled, When a client lists resources, Then exactly two resources are advertised — the RFC 0027 grammar resource andourios://query-schema(application/json); When the client readsourios://query-schema, Then the body parses as JSON withformat_version: 1and carries the §3.2 top-level keys (fields,severity,promoted_attributes,cost_model); Andtools/liststill advertises exactly the RFC 0027 §3.2 three — no new tool.
Scenario RFC0032.2 — content matches the running config. Given
storage.promoted_attributesconfigured with resource and log keys, When the resource is read, Thenpromoted_attributesequals the effectivePromotedAttributesset —service.namefirst, configured keys deduplicated in order; And with the section omitted,promoted_attributes.resourceis["service.name"]and.logis empty; And two servers with different promoted sets serve different resource bodies (the per-deployment property).
Scenario RFC0032.3 — severity scale correctness. Given the resource body, Then the
severity.namesentries equal the DSL’sSeverityNamemapping — for each of the six names,floorequalsSeverityName::floorandceilequalsSeverityName::ceil— the test asserts against theourios-querierfunctions, not repeated literals, so the resource cannot drift from the compiler.
Scenario RFC0032.4 — cost-tier classification stability. Given the resource body, Then every
cost_model.classificationentry withmechanism: "bloom"names only columns the writer actually bloom-filters — the test derives the expected set from the writer’s properties for the configuredPromotedAttributes(template_id,trace_id,span_id, and everyPromotedAttributes::column_namescolumn) and asserts the resource’s index-backed equality kinds cover exactly the DSL fields backed by that set; Andseverity’s entry carriesmechanism: "statistics", never"bloom"; And no classification entry carries a numeric cost value (structure, never numbers).
Scenario RFC0032.5 — tool-description placement. Given
tools/list, Then each ofquery_logs,list_templates, andtemplate_driftcarries exactly one advisory sentence namingourios://query-schema, And no tool description enumerates tiers, severity bands, or promoted keys (the full tiering lives only in the resource).
Scenario RFC0032.6 — read-only contract preserved. Given the amendment applied, Then the RFC 0027 §5 suite passes with the §3.1 two-resource amendment applied (same tools, same outputs, grammar byte-identity and mime assertions intact — the one relocated assertion is
rfc0027_6_grammar_resource’s exactly-one-resource count, which moved to RFC0032.1’s exactly-two; the grammar test now locates the grammar among the advertised resources), And readingourios://query-schemaperforms no query, touches no tenant data, and its body contains no ingested-telemetry-derived content; And an unknown resource URI still returns the resource-not-found error.
6. Testing strategy
Mapped to CLAUDE.md §6.2:
- RFC0032.1/.2/.5/.6 — integration tests in
crates/ourios-server/tests/it/rfc0027_mcp.rs’s harness shape (in-process router, MCP JSON-RPC over/mcp):resources/list,resources/read,tools/listagainst servers built with distinctstorage.promoted_attributesconfigs;.6additionally re-runs the existing RFC 0027 suite untouched (tests are specifications — none may be weakened). - RFC0032.3/.4 — unit tests beside the resource builder in
mcp.rs, asserting againstSeverityName::floor/ceiland against the writer-properties bloom set derived from the samePromotedAttributesvalue, so both halves of the document are pinned to the code they describe rather than to literals. - At validation, the RFC 0027 §5.2 precedent applies: the official
MCP inspector CLI (an independent client) lists and reads the
resource against the served release binary, extending
scratch/validation/rfc0026-0027-validate.sh.
7. Open questions
- Template-vocabulary hints. Should the resource carry a
pointer at (or a sample of) the template vocabulary, or does
list_templatesalready cover the body-shape half cleanly? Current position: the resource stays tenant-independent and static per process; templates are per-tenant, queryable data and belong to the tool. Confirm before green. - Config reload. Configuration is startup-static today, so
the document is built once. If a future RFC makes
storage.promoted_attributesreloadable, the resource must follow and MCPlistChanged/subscription semantics become relevant — out of scope here, but the once-at-startup build is the assumption to revisit. -
severity_textexposure. The stored schema carriesseverity_text, but the DSL deliberately compares on the numeric scale (RFC 0002 §6.1). If the DSL ever exposes it, the resource’sfieldsfollows the grammar automatically — noting so the two don’t drift silently. - Tier vocabulary stability.
index_backed/pruned/scanare this RFC’s names; if RFC 0031’s docs settle on different public terminology for the query classes, align before green (renames after clients consume the resource cost aformat_versionbump).
8. References
- Issue #465 — the scoping record, including the 2026-07-13 maintainer comment adding the query-class cost model and the placement rule.
- RFC 0027 — the MCP query surface this RFC amends (
accepted, terminal); §3.2 resource precedent, §5.2 inspector-validation precedent. - RFC 0022 — promoted attribute columns:
PromotedAttributes,storage.promoted_attributes, the promoted-vs-fallback compile split the cost model encodes (crates/ourios-parquet/src/promoted.rs). - RFC 0002 §6.1/§7 — the DSL field surface and the severity
name→number choice (
crates/ourios-querier/src/dsl/ir.rs,SeverityName). - RFC 0005 §3.6 / RFC 0031 (L3, trace-context blooms) — the bloom
set the tiers rest on (
crates/ourios-parquet/src/writer.rs). - RFC 0033 §3.2 — the
format_versionevolution-hook precedent for small versioned JSON artifacts. - OTel Logs Data Model —
SeverityNumber1–24 and the compare-on-number mandate. - OTel sig-end-user, OTel Night Berlin 2025 transcript — the schema-context-for-agents motivation.
CLAUDE.md§2 (pillars #1/#2 — the pruning structure being published), §4.6 (DSL vs engine leakage — the resource describes the DSL surface only), §3.7 (tenancy — the resource is tenant-independent by design).