Documentation

Query language (PQL)

PQL, the Peryx Query Language, provides one read surface over operational state. POST /+query accepts query text and parameters, then returns a bounded page of typed rows. The language supports selection, filtering, ordering, aggregation, joins, and pagination. It has no mutation operations.

PQL selects structured metadata. Ranked text search remains available through /+search.

Syntax

from <domain>
[ where <predicate> ]
[ select <field> [, <field> ...] ]
[ aggregate <func>(<column>) as <alias> [, ...] by <key> [, ...] ]
[ order by <field> [asc|desc] [, ...] ]
[ limit <n> ]

Predicates support and, or, not, parentheses, comparisons, membership with in (...), and prefix matching with starts_with. PQL has no arithmetic, function calls, or leading-wildcard matches. The planner checks query cost before execution.

Literal forms include strings, integers, booleans, and RFC 3339 timestamps prefixed with @, such as @2026-06-01T00:00:00Z.

Bind caller values through named parameters. The evaluator fills a :name placeholder from params; parameter values cannot change query structure.

curl -u alice:$PASSWORD \
  -H 'content-type: application/json' \
  -d '{
        "query": "from policy.decisions where repository == :repo and state == \"deny\" order by evaluated_at desc limit 25",
        "params": {"repo": "team-cache"}
      }' \
  http://127.0.0.1:4433/+query

The response contains rows and an optional cursor:

{
  "rows": [
    {
      "repository": "team-cache",
      "resource": "component-a",
      "state": "deny",
      "action": "serve",
      "evaluated_at": 1800000000,
      "fresh": true
    }
  ],
  "next_cursor": null
}

Domains

PQL provides two typed domains:

  • policy.decisions: bounded repository policy history
  • usage.reads: durable read and byte totals by repository resource

policy.decisions

ColumnTypeMeaning
repository, resourcestringShared repository and owner-normalized resource
group, artifactstringOptional owner-supplied grouping and artifact identity
actionstringEvaluated operation
statestringallow, deny, or wait
evaluated_attimestampEvaluation time in whole Unix seconds
freshboolCurrent repository, catalog, and policy generations match
source, rule, reasonstringRouted source, matched rule, and explanation; operator access required

usage.reads

ColumnTypeMeaning
repository, resourcestringRepository and resource
readsintLifetime artifact reads served
bytesintLifetime bytes served; operator access required

select * and an omitted select return all columns visible to the caller. A named selection returns those visible columns. Default ordering uses descending evaluated_at for policy decisions and descending reads for usage.

Aggregation

PQL supports count, sum, min, and max over declared numeric columns, grouped by declared keys.

curl -u alice:$PASSWORD \
  -H 'content-type: application/json' \
  -d '{"query": "from policy.decisions aggregate count() as decisions by state"}' \
  http://127.0.0.1:4433/+query

Use /+analytics/timeline for time buckets.

Joins

A query can join two domains on declared shared keys. Joins use inner semantics: an outer row appears after a match in the joined domain.

curl -u alice:$PASSWORD \
  -H 'content-type: application/json' \
  -d '{"query": "from policy.decisions join usage.reads on repository, resource where state == \"deny\" order by reads desc limit 25"}' \
  http://127.0.0.1:4433/+query

The planner admits a join when the joined domain indexes all join keys. It rejects a join that requires an unbounded scan with 400. A field present on either side keeps the stricter visibility class.

Authorization

The evaluator resolves the caller credential and adds a repository predicate before ordering and pagination. Query text cannot name or remove that predicate.

Filter repository by its configured repository name, which is the value stored in grants and records. A repository credential or reader can query its granted repository. An administrator can omit that filter for operator-wide data. A caller without domain access receives 404.

Repository-scoped callers see repository fields. Operator fields such as source, rule, and reason are omitted. Responses containing operator fields use Cache-Control: no-store; repository-level responses use private, no-cache.

Pagination

A page with more matches includes next_cursor. Send it as the request cursor to continue. The cursor binds to the authorization scope that created it. A grant change causes the caller's scope changed; restart the query.

Limits and errors

The default page size is 25, with a maximum of 100. Query text size, predicate depth, and execution cost have fixed bounds.

ResultMeaning
400Parse, validation, budget, join, or cursor error
401Missing or invalid credential
404Caller cannot read the domain
415Request body does not use application/json
422Malformed JSON or unknown body field
503Query backend unavailable

Error bodies omit query text and parameter values.

On this page