Query
Model

GQL-T: a temporal extension of ISO GQL for bitemporal and provenance-aware graph queries.

GQL-T overview

Substrate provides both a query language (GQL-T) and an RPC API. GQL-T extends ISO GQL with first-class bitemporal and provenance support.

AT SYSTEM <timestamp>

Query at a specific system time (what we knew then)

AT VALID <timestamp>

Query at a specific valid time (what was true then)

BETWEEN SYSTEM <t1> AND <t2>

Find changes in a system-time window

PROVENANCE()

Return provenance metadata for matched elements

CITE()

Return artifact citations (hash, byte ranges, commit ref)

Example queries

Q1: Dependency graph as-of valid time

Show dependencies that were true on Nov 15, using current knowledge.

FROM GRAPH glitchy
AT SYSTEM HEAD()
AT VALID TIMESTAMP("2025-11-15T00:00:00Z")
MATCH (s:Service { name:"affiliate-api" })
      -[:DEPENDS_ON*1..4]->(d:Service)
RETURN d.name, d.owner, PATH
Uses: OUT_ADJ interval stabbing Bounded: depth 4

Q2: What did we believe at system time S

Show who we thought owned billing on January 5th.

FROM GRAPH glitchy
AT SYSTEM TIMESTAMP("2026-01-05T10:00:00Z")
AT VALID HEAD_VALID()
MATCH (s:Service { name:"billing" })
RETURN s.owner, PROVENANCE(s.owner)
Uses: BT_PRIMARY lookup Returns: value + provenance

Q3: What changed between two system times

Find all changes recorded in the first week of January.

FROM GRAPH glitchy
BETWEEN SYSTEM TIMESTAMP("2026-01-01T00:00:00Z")
            AND TIMESTAMP("2026-01-08T00:00:00Z")
MATCH (c:Change)
RETURN c.kind, c.subject, c.field,
       c.old_value, c.new_value, c.assertion_id
Uses: SYS_DELTA buckets Joins: affected lineages

Q4: Blast radius for a service

Find all services and teams affected if offer-ingester fails.

FROM GRAPH glitchy
AT SYSTEM HEAD()
AT VALID HEAD_VALID()
MATCH (s:Service { name:"offer-ingester" })
MATCH (s)-[:DEPENDS_ON*0..5]->(d:Service)
MATCH (d)<-[:OWNED_BY]-(team:Team)
RETURN d.name, team.name
Uses: adjacency cache Cap: 50k nodes visited

Q5: Provenance chain for a claim

Trace how a specific version was derived.

FROM GRAPH glitchy
AT SYSTEM HEAD()
MATCH (v:Version { version_id:"ver_abc123" })
MATCH (a:Assertion)-[:ASSERTED]->(v)
MATCH (a)-[:DERIVES_FROM*0..6]->(a2:Assertion)
RETURN a, a2, PATH
Uses: provenance graph Small degree, fast

Q6: Late-arriving data detector

Find facts with old valid-time but recent system-time (backfills).

FROM GRAPH glitchy
AT SYSTEM HEAD()
MATCH (v:Version)
WHERE v.valid_from < TIMESTAMP("2025-01-01T00:00:00Z")
  AND v.sys_from   > TIMESTAMP("2026-01-01T00:00:00Z")
RETURN v.subject, v.field, v.valid_from,
       v.sys_from, v.assertion_id
LIMIT 1000
Uses: VALID_RANGE + SYS_RANGE indexes

Q7: Latest SBOM for a build

Get the software bill of materials for a specific build.

FROM GRAPH glitchy
AT SYSTEM HEAD()
MATCH (b:Build { id:"build_789" })
MATCH (b)-[:HAS_SBOM]->(a:Artifact)
RETURN a.artifact_ref, a.format, PROVENANCE(a)
Supports: SPDX, CycloneDX

Q8: Incident timeline with evidence

Reconstruct an incident with linked artifacts and citations.

FROM GRAPH glitchy
AT SYSTEM HEAD()
MATCH (i:Incident { id:"inc_2026_001" })
MATCH (i)-[:HAS_EVENT]->(e:Event)
OPTIONAL MATCH (e)-[:EVIDENCE]->(art:Artifact)
RETURN e.time, e.type, e.summary, CITE(art)
ORDER BY e.time
CITE() returns: hash pointers + ranges

Query planning

1

Parse + validate

Syntax check, type check, schema validation.

2

Inject authz predicates

Rewrite query with relationship checks and label filters from the caller's permissions. Policy first, not last.

3

Resolve snapshot

Map system-time to snapshot bucket. For historical queries, identify base snapshot + delta overlay.

4

Choose expansion order

Start from most selective seed (indexed property lookups). Prefer pushing filters early.

5

Use interval indexes

Adjacency indexes support temporal stabbing. Valid-time filtering happens at index level.

6

Join provenance early

Attach provenance/citations as joins during traversal, not post-processing. Ensures redaction applies correctly.

Resource guardrails

Every query has hard limits. Breaching a limit returns partial results with a "truncated" flag and an audit entry.

Max expansions

Limit on total edges traversed per query

Max visited nodes

Cap on unique nodes touched (default: 50k)

Max path length

Bounded depth for variable-length paths

Max CPU time

Query timeout prevents runaway execution

Max result bytes

Cap on response payload size

Authz filter pushdown

Never fetch-then-filter; filter at storage

Bulk analytics: Queries lacking bounds are rejected unless the caller has bulk_analytics capability and runs in a batch queue with separate resource limits.

Continue reading