Skip to content

Proposal: Optional strict semantic boundaries for structured queries #273

Description

@KentonParton

An MCP tool normally presents an explicit capability boundary: the agent may use the resources, fields, and operations described by the tool schema, but it cannot reach beyond that interface.

A BSL model describes a similar contract through its dimensions, measures, and relationships. However, BSL does not currently enforce that contract as a strict query boundary. A structured query can reference columns that fall outside the semantic model when those columns still exist on the underlying Ibis relation.

This means a model may appear to expose only a defined set of semantic members while queries can still access undeclared implementation columns. For MCP and other agent-facing use cases, it would be valuable for BSL to optionally enforce the semantic model as the complete queryable boundary.

Minimal reproduction

import ibis
from ibis import _
from boring_semantic_layer import to_semantic_table


source = ibis.memtable(
    {
        "status": ["open", "closed", "open"],
        "private_amount": [100, 200, 300],
    }
)

opportunities = (
    to_semantic_table(source, name="opportunities")
    .with_dimensions(
        status=_.status,
    )
    .with_measures(
        opportunity_count=_.count(),
        pipeline_amount=_.private_amount.sum(),
    )
)

print("Declared dimensions:", sorted(opportunities.get_dimensions()))
print("Declared measures:", sorted(opportunities.get_measures()))

print(
    opportunities.query(
        dimensions=["private_amount"],
        measures=["opportunity_count"],
    ).execute()
)

print(
    opportunities.query(
        dimensions=["status"],
        measures=["opportunity_count"],
        filters=[
            {
                "field": "private_amount",
                "operator": ">=",
                "value": 200,
            }
        ],
    ).execute()
)

Output on boring-semantic-layer 0.3.14 from commit 1c05d19c95262856541e875c40ca4f2c13f21608:

Declared dimensions: ['status']
Declared measures: ['opportunity_count', 'pipeline_amount']

   private_amount  opportunity_count
0             200                  1
1             300                  1
2             100                  1

   status  opportunity_count
0    open                  1
1  closed                  1

Expected: both direct references to private_amount should be rejected because it is not a declared dimension or measure.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions