ActQL language
This document owns source, types, expressions, operations, nest, effect-intents, canonical-semantics, observable-boundaries. It does not define other owners' wire protocols or a particular Host implementation. Cross-owner links outside the declared dependencies are informative navigation to protocol bindings or conformance evidence; they do not import those documents' requirements. Narrow machine contracts remain authoritative for their explicitly referenced values and envelopes.
On this page
- Scope and conventions
- Source and Program structure
- Carriers, values, and types
- Inputs and external contracts
- Expressions and predicates
- Operations
- Named Results and Effect declarations
- Checking and canonical planning
- Structural grammar (normative)
- Complete example (informative)
- Machine contracts
Scope and conventions
An ActQL Program defines a bounded computation over typed observations, with named Results and optional Effect intents in finite linear stages. The same Program can be executed repeatedly; each execution is managed as a Run. For one Invocation, pure computation yields the same logical Result CIDs. This guarantee does not extend to fresh acquisitions or Effect outcomes (Capture, execution, and replay).
The language covers relations, graphs, vectors, event streams, addressable Resources, captured Artifact bytes, bounded external acquisition, and typed provider commands. Arbitrary scripts and raw HTTP are outside the language.
AoA supplies the common algebra and canonical fragment representation. Compilation combines validated AoA Fragments and typed boundary barriers into an Executable Plan (AoA fragments and algebra lowering).
ActQL fixes a Program's observable semantics and leaves physical execution to the Host. Checking establishes static validity and proof obligations under a Check Environment. After binding, planning derives the closed execution envelope, including limits not written in source. A Host MAY fuse, reorder, push down to a Connector, or place computation elsewhere only when the chosen strategy preserves the specified results and ordering, failure and atomicity behavior, observation and data-egress scope, evidence, permission boundaries, and Checkpoints. Sealed Plan identity and approval preservation are specified in Typed barriers and Plan Documents; runtime admission is specified in Checkpoints and Resolutions.
The Program declares semantic choices and any independent author guards. Contracts supply external-system meaning; checking derives execution facts; the Host supplies current policy and execution ceilings. Concrete observations and continuation state belong to the Run, not to Program source.
Normative conventions
The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, NOT RECOMMENDED, MAY, and OPTIONAL in this document are to be interpreted as described by RFC 2119 and RFC 8174 when, and only when, they appear in all capitals.
Except for examples and text explicitly marked informative, this document is normative. YAML examples, including Complete example (informative), illustrate the rules rather than define them. Formal grammars, contract definitions, and semantic tables are normative regardless of their code-block formatting.
Checking determines whether a Program satisfies the static language rules under a Check Environment. Binding supplies declared invocation values. Planning produces the canonical bound Executable Plan. These are semantic phases, not command names or requirements on an implementation's internal pass structure. Their results and permitted external access are specified in Checking and canonical planning; execution is specified in Run execution.
Planning fixes an execution contract, not an immutable physical strategy. Strategy selection and scheduling may continue during preparation and execution under that contract (Typed barriers and Plan Documents).
| Phase | Supplied inputs | Result | External work permitted by this phase |
|---|---|---|---|
| Checking | Program, Check Environment | Rejection for language violations or the checker's claim boundary, or a valid template with binding/proof obligations | None; evidence is supplied, not fetched |
| Binding | Declared parameter and opaque values | Exact typed values, or missing/invalid bindings | No provider observation or Effect |
| Planning | Checked Program, all bindings, Check Environment | Canonical bound Plan and execution-support status | None |
| Execution | Bound Plan, current authorization, initial captured inputs | Captured observations, Results, receipts, or a recorded failure/pause | Only the admitted Plan boundaries |
Initial input capture occurs during each activated stage's preparation, subject to authorization and Checkpoints. Program-wide checking cannot prefetch a later stage's business data (Run admission and identity).
Host Discovery (Host Discovery protocol) explicitly supplies published contracts before checking; optional read collection (Read collection protocol) coordinates separate Runs. Neither adds implicit I/O or failure recovery to compilation or pure operations.
Checking validity, binding completeness, and Host execution support are distinct
judgments. Rejection at a checker's profile-claim boundary does not assert that
the Program violates the language, and does not establish its validity.
A valid template without bindings is not runnable. A fully checked bound Plan
whose execution the Host does not support is unsupported, not runnable;
neither state grants access to external data. The public status mapping is
defined in Plan Documents and public planning results.
Terms
The following terms distinguish source, checked meaning, captured computation, and Host lifecycle. Requirements governing their identities and transitions are specified in Canonical identities, AoA fragments and algebra lowering, Capture, execution, and replay, and Run admission and identity.
-
Generator — an operation that establishes its output without consuming implicit current; any named additional inputs are still explicit dependencies.
-
Delta — typed graph-value change data bound to exact base, target and structural contract identities; it is neither a capability nor a mutation.
-
Coverage — evidence about the scope and completeness of a captured observation, not a percentage or proof of absence beyond that scope.
-
Program — an
actql/0.1document describing a bounded computation, published Results, and optional Effect intents. Its semantic content is distinguished from presentation-only Authoring Context. -
Bounded — each runnable capacity limit has a checked Resolved limit, witnesses, and a defined enforcement boundary; exceeding the ceiling fails the protected work. The applicable limits are specified by the operations and resolved-limit rules. Exact row counts, cost, or duration need not be known before execution; this does not remove their applicable ceilings.
-
Resource — a provider-addressable thing whose identity, kind, authority scope, lifecycle, and version survive any one tabular representation.
-
SourceView — a Connector-owned, versioned typed observation contract over a Resource or resource extent.
-
Carrier — a logical typed value on which the algebra computes: a relation, graph, vector set, or event stream (Carriers).
-
ModelTask — a versioned contract for bounded structured model acquisition (Model acquisition:
infer). -
Effect — explicit intent to change external state.
-
Effect Set — the unordered finite set of Effects declared by one stage and materialized from its Stage Invocation. Set and member semantics are defined in Effect declarations.
-
Stage Definition — a stable local stage ID and canonical local semantics, included in the Program's ordered stage descriptors without hashing its parent.
-
Unit Definition — the common Run Graph local definition of an extracted stage: its stable Program-ID/node-ID namespace, Stage Definition, used parameter declarations and incoming producer/import/freshness/gate declarations. It never includes the enclosing Program revision or unrelated stages; see Local approval dependencies.
-
Program checked template — all stages checked with typed future ports; it is not a collection of fabricated concrete Stage Invocations.
-
Stage Invocation — the semantic identity of one captured stage computation, including its adopted numeric and model observations and imported receipts.
-
Program Run — the common Run Graph execution of a staged Program, retaining its logical request, immutable values, pins and recovery history; not a second orchestration state machine.
-
Author guard — an optional author-declared upper limit that fails protected work when its measured scope exceeds the limit; it does not select the first N items (Author guards, resolved limits, and Plan facts).
-
Program Definition — the authority-bearing identity combining the Program's authored
idand canonical semantics, identified byprogram_definition_cid(Canonical identities). -
Authoring Context — the nonsemantic metadata sidecar associated with a Program Definition and displayed alongside its checked meaning (Authoring Context).
-
Resolved limit — the checked effective ceiling combining available bounds and guards, with their provenance and enforcement boundary (Author guards, resolved limits, and Plan facts).
-
Executable Plan — the closed checked envelope of validated AoA Fragments, typed barriers, and their execution constraints and evidence (AoA fragments and algebra lowering).
Common AoA foundation
ActQL 0.1 normatively pins the AoA Kernel law-manifest identity, AoA Pipeline
IR version, Pipeline law-manifest identity, the reference executable
operation-registry identity, the complete target-module contract and its
required AoA registry additions, and law
aoa.pipeline.effect-plan.terminal-boundary.v0.1. These semantic references
form the machine-readable actql-aoa-contract/0.1 artifact, identified by
ACTQL_AOA_CONTRACT_CID.
A conforming ActQL 0.1 compiler MUST report the ACTQL_AOA_CONTRACT_CID
published by its frozen 0.1 conformance manifest. It MUST NOT silently
substitute a newer AoA contract. Every frozen conformance report MUST include
the exact value it executed.
Package names, registry versions, and source commits are informative reference implementation bindings only. They do not constrain an independent conforming implementation that supplies the same normative semantic identities.
The semantic contracts imported by this document are explicitly pinned in Machine contracts. The family-wide contract index is informative navigation, not an import of every listed protocol. An implementation's internal language, data structures, and compiler pass names do not replace those shared contracts.
Language boundaries
ActQL has no UDF, lambda, custom reducer, user-defined operator registry,
inline JavaScript/Python, arbitrary WASM, runtime macro, or custom_compute
escape hatch. Adding a contract CID around opaque code does not make its
purity, bounds, ordering, pushdown, or replay semantics inspectable and MUST
NOT be treated as an ActQL operation.
Semantics outside ActQL 0.1 or the checker's claimed profiles MUST fail during check with a stable error code. The compiler MUST NOT:
- invent an operation;
- silently drop an input or field;
- change exact computation to approximate computation;
- convert event time to processing time;
- convert one join kind to another;
- return an empty result as a substitute for unsupported behavior.
ActQL 0.1 additionally rejects unbounded watch execution, continuous streams, interactive/duplex sessions, arbitrary binary algebra, raw SQL or provider-specific query languages, raw or uncontracted HTTP, ANN as pure VecA, implicit Carrier conversion, cross-provider transactions, Saga orchestration, and natural-language interpretation. Effect composition and its orchestration boundaries are defined in Effect declarations. A provider session or long-running operation may be represented as a Resource, bounded incoming observations as events, and outgoing work as a typed command, but its interactive lifecycle remains host application state.
The closed vocabulary does not include PageRank, community detection, random walks, PCA, general graph clustering or graph isomorphism. Their addition requires an explicit algorithm, initialization/iteration, numerical and resource contract; they are not rejected on the premise that all such algorithms are inherently nondeterministic. Degree distributions compose degree with group. Arbitrary theta joins, dynamic pivot columns and generic failure-catching remain excluded; interval_join and explicit acquisition coverage do not introduce those broader semantics.
Run Graph and HTTP transport are companion contracts, not Program vocabulary (Artifact and companion contract index (normative)). Proposals for future language extensions follow the separate, informative extension policy.
Source and Program structure
The common Program source model is a closed JSON-model document. Structural grammar (normative) defines its structural grammar; this chapter defines representation, naming, and authoring context. Explicit frontend profiles can lower other source representations into this same model, followed by ordinary admission and the common checker.
Source representation
ActQL source files SHOULD use the .aql extension for YAML, JSON and adopted
query representations. The extension identifies the language, not the
representation, and does not select a parser.
The Frontend r1 adoption registry
records adopted query/pattern profiles and the withdrawal of the old
AQL / PROGRAM / STAGE / STEP / OP text surface separately from installed
parser support. JSON/YAML Program remains public machine input. A caller
selects an installed frontend explicitly; a failure in that frontend does not
trigger reinterpretation as another format. Each frontend preserves the closed
Program model, explicit steps, semantic array order and the existing checking
rules. Profile adoption alone is not runtime conformance evidence.
ActQL source MAY be encoded as YAML or JSON. YAML input MUST be restricted to the JSON data model:
- object with string keys;
- array;
- string;
- finite number;
- boolean;
- null.
YAML tags, anchors, aliases, merge keys, executable types, non-string mapping keys, non-finite numbers, duplicate keys, and implementation-specific scalar types MUST be rejected.
Source text encoding MUST be UTF-8. Object key order MUST NOT affect parsing, lowering, or logical identity. Array order is semantic. Duplicate object keys MUST be rejected before validation.
The actql-source-decoding/0.1 profile uses YAML 1.2.2 Core
scalar resolution, restricted as
above, or JSON syntax. Exactly one non-empty object document is required. A
single leading UTF-8 BOM is permitted and is not a value; additional documents
and invalid UTF-8 are rejected. Numeric tokens decode to binary64 using
round-to-nearest, ties-to-even; overflow and negative zero are rejected.
Thus YAML yes, on, and 2026-09-06 are strings, 01 is number 1, and
1e3 is number 1000. JSON does not admit 01 as a number token.
Source decoding interprets the selected source syntax's escapes and, for YAML, its scalar folding/chomping. The resulting well-formed Unicode string values are preserved exactly, including CR, LF, CRLF and composed/decomposed sequences. Builder-provided JSON-model strings already are values and receive no second escape interpretation. Object member names use the separate identifier rule, except inside the quoted Effect literal payloads specified below: Unicode 16.0 NFC, then CRLF and lone CR to LF; colliding names are rejected. Identifier-valued members are admitted by their grammar-owned identifier rules, not by normalization of every String. Admission of a purportedly canonical machine artifact rejects noncanonical identifiers or bytes rather than repairing signed or hashed material. A data string need not be NFC or LF-normalized.
At a grammar-owned EffectValue position, the explicit {literal: value}
wrapper quotes the complete value subtree. Member names inside that subtree
retain their exact decoded Unicode scalar sequences and CR/LF for source and
Program identity, including when a later checked target contract is an Object.
Exact decoded duplicate keys remain source errors; distinct keys that would
collide only after identifier normalization remain distinct source keys there.
Program syntax, the literal wrapper, Effect names and target-field names retain
their existing identifier rules. This exception does not select the payload's
type or change any other source position. Contract-directed admission of the
payload follows Effect declarations.
Document structure
Complete Programs use the stage structure shown below and in the stage scenarios. Unwrapped pipeline snippets in operation sections describe stage-local bodies.
The language version is actql/0.1; the pinned Grammar identity identifies
the exact grammar. The checker MUST reject root inputs, from, steps or
effects as invalid top-level members. A missing stages fails required-member
checking. The compiler MUST NOT invent a stage or execute an implicit repair.
version: actql/0.1
id: orders.shipped_recent
stages:
- id: main
inputs:
orders:
carrier: relation
schema:
id:
type: string
nullable: false
status:
type: string
nullable: false
created_at:
type: timestamp
nullable: false
primary_key:
- id
from: orders
steps:
- filter: status = 'shipped'
- select:
- id
- status
- created_at
- sort:
- created_at: desc
- id: asc
- take: 100
as: recent_shipments
results:
main:
from: recent_shipments
fields:
- id
- status
- created_at
order_by:
- created_at: desc
- id: asc
results:
main:
from:
stage: main
result: mainStructural grammar (normative) defines the closed top-level shape. The following table is an informative summary, not a second grammar:
| Key | Required | Meaning |
|---|---|---|
version | yes | exactly actql/0.1 |
id | yes | stable author-chosen program identifier |
meta | no | non-executable authoring metadata |
params | no | closed typed runtime value declarations |
bindings | no | host-bound opaque invocation value declarations |
stages | yes | nonempty finite ordered array of stage definitions |
results | yes | explicit exports of closed Stage Results |
Unknown top-level keys MUST be rejected. id MUST be a non-empty string. It is
an authority-bearing definition namespace, not presentation-only provenance
and not an AoA step reference. Changing id intentionally changes Program
Definition, Invocation, Effect Preview, approval, and idempotency identities,
while leaving program_semantics_cid unchanged.
meta is non-executable Authoring Context. Its syntax, identity, and
review semantics are defined in Authoring Context.
Linear stages
Stages are a source and compilation surface, not a second orchestration
runtime. They lower to the common finite Run Graph contract. Every Program,
including a single-stage Program, uses the explicit stages array.
A Program has one nonempty finite stages array. Each stage has a unique
nonempty id, inputs, steps, a results map, optional from, effects
and meta. Root params and bindings are declared once. Dataflow, Result and
Effect names are stage-local. The root results map contains only exports
{from: {stage: StageId, result: ResultName}}; it performs no computation or
projection. A stage may contain a pure dataflow DAG, but the stage list is linear.
All stages MUST be checked before Program execution, including future receipt
schemas, references, contracts, profiles and bounds. A future producer supplies
a typed unbound port, not a fabricated value or Invocation CID. Missing caller
values yield needs_bindings; a checked Stage Plan awaiting its predecessor is
pending_predecessor, not missing caller input. Concrete Stage Plans bind every
used import and effective parameter before admission; Stage Invocation identity
is completed only after its required observations have been captured.
Stage inputs additionally accept {result: {stage, result}} and
{receipt: {stage, effect}}. They import an earlier Stage Result or the typed
success view of an earlier Effect receipt. Self/forward references, temporary
as imports, cycles and cross-stage implicit current are rejected. The ordinary
input/as/multi-output namespace and sole-materialized-input completion apply
within each stage. Imports never trigger a provider read. Zero-row imports
retain their checked schema. Root exports retain the selected Stage Result's
schema, order and policy; re-projection belongs in a stage.
Stage order MUST be preserved even without data dependencies. Fresh SourceView
reads occur only after that stage activates, never before an earlier Effect.
Static contract resolution is not a business read. Within a stage, Effects are
an unordered attempt_all set; member rows default to halt. No member consumes
an outcome of its own stage. Later stages may consume explicitly imported
receipts or perform fresh reads. Arbitrary workflow branching, loops, recursion,
dynamic stage creation and implicit compensation are not admitted; pure if
and CASE expressions and contract-bounded retry/poll remain distinct.
Only a noninitial stage accepts after: completed|accepted; omission normalizes
to completed. It refers to the immediate predecessor, not a selected DAG
edge. Activation requires both predecessor success at that level and availability
of every import. accepted requires every member applied or validly accepted;
it never permits failure or unknown. completed additionally requires terminal
success of the necessary asynchronous operations. A completion-only file Ref
still waits for completion under after: accepted. Missing finite completion
capability is unsupported at check, not an implicitly nullable import.
Any failed or partially applied stage MUST prevent normal downstream activation. An unknown outcome waits for reconciliation. A 99/100 success does not silently advance 99 items into the next stage. A cancelled checkpoint does not resume. Successful stages/items are retained, not rolled back or resent on recovery. Independent per-item progression requires explicitly separate Program Runs; it is not a transparent optimization of the stage barrier. Cleanup, compensation and failure notifications are separate explicit recovery Programs.
Every local Stage Result is an execution root. Delivery selection cannot skip a stage, Result or Effect. Local artifacts can survive later failure for authorized inspection. A complete graph output index requires every explicit export and truthful flow evidence; complete export coverage is not flow or business success.
Program, stage and execution identities follow the canonical envelopes below. Profile support is checked independently of source validity.
Pipeline names and references
steps is an ordered array and MAY be empty. steps: [] is the identity
pipeline over the selected input; Result ordering and projection still lower
to AoA steps when declared. Each step object MUST contain exactly one operation
key plus OPTIONAL from, as, and meta keys, subject to the generator and
multi-output exceptions below. An empty step object is invalid.
- filter: "status = 'open'"
from: opportunities
as: open_opportunitiesHere and in operation snippets, an input/step/Result name is local to the enclosing stage. Root Program Results use only the export form in Stages.
For an ordinary consuming step, if from is omitted it consumes the current
step result. The first
step consumes the stage-level from. If the stage has exactly one input, the
stage-level from MAY be omitted and that input is selected deterministically.
For these selection rules, count only materialized inputs, not search-template
bindings classified under Inputs and external contracts. A template name is
not a valid current Carrier, even when it is the only declared input name.
If more than one input exists and stage-level from is omitted, an ordinary
consuming step before any explicit current selection MUST be rejected.
A compiler MUST NOT select an input by declaration order.
Generators values, time_series, calendar_series and mean_graphs reject
step-level from, require as, and set the current result to their output.
They consume only the explicitly named additional inputs, if any. Thus an
empty inputs map is legal when every dataflow root is generated. Stage-level
from is absent with zero inputs. With multiple inputs, a generator or an
explicit step from can establish the current result without a stage-level from.
search_candidates instead binds the two names in its outputs object,
rejects as, and leaves the current result unchanged. References to these
names have the same uniqueness, scope and no-forward-reference rules as as.
An authored dataflow binding is an input name, a single-output step's as,
or an explicitly named multi-output port. Search preserves the current value
from before the step, even when its query input uses an explicit from.
If current was unset it remains unset. Both output names are registered
together; neither output order nor explicit query selection updates current.
as binds a dataflow name for later reuse. All authored dataflow names MUST be
non-empty strings. Inputs, as names and explicit multi-output names share
one dataflow namespace and MUST be unique within it. Result names form a
separate publication namespace: from: name resolves in the dataflow
namespace, while from: {result: name} resolves in the publication namespace.
A Result MAY therefore reuse a dataflow name. Authors SHOULD NOT reuse one,
because a review reader would see the same token denote two objects. Authors
SHOULD use as for branches, later reuse, or Result publication; generated names are used for
ordinary linear steps.
Name resolution has the following closed scopes:
| Position | Available names |
|---|---|
Top-level from | Declared materialized inputs only; no search templates |
Step from and additional Carrier inputs | Materialized inputs and preceding authored Carrier bindings; search.source alone accepts a template binding |
Result from | All authored Carrier bindings in the completed pipeline; no templates |
Effect from | {result: name} in the publication namespace only |
Forward references, self-references, cycles, duplicate bindings, and unavailable names MUST fail during check. Compiler-generated names are not author-visible bindings.
Join and other multi-input operations name their additional inputs inside the operation. An unavailable or Carrier-incompatible name MUST fail during check.
Field names and nested paths
An AuthoredFieldName is a flat NFC string containing at least one
non-whitespace character. This rule applies to relation/graph/event/vector
fields and recursively to object properties.
Here whitespace is the union of ECMAScript 2025
WhiteSpace and LineTerminator:
U+0009–U+000D, U+0020, U+00A0, U+1680, U+2000–U+200A, U+2028,
U+2029, U+202F, U+205F, U+3000, and U+FEFF.
Authored schema fields MUST NOT contain ., equal __cid, or begin with the
reserved __actql_ prefix. Generated semantic fields such as edge_id,
window_start_ms, session_id, and score/identity restorations are checked
for collision before lowering. A compiler MUST NOT reinterpret a user field as
join provenance or an internal carrier field.
Implementations MUST permit line breaks within field names; source NFC/LF normalization and quoted or escaped representations apply as for other strings.
GeneratedFieldName denotes a field introduced by a specified operation;
it is not permission for authors to declare reserved internal fields.
ProvenanceQualifiedFieldReference is the explicit left.name or right.name
reference to a join operand (join), not an authored field. Subsequent
property access is a distinct expression node (Typed access).
A raw qualified join output requires a flat projection before another join;
recursive join provenance is not inferred from a chain of dots.
A flat field name may hold a closed nested object or array value. Nested
values do not create dotted field names. Typed expression access is specified
in Typed access; json_pick remains the explicit relational form:
- json_pick:
column: payload
path: [customer, id]
output: customer_idNote (informative):
customer.idselects propertyidof fieldcustomer; it cannot name a flat field containing a dot.
Before ActQL checking, a Connector adapter MUST map every provider-specific source-field selector into a declared flat canonical ActQL field name. This rule governs field names, not value shape: a declared field may still contain a closed nested value.
Authoring Context
meta provides author-supplied annotations for presentation and review. It
MAY appear on the Program, a Stage, an Input (including a Result/receipt import),
a Parameter or Binding declaration, a Step,
a Result, or an Effect. It MUST NOT appear inside expressions, predicates,
value contracts, or operation bodies. Short-form inputs use their object form
to carry annotations.
Meta is a non-empty JSON object with service-defined content. Top-level keys
beginning with actql_ are reserved and MUST be rejected in 0.1. Its complete
content uses the AoA Kernel canonical JSON contract (Canonical identities); non-finite numbers,
negative zero, and unpaired UTF-16 surrogates MUST be rejected.
Metadata is nonsemantic authoring context. It MUST NOT change computation, authority, egress, bounds, optimization, Effect Preview identity, or semantic Program, Executable Plan, Invocation, or idempotency identity. It MUST NOT be copied into a model request, Connector request, Effect payload, or provider prompt. Except for validating metadata itself, Hosts MUST NOT use metadata as a structured input to execution admission or policy, permissions, approval routing, Checkpoint requirements, or Resolution validation. A Host MAY impose metadata capacity or content restrictions, but MUST reject violations at intake with a stable diagnostic code rather than silently truncating or rewriting metadata.
An Effect review projection MUST bind both the semantic effect_preview_cid
and the exact authoring_context_cid presented to the reviewer. Metadata MUST
be identified as author-supplied and separated from verified authority and
approval status. A projection MAY present a CID reference to the canonical
metadata instead of rendering its contents; the reference remains subject to
the applicable access and redaction policy. Changing only metadata therefore
does not rebuild the Effect Preview, but it does require a
new review projection and approval attestation. Natural-language metadata MUST
NOT satisfy a missing structured semantic choice.
Carriers, values, and types
Carriers describe collections on which the algebra operates. Field contracts describe the values carried by those collections; they are shared by local schemas, external Contracts, expressions, and Results.
Carriers
The graph structural contract has the closed canonical body
{version: "actql-graph-contract/0.1", domain, node_definitions, edge_definitions, identity_law_cid, value_law_cid}. Each definition retains its
name, nonempty ordered key contracts, matching ordered key_columns, complete property contracts and static
label-to-property sets. Edge definitions additionally name their source and
target node definitions. Element identity is the admitted definition domain,
definition name and complete typed key, not an endpoint tuple. Independently
keyed parallel edges are retained. Property types and shared label property
sets obey the graph-wide rules in Graph construction.
graph_contract_cid is the ordinary ActQL CID of exactly this body, without a
self-CID member. key_columns binds each retained constructor key-column name
to its component position and type; an anonymous structural definition uses
null entries at every position, never a partially named tuple. Snapshot reload
retains this mapping without consulting a catalog. Property-map enumeration,
observations, graph
contents, row counts, policy and provider locators are not contract members.
The retired actql-graph-contract/0.1 body is not an alternate Graph profile:
current admission rejects it without conversion. Historical bytes and evidence
retain their original identity, but do not provide current execution support.
Operations not connected to this keyed representation must reject explicitly;
their old tuple-based implementations are not a fallback.
For the 0.1 contract, both graph law CID members MUST equal
graph_law_manifest_cid in the owned operation-contract inventory.
That pin resolves actql-graph-laws/0.1, maintained inside ActQL,
which contains the semantic identity and typed value equality laws together.
Computing a structural contract CID proves
neither snapshot admission nor base/target content identity.
ActQL source uses four public Carrier names:
relation
graph
vectors
eventsThese map directly to the AoA sorts relation, graph, vector_set, and
event_stream. Every materialized input and every step result has exactly one
ActQL Carrier. Conversions are legal only through the deterministic semantic
expansions defined in this specification. A compiler MUST NOT reinterpret a
value as another Carrier.
Relations contain ordered rows with string-named fields. Graph, event-stream,
and vector carrier representations are AoA-defined; ActQL restores authored
semantic identity field names when returning to relations, except operations
with fixed result roles such as nearest's query_id / candidate_id (Vector operations (VecA)).
Physical enumerability, a proven stable total order, and candidate-key evidence are different properties. A candidate key is a non-null field tuple proven unique; candidate keys form a set of independent witnesses. An ordering witness proves the logical row sequence used by order-sensitive operations. Provider arrival order alone proves neither. A complete schema supplies every field name and recursive value contract; knowing some names is not completeness.
Closed value contracts
The closed value type names are string, number, decimal, boolean, timestamp,
date, json, object, array, vector, resource_ref, and content_ref.
The primitive value domains are:
| Type | Value domain |
|---|---|
string | Well-formed Unicode scalar sequences, preserved without implicit normalization or line-break conversion. |
number | Finite IEEE-754 binary64; negative zero is invalid in canonical source. |
boolean | true or false. |
decimal | Precision, scale, and exact base-10 coefficient (Exact decimal values). |
timestamp | Canonical UTC YYYY-MM-DDTHH:mm:ss.sssZ; offsets and leap-second spellings are invalid. |
date | Canonical YYYY-MM-DD; distinct from a timestamp. |
json provides no recursive static field evidence. object and array
carry the recursive contracts described below; vector carries fixed
dimensions and the 0.1 f32 element law. Opaque reference values are defined in
Opaque reference values. Arithmetic, conversion, and null propagation are specified in Expressions and predicates.
resource_ref and content_ref additionally declare resource_kind or optional media_type
and require issuer evidence as specified in Opaque reference values. Every field contract, including
an array element contract or object property contract, MUST declare nullable.
array contracts MUST declare items; object contracts MUST declare
fields. These members contain recursive field contracts and are legal only
for their respective types. Values without declared internal structure use
json. For json_pick on a typed object, a referenced path MUST be
present in the complete recursive contract. When schema is
present, it is complete: runtime rows MUST have exactly that field set and
every value, nested array element, and declared object property MUST satisfy
the declared type/nullability. nullable: true permits a present value to be
null; it does not make the field or property optional.
Relation column presence is separate from these ordinary object value rules. A finite complete Relation schema MAY declare a column conditional: an absent row key is omission, not an explicit NULL. Required columns MUST occur in every row; every present cell MUST satisfy its declared value type and nullability. The existence guarantee MUST participate in schema identity and retained schema storage. Row content identity MUST distinguish omitted keys from explicit NULL, without including physical presence bitmap/layout choices.
In the initial presence profile, ordinary references and public Result columns
MUST be schema-guaranteed present. Conditional references MUST fail during
checking; sample values, an empty Relation and IS NOT NULL do not prove
presence. Projection of guaranteed columns and filter/derive expressions that
reference only guaranteed columns, Required-key sort, proven-order take and
keyed DISTINCT over guaranteed columns, complete-row DISTINCT and GROUP over
guaranteed references are supported. Structural DROP MAY remove columns from a
conditional schema without reading retained values; it MUST preserve the
retained columns' contracts, presence, order and row correspondence, and admit
and charge the complete input, including removed columns. It does not make
retained Conditional fields ordinary references. Filter MUST preserve the exact
input schema, ordered surviving rows and each cell's presence; it MUST admit and
charge all input rows before evaluating the predicate, including rejected rows.
All syntactic references require presence, including unevaluated branches.
Other consumers of a conditional schema remain explicitly unsupported until their preservation or
refinement rules are implemented; they MUST NOT fill omission with NULL or
silently drop presence. This does not make ordinary object properties optional.
Derive MUST preserve untouched fields' presence, values and row correspondence through execution, retention and reload, without filling omission with NULL.
A computed derive binding MUST be Required with its checked type and nullable contract; ordered replacement refers to the new binding rather than proving the replaced input column present.
Derive MUST reject every reference to an unguaranteed input column during checking, including unevaluated branches; references to a proven earlier binding in ordered derive retain the existing value-version semantics.
Sort and take on conditional Relation inputs MUST preserve the input schema, each selected row's values and presence, and the existing canonical content identity rules through retention and reload. Authored sort keys MUST be schema-guaranteed present, while ordering completion, take determinacy, complete input admission and resource accounting retain their existing obligations; neither observed samples nor an empty result establish order or presence.
Keyed DISTINCT on conditional Relation inputs MUST project only its specified, schema-guaranteed columns before deduplication, preserving their checked value types and nullability and the existing first-occurrence ordering. Unspecified conditional columns MUST NOT survive the projection or participate in equality. The complete sparse input MUST still be admitted and charged, including omitted cells' presence slots and values not selected for output. An unguaranteed key MUST fail during checking.
Complete-row distinct: true on conditional Relation inputs MUST preserve the
input schema, values and presence through deduplication, retention and reload.
Equality MUST include presence at every column: omitted and explicitly NULL
cells are different, and unselected-key projection is not complete-row equality.
The entire input MUST be admitted and charged, including duplicate rows and
omission slots. Existing first-occurrence ordering and type-specific equality
remain in force; deduplication does not make Conditional columns ordinarily
referenceable or publishable and does not establish a new stable-order proof.
GROUP on conditional Relation inputs MUST require schema-guaranteed presence for every group key and every reference in aggregate arguments, filters and aggregate-local ordering, including syntactically present unevaluated branches. The complete sparse input MUST be admitted and charged before aggregation, without reading absent unreferenced cells as NULL or omitting their presence storage from resource accounting. The aggregate result MUST retain the existing GROUP output schema, types, nullability, empty-input behavior and ordering through retention and reload; unselected input columns do not survive grouping.
Both authored and checked object contracts MUST permit fields: {}. This closed
contract admits only the empty object, plus null when nullable: true.
An empty array retains its declared item contract; its contents do not infer
json or change nullability. nest (nest) derives that contract from the
selected child schema, with a non-null array and non-null object items.
The author-facing field contract contains only semantic choices. An authored
object contract omits additional_properties; the compiler inserts the sole
0.1 machine meaning additional_properties: false. An authored vector
contract declares positive dimensions and omits element_type; the compiler
inserts the sole 0.1 machine meaning element_type: f32. These fixed machine
fields appear in checked Plan and protocol contracts but are not authoring
vocabulary.
The author and checked contracts are separate closed grammars. Every row below
also has type and required nullable; ? marks an optional member.
| Type | Other author members | Checked expansion or evidence |
|---|---|---|
string | none | optional min_length, max_codepoints, max_utf8_bytes from admitted evidence |
number, boolean, timestamp, date, json | none | unchanged |
decimal | precision, scale | unchanged; Exact decimal values ranges |
array | items: AuthorValueContract, max_items? | recursive checked items; bound unchanged |
object | fields: {AuthoredFieldName: AuthorValueContract} | recursive checked fields; additional_properties: false |
vector | dimensions | element_type: f32 |
resource_ref | resource_kind | unchanged |
content_ref | media_type? | unchanged |
AuthorValueContract is exactly the author column; CheckedValueContract
is its recursive expansion plus only the checked members listed above.
Unknown or wrong-type members are rejected. Array max_items is a
non-negative safe integer, including zero; it constrains admitted values,
unlike an operation guard over measured work (Author guards, resolved limits, and Plan facts). Checked string extents
are non-negative safe integers and do not become author syntax.
min_length and max_codepoints count Unicode scalar values in the retained
string; max_utf8_bytes counts that value's UTF-8 bytes before JSON escaping.
Neither counts grapheme clusters or UTF-16 code units.
Type equality and compatibility use the following common relations:
TypeEqual(A,B)compares complete canonical checked contracts recursively, including nullability, bounds, decimal precision/scale, vector dimensions, reference kind/media type, and object field sets. Object key order is irrelevant.Assignable(A,B)means every value permitted by A satisfies B. Non-null may flow to nullable, but not conversely. Base types match without coercion; decimal precision/scale, vector dimensions, and ResourceKind must match. Objects have exactly the same field set with recursively assignable values; arrays have assignable items. An actual upper bound cannot exceed an expected bound, and an actual lower bound cannot be smaller. Missing bounds prove no restriction. An unspecified expected Artifact media type admits any media type; a specified type requires that exact type to be proven.JoinNullability(A,B)requires the same recursive base structure and the exact decimal/vector/ResourceKind parameters. It takes nullable OR at each position and the least restrictive common value bounds. Different Artifact media types join to an unspecified media type. Other base-type differences have no join.jsonis not an implicit supertype of typed objects or references.
append, conditional result branches, coalesce, and array construction use
this common join. SourceView request and Command argument checking use
assignability. A literal null acquires a nullable form of the other branch's
known type; all-null expressions alone provide no non-null type evidence.
An explicit cast of null supplies its target type with nullable: true.
An immediate fixed signature also supplies a literal NULL's type when its
argument domain is unique: len(NULL) uses nullable string, concat(NULL,NULL)
uses string arguments, and NULL IS TRUE and CASE WHEN NULL THEN 1 ELSE 2 END
use a nullable boolean condition. An overloaded argument without a unique
domain supplies no type; two untyped NULL operands of IS DISTINCT FROM
still require independent type evidence.
For quantize and decimal_divide, implementations MUST NOT use the output
precision and scale to supply an input type: the decimal input domain has no
unique precision/scale, and the divisor domain also permits number.
Bare literal NULL operands remain
unresolved; an explicit cast such as CAST(NULL AS DECIMAL(8,2)) supplies
the missing input type. Already-typed nullable operands still propagate null.
An empty array obtains its item type from an explicit empty_array.items
contract or the unique common item type of independently typed peers in an
if/case, coalesce, or array constructor. A schema-typed array with
max_items: 0 and an explicitly typed empty array both supply evidence.
Resolution is recursive, ignores only untyped NULL and untyped [] as
type evidence, and fails if no unique type remains.
It never searches arbitrary downstream consumers. The empty array itself is
non-null with max_items: 0; peer nullability is not copied to it.
An unresolved field may acquire its type from one explicitly mapped nullable
ModelTask input. Outside these explicit type, local signature and peer rules, no other consumer
context supplies an unresolved null type in 0.1; absence of a unique type fails
with ACTQL_E_NULL_TYPE_UNRESOLVED.
An operation may impose stricter preconditions, but not a different coercion.
Expression-specific nullability, such as coalesce being non-null when an
argument is proven non-null, is refined by Expressions and predicates after this structural join.
KeyComparable(A,B) compares non-null value domains, not entire contracts.
Nullability and value-size bounds may differ; each input still satisfies its
own contract, and the operation's null policy governs null keys. Base types
must match without coercion. Decimal precision/scale may differ: decimal keys
compare by exact numeric value under Exact decimal values.
ResourceKind must match; ContentRef media restrictions do not change reference equality.
Scalar and reference domains are comparable under these rules; object, array,
json, and vector keys are not admitted. Join-key compatibility uses this
relation, not TypeEqual.
For json_pick through a closed object, result nullability is the OR of the
leaf's nullability and every traversed parent's nullability. A null parent
returns null; an absent declared property violates the input contract.
Parameter declarations reuse the recursive value domains with the parameter-specific
default and nullability rules in Parameters and binding.
date and timestamp use the proleptic Gregorian calendar with years
0000 through 9999 and valid month/day combinations. Timestamps require hours
00–23, minutes/seconds 00–59, and exactly three fractional digits in UTC.
Leap seconds and out-of-range dates fail. These value rules apply without the
optional text-time profile, which only adds transformations over the domain.
Exact decimal values
Decimal follows the AoA RelA exact-decimal contract. The closed type is
{type: decimal, precision: P, scale: S, nullable: boolean},
with integer 1 <= P <= 38 and 0 <= S <= P. Its exact value is
coefficient × 10^(-S). The machine coefficient is a base-10 integer string
matching 0|-?[1-9][0-9]*, with at most P digits excluding the sign; leading
zeros, a leading +, and negative zero are invalid.
Typed value identity is domain-separated from string identity and includes
the decimal contract version, P, S, and coefficient. Each declared type has
exactly one canonical spelling per value. Thus 19.90 at scale 2 and
19.900 at scale 3 compare numerically equal but have different types and
typed value identities. Arithmetic and conversion rules are defined in Decimal expressions.
Decimal comparison, including join-key equality, compares the exact values
coefficient × 10^(-S) without binary64 conversion or rounding. Equivalently,
compare integer coefficients aligned to the greater scale; this comparison
introduces no precision-38 intermediate-value restriction. Different precision
or scale does not prevent comparison or an equal-key match. Hash-based joins
use the same numeric equality, not typed value CID equality. This does not
change either operand's type, canonical spelling, or typed value identity;
changing a stored decimal's precision/scale still requires explicit conversion.
Canonical JSON, JSONL, and CSV serialization uses fixed-scale decimal text:
exactly S fractional digits, no exponent or leading +, and no redundant
integer leading zeros. When S is zero, the decimal point is omitted. JSON and
JSONL encode this text as a string; declared decimal JSON inputs use that same
canonical spelling. A coefficient outside the declared precision fails rather
than being rounded or truncated.
Opaque reference values
Two opaque scalar types cross carrier boundaries:
resource_ref<K>is a projected Resource identity. It contains a provider namespace plus content identities for provider identity, authority scope, Resource boundary, and the complete reference. It contains no native provider locator, view, connector version, observation time, etag, or schema evidence.content_ref<Mime>identifies captured bytes and carries content CID, media type, bounded byte count/range, storage scope, truncation, sensitivity, capture authority, and optional source ResourceRef/version evidence.
ResourceRef equality is equality of identity_cid, scoped by provider
namespace, ResourceKind, provider identity, authority scope, and boundary.
Connection names and authentication revisions do not determine that identity.
ContentRef equality is equality of content_ref_cid, including store, scope,
retention, and policy; equal content_cid values establish byte identity only.
SourceView and serialization output contracts may expose content_cid as a
separate field.
Authored and checked ContentRef media_type descriptors MUST already use
lowercase ASCII type, subtype and parameter names. Opaque parameter values,
quotes, escapes, parameter order, whitespace and Unicode scalar sequences
(including NFC/NFD differences) MUST remain exact descriptor data. Invalid
syntax or noncanonical ASCII name case MUST be refused without rewriting
the authored descriptor or its Program identity.
Connector-captured ContentRefs MUST retain the exact provider-reported MIME
string in metadata.issuer_evidence.reported_media_type. The existing capture
owner binds that report to the same checked Source request, scope, byte facts
and issuer evidence before issuing the reference. The observed media_type
uses case-insensitive MIME type/subtype and parameter names; parameter values
MUST NOT be unconditionally lowercased or Unicode-normalized. MIME metadata
does not prove that the bytes conform to that file format. Store derivation
evidence MUST NOT claim a provider report.
Saved capture recovery MUST reproduce the exact original report, its projected media binding and complete issuer/Ref preimages, in addition to confirming the original Store custody and current authority. Missing, changed or mismatching reports MUST be refused without repair, new Source acquisition or relabeling prior evidence. A report or matching byte CID alone MUST NOT authorize issuance, extend retention, confirm cleanup or permit Model/Effect dispatch.
Reference equality MUST be consistent across predicates, joins, grouping,
distinct, and uniqueness checks, independently of acquisition or in-memory
object identity. Within each reference type, eq/neq use that equality.
References are scalar keys for equality-based operations. For specification-defined
canonical identity ordering and deterministic compiler completion, use AoA code-unit
order of identity_cid for ResourceRefs or content_ref_cid for ContentRefs.
This includes canonical group/partition enumeration and request-key ordering
where the operation already admits reference keys; it does not expand any
operation's admitted key or Carrier types. Machine-only means not
author-selectable as a reference value order, not unobservable: the resulting
canonical order remains part of observable semantics. Author-order restrictions
and their validation boundaries are defined in Lexical and ordering rules.
Only a Connector host may issue a resource_ref. An content_ref may be
issued only by a Connector capture or an authorized content-addressed Artifact
Store derivation. Both issuers bind typed issuer evidence; the public protocol
exports validators, not an author-accessible issuer. A reference grants no
authority. Host-accepted bindings and Artifact or Result inputs MUST revalidate
issuer evidence and current usage authorization; authored JSON alone cannot
establish a valid reference. References serialize as tagged canonical JSON
objects, but their internals MUST NOT be inspected or modified by expressions
or json_pick.
An operation requiring a ResourceRef MUST receive it explicitly; hidden execution lineage MUST NOT restore a projected-away reference or supply an implicit write target. Mapping-based Effect selectors remain valid (Effect declarations). Binary bytes and base64 are not embedded into ordinary AoA JSON values, and image/audio-specific carriers do not exist.
Lexical and ordering rules
AuthorOrderable comprises string, number, decimal, boolean,
timestamp, and date. CanonicalOrderable comprises those types plus
resource_ref and content_ref. These are specification classifications,
not source syntax. A portable order is the specified total order over non-null
values of a CanonicalOrderable type: numeric order for number and decimal, false < true
for boolean, chronological order for timestamp and date, the string order
below, and reference identity order (Opaque reference values). object, array, json, and
vector have no portable order. Null placement follows the operation's rules;
null does not acquire a value order.
Every authored ordering key, ordered-comparison operand, and min/max input
MUST be AuthorOrderable, regardless of operation or field spelling. This
includes ranking and explicit tie-break keys, but not equality-based partition,
grouping, correlation, or uniqueness keys. Reference identity order is available
only for specification-defined canonical identity ordering and deterministic
compiler completion (Opaque reference values).
Statically invalid order domains MUST be rejected during check. Where an
existing rule permits deferred type validation, the Plan MUST retain the
AuthorOrderable obligation and the runtime input boundary MUST validate it
before evaluating the ordering operation. This rule permits no new deferral
and never authorizes reference ordering.
Every lexicographic ordering in this specification uses UTF-16 code-unit order
on the admitted values. String data is not normalized for comparison; identifier
names retain their separate NFC/LF admission contract. Locale collation and Unicode code-point order are not
permitted alternatives. Connector references are non-empty UTF-8 strings whose
normalized spelling is identity-bearing. Author-facing Mapping Contract
references use the typed-qualified closed ASCII grammar in Mapping contracts. They are not
inferred from the surrounding field and do not change spelling during
compilation. A compiler does not perform provider-specific case folding.
Duration tokens have lexical form
[0-9]+(ms|s|m|h|d), are ASCII case-insensitive at authoring, and canonicalize
to positive safe integer milliseconds. Negative zero and non-finite numbers
are invalid before identity computation.
Inputs and external contracts
An input referenced as search_candidates.source declares an acquisition
template, not a captured Relation. Check classifies that binding before input
selection: it resolves the SourceView and static request without dispatch,
and excludes it from initial capture and implicit sole-input selection.
All references to that binding are template references from search steps;
using it as top-level/step from, a local operation operand or Result input
fails check, including mixed template/Relation use in one Program. Multiple
search steps may reuse the template. An independent snapshot requires a
separate input binding with its own complete request and capture evidence.
Dynamic request roles are supplied only by each search step; static and dynamic
roles together match the exact request contract. Template resolution does not
grant provider access, create rows, or establish observation completeness.
Parameter and opaque binding values are declared and supplied under Parameters and binding; they are distinct from the external contracts selected by an input.
The external and computational models are orthogonal:
Resource -> SourceView ---------------------> Carrier
Relation -> ModelTask infer acquisition ----> Relation
Relation -> keyed SourceView join acquisition -> Relation
Relation -> Artifact serialization ---------> Relation<content_ref>
Relation<content_ref> -> typed external SourceView join -> Relation
Carrier -> AoA algebra -> Effect Plan -> ReceiptResource, SourceView, Carrier, and Effect are defined in Terms. SourceView, ModelTask, and Artifact serialization contracts are versioned barrier contracts, not intermediate languages. External inference and the acquisition selected for an external join are not AoA operations or business-state mutation Effects. Relation serialization is an explicit Artifact barrier rather than a hidden output-side post-processing pass.
Input duplicate normalization
Materialized relation inputs accept duplicates: reject or
duplicates: {first_by_order: {order_by: [OrderKey, ...]}};
omission normalizes to reject. The array is nonempty and uses declared raw
fields and the ordinary OrderKey grammar. The bare string first_by_order is
invalid. First-by-order selects the first complete row
per declared primary-key tuple using an independently admitted total order.
The duplicated key itself, provider arrival order or an invented payload-CID
tie-break is not that proof. Without it checking fails; an unkeyed raw input
followed by explicit dedupe_rows is a separate business-selection path.
Admission MUST decode and normalize optional provider fields, then validate all
raw rows' known fields, types, nullability and non-null key components before
selection. A discarded malformed row cannot be hidden. Raw rows/bytes and
normalized rows/bytes have separate limits. Non-null uniqueness is proved only
on the selected output. Evidence binds the raw artifact, policy, order witness,
normalized artifact and excluded count. Raw capture cursor and coverage are
unchanged. An authoritative SourceView uniqueness violation remains a contract
failure, not something an author can repair with first_by_order or a local
schema override.
Local typed inputs
A local input MAY use the short form:
inputs:
rows: relation
graph: graph
events: events
embeddings: vectorsor the object form:
inputs:
rows:
carrier: relation
expected_columns: [id, status]An optional inline schema supplies the closed field contracts defined in Closed value contracts:
inputs:
orders:
carrier: relation
schema:
id: {type: string, nullable: false}
amount: {type: number, nullable: true}
occurred_at: {type: timestamp, nullable: false}
items:
type: array
nullable: false
items:
type: object
nullable: false
fields:
sku: {type: string, nullable: false}
quantity: {type: number, nullable: false}
primary_key: [id]primary_key, when present, is a non-empty field list. When a complete schema
is available, every component MUST exist in it, have a scalar or reference
type (Opaque reference values), and declare nullable: false. At the runtime input boundary,
every component MUST be present, non-null, and a scalar or reference value,
and, after the explicitly admitted input duplicate normalization, the complete
tuple MUST be unique by canonical-value equality, including
when static type evidence is unavailable. Reference components use the
equality defined in Opaque reference values.
The Host may supply schema evidence under Check Environment and source evidence without an authored schema.
Such evidence MUST satisfy any explicit input assertions. schema_ref is not
an input field; unresolved descriptive labels belong in meta (Authoring Context).
carrier is REQUIRED for an opaque local input. expected_columns is OPTIONAL
but, when present, MUST be a duplicate-free array of non-empty field names.
Runtime bindings MUST be checked against it before computation.
It requires those columns to be present but permits additional columns and
specifies neither column order nor value types. A compiler MUST NOT infer a
complete field set from expected_columns alone. When schema is also present,
every expected column MUST belong to that schema.
Check reports one of three evidence levels rather than rejecting every
partially described relation: typed for a resolved complete schema,
columns_only for known column names without a complete typed schema, and unknown when
neither is available. Static evidence checking MUST NOT reject an operation
solely because evidence unrelated to its required proofs is absent.
This rule does not exempt language violations, authorization denials, or runtime failures. A required proof must be established during check unless the operation's existing rules permit a deferred validation obligation; such an obligation is retained in the Plan and discharged at its specified runtime boundary. These evidence levels introduce no additional permission to defer validation.
Known field names, field-set completeness, recursive type evidence,
candidate keys, and ordering witnesses are independent evidence axes.
columns_only may describe a subset or a complete set of names; the complete
set must have separate evidence. The three levels above summarize type
knowledge, not all five axes.
Connector SourceViews
A connector input names a Host-published Connector instance (connector),
a Resource within it (resource), and an observation of that Resource
(view). The instance selector identifies a Catalog entry, not a Connector
implementation name or connection revision (Resource identity contract). For example, if the admitted Catalog exposes
only the records SourceView for Opportunity:
inputs:
opportunities:
connector: crm/main
resource: Opportunity
from: opportunities
steps:
- select: [id, account_id, stage, amount]
- filter: "stage NOT IN ('ClosedWon', 'ClosedLost')"connector and resource are required. When view is omitted, Check MUST
resolve it from all SourceViews for that exact pair in the admitted Catalog:
zero views is rejected as unresolved; one is completed under
actql.source.sole-view-completion; more than one is rejected with
ACTQL_E_SOURCE_VIEW_AMBIGUOUS and the candidate list. Resolution MUST NOT
filter candidates by current execution permissions, request compatibility, or
downstream expressions. An explicitly selected SourceView MUST match the full
(connector, resource, view) selector, including when a contract CID is pinned.
Check does not synthesize a SourceView, implicitly select records, or fall back
across Resources. Catalog publication is governed by Connector Catalog and lock.
SourceView resolution MUST bind the concrete Resource using its admitted output
data-policy source_ref, exactly `"connector://" + connector_id + "/" + resource
- "#" + view_id
, with nonemptyresource;resource_kindis a type, not that address. This is literal string construction, not URL parsing: the knownconnector_idprefix andview_id` suffix delimit the Resource. Percent escapes, slashes and fragment characters inside the Resource remain literal. Authored selectors use their source-normalized strings exactly, with no URI-prefix removal, slash trimming, percent decoding or case folding. Different Resources may expose the same kind and view name.
Before resolving inputs, Check MUST reject every supplied SourceView whose
policy source reference does not have that exact prefix, nonempty Resource and
suffix, and reject duplicate (connector_id, resource, view_id) selectors,
even across different kinds or versions and even when unused. This check follows
policy-reference admission; Catalog integrity alone does not establish Resource
selector uniqueness. Ambiguous candidate names are reported in UTF-16 order.
The resolved Plan MUST identify the SourceView and bind its contract CID. A Catalog change requires rechecking against the new Catalog; it MUST NOT retarget a sealed Plan. Completion is recorded in the normalization journal (Deterministic completion), not required as a rewrite of the authored Program.
The SourceView contract determines acquisition semantics, read authority, output Carrier/schema/facts, ResourceRef issuance, read modes, checkpoint/delivery contract, and pushdown capabilities. Authors MUST NOT repeat the output Carrier. A relation SourceView lowers directly to an AoA relation source; SourceView resolution is not another IR.
Catalog discovery MUST expose each SourceView's name, request contract, acquisition kind, authority, observation classification, output Carrier/schema, modes, and contract CID, including any extraction contract. Human-readable discovery MUST present these facts rather than names alone. Authors and generators select SourceViews from this evidence, not by inferring meaning from their names. Acquisition review projections MUST include the SourceView classification and contract identities reported by the Plan summary (Diagnostic documents), subject to redaction.
request is validated against the resolved SourceView request_schema. It may
contain literals, typed scalar parameters, or an explicit {$binding: name}.
Opaque invocation bindings are declared separately from scalar parameters:
bindings:
target_file:
type: resource_ref
resource_kind: google_drive.file
inputs:
file:
connector: google_drive/main
resource: drive_file
view: content
request:
resource_ref: {$binding: target_file}
max_bytes: 262144
require_full: falseEach field of an authored static request is one whole-value slot. An exact
singleton {$param: name} or {$binding: name} at that slot denotes the named
declaration; other slot values are literal JSON. Request decoding MUST NOT
descend into literal values to discover or substitute references. In particular,
nested parameter-shaped or binding-shaped business objects are data, and a
literal key does not introduce an additional request quotation operator.
The resolved request contract still determines each slot's admissible type and
whether all required static and correlation roles are covered.
The host supplies the opaque binding at invocation. Its declaration and request
position belong to Program identity; the concrete binding belongs to Invocation
identity and resolved_request_cid source evidence. Bindings MUST be used only
inside SourceView requests. A binding MUST NOT create a ResourceRef or ContentRef.
The host invocation contract MUST expose each declaration name and its
resource_kind or media-type constraint separately from the opaque values
artifact. Authors MUST NOT inspect that artifact to discover keys, and authoring tooling
or compiler MUST NOT guess a missing binding name.
Without all declared opaque bindings, checking establishes only a template proof. An executable Plan requires every binding, validated for presence, kind, and request compatibility (Parameters and binding). Execution MUST use those bound values. The public planning result MUST redact opaque values and resolved requests, reporting only declaration names, constraints, and binding status.
Ordinary SourceView requests are static: they may use literals, parameters, and Host bindings. Data-dependent SourceView acquisition uses the checked external-join lowering in External SourceView joins, or search_candidates for ranked candidate retrieval. A finite left key/query set drives one pinned SourceView access path, with the distinct completeness requirements of each operation. Programs cannot author a general fetch/follow DAG or row-driven acquisition loop.
Every field declared by a complete request contract is present in the resolved
request object. nullable: true permits the value null; it does not permit
the field to be absent. A Connector MUST bind every declared request field to
one typed planning/execution consumer. Declaring a field and then ignoring it
is a contract error.
Every resolved SourceView is a canonical actql.source_view/0.1 contract with:
connector_id, connector_version, resource_kind, view_id, view_version,
acquisition {kind, authority, request_schema, modes, default_mode,
optional correlation {request_fields, response_fields, identity, cardinality},
optional batching},
observation {kind, optional transform},
output {carrier, schema, facts}, optional resource_identity,
pushdown {contract, operations}, contract_cidFor a keyed access path, acquisition.correlation.cardinality declares the
response cardinality (zero_or_one | zero_or_many | one | one_or_many) per
requested key. It is not an output.facts field or an authored join assertion.
The correlation contract also names request fields, response fields, and
response identity; External SourceView joins defines their use in external joins. Join cardinality
assertions use the distinct vocabulary and meaning in join.
Each SourceView's request contract MUST be a complete non-null checked object
contract, including for views without correlation. Its fields may themselves
be nullable; a nullable field still requires a value. A declared correlation
uses a search, fetch or follow acquisition and refers to record fields of
a Relation, Events or Vectors output. This field-based correlation grammar does
not flatten a Graph's nodes or edges into a response record. Request and response
field lists are nonempty, duplicate-free and have equal length. Positions pair
in supplied order, and every pair resolves existing fields with compatible
KeyComparable contracts. Their nullability, string extents and decimal
precision/scale need not be identical; declaration compatibility does not bypass
actual request-value constraints or the selected null-request policy.
The declared response identity MUST be a nonempty ordered list of distinct existing response fields with non-null KeyComparable contracts. It may overlap the response key fields and need not equal an Events or Vectors intrinsic identity role. It is an acquisition response identity, not a replacement for that Carrier's semantic identity. Validation retains all supplied tuple orders and exact contract ownership without coercion. Non-null types alone do not prove identity uniqueness, response cardinality, completeness, ordering or successful request execution. The existing acquisition obligations still enforce those claims on actual responses. Native batching's transport contract and value validation are separate from these single-request correlation declarations.
acquisition.kind is exactly one of scan, search, fetch, or follow.
They mean finite collection enumeration, provider candidate search, exact
Resource observation fetch, and provider-declared relationship traversal. Search MAY
be ranked and incomplete; it MUST NOT be promoted to a complete extent.
Read authority is exactly read_metadata, read_content, or read_binary.
A metadata SourceView cannot expose a content or ContentRef field. Referencing
one is a check failure. A reference to such a field MUST NOT trigger an
implicit read-authority upgrade.
observation.kind MUST be one of the following, with the specified meaning.
Observation classification is independent of the output Carrier.
| Kind | Meaning |
|---|---|
projection | A meaning-preserving mapping of existing structured provider data into a Carrier. The contract fixes field and identity mappings, including graph nodes/edges, vector identity/metric, or event identity/time where applicable; it does not infer new facts. |
capture | Provider-returned bytes retained as an Artifact, without interpreting their content. For export, the contract fixes the target scope and supported formats; the resolved request fixes any format choice. |
extraction | Interpretation of captured content by a deterministic transformation defined by a registered ActQL profile. |
observation.transform MUST be a transformation contract reference for
extraction and absent otherwise. Check MUST reject unregistered
transformation contracts under SourceView transformation profiles; a Connector-supplied CID alone
does not register a transformation. Provider export is capture; parsing its returned bytes is
extraction. Extraction evidence MUST bind the captured content CID, resolved
arguments, and transformation contract CID.
A SourceView MUST NOT invoke a model as an implicit part of observation; model acquisition uses the ModelTask boundary (Model acquisition: infer). The Host MUST admit the concrete combination of acquisition, authority, observation, and Carrier under its supported contracts, and reject unsupported combinations before provider access. Classification and self-CIDs do not replace conformance or runtime authority checks. Connector-defined names do not extend these laws.
Read modes are snapshot, changes, and watch. Snapshot denotes a finite
observation, not proof of a capacity ceiling or snapshot consistency. Changes
declares a cursor/watermark/history/resource-version/offset/opaque checkpoint
and may execute in 0.1 only when the requested page/range is bounded. Watch is
unbounded: selecting it is statically unsupported, even if a Catalog advertises
it for another profile. Check MUST report that unsupported selection, and the
runtime MUST recheck it before network access. Delivery
facts distinguish ordered/partition-ordered/unordered delivery, possible
duplicates, acknowledgement, lease, reset, and replay policy. Provider receive
order is not event-time order.
A SourceView's advertised read-mode names MUST be unique, and default_mode
names exactly one advertised mode. A snapshot mode has bounded: true, a
watch mode has bounded: false, and both changes and watch carry a
checkpoint contract. A snapshot may also declare a checkpoint. A changes mode
may advertise either boundedness; admission of its declaration is distinct from
permission to select it for execution. Advertising watch does not make its
selection supported in ActQL 0.1. Validation preserves supplied mode order and
does not insert a missing default, checkpoint or boundedness declaration.
Snapshot acquisition remains subject to checked ceilings and their enforcement boundaries (Author guards, resolved limits, and Plan facts). Consistency within and across acquisitions depends on the admitted source evidence, not the mode name (Runtime source evidence).
The outgoing checkpoint is provider delivery evidence. It is computed from the provider page/range before user projection, filter, ordering, or limit and is carried separately from relation rows. Those relation operations MUST NOT rewind, erase, or otherwise derive the provider checkpoint.
The stable rejection codes are:
ACTQL_E_SOURCE_READ_MODE_UNSUPPORTED
ACTQL_E_UNBOUNDED_EVENTS_UNSUPPORTED
ACTQL_E_SOURCE_CARRIER_UNSUPPORTEDExecution of a SourceView requires support for its acquisition and Carrier contracts. A runtime MUST reject missing support before provider access; it MUST NOT silently convert graph, vectors, or events to relation. Pagination, retry, projection pushdown, filter pushdown, and completeness remain connector capabilities, not ActQL operations.
A compiler or runtime MUST report whether a connector result is complete. It MUST NOT represent a partial result as complete.
The closed author-facing scan vocabulary contains only mode.
mode may select only a mode in the resolved SourceView. A concrete change
checkpoint is Invocation/Run state and is invalid in Program source.
Projection, filtering, ordering, limiting, and
deduplication use ordinary select, filter, sort, take/top_k, and
dedupe_rows steps. A Connector MAY push down those checked operations only
when its capability evidence proves the same semantics; otherwise the runtime
MUST apply them locally or reject before provider access. It MUST NOT interpret
an author-only scan predicate, projection, order, limit, or uniqueness dialect.
Connector inputs MUST NOT repeat local carrier, expected_columns, schema,
or primary_key declarations. The pinned SourceView contract
owns those facts. Such fields are invalid rather than drift assertions that
could disagree with the authority-bearing contract.
For a Relation SourceView, its referenced carrier_facts body MAY contain
relation_keys: {candidate_keys: [[field, ...], ...]}. This component is closed:
the collection and every tuple are nonempty, names in a tuple are distinct,
and repeated identical tuples are invalid. Other carrier-facts components
retain their existing grammar and admission; the enclosing facts object remains
extensible. Each component name resolves exactly against the same SourceView's
complete checked output schema, with required presence, non-nullability and
the existing KeyComparable value-domain rules. A declaration is conditional
input-admission evidence, not proof obtained from a schema or sampled rows.
The checker MUST retain the exact SourceView, facts Envelope, Catalog and schema ownership and the ordinary key-validation obligations. Before a consumer relies on a declared key, the runtime MUST validate it across the complete resolved acquisition, including duplicates on different pages and fully identical repeated rows. Filtering or projection MUST NOT erase that pending obligation, and a filtered subset MUST NOT establish uniqueness of its original acquisition. NULL or missing components and duplicate tuples fail atomically without a published Result. Missing execution support MUST be rejected before provider access. Provider arrival order, delivery-order labels and old facts without this component MUST NOT be promoted to a key or total-order witness.
For a Relation SourceView, its referenced carrier_facts body MAY contain
relation_scores: {field: ScoreContract, ...}. This field-scoped map uses the
existing closed ScoreContract reader: nonempty domain and normalization,
finite ordered two-number range, and optional higher_is_better: true.
Fields MUST resolve exactly against the same SourceView's required, non-null
Number fields. Duplicate names, unknown fields and invalid contracts are invalid.
An absent or empty map asserts no scores. The enclosing facts object remains
extensible; unknown extensions are not implicitly admitted as complete bodies.
The checker MUST retain the original facts Envelope, Catalog, SourceView and schema. It MUST project these assertions through the existing ranking-evidence owner, never through a same-named local snapshot or operation argument. Before relying on an assertion, the runtime MUST validate every declared score across the complete captured input, before filtering, renaming, overwriting or dropping fields. Non-finite, NULL, missing and out-of-range values fail without publishing a Result. Existing lawful metadata transfer applies; computed or overwritten scores MUST NOT inherit an unrelated original assertion. Validation consumes the existing work allowance and retains input custody until cleanup completes. Unavailable runtime support MUST refuse before provider access. Empty and key-only facts and historical saved Run identities retain their meaning.
A SourceView used as the keyed right side of an external join MUST NOT author
scan, including an empty object. Its keyed access path and bounded default
mode are owned by the pinned SourceView contract. Mode selection is available
only when the SourceView is read directly; a future checkpointed
keyed access path requires its own fully bound contract rather than silent
reuse of scan vocabulary.
Whenever the resolved read mode is changes, whether explicit or supplied by
the contract's default_mode, the compiler MUST produce a content-addressed
actql-source-state-requirement/0.1 in the checked Plan. The requirement binds
the Program Definition, owning stage_id, input name, SourceView Contract, mode, and checkpoint
value contract. A concrete checkpoint is private Run material represented by
an actql-source-state-binding/0.1; changing it changes Invocation/Run
identity, not Program Definition identity.
The requirement's stage_id MUST be the normalized authored ID of the Stage
that declares the input, and input_name MUST remain that Stage's normalized
input name without an encoded prefix. Both fields participate in the canonical
requirement preimage. Distinct Stages with the same input name and SourceView
MUST NOT share a source-state requirement. requirement_cid hashes the complete
canonical requirement object except requirement_cid itself; no concrete
checkpoint or internal Stage index is substituted for the owning Stage ID.
For resolved changes inputs, the compiler MUST emit requirement: required
and initial_position: explicit_state_required. ActQL 0.1 does not infer an
initial checkpoint from missing Run state or from the fact that the Contract
advertises a bounded mode. The checkpoint value is admitted separately against
the exact selected mode's checkpoint value contract before acquisition.
Changes-read state is governed by Source-state advancement. Local graph, vector, and event inputs belong to their algebra profiles independently of Connector execution support.
Parameters and binding
ActQL 0.1 has typed invocation parameters for reusable programs:
params:
cutoff: {type: timestamp}
threshold: {type: number}
account_ids:
type: array
items: {type: string}
steps:
- filter: "occurred_at >= $cutoff AND score >= $threshold"Scalar parameter types are string, number, decimal, boolean,
timestamp, and date. Closed typed objects and homogeneous recursive arrays
are also admitted. Arrays declare one item contract under items
and MAY declare a positive fixed
length, which is required when the value is used as a fixed-dimension query
vector. Decimal declarations MUST specify literal precision and scale
under Exact decimal values; decimal array items declare the same pair once under items,
shared by every element. For example:
params:
unit_price: {type: decimal, precision: 18, scale: 2}
tax_rates:
type: array
items: {type: decimal, precision: 5, scale: 4}Decimal bindings MUST use the fixed-scale canonical string representation defined in Exact decimal values. Binding MUST reject noncanonical text, precision overflow, and binary64 numeric input without implicit rounding or conversion. Parameter value identity MUST use the typed decimal identity of Exact decimal values, not the identity of its wire-format string, including for array elements.
Number declarations MAY add integer: true and finite inclusive
minimum/maximum; string declarations MAY add format: duration.
These type-specific constraints apply recursively to Parameter object fields and
array items. integer: false imposes no integrality restriction; integer: true
requires a finite integral binary64 value, not an implicit safe-integer ceiling.
Array length is a positive safe integer and fixes the exact element count;
the shared optional max_items bound remains a nonnegative safe integer.
These constraints are checked at bind time, before AoA lowering. Parameters may
appear in scalar value positions, including
predicate literals, explicit ID lists, time/duration values, k, query vectors,
and numeric author guards, including max_items, max_output_rows, and
serialization bounds. They MUST NOT select an operation, input/result name, field
name, join kind, metric, direction, contiguity, overlap, connector,
resource, view, mapping, permission, approval, or failure policy.
A sequence pattern is structural and cannot be parameterized.
The additional recursive-value positions are exactly a values CellValue
{$param: name}, a pivot fill CellValue {$param: name}, an immediate
EffectValue {literal: {$param: name}}, and a
SourceView request value role accepting the same declared object/array contract.
An array parameter may also occupy an IN literal list, explicit ID list or
query-vector value where that consumer admits its element contract and extent.
Only these whole-value holes are substituted, once; nested business JSON is
opaque. An object EffectValue may supply an object-valued mapped field, but this
does not turn raw create.payload into a recursive template or parameterize its
field names. Existing consumer type, constructibility, egress and bound rules
still apply. A declared recursive value is not permission to use it at an
otherwise structural position.
Invocation value admission is closed:
| Condition | Parameters | Opaque bindings |
|---|---|---|
| Permitted types | Scalar, closed typed object, homogeneous recursive array | The closed opaque binding declarations in Connector SourceViews |
| Missing value | Apply a declared literal default once; otherwise needs_bindings, including nullable declarations | needs_bindings |
| Null or wrong type | Null only with nullable: true; wrong type rejects | Reject; a reference also requires issuer/admission evidence |
| Undeclared supplied name or unused declaration | Reject | Reject |
| Structural selector or authority-policy substitution | Reject | Reject |
In Parameter type syntax, nullable defaults to false at every recursive type
position, including object fields and array items; this shorthand does not change
the explicit-nullability rule for general value contracts or checked contracts.
default and annotation meta are admitted only on a named Parameter declaration,
not on its nested type contracts. A business object field named meta is ordinary
data. Optional default is a complete
typed canonical literal, never an expression, clock, environment lookup or
reference to another parameter. Every object field is required; nullable does
not mean optional. Default substitution is top-level only, without deep merge.
An explicit null is never replaced by the default. Ordinary parameters cannot
fabricate opaque references or secrets. Consumer constraints such as
positive safe-integer guards are checked independently of parameter nullability.
Effective parameter values MUST be resolved once before the Program starts and remain immutable across all stages, attempts and resume. Explicitly supplying a default's value and omitting that value have the same typed values CID; origin evidence is separate. Changing the default declaration changes Program semantics. Usage is checked over all stages; extra supplied names and unused declarations remain invalid. Public explain exposes type, bound state and default presence, not private values.
For a direct SourceView, the authored request supplies its entire closed request schema. For a keyed SourceView join (External SourceView joins), those fields are the disjoint union of authored static request fields and Contract-defined correlation fields; missing fields, overlap, and extra fields are rejected.
Check MUST classify a correlated SourceView input using both its authored request fields and its actual consumers. Supplying every correlation request field selects direct acquisition, including when that resulting Relation is a join operand; a join reference does not remove supplied values or turn that request into a key-driven template. Supplying none selects keyed acquisition only when the input has at least one logical join-right consumer and no other direct operation or Result consumer. Supplying only some correlation fields, using an incomplete template directly, or leaving such a template without a join-right consumer fails Check. A SourceView without correlation uses direct request admission. Every non-correlated request field remains required in either form. Classification does not prove boundedness, response completeness, mode support, policy admission or the remaining join requirements, and does not authorize deleting an unused declaration or an authored operation.
Within EffectValue, {literal: {$param: name}} is an immediate parameter
hole; other literal payloads are opaque JSON, not recursively substituted.
An explicit create.payload is likewise data, including $param-shaped objects.
A parameterized author guard MUST resolve to a positive safe integer before executable planning. It retains the fail-closed semantics of a literal guard (Author guards, resolved limits, and Plan facts), and does not override Contract or Host ceilings or grant authority.
Every declaration MUST be referenced and every runtime invocation MUST resolve
all references by explicit binding or a missing-only default to a value of the
exact declared type. Binding does not
rewrite the Program CID. A change to canonical bound values changes
parameter_values_cid, compiled-plan identity, Invocation CID, and any
resulting Effect Preview CID, even if the lowered AoA computation is unchanged
(Canonical identities). An approval MUST NOT carry across a parameter change.
Parameter binding precedes bound AoA lowering. The semantic phases (Normative conventions) have the following obligations; they do not prescribe compiler pass names:
checking template + Check Environment proof
binding canonical values + range/length/duration proof
planning substitute bound values before AoA Fragment and barrier planning
execution capture source evidence before establishing Invocation identityactql-bound-invocation-template/0.1 binds Program Definition CID,
Parameter Values CID, Check Environment CID, and the fact that runtime source
evidence remains unbound. The compiler MAY use type-valid witness values solely to
prove the template; it MUST NOT publish a compiled-plan or invocation identity
for those witnesses. Planning without every referenced value reports
needs_bindings and does not publish a runnable Plan Document. Emitting a Plan
Document and starting execution require every referenced value. Effect preview
accepts a Captured Run and MUST NOT perform parameter binding again.
An executable Plan MUST bind the exact parameter values used to resolve its
operations and limits; execution MUST NOT substitute different values.
Callers supply concrete values. A Host that derives parameters from clocks, schedules, or other invocation settings MUST resolve them before executable planning and retain them for the Run. Same-Run retry or resume MUST NOT recompute these values. Schedule registration is not authorization of future executions; each occurrence is subject to Run admission (Run admission and identity).
Note (informative): A scheduler may store a Program and parameter-generation settings, then bind values and plan each occurrence. Timezone and whether a date is derived from the scheduled or actual start time are Host settings, not implicit ActQL expressions.
Resource identity contract
A SourceView resource_identity contract contains only the static provider
namespace, ResourceKind, provider-ID field, output field, and required boundary
topology. Connection revision, concrete authority scope, and boundary CID are
resolved connection evidence recorded by the plan/source evidence and hydration
receipt; host connection-secret rotation does not churn the static Connector Catalog or
lock. The SourceView issues the same resource_ref<K> for the same provider
namespace, resolved authority scope/boundary, ResourceKind, and provider
identity across all its views. View/version/etag/observation facts do not alter
Resource identity.
Runtime hydration, Artifact issuance, and availability leases are specified in Reference admission and availability.
Expressions and predicates
Expression text and structured trees share one typed expression model.
Expression<T> produces a value of T; Predicate names a condition position
requiring a boolean Expression, optionally nullable, not a separate value
domain. Comparisons and boolean operations produce true, false, or null
(the three-valued unknown). Storing the result preserves null. No string or
number truthiness is admitted. A boolean field or function is directly usable
as a condition. if(p, TRUE, FALSE) deliberately maps null to false; it is not
the general representation of a boolean expression's value.
| Position | true | false | null |
|---|---|---|---|
Value position, including derive | store true | store false | store null |
filter, aggregate filter | admit row | exclude row | exclude row |
if, case condition | select branch | do not select branch | do not select branch |
assert_rows.where | row satisfies assertion | assertion fails | assertion fails |
count_if | count | do not count | do not count |
count counts non-null argument values, including false; count_if counts
only true. Each consumer uses these rules without inserting a Program-level
cast or changing the boolean expression's identity.
The following table closes expression placement and lowering in 0.1:
| Position | Admitted tree and context | Evaluation/lowering |
|---|---|---|
derive value | Common Expression over the input row, including pure profile calls | Canonical typed expression; conditional demand and per-expression envelopes preserved |
filter, assert_rows.where | Boolean Expression over the input row | Common evaluation followed by the condition-consumption rule |
Aggregate expr | Common row-local Expression | Each aggregate's filter first, expression only for its admitted rows, then null exclusion, distinct, and fold (group) |
| Graph/sequence conditions | Boolean Expression in the operation's property/event context | Same expression laws and profiles; Carrier scope remains operation-specific |
Effect where element | EffectPredicate over target fields (Predicates) | Common decode followed by restricted selector validation; no scalar computation |
An inadmissible expression or selector node MUST fail during check in either notation. Moving a subexpression to a separate step is valid only if its evaluation conditions, failure behavior, and resource bounds remain unchanged; a compiler MUST NOT evaluate an unselected branch by hoisting it before its conditional. Profile admission applies to every branch, but a pure profile does not require an author-visible intermediate step. Effect selector and invocation-value restrictions are not widened by expression composition.
Source notation and precedence are in Expression text; canonical representation, printing, and diagnostics in Decoding, identity, and printing. Value families and their evaluation rules are in Scalar expressions and Decimal expressions–Text, time, and regular expressions; predicate structure and evaluation are in Predicates.
Scalar expressions
An expression or predicate position accepts expression text (Expression text) or the
structured tree form. In the tree form, field references, literals, and
parameters use {field: ...}, {literal: ...}, and {$param: name}.
Text is decoded to the same tree before identity computation (Decoding, identity, and printing).
Strings in non-expression positions, including inside literal, meta,
requests, and template bodies, retain their declared meaning and are not
recursively parsed as expressions. The Core scalar grammar contains:
- arithmetic
add,subtract,multiply,divide, andmodulo; - boolean/comparison expressions,
case, three-argumentif,coalesce, andnullif; len,contains,starts_with,ends_with,concat,safe_div,safe_divide,safe_add,safe_subtract,safe_multiply,safe_negate,sqrt,cbrt,sin,cos,tan,asin,acos,atan,atan2,sinh,cosh,tanh,asinh,acosh,atanh,pow,exp,ln,log,log10,abs,sign,round,trunc,greatest,least, andfloor;- the authoring-only string
templateform; lower,upper,trim,ltrim,rtrim,substring,left,right,lpad,rpad,split, and literalreplace;date_trunc,date_add, anddate_diff; and- explicit
to_number,to_string,to_boolean, andto_timestamp; to_decimal,try_decimal,quantize, anddecimal_divide; and- closed
object, homogeneousarrayand typed empty-array construction; - typed property/subscript access and type-preserving numeric
negate.
coalesce is a lazy expression, not a macro that evaluates a child twice.
template is a deterministic authoring macro for ordinary messages and labels:
- derive:
message: {template: "{name} is at risk — active users: {active_users_30d}"}Each {field} placeholder MUST name one visible string, number, decimal, boolean,
timestamp, or date field. The compiler MUST lower placeholders to canonical
to_string followed by left-to-right strict concat_strict. The lowering uses no locale,
host formatting, or expression evaluator. {{ and }} emit literal braces.
Unmatched braces, empty placeholders, unknown fields, and non-scalar fields are
invalid. Null interpolation follows the to_string and concat_strict
rules and therefore produces null. Parameters and nested expressions are not
template placeholders; authors use the ordinary explicit expression grammar
when those are required.
substring(value, start[, length]) MUST use the GoogleSQL STRING position
rule in the common scalar owner: positive positions are one-based, zero and
positions below negative input scalar length select the first scalar, other
negative positions count from the end, and positions beyond the input select
an empty string. An omitted length retains the suffix, zero length returns
empty, and positive length retains at most that many Unicode scalars. Every
supplied child evaluates once in value/start/length order; any NULL result
propagates after all children succeed and before domain validation. A
non-integer or unsafe start/length, or negative length, fails only when the
non-null call is demanded. Index arithmetic is exact and clamped before
addition. Retained scalars, including astral, combining, NUL and line breaks,
remain unchanged. The result retains known input max_codepoints and
max_utf8_bytes, drops min_length, and unions supplied operand nullability.
The closed source body is {substring: {value, start, length?}}; omission is
preserved in the corresponding machine body. SQL SUBSTR and SUBSTRING
select this same operation, with the existing finite binary64 safe-integer
range rather than the complete GoogleSQL INT64 domain. BYTES is not admitted.
Substring has no text-time envelope/profile requirement; nested profile calls
retain their own obligations, input preflight and selected resource limits.
All scalar logical-work, live-value, output-byte, workspace, failure and
cooperative cancellation requirements remain applicable.
left(value, length) and right(value, length) MUST return at most the
specified number of Unicode scalars from the corresponding end. Zero returns
empty; a length at least the input scalar count returns the entire input.
lpad(value, length[, pattern]) and rpad(value, length[, pattern]) MUST return
exactly the requested scalar length. They truncate the original value to its
leftmost requested scalars when the requested length does not exceed its
length. Otherwise they prepend or append repetitions of the pattern, taking
only the needed prefix of its final repetition. The omitted pattern is U+0020.
An explicitly empty pattern is a domain failure even when no padding is needed.
Every supplied child MUST evaluate once, left to right, before strict NULL propagation and then domain validation. A demanded, non-null call fails for negative, nonintegral or unsafe-integer lengths. Child failure identity and lazy branch demand remain unchanged. All lengths use ActQL finite binary64 safe integers; full GoogleSQL INT64, BYTES and collation remain unsupported. Unicode scalars, including astral characters, combining marks, NUL and line breaks, MUST remain unchanged without normalization or grapheme segmentation.
The common source bodies are ordered arrays: {left: [value, length]},
{right: [value, length]}, {lpad: [value, length, pattern?]} and
{rpad: [value, length, pattern?]}, with corresponding machine operation/body
arrays. Omission stays omitted; an implicit pattern expression is not inserted.
Side slices retain input String maxima and drop minimum length. Padding drops
input maxima because runtime padding can expand beyond them. All supplied
operands contribute to result nullability. The existing scalar continuation
owns cooperative counting, boundary location, exact output sizing and
construction. Before output buffer allocation, all output logical bytes and
workspace MUST be admitted. Retained operand roots, logical work, live values,
control work and cancellation stay with the existing owners.
split(value[, delimiter]) MUST accept one STRING expression and an optional
ordinary STRING delimiter expression, returning an Array of non-null String
elements. The Array is nullable exactly when any supplied operand is nullable;
untyped NULL takes the unique STRING signature at either position. The admitted
domain is the existing finite, bounded String domain without implicit
conversion, BYTES, regular-expression or collation overloads.
A demanded non-null split MUST split at non-overlapping literal matches of
the delimiter in authored input order, preserving leading, adjacent and trailing
empty parts. An omitted delimiter is U+002C COMMA. Empty input produces one
empty String, including with an empty delimiter; an empty delimiter on nonempty
input produces one String per Unicode scalar. No normalization, case folding,
trimming or grapheme segmentation is applied, including to astral characters,
combining marks, NUL and line breaks. A literal match does not restrict the
delimiter expression to a literal or parameter.
The split result type MUST retain the input String's known max_codepoints
and max_utf8_bytes on its non-null element contract and drop min_length.
For a known scalar-count ceiling n, use max_items = n + 1 only when that
addition is representable in the existing safe-integer bound domain. The
ceiling is the smaller of both known input maxima, or the sole known maximum;
a UTF-8 byte ceiling is also a conservative scalar-count ceiling. If neither
is known, or the addition is unrepresentable, omit max_items. Type derivation
does not evaluate the value or delimiter expression or invent a minimum bound.
Every supplied split child MUST evaluate exactly once, value before delimiter,
before strict NULL propagation. A NULL value does not suppress a demanded
delimiter failure. Preserve the first child failure and enclosing lazy demand;
no secondary evaluator or Host splitting helper replaces the common owner.
The common source and machine node MUST retain ordered argument arrays,
{split: [value, delimiter?]} and {op: split, body: [value, delimiter?]},
including a one-element array when the delimiter is omitted. Omission remains
distinct from explicit NULL or an explicit comma expression. Do not fold the
node, reorder children or substitute the separately owned split_part profile.
Earlier pinned bodies retain their original meaning and identities.
The common expression continuation MUST preserve retained operand roots, logical operand/result charges, control work, live-value/output-byte limits, node/depth limits, workspace limits and cooperative cancellation throughout literal search, exact sizing and construction. Admit the complete Array's wire bytes, node/depth shape, String storage and Array slots before allocating output builders or their storage; no eager whole-input split or boundary array substitutes for bounded continuation work. This is an ordinary common expression with no split-specific text-time profile, policy or envelope; nested profile calls retain their own obligations and input preflight.
byte_length(value) and unicode(value) MUST accept exactly one STRING
expression and return Number with that operand's nullability. chr(value)
accepts exactly one finite Number expression and returns String with its
operand's nullability. Untyped NULL takes each operation's unique signature.
The CHR result has intrinsic max_codepoints = 1 and max_utf8_bytes = 4,
without a minimum length; type derivation does not evaluate its child.
There are no implicit conversions, BYTES, Decimal or distinct INT64 overloads.
A demanded non-null byte_length MUST return the exact UTF-8 byte count of
its complete String operand, including zero for empty input. This is not a
UTF-16 code-unit, Unicode scalar, grapheme or JSON-escaped byte count. Preserve
all input scalars without normalization; octet_length is its text alias.
A demanded non-null unicode MUST return the first Unicode scalar's code
point, or zero for empty input or a leading U+0000. An astral scalar contributes
its complete code point, not a surrogate or encoded byte. Combining marks
remain independent scalars without normalization or grapheme segmentation.
A demanded non-null chr MUST require an integral Number in the inclusive
ranges 0..55295 or 57344..1114111 before integer conversion. Zero returns an
empty String; every other admitted code point returns exactly one Unicode
scalar, including noncharacters. Negative, fractional, surrogate and above
U+10FFFF values fail without truncation or wrapping. NULL propagates before
this domain validation and produces NULL.
The common expression continuation MUST demand each sole child exactly once and preserve its failure before strict NULL propagation. Enclosing lazy demand, retained roots, logical operand/result and control charges, live-value, output-byte, workspace and cancellation owners remain unchanged. Admit complete CHR output bytes and storage before constructing the bounded String. These ordinary operations require no text-time profile or frontend/Host evaluator.
Canonical lowering MUST retain the three closed direct unary source bodies
{byte_length: expression}, {unicode: expression} and {chr: expression}
and corresponding {op, body: expression_node} machine nodes, without folding,
duplication or synthetic arrays. SQL and expression-text BYTE_LENGTH and
OCTET_LENGTH share byte_length; no canonical octet_length operation is
added. UNICODE and CHR select their respective common nodes. Earlier pinned
contract bodies retain their original meaning and identities.
sin, cos, tan, asin, acos, atan, sinh, cosh, tanh, asinh,
acosh and atanh MUST accept exactly one finite binary64 Number operand;
atan2(first, second) accepts exactly two ordered finite Number operands.
Each returns Number, nullable exactly when any operand is nullable. Untyped
NULL takes the unique Number signature. No implicit conversion, distinct
INT64/NUMERIC/BIGNUMERIC, Decimal or non-finite overload is introduced.
sin, cos and tan MUST compute their circular functions with radians;
asin, acos and atan return principal inverse values in radians, with
ranges [-pi/2, pi/2], [0, pi] and [-pi/2, pi/2] respectively. sinh, cosh
and tanh compute their hyperbolic functions; asinh, acosh and atanh
compute the corresponding inverse functions. Reject non-finite operands and
preserve the existing arithmetic failure for any non-finite result, including
hyperbolic overflow. Canonicalize every zero result to positive zero.
Demanded non-null asin and acos MUST fail outside [-1, 1], acosh below
1, and atanh outside the strict interval (-1, 1), including both endpoints.
These checks follow strict NULL propagation; the other direct operations add
no finite-input domain restriction. A finite result is still required.
atan2(first, second) MUST compute the principal inverse tangent of
first/second in [-pi, pi], using the signs of both arguments for the quadrant
and passing first then second directly to core atan2, without division or
argument swapping. Raw signed-zero pairs (+0, +0) and (-0, +0) yield canonical
positive zero, (+0, -0) selects positive pi, and (-0, -0) selects negative pi.
Public canonical Number zero is positive, so public atan2(0, 0) is positive
zero; the raw signed-zero diagnostic does not admit negative-zero wire values.
Each direct trigonometric operation MUST use the same pinned portable MoonBit core Double
primitive in src/scalar_numeric_kernel across JS, WebAssembly and Native,
with canonical finite-result validation. Reconstruction from other operations
or a frontend/Host evaluator is forbidden. Preserve all existing numeric
accuracy and exception contracts. Identical admitted inputs and selected
semantic contracts preserve exact logical Number bits across targets;
live-BigQuery last-bit equivalence is not claimed.
Typing MUST check every authored Number operand without evaluating it and return Number, nullable exactly when any operand is nullable. Runtime domain checks do not narrow the static type or add a distinct numeric overload.
The common continuation MUST demand each child exactly once in authored left-to-right order, preserving child failures before strict NULL propagation and only then applying value-domain checks. Preserve enclosing lazy demand, retained roots, logical operand/result and control charges, live-value and workspace limits, and cooperative cancellation through the canonical scalar owner.
Canonical lowering MUST retain each unary source {name: expression} and
matching machine {op: name, body: expression_node}; atan2 retains the
ordered pair source {atan2: [first, second]} and matching machine
{op: atan2, body: [first_node, second_node]}. SQL and expression-text calls
select these same direct operations without folding, duplication, synthetic
children or frontend evaluation, including constant arguments. Earlier pinned
bodies retain their meaning and identities.
cbrt(value) MUST accept exactly one finite binary64 Number operand and return
Number with exactly the operand nullability. Untyped NULL takes the unique
Number signature. No implicit conversion, distinct INT64, NUMERIC, BIGNUMERIC,
Decimal or non-finite overload is introduced.
A demanded non-null cbrt MUST return the real cube root, including negative
inputs, subnormals and largest finite magnitudes. Either zero sign produces
canonical positive zero; NULL produces NULL. The finite Number domain adds no
value-domain failure. Reject non-finite operands and preserve the existing
arithmetic failure for an unexpected non-finite result.
cbrt MUST use the same pinned portable MoonBit core Double
cube-root primitive in src/scalar_numeric_kernel across JS, WebAssembly and Native,
with canonical finite-result validation. Substitution with pow(x, 1/3) or logarithmic reconstruction
or a frontend/Host evaluator is forbidden. Preserve all existing numeric
accuracy and exception contracts. Identical admitted inputs and selected
semantic contracts preserve exact logical Number bits across targets;
live-BigQuery last-bit equivalence is not claimed.
Typing MUST check the sole Number operand without evaluating it and return Number with exactly its nullability. Positive, negative, zero, subnormal and largest finite values remain within the admitted domain.
The common continuation MUST demand the sole child exactly once, preserving its failure before strict NULL propagation. Preserve enclosing lazy demand, retained roots, logical operand/result and control charges, live-value and workspace limits, and cooperative cancellation through the canonical unary scalar owner.
Canonical lowering MUST retain the closed direct unary source
{cbrt: expression} and matching {op: cbrt, body: expression_node} machine
node. SQL and expression-text CBRT select this same common operation without
folding, duplication or synthetic children, including constant arguments.
Earlier pinned bodies retain their meaning and identities.
translate(value, source, target) MUST accept exactly three STRING operands.
instr(value, subvalue[, position[, occurrence]]) accepts two STRING operands
and zero, one or two finite Number operands in that order. Untyped NULL takes
the unique positional signature. No implicit conversion, BYTES, collation,
regex or distinct INT64 overload is introduced.
Demanded non-null translate MUST map each original Unicode scalar at most
once using its source index to select the corresponding target scalar, leave
scalars absent from source unchanged, delete source scalars without a target,
and ignore surplus target scalars. Reject every duplicate source scalar,
including unused duplicates and an empty value. Mapping is simultaneous,
not cascading; preserve astral scalars, combining marks and NUL without
normalization or grapheme segmentation.
Demanded non-null instr MUST return the 1-based Unicode scalar start position
of the requested exact subvalue occurrence, or zero when unreachable. Omitted
position and occurrence each mean one. Positive position starts a forward search
at that 1-based scalar; negative position starts a backward search at scalar
length plus position plus one. Count overlapping matches separately, preserve
exact strings without normalization, and apply the same direction to subsequent
occurrences.
An empty instr subvalue MUST match the scalar length plus one boundaries.
Positive position starts at that boundary; negative position starts at scalar
length plus position plus two. Each further occurrence moves one boundary in
the search direction; return zero outside the boundary range. Empty value and
empty subvalue at default position and occurrence return one. Thus
instr('ab', '', 1, 3) is three, its fourth occurrence is zero,
instr('a', '', -1) is two, and instr('a', '', -2) is one.
After strict NULL propagation, instr MUST require an integral nonzero position
and integral positive occurrence, validating both domains before empty or
out-of-range shortcuts. This explicitly adapts Google's INT64 parameters to the
existing finite binary64 Number domain without a safe-integer or INT64 cap.
Compare valid positions and occurrences with bounded scalar or boundary counts
before narrowing; huge unreachable integral values return zero without a huge
binary64 decrement loop. Earlier SUBSTR and padding domains remain unchanged.
Typing MUST check every authored operand without evaluating it and union all
supplied operand nullabilities. translate returns String, retaining only its
value input max_codepoints bound when present: deletion can empty the result
and substitution can expand UTF-8 width, so min_length and max_utf8_bytes are
dropped. instr returns Number; omitted defaults do not add operands or narrow
the type. Runtime domain checks do not replace static operand checking.
The common continuation MUST demand every supplied child exactly once in authored left-to-right order, preserving later child failures after an earlier NULL, before strict NULL propagation and operation-domain validation. Preserve enclosing lazy demand, retained roots, logical operand/result and control charges, live-value/output-byte and workspace limits, and cooperative cancellation. Admit scratch and complete output storage through existing owners before allocation. No arbitrary scalar cap or frontend/Host evaluator is added.
Canonical lowering MUST retain ordered arrays {translate: [value, source, target]} and {instr: [value, subvalue, position?, occurrence?]}, with matching
{op, body} machine nodes containing the same authored expression nodes.
Preserve two-, three- and four-operand INSTR forms as distinct identities even
when explicit defaults have equal results. SQL and expression-text calls select
these same operations without folding, duplication, injected default children
or rewriting INSTR as STRPOS. Earlier pinned bodies retain their identities.
repeat(value, count) MUST accept exactly a STRING and finite Number in that
order and return String with their unioned nullability. reverse(value) accepts
exactly one STRING and preserves its nullability. Untyped NULL takes each
unique positional signature. No implicit conversion, BYTES, Decimal or distinct
INT64 overload is introduced.
A demanded non-null repeat MUST require a nonnegative integral Number count
before an empty-input shortcut. Negative and fractional counts fail. Zero count
or empty input returns empty, including empty input with a very large valid
count. Otherwise concatenate the complete input scalar sequence count times
without normalization. This explicitly adapts GoogleSQL's INT64 count to the
existing finite binary64 domain without adding a safe-integer or INT64 cap;
NULL propagates before operation-domain validation.
A demanded non-null reverse MUST reverse Unicode scalar order, preserving
complete astral scalars, NUL and combining marks without normalization or
grapheme segmentation. It reverses neither UTF-16 code units nor UTF-8 bytes.
Empty input returns empty and NULL produces NULL.
Typing MUST preserve the complete input String contract for reverse, including
minimum length and codepoint/UTF-8 maxima, because scalar and byte counts do not
change. repeat returns an otherwise unconstrained String, dropping input
minimum and maximum lengths because a dynamic count can be zero or expand it.
Neither rule evaluates operands during checking.
The common continuation MUST demand every child exactly once in authored order, preserving a later child failure after an earlier NULL, before strict NULL propagation. Preserve enclosing lazy demand, retained roots, logical operand, result and control charges, live-value, output-byte, workspace and cooperative cancellation owners. Validate a non-null repetition count before narrowing. Prove a nonempty repetition count fits bounded execution using existing storage limits before narrowing. Admit complete output storage through existing resource owners before allocation. No arbitrary scalar cap, huge binary64 decrement loop, profile or frontend/Host evaluator is introduced.
Canonical lowering MUST retain the closed ordered binary source
{repeat: [expression, expression]} and direct unary source
{reverse: expression}, with matching {op, body} machine nodes. SQL and
expression-text REPEAT/REVERSE select these same common operations without
folding, duplication or synthetic children. Earlier pinned bodies retain their
meaning and identities.
Explicit String transformations follow their declared Unicode and locale-independent laws; ordinary String values are preserved. replace
is literal substring replacement, not regex. date_add and date_diff
require an explicit unit from ms|s|m|h|d; calendar-month or timezone guesses
are not admitted in 0.1. Their forms are
{date_add: [{field: timestamp}, {literal: amount}, {literal: unit}]} and
{date_diff: [{field: end_timestamp}, {field: start_timestamp}, {literal: unit}]}. Timestamp
inputs and date_add results use canonical UTC ISO-8601 strings; date_diff
returns a finite numeric count in the authored fixed unit.
The decoded unit is a {literal: ms|s|m|h|d}. Full-word units and
day-specific helper names are not ActQL source vocabulary.
The expression 0.1 contract retains the binary64 arithmetic laws of
actql-scalar-semantics/0.1, with the conversion/concatenation rules below.
Every binary64 arithmetic result rounds to
binary64; negative zero is normalized at operation boundaries and remains
invalid in canonical source. Overflow/non-finite results and ordinary division
by zero fail the operation except for the explicitly safe operations below.
The legacy safe_div(_, 0) returns null but retains overflow failure; it is not
an alias of GoogleSQL SAFE_DIVIDE or the common safe_divide operation.
safe_add, safe_subtract, safe_multiply, and safe_divide accept exactly
two Number operands and produce nullable Number. They apply the corresponding
binary64 arithmetic operation and return null for their own overflow;
safe_divide also returns null for a zero divisor, including zero divided by
zero. Finite subnormal results and underflow to canonical positive zero remain
ordinary numeric results. safe_negate accepts exactly one Number and returns
its negation with the operand nullability: every finite binary64 value has a
finite negation, so no extra null is introduced in this supported domain.
These operations do not admit Decimal, INT64, NUMERIC, BIGNUMERIC or non-finite
values, and they do not coerce strings or booleans.
The common strict rule evaluates every demanded child exactly once in source order before applying null or arithmetic handling. A null first operand does not suppress a failing second operand. Child-expression, domain, cancellation, control, logical-work, live-byte and workspace failures are not converted to null. Authored SAFE nodes and their children remain in canonical lowering even when constant. Existing lazy expressions retain their demand rules.
Binary64 sum and
avg use the captured numeric-observation boundary defined below, rather than
requiring a placement-independent reduction bit pattern. Exact decimal arithmetic
uses a separate coefficient/precision/scale value domain (Exact decimal values), not an
implicit binary64 or money coercion.
The conversion/boundary truth table is normative:
| Function | Adopted result |
|---|---|
to_number(string) | canonical decimal grammar `-?(0 |
to_boolean(string) | exactly case-sensitive true or false; no trim; otherwise fail |
to_string(number) | AoA canonical number spelling |
to_string(string) | unchanged String value |
to_string(decimal) | fixed-scale canonical decimal text |
to_string(boolean) | true or false |
to_string(timestamp), to_string(date) | unchanged canonical temporal spelling |
to_timestamp(string) | canonical millisecond UTC Z spelling only; offsets fail |
modulo(a,b) | truncating remainder with dividend sign; zero divisor fails |
sqrt(x) | negative fails |
sin(x), cos(x), tan(x) | finite Number circular functions in radians; null propagates |
asin(x), acos(x), atan(x) | principal inverse circular functions in radians; asin/acos require [-1,1]; null propagates |
atan2(first, second) | quadrant-aware inverse tangent in authored order; canonical public (0,0) yields zero |
sinh(x), cosh(x), tanh(x), asinh(x) | direct hyperbolic and inverse sine functions; finite result required; null propagates |
acosh(x), atanh(x) | acosh requires >=1; atanh requires strict (-1,1); null propagates |
cbrt(x) | real cube root of a finite Number, including negatives; canonical positive zero; null propagates |
pow(x,y) | negative base requires integral exponent; zero with negative exponent fails; zero to zero is one; overflow fails and underflow returns canonical zero |
exp(x) | natural exponential; finite-input overflow fails; underflow returns canonical zero |
ln(x), log10(x) | natural/base-ten logarithm; nonpositive input fails |
log(x,base) | positive value and positive base other than one required |
abs(x) | exact finite binary64 magnitude, including subnormals and maximum finite values; canonical positive zero; null propagates |
sign(x) | exactly -1, positive zero or 1 for negative, zero or positive finite binary64 input; either zero sign yields positive zero; null propagates |
round(x) | nearest integer, with exact halfway cases rounded away from zero; finite binary64 number only; null propagates |
trunc(x) | nearest integer toward zero; finite binary64 number only; null propagates |
round(x,n), trunc(x,n) | finite binary64 decimal-position rules below, with integral number n; either null operand yields null; ROUND overflow fails |
starts_with(value, prefix), ends_with(value, suffix) | exact retained-string prefix or suffix comparison; empty prefix/suffix matches every non-null string; either null operand yields null |
len(null) | null |
concat(...values) | string operands; skip nulls, concatenate in order; all null yields the empty string |
concat_strict(...values), text a || b | string operands; null if any operand is null; otherwise concatenate in order |
template | authoring-only macro to canonical to_string and concat_strict; {{ / }} are literal braces |
case | unknown/null condition is not selected; first true branch wins; absent else is null |
if | selects the true branch only when the condition is true; otherwise selects the false branch |
coalesce | first non-null argument; null if all arguments are null |
nullif(a,b) | evaluate a then b once each; null if equality is true, otherwise return the evaluated a |
in, not_in | the ordered membership rules, including an empty or null array as the empty set, override strict null propagation |
date_diff | fixed-unit quotient truncated toward zero |
Strict scalar functions propagate a null input unless the row above states another rule. Conditional selection follows the table; constructors follow Object and array construction rather than null-propagating their children. This table and the machine-readable expression 0.1 laws are the human- and machine-readable views of the same contract.
if evaluates its condition and only the selected branch. case evaluates
conditions in order until the first true condition, then only that branch;
otherwise it evaluates its else expression. coalesce evaluates arguments
in order through the first non-null value. Non-selected expressions do not
produce runtime failures, although all expressions are statically checked.
Static checking validates syntax, names, arity, types, profile admission, and
signature-declared static argument constraints in every branch. Checking a
conversion's input type does not apply its value-conversion law.
Positional arguments, array elements, and n-ary operands evaluate in canonical
array order; object children and one derive map evaluate in canonical
field-name order. Conditional demand takes precedence over these orders.
The first evaluated failure is the operation's failure; JSON/YAML object-key
enumeration never chooses it. Strict functions evaluate arguments before their
null rule. try_* catches only its own specified conversion failure, not an
argument's failure, a malformed static argument, an engine failure, or a bound breach.
| Expression (with required profiles and valid field types) | Required outcome |
|---|---|
if(FALSE, to_number('bad'), 1) | check succeeds; value 1 |
if(FALSE, 1 / 0, 1) | check succeeds; value 1 |
if(FALSE, unknown_function(x), 1) | static rejection |
if(FALSE, date_add(ts, 1, 'month'), ts) | static rejection of the unit |
try_number(to_string(1 / 0)) | division failure, not null |
Decode never computes values. Canonical lowering follows the fixed expression contract, not a compiler-selected constant-folding policy. Physical optimization may fold/reuse values only while preserving canonical identity, evaluation demand, failure precedence, and applicable logical limits. A failing fold can be left unfolded; it is not a new check-time error. Logical size and cardinality obligations survive optimization; physical CPU, allocation, and memory use need not match a reference evaluator. Every limit retains its measured scope and enforcement boundary (Resolved limits).
The signature arities and positional mapping are defined in Expression text; their value
contracts are completed by this table. S is string, N is number, and T
is the common type from Closed value contracts. Strict functions propagate null except for their explicit
value rules, including membership; domain failures are
atomic, not null, except where the named function explicitly specifies it.
| Functions | Input → result | Null/failure rule beyond the common rule | Profile |
|---|---|---|---|
len | S → N | Unicode scalar count | RelA |
lower, upper | S → S | Pinned Unicode mappings | RelA |
trim | S[, S] → S | Pinned Unicode 16.0 White_Space or explicit code-point set at both ends; exact interior preserved and strict-null | RelA |
ltrim, rtrim | S[, S] → S | Pinned Unicode 16.0 White_Space or explicit code-point set at the leading or trailing end respectively; exact remaining scalars preserved and strict-null | RelA |
left, right | S, N → S | Unicode scalar prefix/suffix; zero/oversize clamp; strict NULL; demanded length-domain failures | RelA |
lpad, rpad | S, N[, S] → S | Unicode scalar padding/truncation; omitted space; explicit empty pattern fails; strict NULL | RelA |
split | S[, S] → Array(S) | Literal non-overlapping splitting; omitted comma; empty delimiter selects scalars; strict NULL; non-null String elements | RelA |
substring | S, N[, N] → S | GoogleSQL scalar positions; optional length; strict NULL; demanded index-domain failures; known input upper bounds retained | RelA |
concat, concat_strict, replace | S operands → S | Scalar expressions string rules | RelA |
contains | S, S → boolean | Exact matching | RelA |
translate | S, S, S → S | Simultaneous scalar mapping/deletion; duplicate source fails after strict NULL; retain only value max_codepoints | RelA |
instr | S, S[, N[, N]] → N | Directed overlapping occurrence search; defaults one; empty-pattern boundaries; integral nonzero position and positive occurrence | RelA |
strpos | S, S → N | First exact match at a 1-based Unicode scalar position; absent 0; empty subvalue 1; nullable exactly when either operand is nullable | RelA |
starts_with, ends_with | S, S → boolean | Default binary prefix/suffix comparison; result nullable exactly when either operand is nullable; both children evaluated once in authored order | RelA |
pow, log | N, N → N | Finite binary64 domains below; result nullable exactly when either operand is nullable; both ordered children evaluated once | RelA |
exp, ln, log10 | N → N | Finite binary64 domains below; operand nullability preserved | RelA |
sin, cos, tan, asin, acos, atan, sinh, cosh, tanh, asinh, acosh, atanh | N → N | Direct finite Number functions with the domains above; operand nullability preserved; canonical positive zero | RelA |
atan2 | N, N → N | Authored first/second quadrant-aware inverse tangent; nullable exactly when either operand is nullable | RelA |
cbrt | N → N | Real cube root over all finite Number inputs; canonical positive zero; operand nullability preserved | RelA |
sqrt, floor, safe_div | N operands → N | Negative square root fails; legacy safe division by zero returns null, overflow fails | RelA |
safe_add, safe_subtract, safe_multiply, safe_divide | (N, N) → N nullable | Own overflow returns null; safe_divide also returns null for zero divisor; child failures propagate | RelA |
safe_negate | N → N | Finite binary64 negation cannot overflow; preserves operand nullability | RelA |
abs, sign | N → N | One child evaluated once on demand; operand nullability is preserved; no decimal overload, implicit conversion or non-finite value | RelA |
greatest, least | N × … × N → N (one or more) | Finite numeric order; strict NULL after once-only ordered child demand; nullable iff any child is nullable | RelA |
round, trunc | N → N or N × N → N | One or two children evaluated once on demand in authored order; result nullable exactly when any child is nullable; explicit decimal position must be integral when both values are non-null | RelA |
to_number, to_boolean, to_timestamp | S or the exact target type → indicated type | Explicit casts closed conversion table; same-type identity | RelA |
to_string | string, number, decimal, boolean, timestamp, or date → S | Canonical spelling; json/object/array/vector and both reference types are rejected | RelA |
date_trunc, date_add, date_diff | Date/timestamp and fixed unit/number operands | Same temporal domain; Scalar expressions units and result rules | RelA |
if, case | Predicate conditions, T branches → T | Selected branch only; absent else is null | RelA |
coalesce | T operands → T | First non-null; nullable only if every operand may be null | RelA |
nullif | KeyComparable A,B → nullable A | eager once-only evaluation; no operand conversion | RelA |
negate | number or decimal(P,S) → same type | negate coefficient for decimal, canonicalize zero; no typed-zero subtraction | RelA |
| Decimal functions | Decimal expressions typed operands → decimal(P,S) | Decimal expressions overflow, conversion, rounding | RelA |
| Text-time functions | Text, time, and regular expressions closed argument contracts | Text, time, and regular expressions resource envelopes and total-parse distinctions | Text-time |
vector_score | two same-dimension vectors and literal metric → number | Row-local vector expressions; null propagates | VecA |
vector_concat | at least two typed vectors → vector of summed dimensions | Row-local vector expressions; null propagates | VecA |
ms|s|m|h|d are fixed UTC durations, with a
day exactly 86,400,000ms; these fixed-unit functions perform no DST, month,
or calendar-day arithmetic. Explicit calendar operations use the separate
calendar profile.
Date-only values (Closed value contracts) require explicit conversion before timestamp
comparison, and admit only integer d arithmetic. date_trunc('d', date) is the
identity, date_add(date, integer, 'd') returns a date, and date_diff between
two dates is measured only in whole fixed UTC days. A date/timestamp mix or a
sub-day date unit is rejected rather than silently promoting midnight to a
timestamp.
String value and ordering rules are defined in Carriers, values, and types. len
counts Unicode scalar values; trim follows the pinned Unicode whitespace rule below;
contains is case-sensitive exact retained-value matching. starts_with and
ends_with compare the exact retained Unicode scalar prefix or suffix without
normalization, case folding, trimming or locale rules. An empty prefix or suffix
matches every non-null string, including the empty string; one longer than the
value cannot match. These functions preserve ordinary strict-null propagation
and evaluate both children in authored order, including after a null first
operand. They admit STRING/default binary comparison; BYTES and explicit
collations remain unsupported overloads of the selected GoogleSQL reference.
like is
case-sensitive with %, _, and backslash escape; replace is
non-overlapping left-to-right and an empty search is a no-op. lower/upper
use Unicode 16.0 default mappings; a runtime without exactly those tables MUST
reject those functions rather than use host-dependent mappings.
Finite Number math MUST accept unary exp, ln, log10 and binary pow,
log with a Number result, nullable exactly when any operand is nullable.
These functions MUST NOT coerce STRING, BOOLEAN, DECIMAL, arrays or objects into Number.
JSON pow and log bodies MUST be exactly two ordered expressions, while
JSON exp, ln and log10 bodies are one expression.
Expression text MUST accept power(x, y) as pow(x, y) and log(x) as
ln(x) without a distinct canonical operation.
For non-null finite inputs, POW MUST require an integral exponent for a negative base, fail for a zero base with a negative exponent, and return one for zero to zero.
EXP and POW MUST fail on overflow, preserve representable subnormals and return canonical positive zero for unrepresentable underflow.
LN and LOG10 MUST require a positive argument; binary LOG requires a positive value and a positive base other than one.
These functions MUST evaluate demanded operands once in source order and preserve ordinary child failures before strict NULL handling, including a later demanded child failure after an earlier NULL. They do not catch child errors. The same pinned portable numeric kernel MUST be used across JS, WebAssembly and Native, preserving exact logical Number bits for identical admitted inputs and selected contracts. Live-BigQuery last-bit equality is not guaranteed.
greatest(x, ...) and least(x, ...) MUST take one or more finite binary64
number expressions.
The result MUST be number, nullable exactly when any operand is nullable,
with each immediate untyped NULL taking the unique number signature including
an all-NULL list and any NULL producing NULL only after all demanded child
evaluations succeed.
ActQL's common expression continuation MUST evaluate each demanded child exactly once in authored left-to-right order, preserve the first child failure and enclosing lazy IF/CASE/COALESCE demand, and retain the existing entry/operand/result charges, live-root, frame, control/workspace and cancellation rules without charging fold accumulators as extra semantic results.
For non-null operands the result MUST be the greatest or least operand in the existing finite numeric order, preserving its exact binary64 value with canonical positive zero for either zero sign, with singleton calls using the same zero rule and without arithmetic overflow or precision reduction.
This finite Number subset MUST reject STRING, BOOLEAN, temporal, decimal, BYTES, non-finite and mixed-type overloads, explicit collation, and implicit conversions without adding a private STRING comparator or changing shared global comparison or canonical-key ordering. GoogleSQL string ordering is by Unicode code point, whereas the current canonical ActQL string owner orders UTF-16 code units. Other overloads remain implementation gaps, without imposing new language-wide restrictions.
The closed source forms {greatest: [first, ...]} and {least: [first, ...]}
MUST lower to the same operation with an ordered nonempty machine body array,
retaining singleton calls and every authored child in order including repetitions,
without sorting, deduplication or constant folding and without changing the
meanings of earlier contract bodies or CIDs.
round(x) and trunc(x) MUST each take one finite binary64 number,
optionally followed by a second finite binary64 number expression n for the
decimal position. The result is number, nullable exactly when any operand is
nullable. Untyped NULL takes the unique number signature. The unary value and
canonical body are unchanged: round(x) returns the nearest integral value,
with halfway cases away from zero; trunc(x) discards the fractional part
toward zero. Every already-integral finite input, including the largest finite
magnitudes, is unchanged by the unary forms, which cannot overflow.
With explicit n, positive positions select places right of the decimal point
and negative positions select places left of it. Each demanded child MUST be
evaluated exactly once in authored left-to-right order, including evaluation of
n after a NULL x. Only after both evaluations succeed does either NULL
produce NULL. Otherwise a non-integral n MUST fail with the existing arithmetic
failure, without truncation or implicit conversion. Integral n > 323 leaves
x unchanged and integral n < -308 returns canonical positive zero; these
comparisons MUST precede any narrowing to a bounded integer. n = 0 uses the
unary value rule. The position is an ActQL binary64 number, including integral
values outside the safe-integer range, rather than a distinct GoogleSQL INT64
value. This does not introduce an INT64 type, a full INT64 overload or exact
representation of every INT64 position.
For -308 <= n <= 323, the selected value algorithm is the finite-input
DecimalRounding<false>/RoundDecimal or
DecimalRounding<true>/TruncDecimal in
GoogleSQL round_decimal.cc at revision
81c15b796138185c046aefe852321011fdf96615,
with Full<double> from the same revision's
double_double.h.
The selected product path is the portable ComputeDekkerProduct/Split path,
with every individual binary64 addition, subtraction and multiplication rounded
to nearest, ties-to-even. Implementations MUST preserve the reference's range
reductions, power-of-five pairs high = RN(5^k) and
low = RN(5^k - high) for -323 <= k <= 323, FromSum and Fast2Sum
operation order, relative-error correction with 2^-105, sticky-bit adjustment,
signed halfway-away selection for ROUND and the negative-correction floor rule
for TRUNC, and the Full-by-Full multiplication order. Here RN rounds the
exact operand to binary64, nearest ties-to-even. Implementations may derive
the pinned high/low pairs exactly rather than store the table.
The final product's high binary64 component MUST be selected before the
separate final power-of-two scaling, including its additional rounding for
subnormal results. Fused multiply-add, reassociation, a single binary64
x * 10^n calculation, or ideal exact-rational decimal rounding MUST NOT replace
the prescribed sequence. In particular, the selected results include
round(1.005,2) = 1, round(2.675,2) = 2.67, trunc(0.3,1) = 0.2,
round(2.2250738585072014e-308,311) = 2.2250000000000004e-308, and
round(2.328158521232893e41,-26) = 2.3281585212328932e41.
These results describe the selected binary64 implementation sequence, not
rounding exact decimal spellings to an ideal rational result.
A non-finite ROUND result MUST fail with the existing arithmetic failure. TRUNC cannot overflow for admitted finite input and integral position. All zero results follow the existing positive-zero canonicalization. Child failure identity, lazy demand, first-failure order, cancellation and existing work, frame, workspace and live-value budgets MUST be preserved. Decimal-position work remains bounded and cancellable, and no-op value cases do not skip logical operand charges.
This is a selected finite-FLOAT64 compatibility boundary, not a claim about
live BigQuery behavior or execution coverage on every target. A third
rounding_mode argument, NUMERIC/BIGNUMERIC or other decimal overloads,
distinct integer overloads, implicit conversion and non-finite values remain
outside this ActQL profile; they MUST NOT be silently ignored or coerced.
The common source forms remain {round: expression} and {trunc: expression}
for one child, and are {round: [value, n]} and {trunc: [value, n]} for two.
Machine bodies retain the sole child or the two-element ordered array,
respectively. Explicit n = 0 and no-op positions MUST retain the two children;
they do not become a unary body or a constant. Existing unary canonical bytes
and earlier expression contract meanings remain unchanged; earlier contract
CIDs do not acquire the new overload.
trim(value) with its character set omitted MUST take one string and remove the longest leading and
trailing sequences of Unicode 16.0 White_Space code points: U+0009..U+000D,
U+0020, U+0085, U+00A0, U+1680, U+2000..U+200A, U+2028, U+2029, U+202F,
U+205F and U+3000. This fixed set contains 25 code points; U+FEFF and U+200B
are excluded. The exact interior scalar sequence, including whitespace,
combining sequences and CR/LF, remains unchanged. No normalization, case
folding, line-break conversion or host-dependent whitespace rule is applied.
Empty and all-whitespace inputs produce the empty string; null produces null.
The result preserves operand nullability, and untyped NULL takes the unique
string signature. The sole child evaluates once when demanded; enclosing lazy
demand, first-failure precedence, cancellation and logical charges remain in
force. Physical scanning, output sizing and construction remain bounded and
cancellable. The authored TRIM node and its child remain in canonical lowering,
even for an unchanged value. This selects the unary GoogleSQL STRING behavior.
The explicit STRING character-set overload is specified below; BYTES and explicit
collation remain outside the admitted signature.
ltrim(value) and rtrim(value) with their character sets omitted MUST each
take one string and remove
only the longest leading or trailing sequence, respectively, of the same pinned
Unicode 16.0 White_Space set as trim. They MUST preserve the exact remaining
Unicode scalar sequence, including whitespace at the opposite end, interior
whitespace, combining sequences and line breaks. U+FEFF, U+200B and U+180E are
not members of this set. No normalization, case folding, line-break conversion
or host-dependent whitespace rule is applied. Empty and all-whitespace inputs
produce the empty string; null produces null. Operand nullability is preserved,
and untyped NULL takes the unique string signature. Each sole child evaluates
exactly once when demanded; child failures, enclosing lazy demand, first-failure
precedence, cancellation and existing logical charges remain in force. Physical
scanning, exact output sizing and construction MUST remain bounded and
cancellable. Each authored node and its child remain in canonical lowering even
when the value is unchanged. The closed common source forms are
{ltrim: expression} and {rtrim: expression}. BYTES, implicit conversions and
explicit collation are excluded. Earlier expression and scalar-law CIDs retain
their previous meaning.
trim(value, characters), ltrim(value, characters) and
rtrim(value, characters) MUST take exactly two STRING expressions. They remove
the longest leading and trailing sequences, the leading sequence only, or the
trailing sequence only, respectively, of Unicode code points contained in the
explicit characters string. Each code point is an independent set member;
order and duplicates in the set do not change the result. An empty set MUST
leave the value unchanged, including its whitespace. An empty value stays empty,
and a value made entirely of set members becomes empty. No substring matching,
grapheme-cluster matching, normalization, case folding or line-break conversion
is permitted. A combining mark in the set can be removed from a different
combining sequence: trim('abaW̊', 'Y̊') is 'abaW', while precomposed letters
remain distinct from decomposed sequences. Supplementary code points are whole
members, never separately matched surrogate code units. U+FEFF, U+200B and
U+180E can be removed when explicitly present in the set.
Both operands MUST evaluate exactly once in authored order, value before set, when demanded. A NULL first operand MUST NOT suppress evaluation or failure of the second. Only after both succeed does either NULL yield NULL. Result nullability is the OR of both operand nullabilities; untyped NULL resolves to STRING in either position. Enclosing lazy demand, first-failure identity, cancellation and all existing logical operand charges remain in force. Physical membership comparisons, scanning, exact output sizing and construction MUST be bounded and cancellable, including large sets and nonmatching values. No unbounded set-preparation step may escape those budgets.
The closed source body is exactly a two-element array, for example
{trim: [value, characters]}; the machine body is likewise
{op: trim, body: [value_node, characters_node]}. The same shapes apply to
ltrim and rtrim. Existing unary forms and their canonical bytes remain
unchanged. Both authored children and the operation MUST remain in canonical
lowering: empty, duplicated or reordered set expressions are not rewritten even
when their values are equivalent. Other arities, BYTES and explicit collation
remain excluded. Earlier contract CIDs keep the unary-only signature.
strpos(value, subvalue) MUST take exactly two strings and return the first exact
match's 1-based Unicode scalar position, or positive zero when absent. An empty
subvalue yields 1, including when the value is empty. It counts scalar values,
not UTF-8 bytes, UTF-16 code units or grapheme clusters. Comparison preserves
retained strings without normalization, case folding or locale conversion.
Both children evaluate once, value before subvalue, even after a null value;
only after both succeed does either null produce null. Result nullability is
the union of operand nullability, independent of row count. Enclosing lazy
demand, first-failure precedence, cancellation and logical operand charges
remain unchanged. The result is an exact integer represented by the common
finite number over the existing bounded string domain. This connects the
GoogleSQL STRING/default binary rule without adding its INT64 result type,
BYTES overload or explicit collation.
Before executable compilation, the compiler MUST resolve each LIKE pattern
literal or Parameter from the selected checked bindings to a non-null string
and validate its escape syntax, including in unselected branches. Backslash
quotes exactly the next Unicode scalar; a final unpaired backslash is invalid.
The empty pattern is valid, % matches any scalar sequence, and _ matches
one scalar, including line separators. Other scalars are literal and no regex
syntax or flags are inferred. Validation preserves the normalized pattern text
without rewriting repeated wildcards or escapes. A null value operand retains
the ordinary nullable predicate semantics; it does not excuse an invalid or
null static pattern. LIKE does not acquire the text-time profile's Host policy
requirements merely because its pattern is checked at the same boundary.
Expression text
Expression text borrows SQL-style identifiers, quoting, operators, function
calls, and searched CASE, not SQL type conversion or execution semantics.
ActQL is not PostgreSQL-compatible. The following comparison is informative;
the ActQL rules in this chapter remain normative.
| Expression / name | ActQL | PostgreSQL 18 |
|---|---|---|
5 / 2 | 2.5 (binary64) | 2 (integer division) |
split_part('a,b', ',', 1) | 'a' (one-based) | 'a' (one-based) |
substring('abcd', 1, 1) | 'a' (one-based) | 'a' (one-based) |
concat('a', NULL) | 'a'; non-null arguments must be strings | 'a'; also stringifies non-string arguments |
Bare Name | Case preserved | Folded to lowercase |
PostgreSQL references: arithmetic, strings, and identifiers. PostgreSQL is not an oracle for ActQL expressions; differential tests require an explicitly identified subset with matching semantics. The complete syntax is defined by ActQL expression EBNF and the closed mappings below. An implementation MUST reject unlisted syntax, functions, and wrong arities, and consume the whole expression string.
- derive:
total: "unit_price * quantity"
- filter: "total >= $threshold"
- derive:
label: "coalesce(customer_name, 'Name unavailable')"
category: |
CASE WHEN total >= 100000 THEN 'Large' ELSE 'Standard' ENDThe following are the complete borrowed spellings and their tree meanings:
| Text | Tree form |
|---|---|
name, "quoted name" | {field: name} with the decoded name |
'text', numeric literal, TRUE, FALSE, NULL | {literal: value} |
$name | {$param: name}; bare parameter names only, not positional $1 |
(e) | grouping; no tree node |
a + b, a - b, a * b, a / b, a % b | add, subtract, multiply, divide, modulo |
-e | signed literal only when applied directly to a numeric literal token through grouping alone; otherwise negate(e) for number or decimal |
a = b, a <> b, a < b, a <= b, a > b, a >= b | eq, neq, lt, lte, gt, gte |
a AND b, a OR b, NOT p | and, or, not |
e IS NULL, e IS NOT NULL | is_null, not_null |
e IN (a, b, ...), e NOT IN (a, b, ...) | in, not_in with {literal: [...]}; each item is a scalar literal or scalar parameter reference |
e IN items, e IN if(p, xs, ys) (also NOT IN) | membership in an ARRAY-valued expression at unary precedence, including fields, parameters and constructors |
e BETWEEN lo AND hi | between |
e LIKE pattern | like; pattern is a string literal or string parameter |
e NOT BETWEEN lo AND hi, e NOT LIKE pattern | not around the corresponding predicate |
a || b | concat_strict, never null-skipping concat |
CASE WHEN p THEN a ... [ELSE b] END | ordered case.when entries and optional else |
Function names are exact lowercase names. Their complete signatures are below;
arguments are positional and map to the existing tree operands or named fields
in the order shown. ... means one or more arguments in total; brackets denote
an optional final argument, not expression syntax.
| Signatures | Mapping / constraints |
|---|---|
len(x), sqrt(x), cbrt(x), sin(x), cos(x), tan(x), asin(x), acos(x), atan(x), sinh(x), cosh(x), tanh(x), asinh(x), acosh(x), atanh(x), exp(x), ln(x), log10(x), abs(x), sign(x), round(x), trunc(x), safe_negate(x), floor(x), lower(x), upper(x), trim(x), ltrim(x), rtrim(x) | corresponding unary scalar function |
trim(x, characters), ltrim(x, characters), rtrim(x, characters) | corresponding scalar trim with an explicit STRING code-point set |
atan2(first, second) | canonical atan2 with two ordered Number expressions; preserve authored first/second quadrant order |
pow(x, y), power(x, y) | canonical pow with two ordered Number expressions |
log(x) | canonical unary ln |
log(x, base) | canonical binary log |
round(x, n), trunc(x, n) | corresponding finite binary64 scalar with an explicit integral NUMBER decimal-position expression |
to_number(x), to_string(x), to_boolean(x), to_timestamp(x) | explicit conversion |
contains(value, part), starts_with(value, prefix), ends_with(value, suffix), strpos(value, subvalue), safe_div(left, right), safe_add(left, right), safe_subtract(left, right), safe_multiply(left, right), safe_divide(left, right), replace(value, search, replacement) | corresponding scalar function |
concat(x, ...), concat_strict(x, ...), coalesce(x, ...), if(condition, then_value, else_value) | corresponding scalar function or conditional tree |
| `vector_score(a, b, 'cosine' | 'dot' |
vector_concat(a, b, ...) | {vector_concat: [a,b,...]}; at least two operands |
nullif(a, b), negate(x) | once-only null comparison; type-preserving negation |
date_trunc(unit, value), date_add(value, amount, unit), date_diff(end_value, start_value, unit) | unit is a string literal in ms, s, m, h, d |
to_decimal(value, precision, scale), try_decimal(value, precision, scale) | Decimal expressions fields; precision/scale are integer literals |
quantize(value, precision, scale, rounding) | Decimal expressions fields; precision/scale are integer literals and rounding is a string literal naming an admitted mode |
decimal_divide(left, right, precision, scale, rounding) | same literal constraints as quantize |
left(value, length), right(value, length) | corresponding common scalar side slice |
lpad(value, length[, pattern]), rpad(value, length[, pattern]) | corresponding common scalar padding; omitted pattern remains omitted |
split(value[, delimiter]) | common ordered-array expression; ordinary optional STRING delimiter; omitted delimiter remains omitted |
translate(value, source, target) | common ternary ordered-array expression; simultaneous STRING scalar mapping |
instr(value, subvalue[, position[, occurrence]]) | common two-to-four operand ordered-array expression; omitted defaults remain omitted |
substring(value, start[, length]) | common scalar substring named body; omitted length is distinct from explicit NULL |
split_part(value, delimiter, index), utf8_truncate(value, max_bytes) | Text, time, and regular expressions fields and argument restrictions |
try_number(value), nfkc(value), case_fold(value) | Text, time, and regular expressions unary expressions |
parse_timestamp(value, pattern), try_timestamp(value, pattern), format_timestamp(value, pattern) | Text, time, and regular expressions fields |
regex_match(value, pattern [, flags]), regex_extract(value, pattern, capture [, flags]), regex_replace(value, pattern, replacement [, flags]) | Text, time, and regular expressions fields; flags, when present, are a string literal |
Pure text-time calls require their profile and per-expression envelopes.
object, template, and opaque JSON literals retain their structured
forms; array literals and typed access are defined below. Aggregate and window operations,
subqueries, named arguments, ::, COLLATE, simple CASE, SQL comments, and user-defined functions are
not expression syntax. Text is admitted only at Expression and Predicate
positions, not at restricted EffectValue or request-binding positions.
The decoded tree MUST satisfy the position's type and allowed-node contract.
Each element of the Effect where conjunction array is a restricted condition
position accepting text, with fields referring to the target Resource.
IN (x) is a one-element list, not grouping. Parameter references inside its
literal-list template are resolved before checking the bound literal array;
parentheses around a literal or parameter are permitted, including
x IN ((1)) and x LIKE ('a%'). Validation examines the decoded node, so
x LIKE ('a' || '%') remains invalid. Other literal payloads are opaque data,
not locations for recursive parameter substitution.
Literal-list templates cannot contain row-dependent expressions. An unparenthesized
right operand at unary precedence may be any ARRAY-valued Expression, including a field,
property, subscript, ARRAY constructor, if, searched CASE, or coalesce.
The ordinary type checker rejects scalar right operands. Both membership operands
remain strict and evaluated once; an operand's own conditional branches retain
their existing lazy demand. A NULL ARRAY remains an empty membership set under
the existing membership law.
Unary - on a nonliteral
numeric expression decodes to negate, preserving number or decimal type.
The EBNF fixes precedence, highest first:
| Precedence | Associativity |
|---|---|
grouping, function call, searched CASE, CAST, array constructor | explicit nesting |
| property selection, subscript | left; postfix |
unary - | right |
*, /, % | left |
+, - | left |
|| | left |
[NOT] IN, [NOT] BETWEEN, [NOT] LIKE | non-associative |
=, <>, <, <=, >, >= | non-associative |
IS [NOT] NULL/TRUE/FALSE/UNKNOWN, IS [NOT] DISTINCT FROM | non-associative |
NOT | right |
AND | left |
OR | left |
Bare names match [A-Za-z_][A-Za-z0-9_]* and retain case. Other names use
double quotes with doubled "" for a quote; all decoded names still obey
their field or parameter naming contracts. The closed, ASCII-case-insensitive
reserved words are AND OR NOT IS NULL TRUE FALSE IN BETWEEN LIKE CASE WHEN THEN ELSE END CAST AS UNKNOWN DISTINCT FROM; reserved field names require quotes. Function names are contextual:
len is a field and len(x) is a call. Parameter names after $ retain their
declared spelling, including names matching keywords. String literals use single
quotes and doubled '', with no adjacent-string concatenation, E'...', or
dollar quoting. Backslash has no lexical escape meaning here; a LIKE, regex,
or other pattern consumer interprets the resulting string under its own law.
Unadorned numeric literals denote finite binary64 number, including those
with a fractional part or exponent. Negative zero is invalid. Decimal values
use explicit to_decimal/try_decimal conversion, decimal parameters, or
decimal fields; no decimal/binary64 promotion is inferred. / is number
division even for integral operands; decimal division uses decimal_divide.
An ordinary =, <>, <, <=, >, or >= comparison with a literal NULL
operand MUST be rejected; IS [NOT] DISTINCT FROM follows its explicit
null-safe rule. Nullable fields otherwise retain Predicates
three-valued semantics. SQL implicit conversions and
host-specific arithmetic or evaluation behavior do not apply.
Note (informative): JSON/YAML decoding happens first. Quote the entire expression or use a YAML
|block; a SQL string literal is still quoted inside it. A block avoids YAML double-quote processing of pattern backslashes. YAML comments outside the expression are not expression comments.
Decoding, identity, and printing
values cells are outside expression printing. Their literal wrappers use
CellValue quoting, not expression decoding, template expansion or the mixed
expression printer. Printing never interprets a cell payload as expression
children, even when its keys resemble expression tags or parameter holes.
Text decoding MUST finish before parameter-reference discovery, semantic
checking, and Program CID computation. Structured and text expressions may
be mixed; both use the same tree validation and lowering. The canonical
local_body referenced by program_template contains decoded expression trees, never expression-text
nodes; string literal payloads remain string values. Decoding does not evaluate
expressions, reassociate arithmetic, or infer semantic choices, and is not a
Normalization Journal entry. Existing semantic completion and macro rules
still apply to the decoded tree (Deterministic completion).
Text and structured forms decoding to the same tree MUST have the same Program
identity. Whitespace outside tokens, keyword case, and equivalent field quoting
do not change that tree. Parentheses wrapping an existing subtree preserve its
identity only when they do not change operator grouping or arity. An unparenthesized chain of
AND, OR, or || decodes to one n-ary node (and, or, or concat_strict), in operand order; explicit grouping
retains nesting. Arithmetic remains binary and is not reassociated.
For example, a = 1 AND b = 2 AND c = 3 and
(a = 1 AND b = 2) AND c = 3 have different trees and Program identities.
coalesce remains a dedicated node in both the canonical Program and the
expression 0.1 canonical fragment. CASE expansion is not canonical lowering.
An executor may implement equivalent control flow, but reuses each evaluated
operand rather than duplicating its evaluation.
A canonical expression printer MUST preserve the tree, operand order, and
literal values. It uses uppercase keywords, lowercase function names,
unquoted non-reserved ASCII names where permitted, doubled quote escaping,
AoA canonical number spelling, one space around infix operators, and comma
followed by one space between arguments. It parenthesizes each operator
application, prints conditionals as searched CASE or if according to their
tree kind, and emits no trailing whitespace. It does not resugar arithmetic
subtraction into unary minus. negate prints as unary minus with its operand
parenthesized, or negate(...) when a signed-literal decode would change the
tree; strict concatenation prints as concat_strict(...), never
concat(...). Casts print as CAST(value AS TYPE); other functions use their
named call spelling. Binary arithmetic and boolean expressions use
the operator spellings in Expression text; n-ary and/or print as a parenthesized chain.
Membership prints a literal array as an IN list. Other ARRAY-valued right
operands use their postfix spelling without an enclosing pair of grouping
parentheses, because an initial ( after IN selects a literal list. Nested
operators inside calls, ARRAY constructors, or searched CASE retain their
ordinary parentheses. A tree without an unambiguous text spelling retains its
structured source form. not prints
as NOT (p) rather than rewriting the child predicate.
For each text-representable canonical tree t, decoding its canonical printed
text MUST yield t. Arrays print as [a, ...]; an explicitly typed empty array
retains its structured form. Trees without a text spelling, including object
constructors, opaque JSON literals, and unary and/or nodes, retain their structured envelope with
recursively printed expression children; literal payloads are never traversed.
Reviews may use this mixed printed form, bound to the exact underlying tree
identity. A printer MUST NOT stringify a structured constructor or change it
to an allegedly equivalent literal. The expression grammar, reserved words,
signatures, decoding rules, and printer rules belong to Grammar Manifest
identity, not a separately selectable execution engine.
Expression diagnostics MUST distinguish syntax errors, unknown functions, unknown field/parameter references, and type/arity errors (including a non-boolean condition or an inadmissible selector node). They identify the enclosing JSON path and a zero-based half-open Unicode-scalar offset range in the decoded expression string; EOF is a zero-width range. Public diagnostics remain subject to Diagnostic documents redaction. The versioned diagnostic catalog assigns their codes; this syntax does not authorize uncataloged codes.
Predicates
The structured predicate grammar is below; expression text in Expression text decodes to this same vocabulary. Predicate operands may be scalar expressions:
Operand := Expression
SelectorOperand := {field: TargetField} | {literal: JsonValue} | ParamRef
ComputationPredicate := Expression checked as boolean (nullable permitted)
EffectPredicate := SelectorPredicate<SelectorOperand>
BooleanNode<Operand> :=
{eq|neq|lt|lte|gt|gte|in|not_in|like|contains: [Operand, Operand]}
| {between: [Operand, Operand, Operand]}
| {is_null|not_null: Operand}
| {and|or: [Expression, ...]}
| {not: Expression}
| {is_true|is_false|is_unknown: Expression}
| {is_distinct_from: [Expression, Expression]}
SelectorPredicate<Operand> :=
{eq|neq|lt|lte|gt|gte|in|not_in|like|contains: [Operand, Operand]}
| {between: [Operand, Operand, Operand]}
| {is_null|not_null: Operand}
| {and|or: [SelectorPredicate<Operand>, ...]}
| {not: SelectorPredicate<Operand>}Bare Predicate denotes ComputationPredicate. Boolean nodes are also value
expressions. Effect selectors use their closed structural subset;
boolean fields and new calls are not implicitly added to that subset. IN-list and LIKE restrictions
apply in both variants. Common decoding does not widen Effect operands.
The lt, lte, gt, gte, and between operands require AuthorOrderable
(Lexical and ordering rules), including under not or inside a conditional; reference operands
remain inadmissible in either notation.
AoA RelA boolean predicates evaluate operands left to right, eagerly, before
combining their three-valued results. AND does not skip later operands after
false, nor OR after true; the first evaluated failure aborts the operation.
For example, x <> 0 AND 10 / x > 1 fails at x = 0. Use a lazy conditional
to guard a failure-prone value expression. Pushdown and other optimizations
MUST preserve these evaluation and failure rules, not only the truth table.
Boolean structure uses:
filter: "status = 'open' AND (region = 'jp' OR region = 'us') AND NOT (deleted = TRUE)"The same predicate has this structured form:
filter:
and:
- {eq: [{field: status}, {literal: open}]}
- {or: [{eq: [{field: region}, {literal: jp}]}, {eq: [{field: region}, {literal: us}]}]}
- {not: {eq: [{field: deleted}, {literal: true}]}}The following AoA predicate vocabulary applies only to the restricted Effect selector lowering, not to expression 0.1 computation roots:
- column reference:
{type: col, path: [field, ...]}; - scalar literal:
{type: lit, value: ...}; - comparison:
{type: cmp, op, left, right}; - membership:
{type: in, op: in|nin, left, right}; - interval:
{type: between, expr, low, high}; - deterministic pattern:
{type: like, left, pattern}; - string containment as the admitted two-argument
containsscalar function compared withtrue; - null check:
{type: isnull, expr}; - boolean
and,or, andnot.
Null equality MUST NOT be inferred from ordinary eq/neq; authors use
null tests or explicit is_distinct_from. Reference equality and ordering follow Opaque reference values.
Membership follows the common membership rules,
including their explicit exception to strict null propagation; other missing and
null behavior follows AoA relation semantics. The scalar expression vocabulary is the closed set in Scalar expressions.
like uses deterministic AoA pattern semantics; ActQL does not expose a host
regex engine. The arbitrary-code boundary is defined in Language boundaries.
Common in and not_in use a set of admitted scalar values. A null array and an
empty array both denote the empty set; an array containing a null element is
nonempty. This is an explicit exception to strict scalar null propagation.
After evaluating the left operand and right collection once each in canonical
operand order, apply these rules in order:
- If the set is empty,
inreturns false, including when the left operand is null. - For a nonempty set, a null left operand produces null.
- Otherwise, any equal non-null element makes
intrue, even if another element is null. - Without a match, a null element makes
innull; otherwiseinis false.
not_in is the three-valued negation of in: true becomes false, false becomes
true, and null remains null. The result type is Boolean. A checked collection
with max_items: 0 yields a non-nullable result. Otherwise result nullability
is the disjunction of left and item nullability; collection nullability alone
does not make the result nullable. Operand domains are still checked for every
expression, including an empty set. An empty set does not supply a missing
scalar type for an otherwise unresolved NULL.
| Left value | Right collection | in | not_in |
|---|---|---|---|
1 | [] | false | true |
| null | [] | false | true |
1 | null array | false | true |
| null | null array | false | true |
| null | [1] | null | null |
1 | [1, null] | true | false |
1 | [2, null] | null | null |
1 | [2] | false | true |
1 | [null] | null | null |
The common law corresponds to GoogleSQL set membership, including
IN UNNEST treating a null array as zero rows. This correspondence does not
admit SQL IN (), UNNEST, or subquery syntax in an otherwise unsupported
Frontend profile. Constant evaluation uses the same ordered membership rules;
an optimizer still preserves operand failures, demand, logical limits, and
canonical identity, so a known empty set does not authorize discarding a
failing left operand.
Relation filters and their Connector pushdowns use the common boolean expression;
Effect selectors admit only the restricted selector subset above. A connector MAY advertise only a
subset for pushdown, but the runtime MUST apply the remaining predicate with
the same canonical predicate semantics or reject it; it MUST NOT reinterpret it.
In an Effect where selector, field operands address the target Resource as
specified in Effect declarations, not a computation Result.
Common expression contract
The adopted contract is actql-expression-contract/0.1. It extends the pinned
AoA foundation with the closed expression schema and laws linked in
Machine contracts. It is not a UDF registry or a Host
callback interface. actql/0.1 uses this contract for all general computation
positions; there is no source revision selector or compatibility mode. The
Program semantics envelope pins expression_contract_cid.
A structured typed_literal with exactly type: "json" and value containing
decoded JSON text MUST use the common typed JSON literal law in the expression
contract. The output is non-null json, including for JSON null and scalars;
SQL NULL and ordinary scalar literals retain their existing meanings.
JSON data keys and strings preserve Unicode scalar sequences and CR/LF;
duplicate keys retain the first decoded value. Exact numeric parsing rejects
precision loss under the selected binary64 subset, including unsupported
negative zero; this is not the full GoogleSQL JSON integer domain. The complete
input is bounded, including discarded duplicates, with depth at most 500 and
the caller's tighter value/expression ceilings.
Non-SQL-NULL JSON values MUST retain their common JSON domain in canonical
typed-cell wire as the closed {"$json":payload} envelope. SQL NULL remains
null under a nullable contract. Typed-wire admission decodes this envelope
only under a checked JSON contract; raw authored JSON does not select a type
by its keys. Canonical data-key spelling does not apply identifier normalization.
Literal IR, retention, capture/replay and Result delivery use this same contract;
wire bounds include the envelope. Graph identity and Delta semantic-body codecs
obtain the checked JSON payload explicitly rather than changing their body
format to the typed-cell envelope. Changed contracts and dependent pins reject
old untagged typed-cell data rather than silently reinterpreting it.
| Source | Decoded canonical node |
|---|---|
comparison, boolean operation, contains | the same expression node in value and condition positions |
p IS TRUE/FALSE/UNKNOWN | {is_true: p}, {is_false: p}, {is_unknown: p} |
p IS NOT TRUE/FALSE/UNKNOWN | {not: corresponding_test} |
a IS DISTINCT FROM b | {is_distinct_from: [a,b]} |
a IS NOT DISTINCT FROM b | {not: {is_distinct_from: [a,b]}} |
nullif(a,b) | {nullif: [a,b]}; not a duplicating case macro |
-e (not a signed numeric literal token), negate(e) | {negate: e} |
CAST(e AS TYPE), corresponding to_* call | {cast: {value: e, target: CastType}} |
receiver.name | {property: {value: receiver, name}} |
receiver[index] | {subscript: {value: receiver, index}} |
unquoted initial left.name / right.name | {join_field: {side: left|right, name}} |
qualified {field: "left.a.b"} (likewise right) | {property: {value: {join_field: {side: "left", name: "a"}}, name: "b"}}; each subsequent segment adds property access in order, as in expression text |
[a,b,...], [] | {array: [a,b,...]}, {array: []} |
| explicit typed empty array | {empty_array: {items: AuthorValueContract}} (structured only) |
| explicit typed JSON literal | {typed_literal: {type: "json", value: JSONText}} (common structured expression; GoogleSQL surface lowering is specified separately) |
a || b, concat_strict(a,b) | {concat_strict: [a,b]} |
concat(a,b) | {concat: [a,b]} |
All named expression bodies are closed; no sibling tags or unknown fields are
accepted. Text and structured spellings undergo the same normalization, including
to_* to cast and qualified {field: "left.name"} to join_field.
Bare field spelling and explicit quotation retain the same field node, except
that quotation prevents lexical recognition of a leading join qualifier, not
subsequent whole-field alias normalization. Cast targets
contain type and, only for decimal, precision and scale, not nullable.
An explicit cast of null remains a cast node in Program identity; it supplies
the typed null in checking. A same-type cast also remains a node, even though its
value law is identity. No constant evaluation occurs during this normalization.
IS TRUE, IS FALSE, and IS UNKNOWN require boolean input and produce
non-null boolean. IS UNKNOWN tests boolean null; IS NULL is the general
typed null test. IS DISTINCT FROM requires KeyComparable operands (a literal
null receives its peer's type); both null gives false, exactly one null gives
true, otherwise it uses non-null inequality. Two untyped null operands still
require explicit type evidence. nullif requires KeyComparable inputs,
returns the first operand's type made nullable, and evaluates both arguments
once even if the first is null. It returns null only when ordinary equality
is true, otherwise the already evaluated first value.
| p | NOT p | p IS TRUE | p IS FALSE | p IS UNKNOWN | p IS NOT TRUE |
|---|---|---|---|---|---|
| true | false | true | false | false | false |
| false | true | false | true | false | true |
| null | null | false | false | true | true |
Ordinary equality and ordered comparison propagate null; is_null/not_null
are non-null boolean. AND/OR use three-valued conjunction/disjunction, eagerly:
false dominates AND, true dominates OR, otherwise a null operand yields null.
No truthiness or reference inspection is introduced.
The canonical extension node is {op: tag, body: payload} under
actql-expression-node/0.1. Lowering recursively preserves each normalized tag
and its closed payload shape from the machine expression schema, replacing
expression children with extension nodes; structural arguments and opaque
literal data are copied. Non-null parameter nodes become literals from bound values.
The author-only template is first expanded to its ordered literal segments
and cast-to-string placeholders under one concat_strict; a template with no
segments yields an empty-string literal. It is not an executable template node.
Empty array constructors become empty_array with the canonical checked item
contract; explicit empty arrays lower to the same node. Every typed NULL,
including a null parameter and a missing CASE ELSE, uses
{op: typed_null, body: {contract: CheckedValueContract}}, with nullable: true.
Explicit casts retain their cast node around that typed child. There is no
primitive-versus-structural alternative for implicit ELSE completion.
This machine-only form is not an author conversion to object or array.
Canonical fragments bind the extension contract CID, roots at their semantic expression positions, and profile envelopes sorted by canonical expression pointer. The existing AoA relational operations consume those typed roots; conversion to a scalar node with different meaning is forbidden. Placement requires support for this extension, including boolean value roots, typed empty arrays and per-expression demand. A profile operation is still a pure expression, not an acquisition or arbitrary callback. This defines the canonical lowering under the pinned AoA and expression contracts.
The Relation binding is actql-expression-fragment/0.1, whose closed machine
schema is in Machine contracts. Its request fixes the
canonical source pipeline, checked source schemas, typed parameter values,
max_rows and max_value_bytes. The latter ceiling applies per input value,
evaluated root value and published output value, not to physical allocations.
graph contains inline expression 0.1 nodes with unambiguous typed contracts.
expression_contract_cid pins the exact expression contract. fragment_cid
hashes the whole fragment excluding only itself. The containing executable
artifact pins this fragment CID; its inner graph is not a standalone
AoA execution artifact.
The request is private execution material, not a public diagnostic projection.
In the containing AoA composition, ACTQL.expression_fragment/0.1 has one
Relation input and output. Its params contains exactly fragment; the
fragment request names its single local source input. Admission rechecks the
sealed fragment before evaluating the composition. A carrier-validation
projection is not a semantic graph, a hash input, or an execution replacement.
Optional request.open_sources maps each incompletely described source to its
sorted required column set. Checked field contracts remain in source_schemas;
unknown fields gain no inferred type. Pass-through operations preserve those
fields, while every expression still requires its own type evidence. Derive
rejects collisions with required or actual input fields; group closes its
output. output_open and output_schema are regenerated at admission. Runtime
validates required columns, known contracts and all actual value ceilings.
request.null_contexts optionally maps derive step IDs and output names to
nullable checked contracts for literal NULL roots. The source remains NULL;
the lowered node is typed_null. Program checking derives these contexts only
from the append-peer or explicitly mapped nullable ModelTask rules in
Closed value contracts, through field projection, rename
and row-preserving steps. Conflicting contexts or missing evidence fail;
arbitrary later computations and Effects supply no type evidence.
For text-time calls, request.text_time_policy contains the closed
actql-text-time-host-policy/0.1 value; its CID is the envelope's
host_policy_profile_cid. evaluations lists every profile occurrence once,
sorted by envelope.expression_pointer, or is empty when none occur.
Each entry's node_pointer addresses /roots/<index>/node; its envelope
addresses the corresponding /roots/<index>/source occurrence and binds that
root's step_id and regenerated demand path, including its aggregate filter.
Admission regenerates the envelopes, pointers, argument CIDs and bounds.
Known literal or checked source extents prove input bounds; otherwise a
demanded expression performs input preflight against the selected Host ceiling.
Successful results satisfy the output ceiling. These checks introduce no I/O.
The group binding admits the complete group aggregate vocabulary,
with an expr root and optional own filter. count_if requires
a boolean argument and counts only true values; it does not admit distinct.
Distinct is admitted only by each aggregate's declared signature, preserving
first occurrence before the pinned fold. avg accepts number, not decimal. Decimal sum has precision
38 and preserves scale; array_agg retains order and its positive guard.
The limits list contains a Resolved limit for each request ceiling and each
aggregate max_items or max_bytes, ordered by its RFC6901 request_pointer. Each limit
has that pointer as subject_ref, a contract_ceiling witness binding the
canonical request CID and pointer, law actql.expression-fragment.capacity,
source derivation and that request CID as its sole dependency. Enforcement is
engine_atomic, breach is fail; units are rows, bytes, and array_items
respectively. These are declared ceilings, not estimated or proven row counts.
Admission regenerates these witnesses and limits alongside the graph.
The capacity law is registered by the expression 0.1 contract for this
fragment's admission. Admission uses that pinned law and its witness contract.
The selected pipeline registry MUST register ACTQL.expression_fragment/0.1
with its one-Relation-input/output signature and the execution contract at
spec/contracts/actql-aoa.json#/execution_contracts/ACTQL.expression_fragment~10.1.
That contract pins both the expression contract and its existing fragment schema;
schema references MUST resolve against those pinned bodies, not a network lookup.
A consumer MUST reject a missing registration, mismatched contract or dependency
CID, or unsupported execution capability. Registration is not whole-Program
completion or execution authorization; sealed-fragment re-admission and all
required runtime checks remain mandatory.
| AoA consumer | Root pointer relative to params |
|---|---|
REL.derive | /exprs/<escaped-output-name> |
REL.derive ordered row-kernel alternative | /bindings/<index>/expr |
REL.where, REL.assert_all | /expr |
REL.groupBy, REL.window.project | /aggs/<escaped-output-name>/expr |
| Either aggregate consumer's filter | /aggs/<escaped-output-name>/filter, when present |
Names use RFC6901 escaping. The roots array follows canonical graph step
order, then code-unit order of the parameter pointer within each step. Each
entry binds step_id, parameter_pointer, canonical source, lowered node,
checked contract and demand_paths. The inline graph node equals that root's
node. Semantic pointers address /roots/<index>/source, not the differently
shaped lowered node. An aggregate argument prepends its own filter root's
true test to every inherited path. The filter has no self-demand.
Fragment admission MUST recheck the bound request and require exact equality of the canonical graph, root set and ordering, types and demand paths. A valid self-CID or schema alone is insufficient. An implementation MUST reject a consumer or expression revision outside its declared binding capability; this Relation binding does not implicitly assign root positions to graph, temporal, vector or Effect consumers.
Graph and temporal conditions use actql-predicate-consumer/0.1, carried by
ACTQL.predicate_consumer/0.1 with exactly {consumer} as params and the
underlying operation's carrier signature. Its request fixes the operation,
closed parameters, context schemas and expression ceilings. Roots are ordered
by RFC6901 pointer code-unit order, relative to request.params:
| Consumer | Condition pointer | Typed context |
|---|---|---|
SGA.match_nodes | /where | nodes |
SGA.match_edges | /where | edges |
SGA.traverse | /hops/i/edge_where, /hops/i/node_where; homogeneous /edge_where, /node_where | edges, nodes |
TEA.sequence.match | /pattern/i/where | events |
Each root is a single REL.where fragment with source input and step
condition; its open-source requirements are the sorted context schema keys.
The consumer CID excludes only consumer_cid. Admission regenerates the whole
consumer and validates the pinned operation's structural constraints. Condition
evaluation follows that operation's traversal or matching demand, not a
prepass over unreachable graph elements. VEC has no scalar expression slot;
its filtered Relation input uses the Relation binding. Effect selectors remain
separate and restricted.
Aggregate evaluation follows canonical group order, aggregate output-name order, and logical input-row order within each group. Each aggregate evaluates its own filter before its argument; it does not share an unconditional precomputed argument column with another aggregate. Value failure publishes no partial operation output. Input admission remains independent of row demand.
An envelope pointer addresses the normalized semantic expression tree with JSON Pointer escaping. Every child inherits the outer demand path; the following tests are appended in the listed logical order.
| Child | Additional tests |
|---|---|
if condition | none |
if then | condition true |
if else | condition not_true |
| CASE condition i | each earlier condition not_true |
| CASE branch i | each earlier condition not_true, then condition i true |
| CASE ELSE | every condition not_true |
| coalesce operand i | each earlier operand null |
| aggregate argument | its own filter true, if present |
| other eager child; aggregate filter | none |
Admission MUST rederive demand paths from the expression tree and consumer position and require exact equality, not merely valid pointers or self-CIDs. Tests reference values already evaluated in the same row and evaluation frame; demand testing never reevaluates a condition or operand.
Within one expression frame, failure priority follows its prescribed child evaluation order. For a derive operation it is logical input-row order, then canonical output-field-name order, then expression evaluation order. Parallel completion order does not choose the representative value failure. Failed operations publish no partial output; this is not a Run-wide or cross-Effect transaction guarantee.
Text and structured input pass through the same alias normalization and closed
node validation. left.id, "left.id", and qualified {field: "left.id"} all
normalize to a join-field node; "left".id remains property access on the
ordinary field left. Quoting suppresses the lexical qualifier, not subsequent
whole-field alias normalization. Normalization is idempotent, including
after mixed printing. Intake byte, token, node and actual AST-depth limits are
distinct from evaluation budgets; exceeding a declared intake limit produces
a stable diagnostic, never truncation or a partial tree. Structured input has
the same tree limits; opaque literal payloads are counted but not parsed as code.
Explicit casts
| Cast target | Admitted non-null input | Value law / corresponding source function |
|---|---|---|
| NUMBER | string or number | to_number; number to number is identity |
| STRING | string, number, decimal, boolean, timestamp, date | to_string canonical spelling |
| BOOLEAN | string or boolean | to_boolean; boolean to boolean is identity |
| TIMESTAMP | string or timestamp | to_timestamp; timestamp to timestamp is identity |
| DECIMAL(P,S) | string, safe-integer number, decimal | to_decimal; exact same P/S is identity; otherwise exact conversion without rounding |
Target names are ASCII case-insensitive and normalize to lowercase type names.
Decimal P/S are integer literal type arguments under Exact decimal values.
CAST(NULL AS target) supplies that nullable target without evaluating a
conversion. Other null operands propagate after input-type checking. No
reference, object, array, json, or vector cast is supplied by this table.
Same-type acceptance also applies to corresponding to_* calls, so equivalent
spellings have the same tree. DATE, ::, and arbitrary SQL type names are
not cast targets in this revision. Decimal/binary64 fractional mixing remains
invalid; CAST('0.1' AS DECIMAL(2,1)) is exact, while casting binary64 0.1
fails if evaluated. quantize remains the explicit rounding operation.
Conversion diagnostics distinguish static source/target incompatibility from evaluation-time domain failure. Repair candidates may suggest an explicit cast or parameter, but never automatically choose decimal P/S, rounding, or replace a binary64 value with text claiming the author's original exact decimal intent.
Typed access
Property and subscript access apply to any typed expression, including
items[0].sku and coalesce(primary_address, backup_address).city. They are
pure row-local value operations, not provider navigation or Relation expansion.
Property names are static AuthoredFieldNames; receivers must have a complete
object type. Subscripts require an array receiver and a number expression;
the index is checked at evaluation as a non-negative safe integer without
rounding. Arrays are zero-based, independently of SQL-named string functions.
Receiver then index are evaluated once, eagerly; invalid/null indexes fail
even if the receiver is null. Negative or fractional literal indexes are
evaluation-time domain failures, not static signature constraints.
| Condition | Outcome |
|---|---|
| null object receiver with a statically declared property | null |
| property absent from closed schema | static rejection |
| declared property absent from admitted data | input contract violation |
| null array and valid index | null |
| non-negative safe integer index outside array length | null |
| negative, fractional, unsafe, or null index | evaluation failure |
| untyped json or opaque-reference receiver | static rejection |
Property nullability is the OR of receiver and property nullability.
Subscript results are conservatively nullable, retain the item contract, and
do not infer a minimum length from max_items. An explicit empty-array node
therefore remains typed and always yields null for a valid subscript.
An unquoted leading left. or right. is always a join qualifier, never a
schema-dependent guess. It requires that join scope and selects its following
field; later dots are object properties. left.customer.city therefore means
property city of the join-left customer field. "left".city means property
city of the ordinary field left. Missing join scope is an error, not a fallback.
Property names after a receiver do not create another qualifier. The printer
quotes an ordinary receiver field named left/right when followed by access.
Decimal expressions
Implementations MUST validate decimal precision, scale, coefficient, explicit rounding, and the prohibition on implicit binary64 mixing before publishing results.
The four expressions have these text forms (shown as derive entries):
amount: "to_decimal(amount_text, 18, 2)"
maybe_amount: "try_decimal(amount_text, 18, 2)"
tax: "quantize(raw_tax, 18, 2, 'half_up')"
daily: "decimal_divide(total, 31, 18, 2, 'half_even')"For example, the structured form of tax is:
tax:
quantize:
value: {field: raw_tax}
precision: 18
scale: 2
rounding: half_upConversion accepts decimal values, safe integers, or text matching
-?(0|[1-9][0-9]*)(\.[0-9]+)?. Exponents, leading +, grouping, currencies,
negative zero, excess fractional digits, and overflow are rejected without
rounding. try_decimal makes only invalid/unrepresentable conversion input
null. Null inputs propagate. Only quantize and decimal_divide round, using
half_even|half_up|toward_zero|floor|ceiling. Division by zero fails.
decimal_divide requires a decimal left operand and a decimal or safe-integer
number right operand. A number divisor is interpreted exactly as an integer;
fractional or unsafe-integer numbers are rejected. This is an explicit function
contract, not an implicit decimal/binary64 conversion for other operators.
add, subtract, multiply, and comparison accept two decimals without
binary64 mixing. Addition/subtraction use scale max(S1,S2) and precision
min(38,max(P1-S1,P2-S2)+scale+1); multiplication uses scale S1+S2 and
precision min(38,P1+P2). An unrepresentable scale or coefficient fails.
sum returns precision 38 at the input scale; min/max retain the type.
Decimal avg is rejected: use sum, count, and explicit decimal_divide.
Branches and array elements require one compatible closed decimal type;
explicit quantize resolves different precision/scale choices.
Object and array construction
Implementations MUST preserve closed object/array types and reject unknown constructor fields, heterogeneous elements, and hidden capability references.
{object: {static_name: Expression, ...}} builds a closed, non-empty product;
all named properties are present, including nullable properties. Field names
use the same closed schema rules as relation fields. Computed keys, spread,
merge, omission, opaque JSON, and hidden ResourceRef/ContentRef values are
not admitted. Key order is canonical for identity, not an author-selected
semantic order. Classification/provenance includes every child dependency.
{array: [Expression, ...]} builds a finite sequence with one
homogeneous recursively compatible value contract; its source length proves
the item bound. Dynamic collections use array_agg or nest (nest), expansion uses unnest,
and json_pick derives types from closed object paths. Size proofs use child
contracts or the checked Host envelope when child sizes are unresolved.
Effects remain field/literal consumers of named Results; nested payload
construction happens in preceding computation, not inside an Effect.
The empty form is legal only with the unique type evidence defined in
Closed value contracts, or explicit empty_array.items.
Aggregation and nest retain their operation-defined typed empty cases;
no empty array is guessed to be json or left dynamically untyped.
Text, time, and regular expressions
The optional actql-text-time/0.1 pure scalar profile adds exactly
split_part, utf8_truncate, try_number, parse_timestamp,
format_timestamp, regex_match, regex_extract, regex_replace, nfkc,
case_fold, and try_timestamp.
Profile admission follows Conformance profiles.
The inherited text-time base remains pinned to its original body. Its former
fixed-three-argument substring membership, Host envelope and interval law are
superseded by the current common scalar substring rule.
Current text-time source/envelope/barrier schemas omit that operation; earlier
bodies and CIDs are not reinterpreted. A nested profile call receives a checked
substring String upper bound when known, otherwise the usual selected-policy
runtime input preflight. Profile-derived extents across a substring may be
conservatively widened; identical admission for every old nested profile plan
is not implied.
This profile pins its Unicode tables, timestamp grammar, and regex grammar. Implementations MUST use those pinned definitions as the semantic authority. Host library, ICU, and locale defaults MUST NOT define normalization, folding, or pattern syntax.
Note (informative): A library can implement the pinned definitions; using a library is not itself a fallback. Its environment-dependent defaults do not determine ActQL semantics.
- derive:
region_code: "split_part(external_key, '-', 1)"
short_name: "substring(display_name, 1, 40)"
export_name: "utf8_truncate(display_name, 120)"split_part uses the one-based Unicode-scalar positions defined
below; array subscripts remain zero-based. String position/length arguments
are number expressions checked at evaluation as safe integers without rounding.
utf8_truncate requires a non-negative safe integer byte limit and returns the
greatest prefix whose UTF-8 encoding is within max_bytes. It MUST NOT split a
Unicode scalar.
| Function / condition | Value rule |
|---|---|
split_part(s,d,i) | literal or parameter string delimiter; split by non-overlapping literal matches preserving empty parts; i > 0 counts from 1, i < 0 counts backward from the final part |
split_part: i = 0 or non-integer/unsafe i | evaluation failure |
split_part: part outside range | empty string, not null |
split_part: empty delimiter | one part containing the whole string |
split_part: empty input | one empty part |
split_part: any null operand | null, after eager operand evaluation |
Returned strings retain the operation's resulting scalar sequence without implicit NFC or line-break conversion. Explicit normalization/case-folding operations keep their separately pinned laws. No locale, collation, provider, or Host compatibility mode changes these rules. The admitted overload is the displayed fixed arity; SQL regex-substring overloads are not added.
- derive:
parsed_at: "parse_timestamp(local_datetime, 'yyyy-MM-dd HH:mm:ss')"
maybe_amount: "try_number(amount_text)"
- derive:
export_at: "format_timestamp(parsed_at, 'yyyyMMddHHmmss')"The profile uses the finite grammar artifact
spec/grammar/actql-timestamp-pattern-0.1.ebnf and its closed canonical AST.
Fixed-duration and text-time operations have a fixed UTC timezone and no
timezone author field, TZDB, or locale dependency. Only the explicit calendar
profile uses its admitted timezone and pinned TZDB; those settings never flow
into ordinary timestamp conversion or date arithmetic. parse_timestamp requires
yyyy, MM, and dd exactly once; HH and mm occur together, ss
requires both, SSS requires ss, duplicate semantic tokens are forbidden,
the whole input is consumed, and an invalid calendar value fails.
Missing time components MUST resolve to ActQL-defined zero values, not host defaults:
absent HH/mm means 00:00, absent ss means 00, and absent SSS
means 000. Parsing uses the proleptic Gregorian calendar, UTC, and the
closed year range 0000..9999.
format_timestamp is locale-independent. try_number uses the same canonical
number grammar as to_number, but returns null instead of failing for a
non-null unparsable string.
Timestamp patterns MUST be nonempty sequences of the admitted components;
both parse and format modes reject duplicate semantic tokens and apply the
HH/mm, ss, and SSS dependencies above. Only parse mode requires yyyy,
MM, and dd. Format mode admits a pattern consisting solely of the admitted
literals, producing that literal text for a non-null timestamp. Literal
components retain their individual occurrences in the canonical AST, without
coalescing or reordering. An empty pattern is not a literal-only pattern.
Text, pattern, replacement, and output resource bounds are not expression arguments. The compiler derives the strongest envelope available from the field contract and resolved literal or parameter values. Every text/time expression also binds a versioned Host ceiling; the derived input codepoint and UTF-8 extents MUST fit that ceiling, including literal and contract-bounded inputs. Runtime-bound input is rechecked against the same ceiling when the expression is demanded, before evaluation. If a finite admitted envelope cannot be established, the unit is rejected before evaluation; a runtime MUST NOT silently substitute unbounded host string, time, or regex behavior.
Every checked expression binds one machine-only
actql-text-time-evaluation-envelope/0.1 containing the semantic step, canonical
expression pointer, demand path, and
operation, input codepoint and UTF-8 extents with an evidence CID, parsed
pattern-AST CID and byte extent where applicable, canonical replacement-AST
CID, literal byte total, capture-reference count and maximum capture index,
replacement-count and output-byte ceilings, verification phase, current Host
policy-profile CID, and a self CID. Each canonical AoA Fragment binds these
envelopes in expression-pointer order, including expressions inside conditions,
constructors and aggregate arguments. No derive-only acquisition barrier is
introduced. The portable bound law is versioned as
actql-text-time-bound-laws/0.1; Hosts MUST implement that law rather than a
local estimate. Every text/time expression requires a versioned Host ceiling in
the checked Plan; runtime-bound strings are additionally rechecked at their
demanded evaluation boundary. Without that ceiling, Check MUST NOT issue a checked Plan.
Demand paths preserve selected conditional branches, earlier coalesce null tests, and the containing aggregate's filter. Moving a pure profile call into another physical unit preserves that same row mask and first-failure order. Expression-local enforcement is not performed on undemanded rows. Input admission, static pattern checks, and whole-unit limits retain their separate scopes and cannot be bypassed by lazy evaluation. Logical output/size bounds use the pinned Unicode/regex/time laws with the string rules above; physical scheduling and resource consumption are not deterministic logical charges.
For split_part, the canonical conservative output-byte ceiling is
the input UTF-8 ceiling. utf8_truncate uses min(input_bytes, max_bytes) for a bound nonnegative safe-integer
literal limit, otherwise the input ceiling. These calculations use exact
integer arithmetic before comparison with Host limits. Dynamic lengths and
invalid conversion-data literals do not trigger speculative evaluation or a
new static rejection; their domain checks occur only on demanded rows.
Other output-bound formulas retain the pinned base profile's laws. Each parent
consumes its children's checked extents, including nullable outputs, rather than
requiring those children to be named columns. Envelope self-CIDs exclude only
their self-CID field; argument CIDs bind the canonical argument subtrees.
Input evidence CIDs MUST hash the following complete canonical JSON preimages.
For source: value_contract, the preimage is the complete checked input value
contract. For source: literal, it is
{version: "actql-literal-text-extent/0.1", value: V}, where V is the resolved
canonical string, timestamp wire string, or known null. Selected Parameter
values use the same rule as literals; declaration metadata and binding origin
do not enter this extent identity. Known null has zero scalar and byte extent.
For source: runtime_preflight, the preimage is
{version: "actql-text-time-runtime-preflight/0.1", contract: C, policy: P},
where C is the complete checked input contract and P is the complete selected
actql-text-time-host-policy/0.1 value, not only its input ceilings.
A derived child output uses value_contract with its checked output contract.
This CID identifies the extent evidence content, not its applicability: the owning expression, selected bindings, policy and child producer dependencies MUST still be verified and retained. Equal contract CIDs do not discharge a child's demanded runtime checks or authorize evidence from another Program. These preimages contain no process-local handle or step index and are not Program, Invocation, expression-argument or envelope identities.
Regex expressions use spec/grammar/actql-regex-0.1.ebnf and the closed
actql-regex-ast/0.1 schema. Check MUST parse every literal or bound regex and
timestamp pattern into its canonical AST and bind that AST's CID.
The admitted regex grammar contains literals, classes/ranges, captures,
alternation, anchors, dot, and greedy bounded/unbounded quantifiers. Lookaround,
backreferences, named captures, lazy quantifiers, Unicode property escapes,
and implementation-specific escapes are outside the grammar. Patterns are
literal or invocation-parameter values, not fields; recursion and
host-specific flags are forbidden.
Regex AST literal nodes MUST use the closed form {kind: "literal", codepoint: n},
where n is one Unicode scalar integer (0..1114111, excluding 55296..57343),
not a UTF-16 code unit or string. Assignment in the Unicode version is not
required. Character-class range start and end use the same scalar integer
domain, with start <= end and inclusive membership over the scalar universe.
Decoded \\n, \\r, and \\t therefore carry 10, 13, and 9 respectively.
Source pattern text retains its ordinary NFC/LF admission; decoded AST scalars
are not text-normalized. Literal nodes, class items, branches and captures
retain their prescribed occurrence order.
Canonical regex AST construction MUST retain each capturing group, number captures from one in opening-parenthesis order, and omit sequence/alternation wrappers only when they have exactly one child. Empty patterns, groups and alternation branches are invalid. A leading or trailing unescaped hyphen inside a class is a literal; elsewhere inside a class it separates scalar range endpoints. Outside a class, hyphen is a metacharacter and requires escaping. Quantifier bounds are exact nonnegative integers with the grammar's canonical decimal spelling. They are not rounded to fit AST numbers: a finite binary64 integer must represent the complete authored integer exactly, without an additional safe-integer ceiling. A finite maximum is at least the minimum; an omitted unbounded maximum is represented by null. Identical class items, branches and repeated occurrences are not deduplicated.
The regex AST CID MUST use ordinary canonical JSON of the complete AST body,
excluding only ast_cid. Numeric scalar representation preserves CR versus LF
and range endpoints without an exception to global canonical JSON rules.
Schema checking covers scalar domains and closed node shapes; capture numbering,
capture counts, range ordering and quantified-expression rules remain separate
semantic checks.
The expression contract's pinned regex_ast_contract MUST govern regex AST
representation and identity in preference to the inherited text-time profile's
older string-literal AST representation. Matching, source grammar, flags and
demand laws otherwise remain unchanged. Old string-valued literal nodes and
range endpoints are rejected, not silently converted or assigned the new CID.
A backend without this exact AST dependency and expression-contract support
cannot produce an executable Plan for it; the old registry is not relabeled as
supporting the new representation.
Regex flags MUST be a string containing only m (anchors also match
U+000A line boundaries) and s (dot also matches U+000A), each at most once,
in either order. Omitted flags and the empty string both select neither flag.
The regex_extract capture argument MUST be a nonnegative integer literal or
an invocation parameter of number type. The regex_replace replacement argument
MUST be a string literal or an invocation parameter of string type. Neither
argument admits a row field or a computed expression, including a constant
expression. Check validates the resolved non-null parameter value and its
capture/replacement consistency with the admitted pattern before evaluation;
declaration-only type checking does not discharge that obligation.
The profile requires positive limits for input codepoints, pattern bytes, output bytes,
and replacements. regex_extract requires an explicit capture index and
returns null for no match; regex_replace applies a bounded left-to-right
global replacement. Its closed replacement grammar uses $$ for a literal
dollar and $1..$99 for captures; references to absent captures are rejected
at Check, while an unmatched admitted capture expands to the empty string. The
portable output ceiling accounts for every literal and repeated capture
expansion. A limit breach fails atomically.
Regex replacement tokenization MUST consume $$ as one literal dollar, or a
dollar followed by one ASCII digit 1..9 and, when present, one further ASCII
digit 0..9 as a capture reference. Further digits are literal text: $100
denotes capture 10 followed by literal 0, not capture 100. Other dollar
forms, including $0, $01, a bare dollar and ${1}, are invalid. Backslash
has no special replacement meaning. The canonical replacement AST coalesces
adjacent literal text, including decoded $$, into one nonempty literal part;
capture parts retain their order and repetition. An empty replacement has an
empty parts array. Its literal UTF-8 byte total, capture reference count and
maximum capture index (zero with no references) describe these parts, not the
source spelling or a deduplicated set of captures.
The operation/input matrix is closed: split_part,
utf8_truncate, try_number, parse_timestamp, regex_match,
regex_extract, regex_replace, nfkc, case_fold, and try_timestamp
consume strings; format_timestamp consumes a timestamp. parse_timestamp
preserves input nullability. try_number and try_timestamp have nullable
output because input parse failure produces null.
nfkc uses Unicode 16.0.0 compatibility normalization; case_fold uses full
default C/F mappings, not locale-specific T mappings. Both return NFC. The
profile pins the vendored Unicode table CID and derives expansion ceilings
from those tables (18 decomposed scalars for NFKC, 4 for full folding/NFC per
input scalar). try_timestamp uses the same UTC grammar as parse_timestamp.
It MUST return null for invalid calendar/input text. An invalid pattern, bound
breach, or engine failure MUST fail the operation rather than produce null.
Null input propagates after the pattern has been checked.
A quantified subexpression MUST consume at least one Unicode scalar on every successful match. Check rejects quantified anchors and any quantified capture, sequence, alternation, or repeat whose canonical AST is nullable; this keeps the grammar and the finite matcher state space identical across runtimes.
Implementations MUST use the exact ActQL regex sets and boundaries below.
Regex shorthand classes are exact ASCII sets: \\d is 0..9, \\w is
A..Z, a..z, 0..9, and _, and \\s is TAB, LF, VT, FF, CR, and SPACE.
Their uppercase forms are complements over the Unicode scalar universe. Dot
excludes only LF unless s is set. With m, anchors use document or LF
boundaries; CR remains an ordinary scalar, including in CRLF. An unmatched
capture is null and expands to the empty string in $1..$99 replacement.
All ActQL strings and object keys MUST reject unpaired UTF-16 surrogates.
Operations
Each operation specifies its accepted Carrier, parameters, result, ordering, and failure conditions. Shared expressions are defined in Expressions and predicates; author guards and proof-derived execution limits are defined in Author guards, resolved limits, and Plan facts.
Operation definitions use the following common contracts rather than repeating them for each spelling:
| Concern | Normative home |
|---|---|
| Authoring shape and name scope | Structural grammar (normative), Pipeline names and references, and the operation's definition |
| Required evidence and recursive result types | Closed value contracts, Local typed inputs, and operation-specific preconditions |
| Identity, ordering, and completeness propagation | Evidence propagation; canonical key selection in Deterministic completion |
| Bounds and enforcement boundary | Author guards, resolved limits, and Plan facts and the operation's measured scope |
| Failure and atomic publication | Operation-specific failures and Capture, execution, and replay |
| Canonical lowering | AoA fragments and algebra lowering and the pinned AoA contract (Common AoA foundation) |
Operation inventory
Family is an author-visible functional grouping, not an AoA opcode namespace or a profile identifier. Search/text_rank belongs to actql-text-rank/0.1 and lowers to REL.text_rank; Calendar belongs to actql-calendar/0.1 and lowers to the specified TEA calendar opcodes. Acquisition/search_candidates is a typed barrier, not an algebra opcode. Profile claims and registry admission are checked independently of this display classification.
ActQL has exactly one canonical operation name for each operation in the
Program model. Registered frontend surfaces lower into that vocabulary; they
do not add operation aliases to the structured model or bypass its checker.
Structural STEP and closed OP preserve existing operations. Query sugar is
governed by its explicit frontend profile, not inferred from backend SQL.
The complete canonical vocabulary is:
| Family | Operation | Input -> output |
|---|---|---|
| RelA | filter | relation -> relation |
| RelA | select | relation -> relation |
| RelA | rename | relation -> relation |
| RelA | drop | relation -> relation |
| RelA | derive (snapshot map or ordered array) | relation -> relation |
| RelA | sort | relation -> relation |
| RelA | take | relation -> relation |
| RelA | top_k | relation -> relation |
| RelA | top_k_by | relation -> relation |
| RelA | row_window | relation -> relation |
| RelA | window_group | relation -> relation |
| RelA | lag | relation -> relation |
| RelA | watermark_rows | relation -> relation |
| RelA | pivot | relation -> relation |
| RelA | unpivot | relation -> relation |
| RelA | json_pick | relation -> relation |
| RelA | unnest | relation -> relation |
| RelA | join | relation x relation -> relation |
| RelA | nest | relation x relation -> relation |
| RelA | group | relation -> relation |
| RelA | distinct | relation -> relation |
| RelA | dedupe_rows | relation -> relation |
| RelA | assert_rows | relation -> relation |
| RelA | append | relation x relation -> relation |
| RelA | values | none -> relation |
| RelA | except, intersect | relation x relation -> relation |
| RelA | cross_join | relation x relation -> relation |
| RelA | analytic | relation -> relation |
| SGA | normalize_graph | graph -> graph |
| SGA | match_nodes | graph -> relation |
| SGA | match_edges | graph -> relation |
| SGA | traverse | graph x optional selector relation -> relation |
| SGA | shortest_path | graph x up to two selector relations -> relation |
| SGA | components | graph -> relation |
| SGA | reach | graph x optional selector relation -> relation |
| SGA | graph_bounds | graph -> relation |
| SGA | degree | graph -> relation |
| SGA | graph_diff | graph x graph -> relation |
| SGA | graft_graph | graph x graph -> graph |
| SGA | union_graphs | graph x nonempty finite graph list -> graph |
| SGA | subtract_graphs | graph x graph -> graph |
| SGA | subgraph | graph x optional relation -> graph |
| SGA | graphs_equal | graph x graph -> relation |
| SGA | apply_graph_delta | graph x relation -> graph |
| SGA | mean_graphs | finite graph list -> graph |
| TEA | window_events | events -> events |
| TEA | watermark_events | events -> events |
| TEA | session | relation -> relation; events -> events |
| TEA | time_series | none -> relation |
| TEA | interval_join | relation x relation -> relation |
| Calendar | calendar_bucket, calendar_series | relation -> relation; none -> relation |
| TEA | dedupe_events | events -> events |
| TEA | asof_join | relation/events x relation -> relation |
| TEA | sequence | relation/events -> relation |
| VecA | normalize_vectors | vectors -> vectors |
| VecA | score_vectors | vectors -> vectors |
| VecA | nearest | relation/vectors x relation/vectors -> relation |
| VecA | pool_vectors | relation/vectors -> vectors |
| VecA | merge_rankings | relation x relation (RRF: nonempty finite relation list) -> relation |
| Bridge | graph | finite ordered relation occurrences -> graph |
| Bridge | node_rows | graph -> relation |
| Bridge | edge_rows | graph -> relation |
| Bridge | to_events | relation -> events |
| Bridge | events | events -> relation |
| Bridge | to_vectors | relation -> vectors |
| Bridge | vectors | vectors -> relation |
| Acquisition | infer | relation -> relation |
| Acquisition | search_candidates | relation -> candidates and coverage relations |
| Search | text_rank | relation x relation -> relation |
| Artifact | serialize | relation -> relation containing content_ref |
Reserved operation, argument-key, scalar-function, predicate, Effect-kind, and binding-form spellings are exact lowercase. Author-defined identifiers retain case under Pipeline names and references and Parameters and binding. Case folding and conversion of spaces, dots, or hyphens to underscores are not part of ActQL. Implementations MUST NOT execute an unknown spelling through fuzzy matching or an undocumented synonym.
Bare search and bare dedupe are not ActQL vocabulary. A compiler MUST reject
them as unknown operations; authors choose the exact operation whose semantics
they require. The compiler MUST NOT turn an unknown word into an ambiguity
resolver or operation-discovery side channel.
row_window is the relation-in/relation-out window operation and
window_events is the event-stream-preserving operation; both require explicit
kind. graph, nearest, row_window, and the other declared semantic macros
may insert typed carrier bridges, but their computation remains AoA IR.
infer is an explicit typed external acquisition operation; serialize is an
explicit Artifact barrier. An ordinary join whose right side is a keyed
SourceView may lower to an actql-lookup/0.1 acquisition barrier. These
barriers are not AoA algebra operations, hidden Connector callbacks, Effects,
or control-flow constructs.
The Grammar Manifest operations inventory is the complete author-visible
operation vocabulary. ACTQL_INTEGRATION_PROFILE_CONTRACT identifies the
corresponding optional production-profile contracts. Profile conformance and
unsupported-profile rejection follow Conformance profiles. No operation outside these closed
contracts may execute as ActQL 0.1.
Relation operations
filter
filter accepts the one explicit predicate grammar in Predicates:
- filter:
and:
- {eq: [{field: status}, {literal: open}]}
- {gte: [{field: amount}, {literal: 100000}]}Field-to-field and field-to-literal comparisons use the same binary form:
- filter:
lt:
- {field: score}
- {field: required_score}
- filter:
lt:
- {field: score}
- {literal: required_score}These are structured-form examples. Predicates also accept expression text
(Expression text): score < required_score compares fields, while
score < 'required_score' compares a field with a string literal. Operand
expressions follow Expressions and predicates; names and literals are distinguished by syntax, not
inferred from their values.
filter lowers to REL.where.
select
Simple projection:
- select: [id, status, amount]Projection with deterministic renaming:
- select:
- opportunity_id: id
- account_idThe map form means {output_name: input_field}. Projection order is semantic.
select lowers to REL.select.
rename: {existing_name: new_name} and drop: [field, ...] are convenience
forms that retain the remaining complete input shape and lower directly to
REL.select. They require a complete statically known field set. Duplicate
output names, unknown fields, or dropping every field MUST fail during check.
Given a complete input schema, check MUST accept rename: {} and drop: []
as identity projections retaining every input field, including when the input
schema itself has no fields. The prohibition on dropping every field applies
to removing all fields from a nonempty input, not to preserving an already
empty input. This does not admit select: []. Accepting an identity projection
does not authorize deleting the authored operation or equating its semantic
identity with omission of the operation; ordinary lowering and identity rules
continue to apply. Repeated names in drop denote the same removal, not
duplicate output fields.
sort
- sort:
- {score: desc}
- {id: asc}Each key MUST follow OrderKey in Structural grammar (normative) and specify asc or desc. The
single-field map uses last for null placement where the AoA contract defines
that completion as exact. The explicit {field, dir, nulls} form selects
first or last; nulls is REQUIRED in that form. Null placement is
independent of ascending or descending direction. sort lowers to
REL.orderByStable for a nonempty authored key list.
Check MUST accept sort: [] as an identity operation preserving row values,
multiplicity, schema, current order and existing evidence, including for empty
input. An empty sort MUST NOT perform candidate-key sorting or establish new
order evidence; it is an exception to the completion rule below. Acceptance
MUST NOT authorize deleting the authored operation or equating its semantic
identity with omission. Later consumers still apply their own order requirements,
including any candidate-key completion permitted for take.
Ordering ties that can affect a published Result or an order-sensitive operation MUST be resolved. For a nonempty authored sort, when the current relation retains a candidate key whose fields all have a portable order, the compiler MUST append missing key fields in ascending order as a deterministic identity tie-break and journal the completion. This does not invent a business field: it reuses proven identity evidence. Without a retained candidate key, the compiler MUST NOT invent a tie-break.
REL.orderByStable MUST compare the supplied keys in their declared priority
and preserve input-relative order for rows equal on all keys, without adding
valueCID or any other implicit tie-break. The compiler MUST pass completed
explicit keys and their completion record to lowering; the primitive does not
perform candidate-key inference. An existing admitted total-order witness can
justify stable tie resolution where the consuming operation's rules permit it;
this is distinct from appending candidate-key fields. Stability alone MUST NOT
promote physical input enumeration into deterministic total-order evidence.
The empty-sort identity and evidence rules above remain unchanged.
For nonempty sort, the compiler MUST prefer eligible candidate-key completion.
If no eligible candidate key exists but an applicable admitted total input-order
witness exists, it MUST resolve equal explicit-key tuples using only the relative
order established by that witness. The sorted output MUST identify this sort as
its order producer and retain the prior witness as a tie-resolution dependency,
not copy the prior order as if the complete input sequence were unchanged.
Projection hiding prior order fields does not itself invalidate a retained
occurrence-order witness. With neither strategy available, the compiler MUST
retain the order as unresolved and reject it at a boundary requiring total order.
The checked strategy, authored keys, completion record or exact owned prior
witness MUST be exposed to lowering without requiring order re-inference.
This fallback is specific to sort; it does not relax the separate candidate-key
requirements of top_k or top_k_by.
take
- take: 20The object form is take: {count: 20, offset: 40}; its count member is named
count, not limit.
The count MUST be a positive safe integer. take: n and
take: {count: n, offset: 0} normalize to the same closed object;
offset defaults to zero and otherwise is a nonnegative safe integer.
Skip that many rows, then retain at most count rows; an offset beyond the
input yields empty output. take lowers to REL.limit with both values.
The input MUST already have a proven stable order from sort, top_k, an
ordered deterministic operation, admitted connector order evidence, or a
retained candidate key that the compiler can order by and journal.
Provider return order alone is not evidence and MUST NOT make take valid.
distinct
- distinct: trueremoves duplicate complete rows. A key projection MAY be written as:
- distinct: [customer_id]Keyed distinct projects the declared keys before applying REL.distinct; it
does not retain unspecified fields. A compiler MUST NOT choose a representative
row for unspecified fields.
Distinct MUST retain the first occurrence of each equal complete row in existing input order; keyed distinct applies this rule after the declared projection. It does not sort surviving rows or select values for fields omitted by that projection. Existing logical order is preserved, but provider arrival order without evidence does not become a stable-order proof through deduplication. This rule is independent of candidate-key propagation and does not introduce a non-null scalar identity for arbitrary complete rows.
A keyed distinct list MUST be nonempty and contain no repeated field
references, because it defines a projection with distinct output names.
Check MUST reject distinct: [] rather than reinterpret it as distinct: true.
The list retains its authored projection order; these rules do not reject
empty runtime input or impose a scalar-only schema on whole-row distinct.
Fields selected by keyed distinct and fields in dedupe_rows.by MUST belong
to the scalar or reference domains admitted by KeyComparable; nullable keys
are permitted. Object, array, json and vector fields are not admitted as these
explicit equality keys. This restriction does not apply to complete-row
distinct: true. Authored ordering keys separately follow AuthorOrderable.
Representative-row selection is a separate operation:
- dedupe_rows:
by: [customer_id]
order_by:
- {updated_at: desc}
- {source_id: asc}by and non-empty order_by are REQUIRED. The operation keeps the
first row in that order and fails an unresolved top tie; those are language
invariants rather than authoring fields and are recorded in the Plan.
The compiler lowers this to REL.dedupeRows. If two candidate rows for one
key tie on every authored order key, execution MUST fail; implementation row
order or CID MUST NOT silently choose the representative.
Groups are emitted in ascending canonical typed key order, comparing the
authored by tuple component by component with NULLS FIRST and the declared
type's non-null comparison. This order is separate from canonical key encoding
and from the authored order used to select each representative.
Check MUST accept dedupe_rows with by: [], treating all input rows as one
global partition. Its required nonempty order_by selects the first existing
row; empty input yields zero rows, and a tie between top candidates fails under
the same rule as any other partition. No representative row is synthesized.
This differs from a global aggregate and from sort followed by take-one.
Check MUST accept repeated by references while checking each reference's
existence and applicable type requirements. The checked representation MUST
retain their authored order and repetitions without preimage deduplication;
repetition neither adds output columns nor proves uniqueness or resolves ties.
group
Aggregate calls MUST use the closed function-specific grammar below: field
and expr are mutually exclusive, and only row-count count may omit both.
String shorthand, op, function-name keys and count_if.where are not aliases.
Check MUST accept an empty aggregates map and repeated by references while
checking every input contract, reference and budget. An empty aggregate map
produces only the grouping fields, once each in first-occurrence order; repeated
conditions do not duplicate rows or strengthen uniqueness. With by: [], it
produces one empty object even on empty input. Authored repetitions and the
operation remain in the semantic source; no preimage deduplication, operation
deletion or identity equivalence with another operation is implied.
The distinct member accepts either boolean for count(expr), sum, avg,
array_agg and string_agg; only false for row-count count, min and max;
and neither boolean for all other aggregate functions. No implicit cast is
performed. A unique fixed immediate input domain supplies literal NULL with
type evidence (for example number for avg, boolean for count_if, string for
string_agg), but overloaded input domains and unresolved child computations
receive no inferred type from the aggregate's result.
array_agg constructs a non-null array of non-null root items, retaining nested
nullability. Its item contract follows recursive value-construction rules:
opaque json and reference types, including those hidden in arrays or objects,
are not constructible here. In contrast, first and last select existing
typed values and may retain structured, opaque JSON and reference contracts;
their ordering keys independently satisfy AuthorOrderable. Aggregate capacity,
order completion and numeric observation obligations are not discharged by
static type checking alone.
- group:
by: [customer_id]
aggregates:
event_count: {fn: count}
total_amount: {fn: sum, field: amount}Supported aggregate functions are count, count_if, sum, avg, min,
max, array_agg, and the ordered and statistical aggregates.
Every aggregate MUST have an output name. Every
function except row-count count MUST name an input expression or field.
distinct: true is admitted only for count(expr), sum, avg, and
array_agg and the explicitly admitted additional aggregates; it is invalid
for row-count count, count_if, min, and max.
The fixed evaluation order is each aggregate's filter, its argument expression
on true rows only, null exclusion, canonical-value CID distinct, then stable
fold. Argument expressions are not hoisted before their filters. Separate
aggregates evaluate in canonical output-name order; each consumes rows in its
specified input order. Every array_agg MUST declare its own non-empty
order_by; these orders may differ between aggregates. The compiler applies
each after its own filter, without imposing one aggregate's order or demand
on another. Group
results are canonically ordered by the group keys in ascending AoA order,
including reference identity order (Opaque reference values).
The key tuple is compared lexicographically in authored by order, with
NULLS FIRST independently at every component: two null components compare
equal and comparison continues at the next component; a null component precedes
every non-null value of its declared type. Non-null components retain their
type-specific canonical order. This is group output enumeration, not the
default null placement of authored sort or the input order of an aggregate.
group lowers to REL.groupBy followed by that canonical group order.
The final order also applies when aggregates is empty; primitive deduplication
enumeration is not a substitute for it.
Null group keys of the same declared type form one group. by: [] is the
global aggregate and emits one group even for empty input. count counts rows;
count with an expression counts non-null expression values; count_if
counts only true and excludes false/null. sum, avg, min, and max ignore
null and return null when no non-null value exists. min/max require one
AuthorOrderable type (Lexical and ordering rules). array_agg excludes null, applies distinct
equality by canonical value CID while retaining the first occurrence in the
authored stable order. array_agg.max_items is an author guard as defined in
Author guards, resolved limits, and Plan facts, counting elements in each aggregate array after null exclusion and any
distinct step. Its resolved limit uses input proof, Host policy, and any
authored guard. Its order MUST retain a candidate key so equal authored keys
cannot choose a row by provider enumeration. A breach MUST fail the aggregate
operation without publishing a partial Relation or truncated array.
For groups containing binary64 sum/avg, lowering preserves the same checked
grouping and result contract but inserts typed numeric observation slots for
those aggregate values. Exact aggregates remain AoA computation; replay uses
the captured slot values. Describing group as REL.groupBy does not authorize
recomputing an observed floating reduction on replay.
Captured floating-point aggregates
Binary64 sum and avg, including their group, window_group, pivot and analytic
forms, use a compiler-inserted numeric observation boundary. Both local and
remote reductions MUST capture the admitted result before any downstream
filter, ordering, Result or Preview consumes it. Replay reuses that value,
never a newly computed approximation. Determinism is over the ObservationSet
including numeric observations, not merely over the original input rows.
Placement may choose a Contract-admitted reduction algorithm with its declared
rounding and error law; it cannot choose arbitrary opaque code. A numeric
contract fixes contributors, input type, filter demand, NULL/distinct semantics,
group/window identity, count, finite output, overflow and algorithm/error law.
Wrong groups, counts, domains or demand are not numerical tolerance. The closed
boundary does not authorize unrelated provider computation or additional egress.
No universal epsilon is inferred. Cancellation in [1e16, 1, -1e16] can produce
0 or 1 under different reductions; differences are not necessarily one last bit.
Numeric observations MUST bind the semantic aggregate slot, logical input artifact, post-filter contributor evidence, group/window key, NULL/distinct law, input type/count, adopted finite value, provider/kernel law and policy/cost evidence. Slot identity precedes response capture and excludes future Stage Invocation/Result CIDs. The logical Plan binds the aggregate and slot contract, not future physical parallelism or response bytes. Different fresh placements may change ObservationSet, Invocation and Result identities without changing Program identity; same-observation replay cannot.
count, count_if, exact decimal sum, and a separately proved integer sum
retain exact semantics and final public-domain checks. Integer evidence requires
an admitted integer/range contract, not values that happen to look integral.
Integer sums use exact accumulation; an admitted integer average requires an
explicit result/rounding law. Decimal average is not introduced implicitly.
Decimal scale zero remains an explicit exact route. Min/max, ordered strings,
variance/percentile laws, VecA scores and graph means retain their own laws.
Scalar binary64 arithmetic is unchanged. Money and exact decisions should use
the explicit exact value domains, not assume approximate reduction invariance.
join
- join:
with: contracts
kind: left
on:
- {left: organization_id, right: organization_id}
- {left: account_id, right: crm_account_id}
cardinality: many_to_one
select:
opportunity_id: left.id
renewal_at: right.renewal_atwith names the right relation. left MAY explicitly replace the current
relation. kind is REQUIRED and MUST be one of inner, left, full, semi, or
anti. on MUST state one equality key or a non-empty array of equality keys;
same-name field shorthand is permitted. Arbitrary non-equality conditions are not part of this join;
bounded Cartesian products and temporal overlap use the separately specified
cross_join and interval_join operations.
Check MUST permit step-level from together with join.left: step from
selects the main input first, then join.left replaces it as the actual left
operand. Without step from, join.left replaces current and can establish
the left operand when current is unset. Both explicit selectors MUST resolve
to available Relation bindings under the ordinary name-scope rules; an invalid
from is not ignored merely because join.left overrides its selection.
The checked join's left input edge identifies the final selected operand,
not the overridden binding. The join output becomes current as usual.
Check MUST accept repeated equality pairs and repeated use of one side's field with different fields on the other side. Every pair is checked independently; the checked representation MUST retain the authored pair order and repetitions. Acceptance does not authorize deduplication of the operation's semantic preimage.
cardinality is an OPTIONAL validation assertion, not output semantics. It is
one of one_to_one, many_to_one, or one_to_many; it asserts uniqueness of
both sides, the right side, or the left side respectively. A violated uniqueness assertion MUST fail before a
result is accepted. This failure behavior is a language invariant, not an
authoring policy.
When cardinality is omitted, the compiler MUST derive it only when candidate
key evidence proves the join-key tuple unique on the left, the right, or both.
Uniqueness on both sides proves one_to_one; uniqueness only on the right
proves many_to_one; uniqueness only on the left proves one_to_many. When
neither side is proven unique, cardinality remains unproven; the compiler MUST
NOT infer many_to_many. A derived cardinality and its candidate-key proof,
including any SourceView Contract CID on which it depends, belong to the
Compiled Plan and are not source normalization. An authored cardinality is
recorded as an author assertion and requires the same fail-on-violation runtime
check.
Static uniqueness evidence and the authored assertion are combined per side.
A proven unique right side together with an asserted unique left side yields
one_to_one; the Plan retains both sources of evidence. The assertion does
not discard a stronger static proof.
Omitted null_key has the fixed SQL-style no_match language law and creates
no normalization entry. Explicit null_key: no_match is legal and normalizes
to the same canonical form as omission;
null_key: fail is the sole authored override and rejects a null key component
on either input. max_output_rows is an author guard as defined in Author guards, resolved limits, and Plan facts,
counting joined output rows. Its resolved limit uses safe join-cardinality
transfer over both inputs, current Host policy, and any authored guard. A
breach MUST fail the join before publishing its output Relation. Join-key
types MUST satisfy KeyComparable (Closed value contracts) under the resolved schemas; implicit coercion is
forbidden.
Without select, join fields retain explicit left. and right. provenance.
These compiler-generated names are the sole exception to the authored flat
field rule in Field names and nested paths; an author cannot declare a source field with either prefix.
Unqualified field access on a raw join result MUST fail as ambiguous. semi
and anti joins expose only left fields. full is the reconciliation join:
unmatched rows from either side are preserved, absent-side fields are null,
and output ordering follows the row-order contract below. Both complete typed
schemas and stable identities for both
sides are REQUIRED for full; the compiler MUST NOT synthesize them.
With select, every output field maps to a provenance-qualified source field,
and subsequent steps use the flat output names. A compiler MUST NOT select a
same-named field from one side by precedence.
An explicitly authored join select MUST be a nonempty map; {} MUST fail
during check. Omission retains the qualified output and is not an empty
projection. Check MUST permit distinct output names to select the same source
field, preserving its exact type and nullability in each output. Duplicate
output names remain invalid. Projection mappings MUST retain every alias;
accepting aliases does not itself prove candidate-key preservation.
Because object key order is not semantic, join select output names are
lowered in AoA canonical UTF-16 code-unit order. Authors control final column
order with the selected named result's fields.
All join kinds have a fixed row-order contract. inner and left enumerate
left rows in their stable input order and matching right rows in stable right
input order; unmatched left rows remain in place for left. semi emits each
matching left row once and anti emits each unmatched left row, both in stable
left order. full uses the left order first and then unmatched right rows in
stable right order. Null keys MUST NOT match under no_match; fail aborts before
publishing output. Projection may hide key fields but does not erase the
runtime ordering evidence attached to the relation result.
Candidate-key preservation is separate from row ordering and follows this closed table:
| Join kind | Cardinality | Candidate keys preserved before select |
|---|---|---|
semi / anti | any | left |
inner | many_to_one | left |
inner | one_to_many | right |
inner | one_to_one | left and right, as separate keys |
inner | neither side proven unique | left + right composite only |
left | many_to_one / one_to_one | left |
left | other | none inferred |
full | any | none inferred |
Each input may have more than one candidate key. A select or rename retains
each key for which every field maps to one distinct output field. It may drop
one key while preserving another. Outer-join null extension is not evidence
for right-side uniqueness.
derive
Computations and replacements belong to derive; closing the field set belongs to select. Derive preserves input fields, row order and multiplicity.
| Body | RHS scope | Input-name collision | Binding order |
|---|---|---|---|
| map | operation-start input snapshot only | rejected | canonical UTF-16 output-name order |
| nonempty array of singleton maps | prior slot, otherwise input | allowed once per name | authored array order |
An empty derive map MUST be accepted, including with an empty input schema, and preserves input values, schema, row order and row count. Input-contract and budget checks still apply. No-op admission does not authorize operation deletion or CID equivalence with an omitted operation. This rule does not require a complete input schema for derive in general; the ordinary input contract applies. The array form remains nonempty, with exactly one entry in every slot map.
All map expressions read the input, never siblings. Map keys are canonically sorted
before lowering to the existing REL.derive. An output name MUST NOT collide
with a visible input field in map form.
- derive:
- original_amount: "amount"
- amount: "amount * $multiplier"
- tax: "amount * $tax_rate"
- total: "amount + tax"
- select: [id, original_amount, amount, tax, total]
as: calculatedArray slots MUST be nonempty singleton maps with static flat field names and
common Expression RHSs. Output names are unique after NFC normalization.
Shadowing an input once is distinct from declaring the same slot name twice.
Reserved-name and prototype-safe field handling remains unchanged; lawful
__proto__ and numeric-looking names are data, not dictionary instructions.
No spread, wildcard, dynamic LHS, slot metadata or sequential flag is added.
No project operation is registered by this revision.
For each row start with E0=input. Evaluate slot i against E(i-1), then bind
its name to that value to obtain Ei; publish En. Its own binding is not in
scope during its RHS. A previous slot wins over input; otherwise an existing
input wins even when a future slot uses that name. A name found only in a
future slot, or a self-definition without an input value, fails check.
Save an old value under a different name before shadowing to use it later;
there is no new input. namespace or implicit original-value accessor.
Checking MUST record each reference as the exact input field or earlier value-version slot. A shadow takes its checked RHS type without coercion or joining it to the old type. No type inference flows backwards from future slots. Required input admission precedes shadowing, including a value that will be overwritten. An otherwise admitted open input need not gain a complete schema: retain unknown fields under its existing contract and require the ordinary type evidence for every field actually read. Map collision requirements are not relaxed by this rule.
Array output retains every input field unless shadowed, replacing a shadowed field's value and type. Unmentioned fields remain. Object-key enumeration is not a column-order identity law; final publication order is ResultDefinition.fields. The authored slot sequence is semantic for evaluation and dependency identity, not an implicit reordering of published columns. For amount=10, multiplier=2, tax_rate=0.5 the example yields original_amount=10, amount=20, tax=10, total=30; only the final select removes other input columns. Selecting only final columns before derive may drop required raw fields or refer to fields not yet created. Optional early projection must preserve all input dependencies and admission, failure, bounds and policy obligations. Result.fields may close publication shape without a final select, but does not close the intermediate Relation.
Both forms MUST preserve row-major failure: logical row, canonical map name or authored array slot, then common expression child order. Each demanded RHS is evaluated once in one shared row frame; lazy unselected children remain undemanded. Every authored slot retains its evaluation obligation even if a later select drops it. A discarded division by zero still fails the operation. Whole-Relation derive steps are not an equivalent expansion when they change the representative failing row/slot. No partial row or slot is published.
Candidate keys whose values are unchanged remain. Computed replacement of a key loses its proof even under the same name; an exact copy of the complete non-null tuple to retained distinct aliases can preserve it. Row-position witnesses may survive shadowing, but old sort-key proofs do not describe new values. Field policy follows resolved value versions; replacement by a less sensitive value does not erase required observation provenance or Relation policy. Effects use the actual published target fields, not hidden old values.
Source normalization and printing MUST preserve map versus array shape,
normalizing only RHS expressions. Literal payloads and structural arguments
remain opaque. Map permutation is not array permutation. The common checked
row-binding contract records source shape, ordered {name, expr} slots,
input/prior-slot references, types, demand, retained input field contracts,
proofs and one failure scope. Array roots use /bindings/i/expr; input
references are {input_field: name}, earlier slots {prior_slot: i}.
The ordinary current Relation is the sole graph input. The map
AoA node shape remains unchanged. Ordered bindings require the pinned shared
row-kernel extension, not an unregistered opcode or Host row rewrite.
Input, root, output, live-value and total-work ceilings retain their existing measurement scopes; slots do not reset budgets. Fusion with select must preserve derive's logical retained-output limits.
Ordered derive MUST use the REL.derive registration's ordered_bindings
execution extension, pinned at
actql-aoa.json#/execution_contracts/REL.derive.ordered. The closed params are
{row_kernel_contract_cid, bindings, references}. The CID selects that exact
extension, not a new opcode; bindings is a nonempty authored-order array of
closed {name, expr} entries using the existing machine expression nodes.
The legacy snapshot-map exprs representation is unchanged. An adapter lacking
this extension cannot interpret the alternative as legacy params.
The reference table MUST contain exactly one closed {at, target} entry for
every field-node occurrence, in binding order then semantic expression preorder.
The RFC6901 at pointer is relative to params and starts at /bindings/i/expr;
it never traverses literal payloads. Target is exactly {input_field: name} or
{prior_slot: j}. The field spelling and checked type agree with the target.
For binding i, the latest earlier slot of that name takes precedence; otherwise
the original input is selected. Missing, duplicate, reordered, extraneous,
self or future references are invalid, even when hashes match.
In fragment.request.pipeline, an ordered REL.derive step MUST have exactly
{id, in, op, params}, with params exactly {bindings: [{name, expr}, ...]}.
The nonempty bindings retain authored order and unique canonical names; each
expr is the canonical normalized source Expression, not a machine expression
node. Parameter references remain source syntax, with checked bindings retained
in request.parameters; literal payloads remain opaque. Source params contain
no reference table, extension pin, contexts, limits or derive evidence. The
legacy map alternative remains exactly {exprs: {name: Expression, ...}}.
Request and graph preserve the same canonical step IDs, input edges and binding
names/order. Regeneration lowers the RHSs, adds the selected registry extension
CID and derives the machine reference table, rather than trusting those as
source assertions. Each root's source equals its corresponding source RHS.
A fragment with ordered nodes MUST carry row_kernel_evidence containing
bindings, contexts, limits and shapes: each referenced body once, in
self-CID order, with no unreferenced bodies. There is one existing
actql-derive-binding/0.1 record per ordered node. Its node, input/output,
binding names and types, expression roots, contexts and limits are regenerated
from the same checked request. With no ordered nodes this member is absent.
Root-list serialization still follows graph order then pointer UTF-16 order;
it does not change authored binding evaluation order.
For this extension, checked_expression_cid MUST hash the complete corresponding
fragment root, including its step ID, parameter pointer, source, node, checked
contract and demand paths. context_contract_cid resolves an
actql-derive-row-context/0.1 body binding the same input contract and the exact
prefix of prior {name, contract} slots. Input/output contract CIDs resolve
actql-derive-row-shape/0.1 bodies with known fields, sorted unique
required_fields and boolean additional_fields; known fields are a subset
of required fields. Unknown field types stay unknown. Context, shape and limit
self-CIDs omit only self_cid; no diagnostic spans enter these preimages.
Node identity includes its extension pin, never the evidence that names it.
For ordered derive, derive_binding.aoa_node_cid MUST be the canonical JSON CID
of the complete corresponding fragment.graph.steps[i] object, exactly
{id, in, op, params}, without a wrapper, domain field or omitted member.
op is REL.derive, in is the one-element ordered input-edge array, and
params is the complete machine alternative defined above, including
row_kernel_contract_cid, ordered lowered bindings and the full reference table.
This is not the source request step, params alone, a scalar expression node,
the whole pipeline, a sort-validation projection or a resolved step augmented
with input_sorts/output_sort. The actual canonical fragment graph names are
included; process-local handles are not substitutes. The derive evidence,
contexts, shapes, limits and their self-CIDs remain outside the step. Changing
the step ID, input edge, extension pin, bindings or references changes this
preimage; changing only external evidence does not. Fragment identity still
binds the complete evidence. Node identity proves neither type correctness nor
ownership; admission regenerates the same-request graph and evidence together.
request.row_kernel_limits MUST map exactly the ordered step IDs to their
actql-derive-row-limits/0.1 bodies. Each has nonnegative safe-integer
max_input_rows, max_value_bytes, max_live_value_bytes, max_work and a
self-CID. Evidence limits_cid resolves the exact same-step request body;
the row/value ceilings do not exceed the enclosing request's corresponding
ceilings and selected Host constraints remain applicable. These limits are
declared enforcement bounds, not inferred capacity proofs. A fragment without
ordered nodes omits this request member.
Enforcement MUST count all input row occurrences, and measure each admitted input value, demanded root value and complete retained output row as canonical JSON UTF-8 bytes of its typed wire value. Live-value bytes sum live input, slot, temporary and pending-output roots; aliases count as separate logical roots even with shared physical storage. Release follows last use. A transfer of ownership does not create a second root. These are logical measures, not a process heap ceiling; additional backend resource limits remain separate.
Work MUST accumulate one unit per row, binding and demanded expression entry, plus value weight for input admission, each consumed operand, each produced value and the retained output. Value weight is one per scalar/container plus raw UTF-8 octets of string values and object keys, counting every logical occurrence. Caching and short-circuit scalar comparisons do not discount this weight; unselected lazy expressions incur no evaluation work. Logical work is not an instruction count. Charges precede traversal/construction, check arithmetic overflow, accept exact caps and never reset per row or slot. All required input validation precedes shadowing; failure exposes no partial operation output. Later projection cannot waive retained-output charges.
Saved plans are rechecked under their exact admitted law; map/array conversion, early select or expression deletion is not an automatic meaning-preserving repair. Ordered derive retains the input shape; explicit select closes the output shape. These are distinct operations with their own measured bounds.
top_k
- top_k:
k: 20
by: [{score: desc}, {id: asc}]k MUST be a positive safe integer and by MUST contain at least one
explicit stable order key. Selection requires a total order. When retained
candidate-key evidence exists, the completion algorithm in Deterministic completion selects and
appends missing key fields and journals the change; without it, a possibly
tied order is rejected. top_k lowers
directly to REL.topk.
Partitioned ranking is a distinct operation:
- top_k_by:
partition_by: [customer_id]
k: 3
by:
- {score: desc}
- {candidate_id: asc}partition_by, positive k, and a stable non-empty by order are REQUIRED.
The same total-order and retained-candidate-key rule applies inside every
partition.
The result preserves no input order across partitions: partitions are ordered
by their keys in canonical ascending order, and rows within each partition use
the authored order. It lowers to an AoA partitioned-top-k operation; repeated
global top_k or host-side grouping is not a conforming substitute.
Check MUST use the closed {k, by} body for top_k and
{partition_by, k, by} for top_k_by. The count is a positive-safe-integer
literal or whole Parameter reference, not an arbitrary expression; bound
values satisfy the same range before execution. The operations retain the
input field contracts without fabricating row-count or total-order evidence.
Check MUST accept empty and repeated top_k_by.partition_by references while
validating each equality-key field. Empty partition keys mean one global
partition of existing rows, with no row synthesized on empty input. Repetitions
and authored order remain in the checked representation; they add neither rows
nor uniqueness evidence. Repeated authored order keys are also retained and
cannot establish a missing tie-breaker. Neither empty partition keys nor
repetition authorizes operation deletion, preimage deduplication, or identity
equivalence with top_k.
Relational temporal operators
row_window assigns fixed or sliding window columns without changing sort:
- row_window:
kind: sliding
time: occurred_at
size: 1h
step: 15mkind, time, and positive size are REQUIRED; sliding windows also require
positive step. It lowers to REL.window.fixed or REL.window.sliding.
Window membership and emitted boundary values are defined in Canonical fixed and sliding windows.
window_group lowers to REL.window.project and uses the same named aggregate
grammar and type rules as group (group). Its input MUST carry statically known window_start and
window_end fields produced by row_window or declared by an already-windowed
relation contract; it MUST NOT interpret an ordinary relation as windowed.
lag lowers to REL.window.lag and requires
partition_by, non-empty order_by, field, and an output name. An authored
offset is a positive safe integer; omission and explicit offset: 1
normalize to the same one-row lag. Its partition order must retain a candidate key so lag selection has
no unresolved tie. The output name is written as output. watermark_rows lowers to
REL.watermark and requires time, an
explicit late_policy of drop or mark, and either a finite watermark_ms
or positive max_skew.
watermark_rows MUST specify exactly one of watermark_ms and max_skew;
neither omission of both nor precedence between two supplied sources is admitted.
It adds fresh non-null fields watermark_ms: number, watermark_late: boolean
and watermark_decision: string. Any collision with an input field fails check.
For max_skew, the threshold at input occurrence i is the maximum time over
occurrences 0 through i inclusive, minus the resolved skew. This is an
unpartitioned prefix in the proven logical input order, not a maximum over
the complete Relation or a provider-arrival-order completion. Such order
evidence is required for the prefix path. A row is late exactly when its time
is strictly less than its threshold; equality is not late. mark preserves
every row, using decision mark for late rows and keep otherwise. drop
removes late rows and emits keep for retained rows. Empty input has the same
output contract and no threshold observation. Non-finite threshold arithmetic
fails before publication; invalid time rows cannot be silently discarded or
emitted with missing generated fields.
window_group MUST have checked window-boundary role evidence, introduced by
row_window or an admitted already-windowed input contract. Coincidentally
named fields are insufficient. The evidence identifies the two boundary
fields and their interval law separately from ordinary field-type knowledge.
Value-preserving filters and whole-row distinct retain it. A name-changing
projection transfers each role to every alias in its explicit, value-preserving
field mapping; keyed distinct follows its projection. Removing an alias or
replacing its value through computation removes only that alias's evidence. An exact
whole-field derive copy transfers the copied value's role to its output alias,
including self-copies and prior-slot copy chains. Snapshot and ordered derive
use their checked reference scopes; neither equal field types nor evaluation of
a general expression establishes a role. The final value version of each output
name determines its surviving role. The window evidence becomes invalid
when either boundary has no surviving alias. Window grouping retains only
aliases that remain actual grouping keys, not same-named aggregate outputs.
Required evidence must resolve
before window-group checking is complete; this rule grants no new runtime
deferral. Window keys are prepended exactly once to the ordinary group keys;
the shared empty-group and repeated-key laws remain unchanged.
Reshaping and nested data
- pivot:
index: [customer_id]
column: status
values: [open, won, lost]
value: amount
aggregate: sum
fill: 0
- unpivot:
columns: [jan, feb]
name_column: month
value_column: amount
keep: [customer_id]
- json_pick:
column: payload
path: [customer, id]
output: customer_id
- unnest:
column: items
as: item
with_index: item_indexpivot uses index for its key fields and supports the aggregates sum,
avg, count, min, max, and first; min/max require AuthorOrderable
inputs (Lexical and ordering rules). unpivot requires explicit source
columns plus the canonical name_column and value_column fields. json_pick
accepts a non-empty path of object keys and MUST NOT evaluate JSONPath code.
unnest requires an array-typed field, preserves the source row, emits one row
per array element, and optionally emits a zero-based index named by
with_index; with_index: true names it <as>_index. They lower to
REL.pivot, REL.unpivot, REL.json.pick, and REL.unnest.
All unpivot.columns MUST have exactly the same statically proven type and
nullability. ActQL 0.1 has no implicit common-supertype conversion; authors
derive explicit casts before unpivoting heterogeneous fields. object means a
parsed JSON object only, while json means any canonical JSON value.
json_pick accepts an object or object-valued json; it MUST NOT parse a JSON
string implicitly or inspect an opaque reference (Opaque reference values). For statically typed
objects, every path segment MUST exist
in the recursive fields contract and retains the leaf type with nullability
composed through the parent path under Closed value contracts. An opaque json value may be checked only with host-provided
shape evidence; absence of that evidence is not permission to guess a result
type.
unpivot.columns MUST be nonempty; an empty list cannot establish the value
column's type. A typed input containing zero rows is valid. Repeated source
columns are retained. Omitted keep means []; overlapping keep and columns
is permitted. The output consists of the kept fields followed by name_column
and value_column. Generated names MUST be authored field names and the actual
output names must be distinct, including repeated keep entries. Names of input
fields that are not kept do not cause collisions. The name column is non-null
string; the value column retains the exact selected contract, including nested
structure and nullability. Each input row MUST produce one row per authored
column occurrence, in input-row then column order, retaining null values. Expansion
bounds count repeated columns; repeating a source column does not establish
uniqueness of the input candidate key combined with the name column.
When every component of an input candidate key is retained by keep and the
generated name labels are distinct, the output retains that key extended with
name_column. Each label is the canonical field reference text, including
left. or right. qualification when present. The witness depends on the input
key validation and the checked one-occurrence-per-column expansion. A generated
column does not count as retention of an unkept input field with the same name.
Dropping every alias of a component loses this witness; null values in the value
column and empty input do not invalidate it. Repeated columns remain admitted
but do not generate this composite witness.
json_pick preserves every existing field and adds output; its output name
MUST be an authored field name and MUST NOT collide with an existing field.
Each path segment denotes one exact object key, not a dot-separated path or an
array index. A selected leaf may have type json; shape evidence is needed
only when traversing an opaque json, not when retaining it as a leaf.
For unnest, as and a string with_index MUST be authored field names.
Omitting with_index emits no index field; its only explicit forms are true
or a field-name string. false and null MUST fail during check. The source
array field remains in the output. Without outer: true, the element contract
retains exactly the array's item contract: a nullable array container does not
make its elements nullable. outer accepts only booleans when present.
pivot aggregate: first is order-sensitive and requires a previously proven
stable input order. Pivot values are REQUIRED, NFC-unique output field names.
Dynamic data-derived output columns are not part of ActQL 0.1. Null pivot
values do not match a declared string value; the string "null" is ordinary
text. A generated column that collides with an index field, another generated
field after NFC normalization, or the reserved namespace fails during check.
Pivot aggregates ignore null input values. count returns the non-null count;
sum, avg, min, max, and first return null for an empty non-null
bucket, after which an authored fill is applied. Binary64 sum/avg use the same
captured numeric boundary as group; exact aggregates keep their own laws.
Only first observes the
already-proven stable input order.
Check MUST accept pivot.index: [] and pivot.values: [] while retaining all
input-contract, name, type and budget checks. Empty index means one global
group, including empty input; empty values emits only index fields, once in
first-occurrence order. Both empty emits one empty object. Repeated index
references are retained without adding output columns or uniqueness evidence.
The operation and authored value order remain identity-relevant; empty axes
do not authorize operation deletion or CID equivalence with another operation.
The pivot column MUST have string or nullable string type, without implicit
conversion. A null or undeclared string contributes to no generated bucket but
still establishes its index group. Generated columns follow canonical UTF-16
string order, independently of authored values order; index fields precede
them. Empty buckets produce zero for count and null for the other five functions,
then apply fill only to null, never to count's zero.
Pivot fill MUST use the whole-value CellValue alternatives and satisfy the
aggregate result contract without implicit coercion or deep merge. Bare
unreserved wire values and {literal: WireValue} are literals, with the latter's
payload opaque. Only an exact singleton {$param: name} is a Parameter hole;
malformed reserved roots fail, and nested holes are not traversed. Recursive
object/array Parameters are admitted at this whole-value position. Bound
Parameter values receive the same value validation before execution. Decimal
literal fills use the exact canonical fixed-scale wire string and are validated
in the aggregate's precision/scale context. A compatible non-null fill makes
an otherwise nullable result non-null; nullable fills do not. Fill is validated
even for count and empty generated-column lists. Reference values still require
their own evidence: a raw JSON object is not reference proof. Literal NULL
requires only a nullable result and constructs no reference value.
For unnest, optional outer: true retains a parent with an empty or null
array as exactly one row whose element and exposed index are null. Omitted
or explicit outer: false normalizes to false and emits zero rows in those
cases. Nonempty arrays retain ordinary element multiplicity, including null
elements; their indices are non-null. Outer output therefore has nullable
element and index contracts. A missing or non-array value is a runtime type
failure. as, with_index, and
existing fields MUST NOT collide. The internal row identity is source primary
identity plus zero-based element index. It remains provable when the source
identity is known during the operation. Because the index is otherwise not an
author-visible field, ordinary non-outer unnest retains a candidate key only
when with_index is emitted and retained alongside the source key. This rule
does not infer a non-null candidate key from the nullable outer index.
The synthetic outer row has an internal missing-element identity distinct
from every nonnegative element index. Exposed nullable index is not a
non-null primary key. Outer output has conservative per-parent bound
max(1, array_max_items), with the normal nested byte and output-row limits.
An independently proven at-most-one expansion per parent preserves the parent
key. Otherwise outer unnest retains internal occurrence/order evidence only;
later non-null refinement and uniqueness assertions may establish new keys.
append
- append: {with: archived_rows}append requires two available relations and has no author-selectable schema
mode. Both inputs MUST have complete schemas with the same field names and
compatible recursive contracts under Closed value contracts. Field order may differ; the compiler deterministically aligns the
right relation by name before lowering to REL.unionAll. Missing or extra
fields and incompatible types MUST fail. The common contract is
JoinNullability (Closed value contracts); no implicit base-type coercion occurs.
Field-set comparison MUST include typed qualification: an unqualified id is not
left.id or right.id. Compatible qualified schemas MAY be appended without
flattening, and their qualification is preserved. Two empty schemas are
compatible; appending their zero-column rows still adds their multiplicities.
The closed body is {with: InputOrStepName}. Its left operand is the current
relation selected by the normal step from rule; there is no append.left
selector. Both operands may name the same available relation, in which case
both occurrences are retained, without deduplication or no-op elimination.
Contextual null typing follows Closed value contracts.
The compiler MUST NOT add a missing field; the author writes the explicit
null-producing derive when a branch lacks it.
Append does not deduplicate; authors add distinct explicitly when required.
It is not an identity assertion. When both inputs retain the same local
candidate key and a retained field has statically known, disjoint literal
domains, the compiler proves the composite candidate key consisting of that
discriminator followed by the local key. For example, local key [source_id]
plus source = crm on one branch and source = support on the other proves
[source, source_id]. Without such a proof, append does not invent a global
identity and infer reports ACTQL_E_APPEND_IDENTITY_UNPROVEN when one is
required.
Append order is every left row followed by every right row. Completeness is
complete only when both inputs are complete; source provenance remains
evidence even when not projected.
Complete-row distinct compares canonical AoA value CIDs. Object key order is
therefore irrelevant, array order is relevant, strings/timestamps use their
canonical spellings, and null differs from a missing field (the latter is
normally excluded by a complete typed schema).
assert_rows
- assert_rows:
where: {not_null: {field: customer_id}}
- assert_rows:
unique_by: [account_id, external_id]Exactly one of where and unique_by is present. where requires strict
true on every row; false and three-valued unknown fail. unique_by is a
non-empty, duplicate-free list of visible fields and rejects null or
missing components and duplicate canonical tuples. Both variants preserve row
bytes, shape, order, and identity on success. The unique_by variant additionally
proves non-null uniqueness for the declared tuple. The physical assertion
precedes every consumer of that proof.
Any violation fails before publishing the assertion output. Unconsumed
assertion branches fail planning rather than silently disappearing. No
on_failure, skip, warning, catch, or custom-message form is admitted.
Fields in assert_rows.unique_by MUST belong to the scalar or reference
domains admitted by KeyComparable (Closed value contracts),
including ResourceRef and ContentRef. Object, array, json and vector fields
are not admitted. Nullable field contracts MUST be accepted during type
checking; the physical assertion rejects null or missing component values.
Type checking alone neither narrows those contracts nor establishes a
non-null uniqueness proof.
Implementations MUST execute an atomic row assertion before any consumer of
its candidate-key proof and preserve the successful input relation unchanged.
where lowers to REL.assert_all; unique_by lowers to REL.assert_unique.
nest
nest is a pure Relation operation.
It attaches an ordered child-object array to each parent row. Example fragment:
- from: customers
nest:
with: orders
on: [{left: customer_id, right: customer_id}]
into: orders
fields: [order_id, amount, created_at]
order_by: [{created_at: desc}, {order_id: asc}]
as: customer_treeThe closed body contains required with, on, into, fields, and optional
order_by, max_items. with follows dataflow scope; on is a nonempty list
of explicit equality-key pairs checked with KeyComparable. fields reuses
select projection/rename syntax; calculations belong in preceding derive.
into must be a fresh parent field. No implicit relationship lookup, recursive
query, overwrite mode, or error handler is added. Both operands are Relations
from the checked graph; keyed-only acquisition must be explicit upstream work.
Parent and child schemas may carry qualified fields. fields MUST use only
field strings or singleton {output_name: input_field} maps, with distinct flat
authored output names. A qualified child field requires an explicit flat alias;
the legacy {from: ..., to: ...} object is not authored projection syntax.
Parent fields retain their qualification. Repeated on pairs MUST retain their
authored sequence and do not multiply matching child rows or prove uniqueness.
This does not alter the canonical lowering's first-occurrence deduplication of
right correlation fields for the internal group key.
Values, types, and order
Output preserves every parent row, its fields, order, and candidate keys.
Each parent receives all matching children; null correlation components never
match. Duplicate parent correlation keys remain separate rows. Legal child
multiplicity is preserved; duplicate identities still violate input contracts.
Zero matches produce []; an existing all-null child produces an object with
null fields, not an empty array. Acquisition failure never produces [].
The new field is a non-null array of non-null closed objects. Selected field contracts, including nullability, are preserved even for empty inputs. Required field/type evidence is static. Constructibility follows Object and array construction: nesting does not permit hidden references or infer safe structure from opaque JSON. Reference correlation/tie-break keys need not appear in the child payload.
Before child projection, resolve order using Named Results and Effect declarations's precedence: explicit order,
existing total witness, canonical candidate key, eligible full projected row,
otherwise rejection. Authored order requires AuthorOrderable; canonical
completion uses CanonicalOrderable and Deterministic completion's key selection. Internal reference
order remains in Plan/evidence, not authored source. Array order is observable
and identity-bearing. Projection does not discard ordering evidence.
With explicit order_by, ties require an eligible candidate key or an
existing total witness preserved by stable sorting; otherwise checking fails.
Full-projection fallback applies only when order_by is omitted. Its comparison
priority MUST follow the declared projection sequence, not object property
enumeration or canonical object-key serialization.
Lowering and limits
The canonical Program retains a nest node. Its value-level expansion is:
child semijoin against original parents (null keys never match)
→ effective child order → closed child objects
→ array_agg by correlation key → many-to-one left join to original parents
→ typed empty array for missing groups only → parent fields plus intoThe canonical node sequence is fixed below; equivalent physical rewrites do
not replace it. Generated IDs follow the ordinary AoA operation-base allocator
in insertion order; only the final projection may use the authored as.
Reference key aliases are __actql_nest_ref_i, where i is the field's index
in the lexically sorted complete input schema. Derive aliases on parent first,
then child, only if that side contains reference fields; their values are the
reference identity components defined in Opaque reference values. All correlation and completed
order terms use these aliases. Original equality-pair order is retained.
| Node | Canonical parameters and wiring |
|---|---|
| matched children | REL.join(child, parent), semi, reversed equality pairs, null_key: no_match; no distinct-parent node |
| child provenance | REL.select removes left. from the complete child schema, followed by reference aliases |
| child order | REL.orderByStable with resolved order; omitted only when the existing witness is reused unchanged |
| child arrays | REL.groupBy, distinct right correlation fields in first-occurrence order; __actql_nest_items = array_agg(object(projected fields)) with the resolved item limit |
| empty relation | REL.where over the original child with literal 0 = 1 |
| empty constant | global REL.groupBy, the same typed object aggregation as __actql_nest_empty, item limit 1 |
| attach groups | many-to-one left join of original parent (with reference aliases) and child arrays; duplicate groups fail; null keys never match |
| retain parents | select parent fields in their retained field sequence, then __actql_nest_items |
| attach empty | left join the retained parent rows to the singleton empty constant on literal 1 = 1 |
| flat values | select parent fields, items, empty constant, removing provenance qualifiers |
| fill missing | derive into = case(is_null(items), empty, items); an existing all-null child object is not missing |
| result | select original parent fields then into |
The child schema projection uses canonical schema-key order; this is separate
from the authored payload projection sequence. Item-limit bindings target each
group node's params.aggs.<internal-field>.max_items. Byte bindings target the
child-array group and final projection as specified below. Empty-array evidence
uses the fixed upper bound 1, not a guessed element type or a Host replacement.
Generated aggregation consumes verified effective order; it does not relax
authored array_agg rules. Required upstream failures cannot be optimized away.
Unmatched child groups do not incur nest-specific per-array guards. Independent
collections aggregate separately, avoiding sibling Cartesian products. Deeper
nesting uses finite bottom-up dataflow steps, not recursive acquisition.
max_items, when supplied, is a positive-safe-integer guard or an admitted
parameter bound before planning. It caps each emitted child array, never
truncates it; use preceding top_k_by for selection. With parent bound P and
per-parent bound M, total nested elements are bounded by P×M. A child-total bound
can tighten this only when parent correlation keys themselves are proven unique.
Other parent primary keys do not prove that fact. Sibling bytes add; deep values
compose recursive byte/depth bounds, checked for overflow. Resolved ceilings and
enforcement boundaries follow Author guards, resolved limits, and Plan facts; violations fail publication atomically.
Statistics and shared physical storage do not reduce logical expansion bounds.
Count bounds derived from the attachment byte ceiling MUST remain conditional
on successful enforcement at that output, not evidence authorizing an upstream
read. For ceiling B, each preserved parent contributes at least [] (two bytes),
and every emitted child element is a non-null object contributing at least {}
(two bytes). Thus floor(B/2) separately bounds successful parent occurrences and
total child elements. These are alternative bounds from the same byte scope,
not independent byte charges to add together.
When combining these byte-derived bounds with P×M, the expansion calculation MUST compute the mathematical minimum exactly without wrapping or rounding an intermediate product. A product larger than an already applicable finite bound need not be materialized and is not a reason by itself to reject the operation; no saturated product is emitted as a multiplication proof. This is a nest expansion-bound calculation, not a change to other registered arithmetic laws. Using a total child-row bound as a total-element bound still requires the parent correlation-key uniqueness specified above; unrelated parent keys do not suffice.
The Host capacity contract supplies max_nested_value_bytes and
max_nested_value_depth. Depth counts array/object containers in the child
payload and is checked from its recursive type before execution. Byte usage
is the sum of UTF-8 AoA canonical JSON encodings of the emitted array values,
including each repeated parent copy. The Plan binds byte ceilings both after
child aggregation and after parent attachment as aoa_output limit targets
(step_id, canonical sorted fields, measure: bytes). Attachment measures
all retained top-level object/array fields together, so sibling collections
share the ceiling; nested containers are counted within their enclosing value.
Core admission checks these outputs
before any consumer, even if a later step would discard them. This envelope
validates values; it performs no construction or acquisition.
Policy and completeness propagate through payload, membership, correlation,
ordering, and count dependencies. Partial child observations do not become a
complete parent history. [] proves absence only in the supplied Relation.
Results remain Relations; delivery only serializes their materialized values.
The typed empty constant lowers to REL.where(false) on the child input and
global REL.groupBy([], array_agg(object(...))). Attach this singleton and use
case only for missing groups. Host JSON postprocessing is forbidden; no
untyped empty author constructor is introduced. Hand
expansion need not have the same Program Definition CID as a nest Program.
Finite relation construction: values
values: {schema, rows} is an input-free generator; schema is a nonempty
closed author field-contract map and rows is a finite list of complete objects
with exactly those fields. An empty list has the declared schema. Each cell is
a CellValue as defined below and in Structural grammar; nested literal data
is opaque to binding resolution. Decimal cells use fixed-scale
wire strings decoded under the declared type. No row expressions, provider
access, implicit casts, or opaque-reference fabrication occur here. Constructor
admissibility follows Object and array construction.
Rows retain list order and multiplicity. An internal occurrence ordinal proves
order, not uniqueness of projected field values. Generated Relations have local
definition provenance, not external-observation completeness evidence.
At a cell root, an exact singleton {$param: name} is a parameter hole and
an exact singleton {literal: WireValue} quotes literal data. A malformed
parameter name in the singleton hole fails; schema compatibility or parameter
existence never chooses its interpretation. Every other wire value is literal.
The quoted payload is opaque, including nested parameter or literal shapes.
For example {literal: {$param: business_value}} stores the business object;
{literal: {literal: 1}} stores the object {literal: 1}. Both plain 1 and
{literal: 1} normalize to the same canonical cell {literal: 1}. Canonical
cells are exclusively {literal: WireValue} or {$param: name}; the printer
quotes every literal whose root collides with either reserved singleton shape.
This cell grammar admits no expression evaluation and changes no value-domain
or constructor eligibility rule.
Checking MUST validate the complete schema, bindings and every literal row;
resolved row, byte and recursive-value limits apply before publication. A
values, time_series, calendar_series, or mean_graphs generator requires
as, does not consume the current Carrier, and rejects a step-level from.
inputs: {} is legal when all dataflow roots are generators; top-level from
is then absent. Current-input selection follows Pipeline names and references.
For values, recursive-value byte usage MUST equal the sum of canonical typed-wire UTF-8
encodings of all cells whose declared root type is array, object or vector,
across all row occurrences. Nullable cells contribute the encoding of null
when null. Siblings and repeated rows contribute separately; containers inside
a selected cell are counted only within that cell's encoding. Row-object keys,
row wrappers, the outer rows array and scalar-typed cells are excluded from this
scope. The selected Host max_nested_value_bytes applies to this sum, separately
from the complete /rows wire limit. No selected cells means zero byte usage.
The selected Host max_nested_value_depth MUST apply to declared cell type depth:
scalar types have depth zero, vector has depth one, array has one plus its item
depth, and object has one plus its maximum field depth (zero for no fields).
Use the maximum across declared fields, without adding row or rows wrappers.
Check this type depth even for empty rows, empty arrays and nullable values;
those values do not remove the declared resource requirement. These scopes are
specific to values, not a redefinition of nest or Artifact limits.
Binding and typed-value admission precede measurement. Limit breaches fail
before any consumer receives the Relation; no row or cell may be truncated.
Row set operations: except and intersect
Both bodies are {with, duplicates}, where with names a Relation and
duplicates is required distinct|all. Fields are matched by exact name,
never position; the field sets and recursive value domains agree, with
nullability and extents joined under the common type rules. No implicit cast
or field inference is performed. Equality compares the complete typed row:
NULL equals NULL here, references use semantic reference identity, arrays are
ordered, objects compare exact field sets recursively, and opaque JSON compares
canonical JSON values. Decimal equality uses the common decimal equality law,
not wire spelling or a different precision-tag hash. Hashing is an implementation
of this equivalence, not an alternative definition.
For an equivalence class with left multiplicity m and right multiplicity n,
except all MUST emit max(m-n,0) left occurrences; intersect all emits min(m,n).
The ALL variants retain the earliest required number of left occurrences.
distinct emits the first left occurrence exactly when m>0 and respectively
n=0 or n>0. Surviving occurrences retain left input order. Absence here is
absence from the right captured Relation, not proof of absence outside its
observation scope. Output bounds never exceed the left bound; comparison,
hash-table and byte resources remain separately limited.
cross_join
The closed body is {with, select?, right_rows?, max_output_rows?}.
right_rows, when present, is exactly one: a checked assertion of one right
row, not a request to select one row. Both operands are Relations. Provenance,
projection and collisions follow ordinary join rules. Without the assertion,
an empty operand yields no rows; with it, zero or multiple right rows fail.
Output enumerates left occurrences then right occurrences in their logical
input order. The product of retained operand identities proves output identity.
The compiler MUST derive the product bound with checked arithmetic and combine it with Host ceilings and any positive author guard. A singleton assertion is discharged statically or enforced before any consumer uses it; a failed assertion publishes no rows. No input is silently truncated or null-extended. This operator introduces no acquisition or permission. In particular, a singleton aggregate can be broadcast without a fabricated equality field.
Row-preserving analytics: analytic
Check MUST retain repeated partition_by references and their authored order
while validating every key. Repetition adds no rows, output fields or key
evidence. An empty partition list denotes one global partition of existing rows;
empty input remains empty. Names in functions are checked against the input
snapshot, not earlier sibling outputs. Neither identity rewriting nor a new
publication order follows from these acceptance rules.
Navigation offset and numeric preceding/following boundaries MUST be a
literal or whole Parameter reference, not an arbitrary expression. Offset is
a nonnegative safe integer; preceding/following distance is positive. Invalid static
literals and structurally reversed boundary classes fail during checking;
unbound values and unresolved within-class boundary comparisons are validated
before execution. A Parameter cannot select frame kind, boundary direction or
function name. Selecting existing structured or reference values with navigation
or first_value/last_value does not construct new reference evidence; their
argument contracts are retained with nullable root output.
The body contains required partition_by, order_by, functions, and optional
frame. partition_by is a possibly empty list of equality-key fields;
order_by is a nonempty authored OrderKey list. functions is a nonempty map
of fresh output names to closed calls. Partition NULLs group together. Results
retain original input-row order, fields, multiplicity and candidate keys;
window ordering determines calculations, not a new publication order. Ranking
functions return non-null safe-integer numbers; navigation/value functions
retain their argument domain and bounds with nullable output. Frame aggregates
have their ordinary result contracts and empty-input behavior.
Calls are {fn: row_number|rank|dense_rank}, {fn: lag|lead, expr, offset?},
{fn: first_value|last_value, expr}, or a group aggregate call without
an aggregate-local order_by. Offsets are nonnegative safe integers, default 1;
out-of-partition navigation yields typed NULL and does not fabricate a row.
first_value and last_value respect NULL elements. Rank/navigation calls
reject filter and distinct. Aggregate calls retain their own filter/null/
distinct laws. Result types follow the function, including nullable navigation.
First_value and last_value are frame functions, not navigation functions;
they require frame and reject filter/distinct. Only lag/lead are navigation
calls. Aggregate frame functions retain their explicitly admitted modifiers.
frame is {start, end}. A boundary is unbounded_preceding, current_row,
unbounded_following, {preceding: n}, or {following: n}, with n a positive
safe integer. It is a ROWS frame; RANGE, GROUPS and exclusion clauses are not
admitted. Start cannot be unbounded_following, end cannot be
unbounded_preceding, and start cannot follow end in logical offset order.
Intersect the logical inclusive interval with the partition; do not clamp an
out-of-partition start to an existing row. Empty frames use aggregate empty-set
laws and yield NULL for first/last value. Frame is required for frame functions,
absent for calls consisting solely of ranking/navigation. It does not affect
lag, lead or ranking. A bounded partition may use unbounded frame boundaries.
Checking MUST separate authored peer order from physical position completion.
rank is one plus the number of preceding rows with different authored order
values; dense_rank is one plus the preceding distinct peer-group count.
row_number starts at 1 in completed order. Missing candidate-key fields or a
retained total witness complete position order without splitting peer groups.
Reference keys are eligible for internal completion, not authored value order.
Without an adequate position witness, position-sensitive functions fail check.
Position completion is journaled under Deterministic completion, recording the
authored peer order, the added identity terms or reused total witness, and the
supporting evidence. It does not change peer groups or publication order.
Each call's filter and argument MUST be evaluated once per demanded source row in its partition, then reused by every output frame that consumes it. Filter false/null excludes the argument from demand. A navigation or first/last call demands only referenced rows. A row used by several calls has separate call frames; no callback or hidden source retrieval is permitted. Representative value failure order is canonical partition-key order, source position, canonical output name, then child-expression order, independent of parallel completion. Binary64 frame sum/avg are numeric observation slots per output frame under Numeric aggregate observations; exact aggregates keep their own laws. Frame position selects members and binds the observation demand, not a bit-fixed floating-point summation order. Bounds include input and output bytes, partition storage, and total frame memberships; optimized prefix sums are legal only when they preserve specified values and failures.
Ordered and statistical aggregates
The aggregate vocabulary additionally contains string_agg, first, last,
percentile_disc, percentile_cont, var_pop, var_samp, stddev_pop, and
stddev_samp. They use existing {fn, expr, filter?} calls. First/last and
string_agg require an aggregate-local total order_by outside analytic frames;
different aggregates may use different orders. first and last exclude NULL
like other group aggregates; this differs intentionally from analytic
first_value/last_value. string_agg accepts strings, requires a literal string
delimiter, ignores NULL, returns NULL on no values, and admits distinct.
It has optional positive max_bytes, enforced on UTF-8 output without truncation.
Ordered aggregates evaluate their own filter before arguments and sorting.
For string_agg, the logical sequence is filter, demanded argument evaluation,
NULL exclusion, stable ordering by the aggregate's total order_by, distinct by
canonical value CID retaining the first occurrence in that order, then joining
with the delimiter. In analytic frames, the completed frame position order
supplies that order. Argument failures follow the shared aggregate evaluation law;
sorting or deduplication cannot remove a demanded argument failure.
Percentile calls require fraction in [0,1], a bound finite number; they reject
distinct and explicit order_by. Values sort ascending in their value domain.
percentile_disc accepts AuthorOrderable and returns item max(1,ceil(p*n)) in
1-based order. percentile_cont accepts number and for n>0 computes
h=(n-1)p, i=floor(h), f=h-i, then x[i] if f=0, otherwise
x[i]+f(x[i+1]-x[i]), with binary64 rounding after each arithmetic operation.
No rows returns typed NULL; no implicit decimal-to-number conversion occurs.
Variance and standard deviation MUST use number inputs, NULL exclusion, and canonical value-CID input order. First compute the binary64 left-fold sum and mean; then left-fold (x-mean)*(x-mean), rounding after each subtraction, multiplication and addition. Divide by n for population and n-1 for sample; insufficient n (0 or <2 respectively) returns NULL. Standard deviation uses correctly rounded binary64 square root. Non-finite intermediates fail, even if a different algebraic formula could avoid overflow. These definitions and percentile interpolation admit no reassociation or approximate sketch silently. Statistical/first/last calls reject distinct; all input, sort and output bounds remain enforced. Physical memory/CPU savings need not simulate discarded allocations, but do not erase logical membership or output limits.
The mean inside variance/stddev is part of that operation's own strict fold, not an observed avg slot and not required to equal a separately captured avg. These statistical operations retain the stated deterministic numerical law; the sum/avg observation boundary does not implicitly relax them.
Event operations (TEA)
actql-tea/0.1 is the authoring-complete deterministic event-time module. A
module claim covers fixed, sliding, and session assignment; explicit late
policy; stable event deduplication; bounded as-of correlation; bounded event
sequence matching; and relation projection. The module also covers finite time_series and interval_join;
calendar operations require the separate calendar claim. Fixed/sliding support alone is
partial TEA support and MUST NOT be reported as module conformance.
Processing-time substitution, unbounded state, implicit reordering, implicit gap fill, and interval relations beyond interval_join are outside 0.1. An implementation MAY support them only under a separately versioned profile.
Canonical fixed and sliding windows
- row_window:
kind: fixed
time: occurred_at
size: 1hRelation input uses row_window; event-stream input uses window_events.
There is no generic window spelling in ActQL 0.1. This keeps carrier choice
explicit without adding an alias or another authoring layer. kind is
REQUIRED and MUST be fixed or sliding. size is REQUIRED and accepts a
positive integer millisecond value or a duration string using ms, s, m,
h, or d. Sliding windows additionally require step.
Fixed windows MUST admit an explicit step, validate it under the same positive
integer millisecond constraints as a sliding step, and retain its authored and
identity representation. It does not participate in boundary, membership or
cardinality-bound calculations, which continue to use size and offset.
Omission does not insert a step. An explicit Parameter step retains its binding
and value-validation obligations even though it does not affect these calculations.
Both row_window and window_events MUST admit offset. It is a
signed safe-integer millisecond value, a positive duration token under the
common lexical rule, or a whole Parameter reference to a compatible number
or duration declaration. Numeric values and bound numeric Parameters must be
safe integers. Omission inserts numeric zero; explicit zero is valid.
Fixed-window starts are offset + n * size, and sliding-window starts are
offset + n * step, for integer n. A window contains t exactly when
start <= t < start + size; emitted memberships are in ascending start order.
Checked arithmetic must not saturate or wrap. offset is not reduced modulo
size or step for canonical identity. In particular, a supplied sliding offset
must not be ignored by a lowerer or evaluator.
Membership and the grid are computed at the exact value of the admitted time,
including fractional numbers and large timestamps. An intermediate grid value
need not fit binary64. A required output boundary that cannot be represented
exactly as binary64 fails with the window arithmetic error; rounding it to
change the width or membership is not permitted.
Window output MUST enumerate input occurrences in their existing logical order, then each occurrence's memberships in ascending window-start order, for both Relation and Events inputs. This is not a global sort by window start. Fixed windows retain one occurrence; sliding windows retain each matching membership. The operation does not establish input order when no such evidence exists. Removing boundary columns can lose window-role evidence without changing the retained occurrence sequence. Membership expansion alone does not prove ID uniqueness, completeness or successful discharge of resource obligations.
row_window preserves relation fields and adds window_start and
window_end. It lowers directly:
relation
-> REL.window.fixed or REL.window.sliding
-> relationwindow_events preserves the event-stream carrier, adds window_start_ms and
window_end_ms, and lowers to TEA.window.fixed_label or
TEA.window.sliding_assign. Event windows use closed-open intervals;
the boundary is a language invariant recorded in the Plan. Fixed windows
preserve the event-count upper bound. Sliding windows derive an event-membership
upper bound of input_events * ceil(size / step) using checked multiplication;
overflow fails planning rather than saturating. session has
a distinct shape in session and MUST NOT be inferred from either operation.
Shared event clock domain
Clock fields consumed by to_events, sequence, row_window, watermark_rows,
asof_join and interval_join MUST use one non-null domain: a typed UTC
timestamp or a finite exact safe-integer number of UTC milliseconds.
This applies to each side of a temporal join and to both Relation and Events
inputs. Timestamp conversion is internal, with the source column retained in
its original type and value. No rounding, string conversion or host-clock
sampling supplies a clock; bounds and timestamp range checks remain mandatory.
A watermark threshold is a scalar value with its separately declared numeric
constraints, not an alternative row clock source.
Event-stream-preserving operations and explicit bridges
When a later TEA operation must consume the carrier, authors use the explicit event-stream form:
- to_events: {id: event_id, time: occurred_at}
as: events
- window_events: {kind: sliding, size: 1h, step: 15m}
- watermark_events: {watermark_ms: 0, policy: mark}
- events: trueto_events and events lower to BRIDGE.rows.to_events and
BRIDGE.events.to_rows. window_events uses the same fixed closed-open
boundary and lowers to
TEA.window.fixed_label or TEA.window.sliding_assign and preserves
events. watermark_events requires a finite watermark_ms and an
explicit drop or mark policy, lowers to TEA.watermark.label, and also
preserves events.
The to_events body MUST contain exactly id and time field references on
its Relation input, with the time source having a non-null UTC timestamp
or exact integer-millisecond number contract. Other field types may remain unknown, and the retained payload field
set may be open. A timestamp source is converted to the internal numeric UTC-millisecond
clock without replacing its authored payload value or inserting a public
event_time_ms alias. The Event carrier retains the source role and its clock
encoding separately; clock-consuming operations validate values and perform the
required conversion before temporal arithmetic. An explicitly authored clock
field on an existing Event carrier uses the same clock domain, including a
retained timestamp payload field. The authored payload column is preserved.
watermark_events MUST have a closed body containing watermark_ms as a
finite numeric literal or whole declared numeric ParamRef and an explicit
policy, with the bound value validated against the same finite-number domain. Both policies add fresh non-null fields watermark_ms: number,
late: boolean and watermark_decision: string to every retained event.
Existing fields with any of these names are collisions; unknown field-set
completeness does not establish their absence. The event is late exactly when
its numeric millisecond time is less than the threshold; equality is not late.
mark retains every event, with decision mark for late events and keep
otherwise. drop removes late events and labels all survivors keep. Both
preserve retained events' semantic identity, time, payload and relative order;
they do not establish uniqueness or a new ordering proof. The threshold is a
finite numeric literal or whole declared Parameter, and may be negative or
fractional. No row expression, implicit NOW(), or clock recomputation on resume
is admitted; the Run retains the original admitted Parameter value.
to_events.id MUST have a complete, non-null KeyComparable value contract.
The semantic identity retains that contract and its values; it is not limited
to strings. Type comparability does not prove uniqueness: duplicate event
identities MUST be rejected separately before identity-dependent operations.
Session and sequence order equal-time events by the existing type-specific
scalar order, or by the existing internal identity order for reference values.
This does not admit references as authored OrderKey fields. Lowering MUST
preserve typed equality and ordering, and MUST reject unsupported identity
domains before claiming executable support rather than implicitly converting
identities to strings or replacing them with CIDs.
Every relation-form TEA time field MUST have complete typed evidence as either
a non-null timestamp or a non-null number containing UTC epoch
milliseconds. For a typed timestamp, the compiler inserts a reserved,
collision-checked REL.derive using date_diff(authored_time, 1970-01-01T00:00:00.000Z, ms) before the event bridge, records
the fixed UTC epoch-millisecond carrier law in the checked Plan, and preserves the authored
timestamp in public relation results. A numeric field passes through
unchanged. Unknown, nullable, date-only, string, or other time evidence is
rejected; an explicit not-null filter may refine nullable evidence, but the
compiler MUST NOT guess a parse, timezone, or null policy.
session
- session:
id: event_id
time: occurred_at
by: [customer_id]
gap: 30mby and positive gap are REQUIRED. Sessions use the closed-open
boundary recorded in the Plan. Relation input
also requires id and time; event-stream input already carries those roles.
Within each key, events are ordered by event time ascending then event identity
ascending. An event belongs to the preceding session only when its time is
strictly less than the preceding event time plus gap; an event exactly on the
gap boundary begins a new session. The result preserves the input Carrier and includes the key fields,
event fields, session_start_ms, session_end_ms, and a stable session_id
derived from the key tuple, session start, and first event identity.
session_end_ms is the last event time plus gap and is exclusive.
Events retain their original event identity and time; session labels do not
change event order or identity. Relation results retain the specified event
ordering. Later sequence/dedupe operations use session_id explicitly in their
partition keys to prevent crossing sessions; Carrier preservation alone does
not provide that guarantee.
The closed session body MUST contain by and gap, plus exactly id and
time on Relation input; Events input uses its intrinsic roles and does not
admit role overrides. by contains known KeyComparable fields, preserves
repeated references and admits an empty global partition. Empty input remains
empty. gap uses the same positive safe-integer millisecond, positive fixed
duration token or whole non-null number/duration Parameter forms as window
size; a Parameter retains validation obligations even if its default is known.
The three generated session fields are fresh and non-null: start/end have
number type and session_id has string type. Unrelated payload contracts may
remain unknown, but generated-field absence requires field-set evidence.
time_series
The generator body is {start, end, step}: start/end are bound non-null UTC
timestamp values, and step is a positive fixed duration. Start<=end is required.
It emits {time: timestamp} at start+i*step while time<end, in ascending time
order; start=end emits an empty typed Relation. Time is its candidate key.
No end sentinel or extrapolated row is emitted. Timestamp domain or safe
integer overflow fails before publication. Its row bound is the checked ceiling
of duration/step. No timezone or calendar arithmetic is implicit.
The closed time_series body MUST contain exactly start, end and step.
Each endpoint is a canonical UTC millisecond timestamp string or a whole
Parameter reference with a non-null timestamp declaration. Step uses the shared
positive safe-integer millisecond literal, fixed duration token or whole non-null
number/duration Parameter forms. Literal endpoint validity and reversed ranges
are checked statically; Parameter defaults are not substituted during declaration
checking, and bound ranges and positive steps are validated before generation.
A generated time grid MUST NOT attest observed absence. Joining it to counts and replacing missing counts with zero is explicit computation; describing a zero as confirmed absence additionally requires complete source coverage of that interval. Neither an empty grid nor a synthetic row replaces failed acquisition. Grid, partition cross-product and final output have separate resolved bounds.
interval_join
Full is deliberately not admitted here: 0.1 temporal correlation covers left-driven matching and absence within captured scope, not symmetric temporal reconciliation. A full variant is possible, but would additionally need a closed unmatched-right ordering, identity transfer and coverage contract. This restriction is not a claim that full temporal joins are inherently unsafe.
The closed body is {with, kind, mode, on, left, right, select?, max_output_rows?}.
kind is inner|left|semi|anti; mode is point_in_interval|overlap; on is
a possibly empty equality-key-pair list. left is {time} for point mode or
{start,end} for overlap; right is {start,end}. These names refer to fields
of two Relations, not dataflow sources. Each temporal field has the same
non-null timestamp domain or all use safe-integer UTC milliseconds; explicit
casts precede mixed-domain joins. Every interval requires start<end, even when
its equality key matches no row. Null equality keys do not match.
Each on item MUST be exactly {left: Field, right: Field}; the list preserves
authored pair order and repeated pairs without multiplying matches. Empty on
applies temporal matching across the two captured Relations without equality
partitioning. Ordinary join's single-name or standalone-pair shorthand is not
part of this body's list grammar. Step from selects the left Relation;
body left specifies its temporal fields and does not replace that selection.
Point mode matches r.start<=l.time<r.end. Overlap matches l.start<r.end and r.start<l.end. Endpoints are closed-open, so touching intervals do not overlap. All matching pairs are retained; kind, null extension, projection, provenance and left-major/right-minor order follow ordinary join. Semi/anti emit a left occurrence at most once. Input endpoint validation is atomic before matching. Candidate-key transfer follows retained pair identity.
Interval-join candidate-key transfer MUST use the following closed rules, subject to retaining a complete mapped key in the output. Semi and anti retain left keys only. Inner and left retain every available pair of left and right keys; left null-extends the right components without promoting them to non-null. Inner and left additionally retain a left key when the equality correlation fields cover an entire right candidate key; inner additionally retains a right key when they cover an entire left candidate key. Temporal overlap alone does not establish this at-most-one condition. These independent-side conclusions do not suppress retained pair witnesses for interval joins. Projection must retain at least one exact alias per required key component; a missing component invalidates that witness. Input-key and endpoint validation remain prerequisites. Pair uniqueness with nullable components does not establish eligibility for a non-null primary key or canonical-order completion. Full is not an admitted interval-join kind and has no transfer rule here.
An interval join MUST resolve output and search-work ceilings separately, using the checked operand product bound where no stronger proof exists. Output overflow fails, never truncates. Membership or nonmembership refers to the captured right Relation; an external absence assertion requires sufficient per-key and interval coverage. This operation is local computation, not a complete external-join acquisition path or permission to run arbitrary theta joins.
The search ceiling MUST measure ordered left/right row-occurrence pair visits, including visits that fail the equality or temporal predicate and repeated visits to the same pair. The product fallback applies only to a realization that examines each pair at most once; semi/anti output bounds and output guards do not reduce it. Missing or overflowing operand products leave this fallback unavailable, rather than prohibiting another admitted finite bound. A known zero operand bound yields zero search visits, not exemption from whole-input validation. Endpoint admission, index construction, ordering and memory retain their separate physical budgets. This fixes the search quantity; it does not assert that a particular backend satisfies the fallback's conditions.
../../spec/contracts/actql-interval-search.json# sha256:4a41fc2f6967ceecd95fcd2ae1f5accc722af3090a6db6a1405d8091a8bae3c3A persistent product-fallback limit MUST use the closed
actql-interval-search-limit/0.1 body below. Its CID hashes the whole canonical
body without a self CID; its subject is the owning interval operation's
unit:<Unit Definition CID>#/steps/<authored ordinal>. The separate left/right
arrays contain sorted unique applicable Program row-witness CIDs, after
producer subjects and all dependent identities have been rebuilt. Admission
checks those proofs, their exact operand correspondence and effective binding,
and recomputes the product of the two minima; one proved zero permits an empty
opposite set. Equal numeric bounds do not substitute for these dependencies.
search_contract_cid identifies the pinned search contract, not an arbitrary
same-shape schema. Internal handles, observed counts and backend proposals do
not enter this preimage. Shape and digest alone establish neither applicability
nor execution; the single-visit condition and atomic enforcement remain required.
../../spec/contracts/actql-interval-search.json#/schemas/limit sha256:88d201eeb401828d32ed3a07172c6d406f48dae726071d01ac7de3a0ed281dedCalendar profile
actql-calendar/0.1 adds calendar_bucket and calendar_series; it does not
change fixed-duration arithmetic or the text-time UTC functions. Calendar
calculations use the proleptic Gregorian calendar in years 0001..9999.
The closed shared parameters are unit: day|week|month|quarter|year, timezone,
and, for week only, required week_start: monday|sunday. Timezone is UTC or
an exact IANA name in the profile's pinned TZDB artifact; Host locale/timezone
and ambient OS databases are irrelevant. No optional gap/fold interpretation
switch is admitted. Unknown zones or out-of-domain boundaries fail.
unit, timezone, and week_start MUST be literal strings, not expressions or
Parameters. week_start is required exactly for unit: week and forbidden for
other units, including explicit null. Series start and end are canonical UTC
timestamp literals or whole non-null timestamp Parameters; bound values retain
calendar-year and interval validation obligations. Literal endpoints use years
0001..9999, reject reversed ranges, and admit equal endpoints as an empty range.
Bucket time is a field reference and into an ordinary authored output name.
Source-option admission alone is not recognition of a timezone: a checked
Calendar operation requires membership in the selected fixed semantic artifact.
calendar_bucket: {time, unit, timezone, week_start?, into} is row-preserving.
time names a non-null timestamp field; into is a fresh object field with
non-null timestamp start and end. Local calendar boundaries start at
midnight, day 1 for month, Jan/Apr/Jul/Oct 1 for quarter, Jan 1 for year.
Week boundaries use the selected weekday. An ambiguous boundary uses its
earliest UTC occurrence; a nonexistent boundary advances to the first valid
instant at or after that local boundary. Coincident UTC boundaries collapse;
a skipped civil day produces no zero-duration bucket. Bucket is the unique
adjacent-boundary interval containing time; boundaries are closed-open.
calendar_series: {start,end,unit,timezone,week_start?} generates the distinct
full {start,end} bucket intervals intersecting the nonempty UTC range
[start,end), in ascending UTC start order; start=end emits none, start>end
fails. It does not clip buckets to the requested range. Bucket start is the
candidate key. The planner derives a conservative finite calendar-count bound
from the admitted year range and input interval, then enforces actual row/byte
ceilings. TZDB transition tables are checked local semantic artifacts, not
implicit provider acquisition.
Calendar Plans MUST pin the TZDB version, canonical transition-table bytes and conversion-law identity in Check Environment and semantic dependencies. Missing artifacts prevent a runnable Plan; data cannot select a different zone or database during execution. DST buckets need not be 24 hours. Generated calendar rows carry no external absence evidence. Independent vectors cover leap years, quarter/year boundaries, both weekly origins, folds, gaps, skipped days, coincident boundaries and version-sensitive historical transitions.
The selected transition artifact MUST be exact canonical JSON with closed shape
{version: "actql-tzdb-table/0.1", tzdb_version, conversion_law: "actql-calendar-boundaries/0.1", coverage_start_ms: -62135596800000, coverage_end_ms: 253402300800000, zones}. Coverage is UTC year 0001 inclusive
through year 10000 exclusive; lookup outside it fails without extrapolation.
tzdb_version is the nonempty external release identifier selected by Check
Environment, not an ActQL-owned version. zones maps exact nonempty names to
closed {initial_offset_seconds, transitions} objects; each transition is closed
{at_ms, offset_seconds}. Times are exact safe-integer UTC milliseconds,
strictly increasing and interior to coverage. Offsets are signed 32-bit integer
seconds excluding -2147483648. Initial offset holds at coverage start; each
transition changes the offset at its instant. Repeated offsets are omitted from
this offset-only representation, regardless of abbreviation or DST-label changes.
The map includes UTC with zero initial offset and no transitions. Other selected
names, including aliases, retain exact spellings and complete offset tables.
Artifact admission MUST compare the original bytes' SHA-256 CID and external release identifier against the independently selected Check Environment pins, reject noncanonical bytes without repair, and validate table structure within explicit byte, node, depth, zone and cumulative transition ceilings before exposing a profile. The conversion-law domain identifies the Calendar boundary rules above, including earliest fold resolution, gap advancement and coincident boundary collapse; it is not a Host-selectable interpretation. A matching CID proves selected-byte integrity, not independent IANA authenticity. Establishing release provenance belongs to selection of the trusted semantic artifact. Synthetic tables test admission but do not establish historical TZDB or Calendar conformance. Exact-name lookup has no locale, case-folding, alias inference or ambient database fallback.
dedupe_events
- dedupe_events:
by: [event_id]
order_by:
- {occurred_at: asc}
- {source_sequence: asc}The shape matches dedupe_rows. by and a stable non-empty
order_by are REQUIRED. It keeps the first event in that order and
fails an unresolved top tie. It preserves an
event stream and MUST fail when the authored order cannot choose one record.
Arrival order MUST NOT serve as an implicit tie-break.
The closed body MUST contain only by and order_by, using the same partition
and OrderKey rules as dedupe_rows: empty by selects one global partition,
repeated partition references remain in authored order, and order_by is
nonempty. An empty input yields no event, including for the global partition.
Partition fields require known KeyComparable contracts; order fields require
known AuthorOrderable contracts. Unrelated payload contracts and field-set
completeness may remain unknown. The operation retains the chosen events'
identity, clock encoding, payload, and window boundary values, but a repeated
key does not prove uniqueness or resolve a top tie. Existing duplicate event
IDs are not repaired by business-key deduplication.
asof_join
- asof_join:
with: account_history
kind: left
by:
- {left: account_id, right: account_id}
left_id: event_id
left_time: occurred_at
right_time: valid_from
direction: backward
tolerance: 90d
select:
event_id: left.event_id
owner_id: right.owner_idThe body MUST contain exactly with, kind, by, left_time, right_time,
direction, tolerance, select, and optionally left_id and
right_tie_break. by is a possibly empty array of exactly
{left: Field, right: Field} pairs. Empty by correlates across the entire
captured right Relation; repeated pairs retain authored order but neither
multiply candidates nor prove uniqueness. Each pair has compatible
KeyComparable types. select is a nonempty ordinary join alias map; repeated
source fields under different output aliases are allowed. null_key is not
an authored option. Explicit null is not omission for any optional member.
Both time fields MUST have complete non-null number or timestamp contracts.
Number values are safe-integer UTC milliseconds; timestamp values use canonical
UTC millisecond precision and are mechanically converted to that clock for
matching without changing their authored values or contracts. The two fields
may use different representations of this same clock. tolerance uses
MillisecondValue: a positive safe-integer millisecond number, a positive fixed
duration token, or a whole non-null number/duration Parameter. Parameter binding
does not bypass the positive safe-integer constraint. All time values and checked
distance arithmetic are validated before output is exposed, including rows with
no key match. The explicit left_time selects the comparison clock on either
Relation or Events input; it does not rewrite an Events carrier's intrinsic role.
Left identity MUST be complete, non-null and KeyComparable, with uniqueness
validated independently of its type. Relation left_id selects a single field;
when omitted, retained candidate-key evidence supplies the entire ordered key
tuple, never just its first field. Without that evidence omission fails during
Check. Events use their retained semantic identity; an explicit left_id, if
present, names that same role rather than replacing it. Internal ascending
identity order is the existing type-specific order, including internal Ref
identity order; this does not make Ref fields authored order keys. No implicit
stringification or hidden row CID replaces semantic identity.
kind: left|inner, direction: backward|forward|nearest, and positive
tolerance are REQUIRED. Relation input additionally requires left_id unless
a retained candidate key supplies it. Null join keys mean no match; this
is a TEA 0.1 law rather than an author field. An unresolved time tie
fails. The chosen right row must share the complete key
and be within tolerance. Backward chooses the greatest right time not after
the left time; forward chooses the least right time not before it; nearest
chooses minimum absolute distance. Equal candidates fail unless a complete
right candidate key supplies a compiler-journaled stable tie-break. Provenance
and select follow ordinary join rules. left preserves unmatched left rows
with null right fields; inner drops them. Results use stable ascending left
(time, identity) order.
right_tie_break is an ordered list of field names in ascending order.
Descending business priority requires an explicit preceding derive that
produces an ascending surrogate; a compiler MUST NOT infer direction from a
field name or input order.
Each authored right_tie_break field MUST be AuthorOrderable; ascending null
placement is last. An empty list supplies no authored tie evidence. Repeated
fields and their order are retained, without strengthening uniqueness. A valid
complete right candidate key can complete either an omitted or an insufficient
authored tie-break, with that completion recorded in the compiler journal.
Absent such evidence, equal first-place candidates fail rather than selecting
input order or a hidden identifier. Merely naming fields is not proof that their
tuple is unique. A nearest tie can involve different right timestamps at equal
distance, not only two rows at the same timestamp.
The output has at most one row per left occurrence and therefore inherits the checked left row-count upper bound. Search-work and allocation ceilings remain separate requirements; a small output bound does not authorize an unbounded right scan. Empty input and no-match results refer only to the captured operands, not to unobserved external coverage. None of these static contracts authorizes execution before the remaining value, identity, tie and resource obligations are discharged.
As-of search MUST select a finite ceiling independently of the output-row bound.
One pair_visits unit examines an ordered captured left/right occurrence pair
for correlation, direction or tolerance eligibility, including rejected pairs.
A realization examining each pair at most once MAY use the exact product of
applicable operand row upper bounds; re-examinations count again and require
another admitted bound. This fallback applies to both kinds and all directions.
A proved zero operand permits zero visits even if the other bound is unknown,
but does not waive either input's mandatory validation. Supplied bounds must be
validated; missing or overflowing products leave the fallback unavailable rather
than imposing a language prohibition. Equality with the chosen ceiling succeeds;
exceeding it fails atomically. Candidate comparison, tie resolution, sorting,
indexing, bytes and retained memory require separate physical budgets. A small
output or an in-tolerance candidate does not discharge these requirements.
../../spec/contracts/actql-asof-search.json# sha256:f6638f0527a537a66cb1bfee82c1d01ae9fb3756356fd6360b514c092f56d3f4../../spec/contracts/actql-asof-search.json#/schemas/limit sha256:22b4ebc943770dde101f2f1e6cf70d2627dbc5f70eadbb7f49fc59e8f07651ecA persistent as-of search limit MUST use the complete canonical body without a
self CID, with subject unit:<Unit Definition CID>#/steps/<source ordinal>.
Its separate left and right arrays MUST retain the sorted unique CIDs of all
applicable reidentified Program row witnesses. Rebuild producer subjects and
dependent proof/witness CIDs before taking these references. Internal claim
indices, observations and output limits MUST NOT replace them. The admitted
search contract CID, exact operand correspondence, effective binding and product
arithmetic require semantic validation beyond the closed schema. A proved zero
permits an empty opposite set; otherwise both sets are required. This identity
does not attest completed single-visit enforcement or authorize execution.
sequence
- sequence:
by: [customer_id]
time: occurred_at
id: event_id
pattern:
- {as: opened, where: {eq: [{field: event_type}, {literal: opened}]}}
- {as: purchased, where: {eq: [{field: event_type}, {literal: purchased}]}}
within: 30m
overlap: disallow
contiguity: subsequenceThe checked canonical body MUST contain by, id, time, pattern, within,
overlap and contiguity, completing omitted authored by, overlap and
contiguity to [], disallow and adjacent respectively.
The authored body requires id, time, pattern and within; the two limit
guards remain optional. The schema admits the authored shorthand, but a checked
canonical body records every completed key explicitly and remains closed.
by is a possibly empty list of KeyComparable fields: empty denotes one global
partition. Repeated grouping fields retain their source occurrences but do not
duplicate output columns or strengthen uniqueness. Each pattern element is
exactly {as: label, where: predicate}; the predicate has Boolean result type,
possibly nullable, and only true matches. It sees the original input fields and
Parameters, not fields captured by earlier pattern labels. Generated column names
obey the ordinary output-field name rules in addition to collision checks.
id is non-null and KeyComparable; on Events it names the retained semantic
identity, not a replacement identity. within uses MillisecondValue, and the
optional guards use PositiveIntegerValue; supplied Parameters retain binding
and value-validation obligations. Omitted guards insert no fixed language
default and still require resolved execution limits from current Host policy.
The event time, event identity, at least two pattern elements and positive
within are REQUIRED; omitted grouping and matching modes use the fixed
normalization above, with vocabulary allow|disallow and adjacent|subsequence. adjacent permits no
non-matching event between pattern elements; subsequence may skip them but
all selected events remain ordered and within the bound. Each pattern
predicate uses the closed RelA predicate grammar. Pattern as labels MUST be
non-empty and unique. Selected event indices are strictly increasing
(i1 < i2 < ... < in), so one event record cannot satisfy two positions.
The inclusive temporal bound is
last.event_time_ms - first.event_time_ms <= within. Matching is in ascending
(time,id) order. disallow chooses the lexicographically earliest complete
match, emits it, and advances the per-key cursor to the event immediately
after that match's final selected event; skipped events before that final event
are consumed with the interval. allow emits every complete bounded match.
Output is a stable relation containing the key, match start/end, and the
authored label's {label}_id and {label}_time_ms for each pattern element.
It does not copy arbitrary event payload fields into the match relation. No
unbounded automaton or processing-time interpretation is permitted.
The match boundary fields MUST be exactly match_start_ms and match_end_ms,
both non-null numbers in UTC milliseconds. Each {label}_id retains the
complete non-null semantic event-identity contract, and each
{label}_time_ms is a non-null numeric millisecond value. Group fields retain
their contracts and appear once for each distinct authored grouping field.
All generated names must be distinct from one another and the retained group
fields. Collisions fail check, not overwrite or implicit renaming. Empty
results retain this complete contract.
max_matches_per_key and max_output_rows are author guards as defined in
Author guards, resolved limits, and Plan facts. They count matches for one key and total output matches respectively.
Both limits use current Host policy and any authored guards; this specification
does not define a static sequence match-count proof. A breach of either limit
MUST fail the whole operation without publishing partial matches.
The authored sequence clock MUST satisfy this closed carrier contract: a Relation or Events input accepts a typed non-null UTC timestamp or exact integer UTC-millisecond field, converting timestamp values only for internal temporal arithmetic while retaining the authored column and its type; every other type is rejected rather than coerced, rounded or parsed from a string.
For overlap: disallow, cursor advancement and unique event-carrier identity
prove each emitted {label}_id to be a candidate key. For overlap: allow, no
individual label ID is inferred as a candidate key because an event may appear
in more than one match.
Event carrier record identity is the unique to_events.id value. Duplicate
record identity is invalid before session, dedupe, as-of, or sequence
semantics. Business duplicate keys belong in dedupe_events.by; they are not
carrier identity. Session-generated session_start_ms, session_end_ms, and
session_id MUST NOT collide with source fields. The complete event schema is
preserved alongside those generated fields.
Graph operations (SGA)
actql-sga/0.1 is the authoring-complete deterministic graph-value module. A
module claim covers graph construction, node/edge property matching, typed and
directed bounded traversal, stable node/path projection, shortest path,
connected components, degree/bounds, and pure structural graph changes.
Construction plus simple reachability alone is partial SGA support.
Unbounded traversal and a general graph-pattern language are outside 0.1. The
closed traverse shape below is sufficient for bounded repeated-edge and
authored hop-by-hop patterns without importing Cypher or arbitrary predicates.
Master-data graph boundary
The SGA module does not define canonical-entity or persistent-MDM semantics. A graph node identifier is only an authored semantic identity. It is not proof of any of the following distinct concepts:
- canonical entity identity;
- source-record binding;
- entity type;
- merge or split lineage; or
- entity-resolution evidence.
An implementation MUST NOT infer those concepts from an ordinary node, edge,
label, property, or vector score. It MUST reject authoring terms such as
canonical_entity, bind_source_record, merge_entity, or split_entity as
unsupported unless a future separately versioned MDM module is explicitly
claimed.
The 0.1 SGA module is a computation carrier rather than an authority-bearing
persistent master graph. ActQL 0.1 does not define persistent graph source
versions, graph-delta proposals, compare-and-swap base versions, merge/split
preview, approval, apply, or graph-mutation receipts. Ordinary
graft_graph, union_graphs, and subtract_graphs are pure graph-value
computation and MUST NOT be presented as persistent entity mutation or
authorization. apply_graph_delta is pure computation over captured graph
values, not persistent MDM identity change, provider mutation, approval or
application to an external graph store.
MDM completeness remains a separate future profile. Property match and typed traversal/path query are part of SGA 0.1 and do not imply MDM authority.
graph: keyed Graph construction
actql-graph-constructor/0.1 selects the sole
actql-graph-contract/0.1 representation. Unversioned constructor bodies and
previous stored contracts are rejected without conversion. Historical evidence
retains its original bytes and identities. The Graph profile requires explicit
installed profile selection; claiming the SGA module alone does not enable it.
The closed constructor body has version, nodes and edges. Each ordered
node definition has name, from, a nonempty ordered key column list and
optional labels. Each edge definition additionally has source and target,
each with a node definition, ordered source key columns and optional
references_key naming that node's declared key columns in the same order.
Names resolve through common checking. A constructor requires step as and
has no implicit current operand or outer step from. Repeated producer
references remain separate input occurrences; an empty definition family has
zero occurrences, not an invented operand.
A label entry has a static name and optional closed properties selector:
{mode: "columns", columns: [{from, to}, ...]}, {mode: "all_columns"},
{mode: "all_columns_except", except: [...]}, or {mode: "no_properties"}.
Omitted labels use the definition name; an omitted selector selects all
columns, including key columns. An explicit empty label list has no labels.
Property aliases remain property mappings, never semantic label roles.
Conflicting aliases, unknown columns and incomplete types reject admission.
Property value types are compatible across all definitions under a case-insensitive property name, even when labels differ or one definition is a node and the other an edge. Nullability remains declaration-local rather than making otherwise compatible value types conflict. Independently, a shared case-insensitive label must expose a compatible complete property set across both node and edge definitions. Construction and external snapshot admission enforce both obligations; neither label separation nor absent rows can bypass them.
Identity is the admitted constructor-definition domain, element definition name, ordered semantic key types and complete non-null key tuple. Edge keys are independent of endpoints: different keys retain parallel edges and self-loops. NULL element keys exclude that element. Duplicate non-null keys reject even identical payloads, including duplicate edge keys whose endpoints would later be excluded. NULL or missing endpoints exclude an edge without inventing nodes. These are different rules from legacy equal-payload collapse.
Domain issuance projects resolved constructor source references to ordered input occurrences and retains the unbound producer definitions. Outer WITH binding names and source locations do not enter identity. Neither bound Artifact CIDs, parameter values, Run/Invocation CIDs nor Graph content enters the domain. Editing inline VALUES or semantic mapping defines a different domain; cross-domain comparison has no implicit compatibility mapping. Within one admitted domain and typed key, changed properties, labels or endpoints change snapshot content, not element identity. SourceView inputs retain the original Stage declaration and the selected Catalog Carrier owner. Their domain projection includes the unbound Source declaration, the canonical static Carrier schema body CID and the selected SourceView member body CID; it does not include the enclosing Program ID, WITH binding name or Environment bundle CID. The common checker requires the original Stage's steps and registered input, and SourceView attachment requires the exact selected Catalog owner. This retention is not acquisition or execution permission: the existing request, policy and runtime admission still apply. A bare external Carrier/name is not producer-definition provenance and is rejected rather than inventing a domain.
Definition-scoped match_nodes and match_edges select a required
definition, optional static label, existing predicate/projection fields and
optional explicit bindings (identity, plus edge source/target). Binding
names are ordinary caller-selected fields; collisions reject. Bounded keyed
traverse requires explicit path_mode (walk, trail or acyclic) and
emit: "paths" or emit: "nodes". TRAIL tracks independent edge identities, not endpoint tuples.
Unimplemented SGA consumers reject this profile; keyed construction and this
vertical path are not a claim of complete SGA support.
Construction and reload reuse the existing bounded Graph storage, checking,
stop and cleanup owners. Selected construction row/node/edge ceilings apply
before exposing a complete Graph; failure or cancellation exposes no partial
Graph. actql-graph-snapshot/0.1 separately records the admitted
contract, full typed identities, properties, labels and endpoints. Reload
revalidates this correspondence and exact content identity, without creating a
new domain or implicitly interpreting legacy snapshot bytes.
Graph construction MUST select all three construction ceilings from the admitted
Host capacity policy: max_graph_construction_input_rows,
max_graph_construction_nodes, and max_graph_construction_edges.
They are positive safe integers; absence does not select a default or waive a
construction check. Input rows sum the ordered node-definition and
edge-definition input occurrences before NULL-key or missing-endpoint
exclusion. Repeated producer references count once per occurrence.
Output node and edge scopes count the independently keyed elements in the
successfully constructed Graph. Input-Graph preflight ceilings and query-row
ceilings are separate scopes.
Construction MUST enforce these ceilings, value admission, work and retention budgets using the existing resource owner. Failure or cancellation MUST expose no partial Graph. Pending physical cleanup retains its reservation until the same owner confirms release. Neither a checked contract nor a content CID is execution, storage or cleanup authority.
node_rows and edge_rows
These operations convert the admitted Graph to the existing Relation
using the corresponding registered bridge. Bare to_rows is not accepted for a graph because node rows and edge
rows are distinct meanings.
{"from":"g","node_rows":{"definition":"Person"},"as":"people"}
{"from":"g","node_rows":{"label":"Person"},"as":"people"}The body MUST be either true (all definitions of the corresponding element
kind), a closed object containing only definition (one definition of that
kind), or a closed object containing only label. The forms are exclusive;
mixed selectors, missing selectors and additional fields MUST reject at check
time. Name lookup MUST use the admitted Graph name identity and reject
unknown names or a name belonging only to the wrong element kind, rather than
return an empty Relation. Output schema and presence MUST
derive from the selected declarations even for zero rows; nullable remains
independent of presence. Selection is part of the Program and emitted Plan
meaning, not a runtime inference from data. Selected rows retain their original
typed identities, domains, key components and node endpoint identities.
A label selector MUST select all definitions of the corresponding element kind whose admitted declarations contain that label. Candidate membership MUST NOT depend on observed rows, non-NULL values or empty results. The finite output schema MUST use all candidate declarations, including their retained key aliases and properties not associated with the selected label. A column is Required only when it exists in every candidate and each candidate guarantees its presence; otherwise it is Conditional. Compatible value types use the existing common type composition and nullable remains independent of presence; incompatible types MUST reject. The resolved label MUST be retained in the emitted Plan parameters, with deterministic declared spelling under the existing Graph name rules. Label selection filters canonical Graph row enumeration without weakening input admission, traversal work, output ceilings or cleanup obligations.
Graph row projection MUST retain the admitted identity types and property
evidence. Node rows expose required id, labels; edge rows expose required
id, src, dst, labels. Identity roles retain domain, definition and typed
key; endpoint tuples and strings MUST NOT replace them. labels is a non-null
array of non-null strings, with [] for no labels, using the admitted Graph's
existing label identity, duplicate rejection and deterministic canonical order.
There is no nullable rel role or compatibility output.
Declared properties retain their saved output names. A retained key-column name
exposes its original typed key component, not an alias of id. Composite keys
retain every column-to-position/type correspondence. The schema is the finite
union of these declared columns across the selected node or edge definitions.
A column is Required only if every such definition guarantees it; otherwise it is
Conditional, independently of nullable. Missing cells MUST remain omitted, not
NULL. Save/reload MUST retain names, types, presence and component correspondence.
Projection MUST reject collisions between output roles, retained key aliases and declared properties using the existing Graph case-insensitive name identity. It MUST NOT overwrite, rename or duplicate a colliding column. Compatible names across distinct definitions share a column, with deterministic declared spelling and joined nullability; incompatible value types reject. Unknown Graph contracts MUST reject rather than invent columns or string/json identity types. Ordinary references to Conditional columns MUST reject at check time until a static presence proof is supported; sample values, empty data and IS NOT NULL are not such proof. Initial public Results require projection to Required columns. The checked Bridge MUST complete bounded enumeration in the existing canonical Graph identity order before exposing its Relation. That retained producer order can pass through guaranteed-column projection to Result publication; a standalone output schema does not prove order or a global candidate key. Successful data admission, exact input ownership and bounded execution remain separate obligations.
Graph row projection MUST enforce the selected Host
max_relation_output_rows on its produced Relation, accepting counts equal to
the ceiling and rejecting excess before exposing the operation output. This
selects the Relation output scope, not max_graph_output_rows. Graph input
nodes/edges admission and their selected preflight limits remain separate.
An unknown static count, or an upper bound above the ceiling, is not evidence
that the actual output exceeds it; enforcement remains an execution obligation.
This ceiling supplies neither an observed count nor order, completeness or a
bound on value bytes, retained copies or backend memory. Other applicable
value and physical resource contracts remain in force.
degree
- from: customer_graph
degree:
direction: outdirection is REQUIRED and MUST be in, out, or both; its output columns
are named node_id and degree.
The output Relation MUST contain exactly node_id and degree, retaining the
Graph's non-null node identity contract and a candidate key on node_id.
There is no compatibility node column. Join node_id back to a source
Relation explicitly when source metadata or another public field name is needed.
reach
- from: customer_graph
reach:
start: customer_42
direction: out
max_depth: 2start, explicit direction: in|out|both, and positive integer max_depth
are REQUIRED. The operation lowers to
SGA.reachable. When the graph was authored with a semantic node ID other than
the AoA carrier name id, the compiler MUST restore that semantic name in the
relation result. It MUST NOT require the author to route through carrier-only
identity spellings.
reach is the concise form of traverse with a start selection, no property
filter on traversed edges or nodes, homogeneous edges, min_hops: 0,
emit: nodes, and the authored
direction and max_depth. The compiler journals this expansion. There is no
outgoing-direction default.
The closed reach body admits only start, direction and max_depth.
For compatibility, start can be one explicit value admitted under the
semantic node-ID contract; authored JSON cannot issue an opaque Ref. It can
also use the common Graph node selector: nonempty ids, a named Relation
with an id field, or a node where predicate. A selector is not an implicit
Source acquisition. The common selector's type, membership, deduplication,
empty-selection and predicate rules apply before Reach expansion. A nullable
Relation field is allowed when its non-null values match the semantic ID
contract, but a selected NULL is an invalid identity. max_depth is a positive finite
integer-valued number, not a resolved Host ceiling. The result follows the
node-emitting traversal shape: start_id and node_id retain the semantic ID
type, and depth is a non-null number holding the minimum hop count for each
start/end pair. Every selected start node is included at depth zero; output is
ordered by start_id, node_id, then depth using the common node-emission
order. A declared semantic node name other than
node_id is an additional alias of the reached node_id, not a renamed
start_id; collisions with distinct fixed output columns MUST fail during
check. No other node properties are implicitly projected. This output-shape
clarification does not create uniqueness, order or resource proofs at check.
Property match
- from: customer_graph
match_nodes:
definition: customers
bindings: {identity: node_id}
where:
and:
- {eq: [{field: entity_type}, {literal: company}]}
- {in: [{field: country}, {literal: [JP, US]}]}
fields: [node_id, name, domain]- from: customer_graph
match_edges:
definition: ownerships
bindings: {identity: edge_id, source: source_id, target: target_id}
types: [owns, subsidiary_of]
where: {eq: [{field: active}, {literal: true}]}
fields: [edge_id, source_id, target_id, edge_type]Both operations use the closed RelA predicate grammar over statically known
node or edge properties. fields is REQUIRED; the compiler MUST NOT expose
undeclared carrier fields. Node results are ordered by semantic node identity.
Edge results are ordered by semantic source identity, target identity, edge
type, then edge identity. A graph lacking a requested property or identity
MUST fail during check.
match_nodes admits only definition, label, bindings, ids, where and
fields, with required definition and exactly one of ids and where.
ids is a nonempty array of explicit values admitted under the
semantic node-ID contract, without string conversion or implicit Parameter
substitution. Authored JSON alone cannot establish an opaque Ref as specified
in Opaque reference values. Repeated IDs remain in the
authored representation; membership selection deduplicates them and requires
every selected identity to exist before publication. match_edges admits only
definition, label, bindings, types, where and fields, with required
definition; both filters are optional and, when present, are conjoined.
types contains nonempty normalized strings. An empty type list
selects no edges, while absence does not filter types. Repeated types do not
repeat output rows and remain in the authored representation.
The required definition names one admitted node or edge definition. Optional
label selects a static label of that definition. Optional bindings is closed:
node matches admit only identity; edge matches admit identity, source and
target. Each binding names a caller-selected output field; name collisions
reject rather than supplying implicit identity aliases.
Both fields lists MUST be nonempty and free of repeated references. Their
order is the output projection order. Predicates admit nullable boolean and
retain only rows for which the result is true. Only referenced fields require
static type evidence; unrelated unknown property types do not invalidate a
match. Visible identity and label names come from declared semantic roles, not
implicit carrier aliases. Matching does not create a missing edge identity.
Both match operations MUST inclusively enforce the admitted Host policy's
max_relation_output_rows on their projected output before publication, with
atomic failure on excess. Repeated selectors do not increase output multiplicity.
Empty selection uses zero rows. This Relation-output scope does not reapply
external Graph-input ceilings to internal producers or replace their admission
and capacity prerequisites. Predicate and ordering work, temporary bytes and
retained state remain subject to independently selected implementation limits;
a small result does not establish a bound on that work or storage.
traverse
Homogeneous bounded traversal uses:
- from: customer_graph
traverse:
start: {from: start_nodes, id: node_id}
path_mode: trail
edge_types: [owns]
direction: out
min_hops: 1
max_hops: 3
emit: pathsAn authored typed pattern uses hops instead of edge_types, direction,
min_hops, and max_hops:
- from: customer_graph
traverse:
start: {where: {eq: [{field: entity_type}, {literal: customer}]}}
path_mode: walk
hops:
- {edge_types: [purchased], direction: out}
- {edge_types: [belongs_to], direction: out,
node_where: {eq: [{field: entity_type}, {literal: category}]}}
emit: nodesAll graph node selectors in traverse.start, reach.start, subgraph.nodes and shortest_path.source/target share membership admission: explicit IDs and IDs read from a selector Relation are deduplicated and every selected ID must exist in the admitted graph. Missing IDs fail before traversal, not an empty path result. An empty Relation or predicate selection remains a valid empty selection. Membership admission does not demand traversal edge/node predicates; a selector's own where predicate still selects from its declared node scope.
start is exactly one of non-empty ids, a named relation plus id field, or
a node where predicate. Direction is in, out, or both. Homogeneous
traversal requires non-negative min_hops, positive max_hops, and
min_hops <= max_hops. hops is a non-empty finite list; each hop requires
direction and may constrain edge types, edge properties, and the reached node.
No implementation may widen the authored maximum.
The closed body MUST admit only required start, emit and path_mode,
optional max_output_rows, and exactly one traversal mode. path_mode is
explicitly walk, trail or acyclic; omission does not select a default.
Homogeneous mode requires literal finite integer-valued
min_hops and max_hops in the stated ranges and explicit direction, with
optional edge_types, edge_where, and node_where. Pattern mode instead
requires a nonempty hops array
and rejects the homogeneous direction, bounds and filter members. Each hop admits only direction,
optional edge_types, edge_where, and node_where. A pattern follows its
entire ordered hop list. Predicates require nullable or non-null boolean and
retain only true. Edge-type lists contain nonempty normalized strings; absence
does not filter types, empty lists admit no matching edge, and repetition does
not repeat paths. Any authored list, including an empty one, requires semantic
edge-label evidence. An empty selection does not bypass static checks.
For each hop predicate, the checker determines candidate edge definitions from
the admitted Graph and the authored static edge_types filter. Candidate reached
node definitions follow those edges' declared endpoints and the hop direction.
Every referenced edge_where or node_where property MUST exist with a
predicate-compatible type in every respective candidate definition. Data rows
and predicate results do not narrow this static set. A missing property is a
check-time error, not an implicit NULL or a runtime fallback. A declared
nullable property whose value is NULL retains the ordinary expression NULL
rules. A statically empty edge selection still checks any authored predicate.
emit: nodes returns the required, non-null start_id, node_id, and minimum
depth columns. Identity columns retain the complete admitted typed Graph
identity, not an endpoint key, display string or provider locator. The output
contains exactly one row per typed (start_id, node_id) pair and is ordered by
start identity, then node identity, then depth, using the existing typed
identity comparison. emit: paths additionally requires semantic edge identity and
returns start_id, end_id, hop_count, node_ids, and edge_ids. Paths are
eligible under the authored path_mode and are ordered by start identity, hop count,
node-identity sequence, then edge-identity sequence using AoA canonical value
order. Duplicate paths are removed by exact identity.
For both emissions, walk permits repeated nodes and edges, trail forbids
reusing an edge identity, and acyclic forbids revisiting a node identity.
Finite authored hop bounds still apply to every mode. Node emission takes
minimum depth only over paths satisfying the entire authored mode, hop range,
label and predicate conditions. A shorter path below min_hops does not
suppress an eligible longer path; an intermediate node visit MUST NOT prune
other eligible path states. Parallel edges remain distinct during exploration,
then node emission collapses their eligible endpoints to the same start/node
pair. The zero-hop path participates exactly when homogeneous min_hops is
zero. For example, distinct edges A→B and B→A with exactly two hops produce
(A, A, 2) for walk and trail, but no row for acyclic.
Node deduplication does not change path emission's exact-path identity or tie
order. The search-path ceiling and the node-output row ceiling remain separate;
multiple eligible paths to one node do not consume multiple node-output rows.
In supported path output, each edge_ids element MUST retain the complete
admitted Graph identity: constructor definition domain, edge definition name,
ordered semantic key type descriptors and complete non-null typed key tuple.
Edge endpoints, labels and properties are snapshot content, not edge identity.
Distinct independent edge keys preserve parallel edges even when their
endpoints and labels agree. Incoming traversal preserves that same edge
identity. Ordering uses the admitted typed identity comparison, not JSON field
order, display strings or serialized text. No endpoint tuple, provider locator,
property named edge_id, or newly computed CID substitutes for this identity.
This follows the current Graph law
identity, content and path requirements; it does not establish execution
support for an otherwise unimplemented Graph consumer.
shortest_path and components
- from: customer_graph
shortest_path:
source: company:A
target: company:B
edge_types: [owns, subsidiary_of]
direction: out
max_hops: 8The closed body MUST admit only source, target, selection, direction, max_hops, weight, edge_types and max_output_rows. Source, target and direction are explicit; direction is in, out or both, and max_hops is a positive finite integer-valued number. Optional edge_types follows the homogeneous traverse filter rules, including semantic label evidence, empty filters and retained repetitions. No max_paths option or implicit output ceiling is added.
max_hops is REQUIRED. Without weight, paths have unit edge cost; optional
weight names a non-null number edge field. Every admitted edge cost is finite
and nonnegative; invalid costs fail input admission, not merely a visited path.
Costs fold in path-edge order with binary64 rounding after each addition;
overflow enters an internal overrange state, ordered after every finite cost.
Overrange is absorbing for subsequent nonnegative additions and is not a
public number or Infinity. Nonselected overrange candidates do not fail the
operation. If any selected path is overrange, the whole operation fails before
publication, never returning an empty result in place of that path. Overrange
candidates tie on cost and use the ordinary hop/identity tie-breaks. These
rules are independent of exploration and early termination strategy; negative
or non-finite input edges still fail admission even when unvisited.
For each_pair, one selected overrange path fails every pair atomically; other
finite pairs are not published. Authors may explicitly validate admissible
weights upstream; there is no weight-ceiling author guard in this vocabulary.
Paths are simple and satisfy the hop bound. Compare by total
cost, then hop count, then lexicographic node/edge identity sequence, using the
same identity order as traverse. No path produces an empty relation.
source and target accept a single ID or the same closed IDs/Relation/where
selector as traverse.start. A set selector requires explicit
selection: nearest_per_source|each_pair|nearest_pair; two single IDs omit it
and mean each_pair. Deduplicate selected identities and require membership in
the graph; an empty selected Relation or predicate result yields no paths.
Nearest_per_source selects one path from each source to the target set;
each_pair selects at most one per pair; nearest_pair selects at most one
overall. Cross-pair ties add source then target identity after cost and hops,
before path sequence. Source=target admits the zero-hop zero-cost path.
Output has source_id, target_id, total_cost, hop_count, node_ids, edge_ids and
is ordered by source then target identity. max_output_rows is an optional
guard; search-work and output ceilings are separate. Weighted search with a
hop cap preserves the cap during optimization, not by filtering the result of
an unconstrained shortest-path algorithm.
- from: customer_graph
components:
kind: weakkind is weak or strong. The result schema is exactly node_id,
component_id, and component_size. node_id is the semantic graph node
identity, component_id is the canonically least semantic node identity in
the component, and component_size is its positive member count. Results are
ordered by component identity then node identity.
Structural graph operations
normalize_graph lowers to SGA.normalize. graph_bounds requires at least
one non-negative max_nodes, max_edges, or max_degree and lowers to
SGA.bounds_check. Delta application uses only apply_graph_delta;
the compiler MUST NOT synthesize an application from prose or untyped data.
normalize_graph, degree and graph_bounds MUST retain the admission and
resource prerequisites of their actual Graph producer. They do not reapply
external Graph-input preflight ceilings to an internally produced Graph or
invent an additional node/edge limit from diagnostic thresholds. Normalization
adds no Relation-row limit. The Relation outputs of degree and graph_bounds
MUST select and inclusively enforce max_relation_output_rows from the admitted
Host capacity policy before exposing output. These are Relation-output scopes,
not the component/traversal max_graph_output_rows scope. Empty output uses zero
rows; an unknown static count or conservative bound above the selected ceiling
is not an observed breach. Excess fails atomically without a partial Relation.
Input validation, producer enforcement, selected consumer capacity, physical
work, byte, retention and cancellation contracts remain independently required;
this association neither waives them nor proves that they have run.
graph_bounds MUST return a Relation with exactly kind: string,
node_id: nullable semantic graph ID, actual: number, and max: number.
The body admits only max_nodes, max_edges and max_degree, at least one
present, each a nonnegative safe-integer literal or a whole non-null number
Parameter subject to that range at binding. A violated node/edge count emits
kind: nodes/edges and node_id: null; a violated total degree emits
kind: degree and the original semantic node ID. Total degree counts both
incoming and outgoing incidences, including two for a self-loop. Only strict
excess (actual > max) emits a row; satisfied bounds, including empty input
under zero bounds, emit no rows. Emit the node-count violation, then edge-count
violation, then degree violations in canonical semantic node-identity order.
These thresholds select diagnostic rows, not execution-resource ceilings:
violations do not truncate or mutate the graph or themselves fail the operation.
Data admission and resource failures retain their ordinary failure boundaries.
The adapter supplies explicit nulls instead of the AoA representation's absent
node_id member for whole-graph violations.
Binary graph operations name the second graph with with:
- graph_diff: {with: previous_graph}
- graft_graph: {with: additions}
- union_graphs: {with: peer_graph}
- subtract_graphs: {with: removals}Property-bearing structural union/subtraction uses the closed form:
- union_graphs:
with: peer_graph
merge:
node_properties:
tags: set_union
score: number_max
edge_properties:
observed_at: number_maxAn unspecified property conflict fails; this is a graph law rather than
an author field. Field strategies are
exact_equal, set_union, set_difference, number_sum, number_mean,
number_max, and number_min. Set strategies require arrays interpreted as
canonical-value sets; numeric strategies require finite numbers. Missing and
null are values, not absence-compatible defaults: unless the named strategy
explicitly admits both operands, a one-sided or type-conflicting property
fails. The compiler lowers node and edge maps separately to AoA structural
field laws. Associativity/commutativity claims apply only to strategies whose
published AoA law says so; set_difference is directional.
They lower respectively to SGA.diff, SGA.graft, SGA.union_structural,
and SGA.diff_structural. graph_diff returns a relation; the other three
return a graph. Graph outputs must be consumed by a later graph operation or
converted with node_rows/edge_rows, because ActQL 0.1 artifacts are
relation outputs.
graft_graph is add-only: identity-equal complete payload is idempotent and a
different payload fails. In union_graphs and subtract_graphs, every
property without an authored strategy fails on conflict as a fixed language
law; this is not a source completion and creates no normalization entry. An
authored structural merge policy must name every non-failing property
strategy; no last-write or provider-order precedence exists.
Subtraction is identity-based and conflicting payload evidence fails unless the
authored merge contract explicitly admits the structural difference.
Graft, structural union and subtraction MUST require the same node-property and edge-property field sets respectively, with compatible recursive value domains. The common type rules join nullability and extents; missing properties are not filled with null. A present record must contain every declared property even when nullable. Empty graphs retain those contracts and do not bypass compatibility checking.
The initial Graph union realization MUST accept only a single identity
domain and the same named node and edge definition sets. Definition and property
declaration order may differ; align them by the existing name rules. The exact
definition spelling embedded in identity and each ordered key type descriptor
must nevertheless match: N and n, decimal precision/scale changes, and
different ContentRef media types are not implicit identity conversions.
Check rejects those mismatches with typed identity reasons. Key-component order
and original key-column correspondence, label sets and label-visible property
sets, and source/target definition mappings must agree. Property value contracts
may join nullability and extents under the existing rules; key contract joins
must preserve their identity descriptor. Output contracts must also represent
the selected strategy's results (for example, array union cannot retain an
insufficient input array-length bound). This limited compatibility does not
change constructor identity or authorize relabeling, rekeying or migration.
An entity present only on one side is distinct from a missing property in a
present entity: union keeps its complete record without applying a binary
property strategy, while subtraction keeps only left-only entities.
For a shared edge identity, union MUST require exact typed source and target identity equality separately. A conflicting endpoint fails the whole operation without publishing a partial accumulator or modifying either input. Property strategies do not overwrite endpoints or allocate a replacement edge identity.
On a shared identity, an explicitly named strategy applies even when its two
values are equal. exact_equal requires typed value equality and returns that
value; a property without a strategy has the same equality requirement.
number_sum, number_mean, number_min and number_max require two finite
number values and compute addition, binary pairwise average, minimum and maximum
respectively. number_mean takes the exact mathematical average of its two
finite binary64 operands and rounds once to binary64, nearest with ties to
even. It uses the existing zero-normalization rule. An intermediate sum that
would overflow binary64 does not by itself make the average invalid.
number_sum keeps its own overflow rule, and a finite-list fold of pairwise
means is not a multi-input global mean. set_union and set_difference
require arrays with compatible
item domains and compute canonical typed-value set union and left difference.
Nullable property contracts may reach these strategies, but an actual null
numeric or array operand fails before result publication: null is never zero
or an empty array. Null array elements are ordinary members when their item
contract admits null. Set results contain one representative per typed equality
class in canonical typed-value order. Output contracts must cover both shared
identity results and untouched one-sided records; no non-null or size proof
may be inferred from the strategy alone for those untouched records.
Subtraction MUST treat the node and edge strategy maps independently. For a shared
identity with no nonempty strategy map for its entity kind, equal complete
payloads remove the entity and a payload conflict fails. With a nonempty map,
apply its property strategies and the equality requirement for unlisted
properties, then retain the transformed entity. An explicit exact_equal
entry is a strategy, not an entity-removal instruction. A zero, empty array,
or unchanged transformed payload never implicitly deletes its entity.
Strategy names keep their meaning in both operations: number_sum remains
addition in subtraction, not a context-dependent spelling for subtraction.
After node subtraction, remove every remaining edge whose endpoint is absent
from the retained node set. This induced endpoint closure never resurrects a
deleted node. It is specific to subtraction and does not change the exact
endpoint/admission rules of apply_graph_delta.
Thus union is not unconditionally idempotent under explicit non-idempotent
strategies, and subtraction is not unconditionally self-annihilating under
explicit transformation maps. In particular a shared value 2 under
number_sum becomes 4, including when subtracting a graph from itself with
that strategy. Unconditional versions of those algebra laws cannot justify
an optimization or be retained as the admitted law for these operations.
graph_diff returns exactly one relation row with field delta. That result
has the checked Delta contract defined in Graph equality and Delta,
and can be inspected, retained or applied as pure computation.
union_graphs.with additionally accepts a nonempty finite graph-reference list.
The current graph is first, followed by that list in declaration order;
left-fold the specified binary operation. This list order is semantic.
Reassociation or permutation is legal only if every admitted field law and
its failure behavior proves the transformation. In particular, pairwise
number_mean is not a global mean; use mean_graphs.
For graft, structural union and subtraction, static construction MUST retain the actual operation, operand dependencies and selected capacity policy together with requirements for composition work, value/temporary bytes and retained state. The executable realization MUST bind finite enforceable resource contracts before composition uses its inputs. Those contracts specify each meter's quantity, unit, aggregation scope, limit and breach/cancellation behavior. A static requirement is not completed resource admission, and a realization unable to provide the required contracts is unsupported, not exempt. Physical realization limits may differ; selected logical constraints and producer obligations remain unchanged. External Graph-input preflight limits and graph-construction limits retain their own applicability; they MUST NOT be silently reapplied as internal composition ceilings. An input/output count proof is not a work, byte or live-state limit.
For a union list, all folds belong to one operation-wide resource scope, not fresh budgets for each fold. Cumulative meters include every fold's actual work and applicable traffic; peak meters include all simultaneously retained input, accumulator, result and temporary state. Repeated operand references retain their ordered semantic uses. Legitimate immutable sharing does not imply a fictitious copy charge, but cannot hide actual work, allocation or traffic. The selected contract determines the meters; the compiler MUST NOT replace their cumulative usage with the maximum usage of one fold. Failure exposes neither an intermediate accumulator nor a partial Graph. Empty operands, self-composition, unchanged output and one-sided records do not waive admission, policy dependencies or selected resource contracts.
Traversal path exploration receives a resolved Host ceiling in the machine
Plan; max_paths is not authoring vocabulary. traverse.max_output_rows and
components.max_output_rows are author guards as defined in Author guards, resolved limits, and Plan facts, counting
the emitted traversal rows or component membership rows respectively.
shortest_path has bound 0..1 for single IDs or nearest_pair, at most source
count for nearest_per_source, and at most the source/target count product for
each_pair; component output
is bounded by the graph node proof and Host policy. A breach MUST fail the
whole operation without publishing a partial graph/path Relation.
subgraph
subgraph: {nodes, edges?} consumes a graph and returns a graph. nodes
uses the same closed IDs/Relation/where selector as traverse.start.
Selected IDs are deduplicated; explicit IDs absent from the graph fail.
An empty selected Relation or predicate result selects no nodes. Retain
selected nodes and exactly those original edges whose two endpoints are
selected. Optional edges is a boolean expression in the match_edges
scope, applied after this induction; only true retains an edge. Removing an
edge does not remove either endpoint. Ego extraction composes bounded
reach/traverse with this selection; it does not require another traversal
syntax inside subgraph.
Selection MUST preserve retained semantic identities, labels, property contracts and values, canonical graph order, policy and observation scope; it neither invents edges nor upgrades completeness or authority. Output node/edge counts are bounded by input counts; selector evaluation and graph materialization have separately resolved work and byte ceilings. No partial graph is published on failure.
Graph equality and Delta
graphs_equal: {with: graph_name} returns exactly one row with non-null
boolean equal. Compare canonical admitted graphs by semantic node and edge
identities, labels and typed property values, independently of input array
enumeration or acquisition identity. Both graphs require the same structural
identity law and compatible closed property domains. This is not graph
isomorphism or approximate similarity. Invalid input fails admission rather
than returning false. Use ordinary assert_rows on this result for an
execution assertion; equality of arbitrary future inputs is not a static
type-checking guarantee.
graph_diff: {with: target} computes a Delta from the current base graph to
the target. Its one-row delta field is a non-null closed object with
version: "actql-graph-delta/0.1", base_graph_cid, target_graph_cid,
graph_contract_cid, nodes and edges. The three CID fields are canonical
string identities, not capability references. Each of nodes and edges
is a non-null closed object with non-null arrays add, remove, update.
Add/remove elements are complete after/before records respectively; update
elements are closed {before, after} objects of complete records with the
same semantic identity. Their checked item types come from the graph contract,
including for empty arrays. Arrays are sorted by the graph identity order.
Unchanged records are absent; an identity occurs in at most one action array
per entity kind. A changed identity is a removal and addition, not an update.
The graph contract fixes all record fields and equality laws; no patch paths,
arbitrary JSON instructions or implicit property merge exist.
The base and target graph CIDs hash one canonical JSON array with preimage
["actql-graph-snapshot/0.1", graph_contract_cid, admitted_definition_body, nodes, edges]. This is the formal Graph
snapshot identity, including the saved admitted constructor definitions;
Delta MUST reuse that snapshot contract rather than a separate digest domain.
nodes and edges contain complete closed records in their respective
canonical typed identity order. In particular, edges are ordered by their
independent edge identities, not by endpoint tuples. Records and admitted
values use canonical JSON object-key order and the typed wire representation
fixed by the common graph contract. This identity excludes acquisition order,
provider locators, process handles, indexes and physical storage.
Implementations MUST validate strict record order while hashing and MUST NOT
substitute an unordered container digest, backend-specific serialization or
the retired four-element actql-graph-snapshot/0.1 preimage.
Delta node records MUST use the formal snapshot's closed shape
{id, key, props} and edge records its closed shape
{dst, id, key, props, src}. Node and edge IDs retain domain, definition and
typed key identity without string conversion; edge identity MUST NOT be
reconstructed from endpoints or labels. key preserves the original typed
key components and MUST agree with id; src and dst retain typed node
identities. The saved admitted definition body supplies label sets, key-column
metadata and per-definition property contracts. Each non-null props object
contains the complete property field set for its own definition, not the union
of all definitions. A property absent from that definition is not filled with
NULL, while a declared nullable property's NULL remains explicit. An empty
property map is {}, not an omitted member. Snapshot and Delta record
validation, normalization and identity checks MUST share the same contract;
old tuple records, guessed definitions and implicit format conversions are
rejected. Before and after snapshots are admitted under the one common graph
contract. Empty graphs do not weaken these contract requirements.
apply_graph_delta: {with: relation_name, field: delta_field} consumes the
base graph and exactly one Delta row; zero or multiple rows fail. Delta is
ordinary typed data with this additional semantic admission, not a new
Carrier or an authority-bearing token. Reading it grants no provider access.
Application MUST verify the closed shape, canonical ordering, graph contract,
base identity, unique action identities and exact before records before any
result publication. Add requires an absent identity; remove/update require
an existing exact before record. Apply the node and edge changes together,
then validate endpoint existence, complete result contracts and target CID.
No missing update is skipped and no incident edge is silently deleted.
apply(A, diff(A, B)) yields the same canonical graph as B for admitted
compatible A and B. Wrong-base, malformed or inconsistent Delta fails;
rehashing its container does not bypass these checks. Diff/apply preserve
policy dependencies and have resolved graph/action/byte/work limits. They
perform pure computation only; external persistence remains an explicit
Effect. A Delta binds its exact base snapshot; applying it to a different
snapshot fails, even if the changed records happen to match. Rebase, Delta
composition and inversion are outside this vocabulary.
mean_graphs
The body MUST contain exactly with, weights, support and properties. Weights and support thresholds are literal numbers, not Parameter references. Both property-strategy maps are required; an empty map is valid exactly when the corresponding complete property domain is empty. Compatible property extents and nullability are joined using the common typed-domain alignment rules. Semantic row-role aliases survive only when all samples declare the same roles; no sample-order winner supplies aliases for the generated graph.
mean_graphs: {with, weights, support, properties} is a generator over a
nonempty finite list of graph names. weights has one finite nonnegative
number per graph and positive total weight. support is a closed object
with nodes and edges, each a number in (0, 1]. properties has separate
nodes and edges maps naming every non-identity property and its strategy.
All graphs have the same identity law and closed property domains. List
positions are samples, so repeated graph names contribute their weight again;
zero-weight samples are validated but make no support or property contribution.
This is a statically finite graph list, not a dynamic Graph-valued Relation.
For each identity, support is the weight of samples containing it divided by total weight. Retain nodes meeting node support, then edges meeting edge support whose endpoints are retained. Compare support using exact rational values of the bound binary64 weights and threshold, not a rounded quotient. Labels for an identity must agree among positive-weight samples containing it. Different entity IDs are not inferred to represent corresponding roles; cross-company or cross-version alignment is an explicit upstream operation.
Property strategy is exact_equal or the closed object
{mean: {missing: exclude|zero, nulls: exclude|zero|fail}}. Exact_equal
requires equal typed values among positive-weight present records. Mean
requires number properties. Missing means the entire node/edge is absent
from a sample, not a declared property missing from an admitted record.
Exclude removes that sample's weight from the property denominator; zero
retains its weight with zero numerator; null fail rejects an observed null
on a retained identity. A zero denominator yields null, so the checked mean
property is nullable. Identity and labels are never averaged.
For example, three samples with weights 1, 2, 3 contain respectively value 6, an observed null, and no record. With both policies exclude the mean is 6; missing=exclude/nulls=zero gives 6/3=2; missing=zero/nulls=exclude gives 6/4=1.5; both zero gives 6/6=1. A present record missing a required property is invalid input, not either case.
Weighted property means MUST sum exact rational products of admitted binary64 values and weights, divide by the exact contributing weight, and round once to finite binary64, nearest ties-to-even; non-finite conversion fails. No input-order winner, binary pairwise mean, or host-native reduction substitutes for this rule. Canonical output order uses graph identity order. Node/edge bounds are at most the respective input-count sums; sample work, exact arithmetic and materialization have resolved ceilings. Mean preserves observation scope and policy and grants no master-data authority. Structural subtraction from this output is not automatically a statistical deviation.
An executable realization MUST bind finite enforceable sample-work, exact-arithmetic-work, value/temporary-byte and retained-state contracts before using mean inputs. Each selected meter specifies its quantity, unit, aggregation, ceiling and failure behavior; a node/edge count bound does not substitute for work or storage bounds. Exact arithmetic includes the storage growth of rational accumulators, not only their object count. Physical realization choices may use different resource contracts without changing the exact mean semantics or waiving applicable logical limits and producer obligations.
The realization MUST enforce each selected meter over the entire mean operation, without resetting cumulative budgets per sample, identity or property. Cumulative work and byte traffic include all actual uses; peak storage counts simultaneously live inputs, accumulators, output and temporary storage. Repeated samples remain distinct semantic contributions; immutable sharing need not be charged as a copy that did not occur. Empty graphs and zero-weight samples do not waive admission or resource enforcement. Failure, cancellation or incomplete validation prevents publication of a partial mean result.
Vector operations (VecA)
actql-veca/0.1 is the authoring-complete deterministic exact-vector module.
A module claim covers finite vector preparation, exact scoring/search, grouped
pooling, thresholding through RelA, stable ranking-list merge/dedupe,
per-query top-k, semantic identity restoration, and relation projection.
Row-local vector_score/vector_concat and reciprocal-rank fusion are included.
Normalization plus KNN alone is partial VecA support.
Metadata filtering occurs in RelA before vector conversion and metadata is
rejoined in RelA after ranking; ActQL inserts only the typed carrier bridges.
Embedding generation and ANN are external reads. ANN access MUST use
search_candidates with a SourceView binding explicit
index, model, filter and exactness identities; it MUST NOT weaken pure exact
nearest. There is no separate search_index operation.
Row-local vector expressions
vector_score(a, b, metric) and vector_concat(a, b, ...) are common pure
expressions, not acquisition operations. Score's structured body is
{left: a, right: b, metric: cosine|dot|l2}; left/right are expression children
evaluated in that order and metric is structural. Concat's body is an ordered
expression array with at least two children. They compose at all ordinary expression positions, including
conditional branches and filtered aggregate arguments, under the common
demand and first-failure rules. Effect selectors retain their closed subset.
Score requires two vectors with identical positive dimensions and returns a number. It shares nearest's f32 admission, dimension-order numerical fold, zero-vector behavior, negative-L2 score orientation and pinned metric law. It does not compare model identities or infer that two embedding spaces are semantically interchangeable from dimensions alone. Such an application constraint is checked by the source/model contracts used by the Program.
Concat requires at least two typed vectors. It preserves argument/component order, yields dimensions equal to the checked sum, and performs no scaling, averaging or L2 normalization. Its result components use the same f32 value contract. Positional arguments evaluate eagerly and once; either function returns null when any vector operand is null, after argument evaluation.
The compiler MUST bind checked dimensions, metric law and component/work ceilings to the expression root and preserve its demand conditions during lowering. Overflow in dimension addition, incompatible types, unsupported metric or unavailable profile is a check failure; invalid values and exceeded evaluation limits fail only at their specified admission/evaluation boundary. The same expression tree is used in derive, filter and aggregate consumers; host callbacks or implicit carrier conversion are not alternative meanings.
nearest
- from: candidates
nearest:
queries: queries
candidate_id: product_id
candidate_vector: embedding
query_id: request_id
query_vector: embedding
metric: cosine
k: 20The corpus MUST be the operation's step input: the immediately preceding
result in a linear pipeline or the explicit step-level from result. The
nearest parameter object has no second corpus reference. This single wiring
surface prevents a filtered current relation from being silently replaced by
its unfiltered source input. queries, positive integer k, and metric are
REQUIRED.
metric MUST be cosine, dot, or l2. ActQL 0.1 nearest-neighbor
execution is exact; exactness is recorded in the Plan rather than
authored in source. Approximate nearest-neighbor execution is unsupported and MUST NOT
be silently substituted.
Vector similarity has no master-data authority. In an entity-resolution workflow it may generate or rank candidate pairs, but it MUST NOT establish a canonical entity, source binding, merge, or split. A future MDM workflow may use VecA candidate generation followed by RelA comparison and explicit evidence, but only an approved graph-binding delta may become authoritative. A compiler or host MUST NOT auto-merge entities solely because a vector score exceeds a threshold.
The closed body MUST admit only queries, candidate_id, candidate_vector, query_id, query_vector, metric and k. The k option is a positive safe-integer literal, not a Parameter reference. Optional field references on an already-typed Vectors input may restate its roles but cannot override them; an identity field reference can restate only a singleton identity tuple. Unknown Vectors role and dimension information remains an input-validation obligation before scoring and does not invent a public identity type. A known role conflict fails during Check.
Relation inputs require explicit semantic ID and vector fields. Vector-set
inputs already carry those roles. The compiler inserts BRIDGE.rows.to_vectors
where necessary and lowers to VEC.knn_join. The ranking Relation exposes
query_id, candidate_id, score, and rank: the IDs contain the authored identity
values, not internal bridge record IDs. These two role names are fixed even
when the source identity fields have different names.
A single identity component MUST retain its value and type directly in its
fixed output role. A composite identity is represented by a non-null closed
record whose keys are the zero-based decimal positions "0", "1", and so on
in the Carrier's declared identity tuple. Each slot retains its original value
and type, including decimal or reference contracts. Input field aliases are not
encoded into this record. Identity comparison uses the ordered typed components,
not serialized record text or lexicographic ordering of position keys. This
representation does not make arbitrary objects KeyComparable or authorize a
string/CID replacement. It fixes the previously unspecified public tuple shape
without changing the Carrier's internal semantic identity.
Rank is a non-null positive safe integer: the one-based position in each query's completed score-descending/candidate-identity order after top-k. It is contiguous from one, not a dense rank or provider-supplied position.
For relation inputs, the compiler MAY deterministically complete a side's ID
field only from that side's sole non-null string primary-key field, and MAY
complete its vector field only from that side's sole statically known
fixed-dimension vector field. Every such completion MUST be journaled as
actql.vector.typed-field-completion; zero or multiple candidates require an
explicit authored field and MUST NOT be guessed. queries and the step-level
from resolve in the available dataflow namespace (Pipeline names and references). No sigil stripping
or fuzzy name resolution is permitted.
Every finite vector component is quantized to IEEE-754 binary32 using round-to-nearest, ties-to-even before scoring. Query groups are ordered by query ID ascending. Within a query, results are ordered by score descending then candidate ID ascending. Zero-vector and non-finite-input behavior follows AoA VecA semantics; non-finite input MUST be rejected.
Every authored vector input schema MUST declare positive dimensions; the
canonical checked contract inserts the fixed 0.1 element_type: f32 law.
Query/corpus dimension mismatch is a check error when
evidence is available and otherwise a runtime failure before scoring. Query
and candidate identities are globally unique within each carrier; duplicate
identity is invalid rather than an implicit dedupe. Cosine and dot scores use
their usual higher-is-better orientation. The canonical l2 score is
negative Euclidean distance, so zero is best and all three metrics share
descending ranking order.
An executable nearest realization MUST bind finite enforceable input-admission, pair-scoring and ranking-work contracts, value/temporary-byte contracts and retained-search-state contracts before input use. Each selected meter specifies quantity, unit, aggregation, ceiling and failure behavior. The selected Host Relation-output ceiling and any other applicable logical limits remain enforced; k bounds selection per query, not input validation, scoring work or storage. A count proof is not a substitute for these work and byte contracts. Different exact realizations may have different physical meters without weakening the pinned numerical law or admitting approximate search.
Each selected meter MUST cover the entire owned operation, retaining candidate and query roles separately even when they refer to one producer. Cumulative work and byte traffic count actual uses without resetting per query or candidate; peak storage includes concurrently live inputs, ranking state, result and temporaries. Shared immutable storage is not an imaginary copy. An empty side does not waive the other side's admission, identity uniqueness or completion requirements. Failure, cancellation or incomplete validation prevents partial ranking publication. A zero output-count bound proves neither zero input work nor completed input validation.
Deterministic vector arithmetic
The pinned VecA numeric law fixes the arithmetic shared by row-local vector expressions and Carrier operations. Let R round each individual operation to binary64, nearest ties-to-even, and Q round to binary32, nearest ties-to-even, represented exactly in binary64. Admit finite input components through Q before use and reject non-finite quantized values. Every arithmetic intermediate and output must be finite. Neither an arbitrary magnitude ceiling nor Decimal128 arithmetic is implied by the finite binary64 score contract.
| Operation | Arithmetic after component admission |
|---|---|
| dot | Starting at +0, fold R(sum + R(a[i] * b[i])) in increasing dimension order. |
| cosine | Compute dot and each squared-norm fold separately in dimension order. If either norm-squared is zero, return +0; otherwise return R(dot / R(R(sqrt(left_squared)) * R(sqrt(right_squared)))). |
| negative L2 | For each dimension compute d = R(a[i] - b[i]); fold R(sum + R(d*d)) from +0; return -R(sqrt(sum)). |
| normalize | Fold squared norm identically, compute n = R(sqrt(sum)), reject n=0, then emit each Q(R(a[i]/n)). |
| pool sum | For each dimension fold R(sum + component) from +0 in ascending semantic member-identity order, then emit Q(sum). |
| pool mean | Use the same sum; divide once by member count with R, then emit Q. No incremental mean. |
| pool max | Take the maximum admitted component among the nonempty group's members. No infinite sentinel is an admitted operand. |
| weighted ranking merge | Substitute +0 for a missing score; compute R(R(left_weight * left_score) + R(right_weight * right_score)). |
Square roots MUST be correctly rounded binary64 operations. Fused multiply-add, reassociation, pairwise reduction, intermediate f32 accumulation and score clamping cannot replace the prescribed tree. In particular rounded cosine scores are not silently clamped to [-1,1]. All emitted zero scores and vector components are canonical positive zero; internal arithmetic retains IEEE signed-zero behavior. A nonzero source vector quantized entirely to zero is a zero vector for scoring and normalization. Zero vectors are valid results of pooling, but cannot be L2-normalized. Vector-valued output components use the final f32 conversion; scores remain binary64. Empty grouped inputs create no groups and no artificial zero-member pooled vector. These laws and their independent fixed vectors must be pinned before an executable profile claim.
Vector-set-preserving operations and explicit bridges
to_vectors MUST have a closed body containing id and vector field
references on its Relation input. The distinct selected roles require a complete
non-null KeyComparable identity contract and a non-null fixed-dimension vector
contract respectively. Other payload contracts and field-set knowledge remain
unchanged; no complete payload schema is required. The bridge retains authored
field names and semantic identity values without inserting physical id or
vector aliases, stringifying identities or replacing them with CIDs. Actual
identity uniqueness and finite component admission are separate validation
obligations. A backend unable to preserve the selected identity domain rejects
lowering explicitly rather than changing that domain.
- to_vectors: {id: product_id, vector: embedding}
- normalize_vectors: true
- score_vectors: {query: [0.1, 0.2], metric: cosine}
- vectors: trueto_vectors and vectors lower to BRIDGE.rows.to_vectors and
BRIDGE.vectors.to_rows. normalize_vectors lowers to VEC.normalize_l2.
score_vectors requires a non-empty finite query vector and an explicit
cosine, dot, or l2 metric, and lowers to VEC.score_sort. These forms
preserve the vector carrier until vectors; nearest remains the
two-input relation-returning exact KNN form.
The normalize_vectors body MUST be exactly true; the score_vectors body
is closed and requires exactly query and metric. A query is either the
nonempty finite numeric array above or a whole Parameter reference whose
declaration is a non-null array of non-null numbers. A known exact query
length is checked against the carrier dimensions; otherwise matching dimensions
remain a validation obligation before scoring. Defaults are not type evidence,
and binding does not waive finite-component, nonempty or dimension validation.
Scoring MUST preserve semantic identities, vector components and payload fields
and add a fresh non-null number field named score. An existing score field
is an output collision, and unknown field-set completeness does not prove its
absence. This fresh-field rule prevents silent payload replacement; repeated
scoring requires an explicit transformation that removes the previous score.
Normalization preserves these fields and their contracts except for replacing
the component values with normalized values; it does not recompute an existing
score or establish that score's validity for the changed components. Neither
operation's successful type check proves arithmetic validity, uniqueness,
execution bounds or the score ordering required at execution.
The explicit to_vectors and vectors bridges and normalize_vectors MUST
retain input occurrence order; none establishes order on an unordered input.
Normalization after scoring preserves that sequence and stored score, but does
not assert that the score measures the normalized components.
score_vectors MUST order output by score descending, then semantic vector
identity ascending using the existing internal type-specific identity order.
This matches the single-query ordering of nearest; L2 scores remain negative
distances under the numeric law. Identity uniqueness is validated independently;
arrival order, stringification or a synthesized row CID cannot break ties.
Binding, profile, finite-arithmetic and resource validation precede publication.
Executable vector preparation, normalization and scoring MUST bind finite enforceable component-work, value/temporary-byte and retained-state contracts before input use; scoring additionally binds score-ordering work. Each selected meter specifies its quantity, unit, aggregation, ceiling and failure behavior. Dimensions and row-count bounds do not substitute for these contracts. A backend resource choice does not waive applicable logical limits, input obligations, query admission or the pinned numerical and identity-order rules.
Each selected meter MUST cover the whole owned operation rather than resetting
per vector or component. Cumulative work and byte traffic include actual uses;
peak storage includes simultaneously live inputs, query, output and temporary
state, without charging imaginary copies when immutable storage is shared.
Empty input does not waive query validation or completion of the selected
contracts. Failure, cancellation or incomplete validation prevents partial
output publication. The explicit vectors projection preserves the producer's
obligations; changing Carrier does not erase resource or arithmetic checks.
pool_vectors
- pool_vectors:
vector: embedding
by: [customer_id]
method: meanRelation input requires a vector field and a retained candidate key;
vector-set input already carries
identity. ActQL source does not expose a separate relation-pooling carrier
id: the compiler MUST derive member identity from that proven relation
identity and MUST reject relation pooling when none is proven. by is
non-empty and method is mean, sum, or max. A single by field becomes
the pooled identity; multiple fields use their canonical tuple and are restored
as separate fields by vectors. Optional output_id may rename a
single-field identity. All vectors in a group MUST
be finite and have the same non-zero dimension. Groups are ordered by their key
tuple; binary32 quantization occurs before pooling, and each output component
is quantized again after the AoA-defined deterministic fold. Members are
ordered by semantic vector identity ascending before the left fold. Mean and
sum round at the law-defined fold points; max is componentwise after input
quantization. Duplicate vector record identity anywhere in the carrier fails,
even when duplicate records belong to different authored groups.
The body MUST admit only vector, by, method and output_id. The by list is nonempty, ordered and has no repeated field reference, because its fields are restored as distinct output columns. Repetition is not silently removed. Method is explicitly mean, sum or max. The optional output_id is an authored output field name and is permitted only for a single grouping field; it does not provide a member-identity override or encode an entire tuple into a name.
Each grouping field MUST have a known non-null KeyComparable contract, because its value becomes a component of the output Vectors identity, not merely an arbitrary nullable grouping label. The result retains the grouping fields and the selected component field's name and dimensions, drops other input payload, and adds non-null number member_count with the positive safe-integer obligation. For a single grouping field, a different output_id adds an alias with the same value and type and makes that alias the identity role; the original grouping field remains available. An identical output_id adds no duplicate field. Generated columns and aliases cannot collide with retained output columns; names of dropped input columns do not cause a collision. A vector option on a Vectors input can restate, but not override, its existing component role.
Relation pooling MUST select its member candidate key using Deterministic completion, after retaining only keys with known non-null scalar component types eligible for this encoding. A shorter or lexicographically earlier ineligible key cannot hide an eligible alternative. The selected tuple's fields, including explicit surviving aliases, are in canonical code-unit order. Input key declarations and their admission obligations retain their original names and declaration order; that order does not override the selected completion tuple. Selection does not establish actual row uniqueness.
A selected singleton non-null string candidate-key field is used directly and journaled as
actql.vector.candidate-key-identity-completion. Any other non-null scalar
candidate key, including a singleton non-string key, is encoded into reserved internal field
__actql_vector_record_id: for each component in the selected canonical key order the
compiler concatenates its Unicode-scalar length in AoA canonical to_string
form, :, that canonical form, and |. The reserved field is
collision-checked and the completion is journaled as
actql.vector.composite-candidate-key-identity-completion. This carrier-only
field is not part of the public pooled relation. vectors exposes the
group fields, optional output_id, pooled vector, and exact positive
member_count.
For this Relation member-identity completion, scalar excludes reference values: no reference-to-string or reference-to-CID conversion is inferred. Member ordering uses the completed string identity, including its length prefixes, while output group ordering continues to use the semantic group tuple. A declared input key retains its input-admission obligations; its use does not certify that rows have already passed non-nullness, domain and uniqueness checks.
An executable pooling realization MUST bind finite enforceable input/grouping, member-ordering and fold-work contracts, value/temporary-byte contracts and retained-pool-state contracts before input use. Each selected meter specifies quantity, unit, aggregation, ceiling and failure behavior. Member identity completion, key validation and quantization remain required; a group-count bound does not substitute for work or storage limits. Backend choices cannot change the required member order, rounding points or global uniqueness rule.
Each selected meter MUST cover the entire owned pooling operation without
resetting cumulative budgets per group or component. Cumulative work and byte
traffic count actual uses, including selected member-key encoding; peak storage
counts concurrently live inputs, group state, output and temporaries without
imaginary copies of shared immutable values. Empty input retains admission and
completion requirements and creates no fabricated group or member_count. Failure,
cancellation or incomplete validation prevents partial pooled output publication.
The explicit vectors bridge does not remove these producer obligations.
merge_rankings
- merge_rankings:
with: lexical_ranking
by: [query_id]
candidate: candidate_id
score: score
score_policy:
weighted_sum: {left: 0.6, right: 0.4}
score_contract:
domain: normalized-relevance.v1
range: [0, 1]
normalization: min_max.v1
k: 20The source body MUST be closed to with, by, candidate, score, rank, score_policy, score_contract, k and tie_break, with policy-specific required and forbidden members as specified below. An empty by list denotes one global query group; repeated by references retain their authored order but create each output field only once, in first-occurrence order. For max and weighted_sum, with is one input name. An explicitly empty tie_break adds no business fields and still completes to candidate identity; repeated business tie-break references are retained and do not strengthen uniqueness evidence. Omission and an explicit empty list are not canonical-source substitutions. Score_contract admits exactly domain, range, normalization and optional higher_is_better: true; domain and normalization are nonempty strings, range has exactly two finite endpoints in nondecreasing order. The fixed higher-is-better law applies when the redundant true member is absent as well. All k, rank_constant and rank_window options are positive safe-integer literals. Weighted-sum admission compares the binary64 sum of its two weights with exactly one, without an epsilon tolerance.
Both inputs are relation rankings with complete typed schemas. by,
candidate, score, score_policy: max|{weighted_sum:{left,right}}, and
positive k are REQUIRED. Weighted-sum weights MUST be finite, non-negative,
and sum exactly to one under the numeric contract. A candidate missing from
one input contributes positive zero for that input; weights are not
renormalized over the inputs containing that candidate. Output score bounds
must therefore include one-sided participation and the prescribed rounding;
they are not automatically the declared input score range. For example an
input range [1,2] and equal weights can emit 0.5 for a candidate whose only
score is 1. max takes the maximum of present scores only, not the maximum
with an invented zero for an absent side. Raw sum is not an ActQL 0.1 policy.
An explicit identical score_contract is REQUIRED for both ranking semantics:
it binds domain, finite range, and normalization identity. Higher scores are
better in ActQL 0.1 and the checked Plan records that fixed law.
Raw BM25 and cosine values, or two differently normalized lists,
cannot be summed merely because both fields are named score.
Omitted tie_break completes to the semantic candidate identity; an authored
tie-break may add business fields before that final identity. Rows for the same query/candidate are combined by
the declared score policy, then ordered by score descending and the authored
tie-break ascending; only the first k per query remain. Query groups are
ordered canonically. This operation lowers to exact VecA ranking composition
plus RelA projection; host-language list merging is not conforming.
Every policy MUST require at most one row per query-key/candidate tuple within
each individual ranking input. Validate complete inputs before fusion or
truncation; duplicate rows fail rather than overwrite, collapse or supply an
implicit within-list score aggregation. Repeated references to the same
ranking in an RRF input list remain separate weighted input occurrences.
For max and weighted_sum, output consists only of the by fields, candidate
field, non-null number score, and fresh non-null positive safe-integer rank.
Business tie-break fields influence ordering but do not implicitly copy a
representative payload into the output. When the same query/candidate occurs
in both inputs, corresponding business tie-break values must agree under
typed equality; disagreement fails before publication. A candidate present
only on one side uses that side's tie-break values. Hidden row identities,
input order and maximum-score representative selection do not resolve a
tie-break-value conflict. Additional business payload is obtained by an
explicit subsequent join.
The preceding score-contract requirement applies to max and weighted_sum,
not to reciprocal-rank fusion. RRF uses the same current ranking Relation and
with, where with may be a nonempty finite list. It requires by,
candidate, rank, positive k and the closed policy
{rrf: {rank_constant, weights, rank_window}}; it rejects score and
score_contract. Input order is current ranking first, then the declared
list. Weights has exactly that length; each is finite and nonnegative with
positive total weight. Rank_constant is a positive safe integer; rank_window
is a positive safe integer, independent of output k.
Each query/candidate occurs at most once per input. Rank is a non-null positive safe integer, unique within each input query and contiguous from one; it is the total position after that input's own ranking tie-break, not dense_rank. Inputs have matching query-key and candidate value domains; their score scales need not match. Validate each complete ranking input before applying rank_window, then only positions at most rank_window contribute. A candidate absent from one list contributes zero for it. A query absent from every list has no output row; this is not evidence that a provider returned no candidates.
For each query/candidate, RRF MUST compute contributions
weight / (rank_constant + rank) using binary64 round-to-nearest ties-to-even
at each addition and division, then left-fold contributions in declared input
order with the same rounding. Any non-finite intermediate fails. Emit the
union of participating candidate identities, ordered by fused score descending
and semantic candidate identity ascending, retaining at most k per query.
Authored tie_break is not admitted for this policy: list-dependent payloads
do not silently select a representative. Output contains the by fields,
candidate field, non-null number score, and positive safe-integer rank,
with collision checks. Query
groups use canonical key order. The input list, weights, rank_window and
law identity belong to the canonical Plan; bounds cover admitted input rows,
fusion work and output separately. Optimization preserves the declared fold.
Before consuming ranking inputs, the selected execution contract MUST bind finite limits for complete-input admission work, fusion work, ordering work, value and temporary bytes, and retained ranking state, with explicit measured quantities, units, aggregation rules and atomic failure behavior. Positive k and rank_window are not substitutes for these limits. The Host Relation output ceiling and all applicable selected logical limits remain enforced separately; a Host ceiling does not replace a tighter Program bound.
Resource enforcement MUST cover the entire fusion operation, not reset per input occurrence, query group or candidate. Repeated references preserve their semantic contributions; actual shared storage is counted according to its selected meter rather than as imaginary duplicate allocations. Work and byte traffic accumulate, while peak-state scopes include simultaneously live state. Zero weights and rank_window exclusions do not waive complete-input validation, and empty rankings do not excuse validation of other inputs. Exceeding any selected limit rejects the operation output without publishing a partial ranking.
Every merge_rankings policy emits rank as the one-based position within each query after final tie-breaking and top-k. The output rank field is fixed and fresh: by/candidate fields cannot collide with rank or score. RRF consumes the authored input rank field but emits newly computed contiguous ranks, not the old positions. Nearest, text_rank and merge_rankings therefore compose directly with RRF. A later filter may create rank gaps; authors explicitly recompute row_number in the retained ranking order before using such a list as RRF input.
Score thresholds are ordinary explicit RelA filters after nearest,
score_vectors/vectors, or merge_rankings. The compiler MUST NOT invent
a threshold or silently change exact search into approximate retrieval.
Pure lexical ranking: text_rank
text_rank consumes the current Relation as corpus and names a finite query
Relation with queries. Its closed body requires queries, query_id,
query_text, candidate_id, candidate_text, tokenizer, scoring, and
positive safe integer k. Both identity fields are non-null string candidate
keys; text fields are non-null strings. Duplicate identities or malformed
text fail admission, not implicit deduplication. The profile is
actql-text-rank/0.1; it is pure computation over the two captured Relations.
Tokenizer is unicode_words or char_bigram. Both first apply the pinned
Unicode 16.0 NFKC and full default case-fold laws, returning NFC. Unicode_words
emits maximal runs of letters or numbers and following combining marks; a
run cannot start with a mark. Categories L*, N*, M* use the pinned Unicode
tables. Other scalars are delimiters. Char_bigram uses the same runs, emits
each adjacent pair of Unicode scalars without crossing a delimiter, and emits
a singleton token for a one-scalar run. Repeated tokens retain multiplicity.
No host locale, dictionary, stopword list, synonym expansion or stemming is
implicit. Token-size, token-count and normalized-text ceilings are resolved.
Scoring is exactly {bm25: {k1, b}}, with finite number k1 > 0 and
0 <= b <= 1. Corpus size N counts every admitted document, including empty
documents. Document length counts its tokens; df(t) counts documents with t.
Average length is total token count divided by N. Query terms are deduplicated
and enumerated in canonical string order. For each term present in a document,
its contribution is:
idf = ln(1 + (N - df + 0.5) / (df + 0.5))
norm = 1 - b + b * (document_length / average_length)
term_score = idf * ((tf * (k1 + 1)) / (tf + k1 * norm))Implementations MUST evaluate each displayed operation in its parenthesized tree order with binary64 nearest-ties-even rounding; ln is correctly rounded for its binary64 argument, not an unspecified host math function. Fold term contributions from positive zero in canonical query-term order with rounding after each addition. Non-finite arithmetic fails. An empty corpus, all-empty corpus or empty tokenized query yields no ranking rows, without division by zero. Unmatched terms contribute zero and documents with score zero are not emitted. Results expose query_id, candidate_id, non-null number score and non-null positive safe-integer rank; query groups sort by query identity, candidates by score descending then candidate identity ascending, retaining at most k. Input-order permutation does not change corpus statistics, scores or Result identity. Rank is the contiguous one-based position in the final per-query order after top-k, matching nearest and merge_rankings, including deterministic score ties.
Corpus statistics are computed only from the operation's actual admitted input, including its filters and authorization scope. They are not borrowed from an external index containing hidden documents. Scores are comparable only under the same profile, corpus and scoring contract; RRF does not require such score comparability. Output bound is query_count * min(k, corpus_count). Tokenization, corpus statistics, candidate scoring, work and bytes have separate resolved ceilings; k alone does not bound scoring work. The profile pins tokenizer tables, the complete numerical law and conformance vectors.
Before input use, an executable lexical realization MUST select finite per-token UTF-8 byte, per-text token-count and per-text normalized UTF-8 byte ceilings, normalization-work limits, and separate input-admission, corpus-statistics, candidate-scoring, output-ordering, value/temporary-byte and retained-state contracts. Repeated tokens count with multiplicity before query term deduplication. Every meter specifies quantity, unit, scope, aggregation, ceiling and failure behavior. The Host Relation-output ceiling and every other applicable selected logical limit remain enforced independently of k.
Work and byte-traffic meters MUST accumulate across the whole operation without resetting per corpus document, query or term. Per-token and per-text ceilings retain their declared scopes; peak state includes simultaneously live corpus, query, statistics, result and temporary state. Actual immutable sharing is not an imaginary copy. Empty corpus, empty tokenized queries and zero output scores do not waive the other input's admission or selected resource contracts. Incomplete validation, cancellation or any exceeded limit prevents partial ranking publication. Byte integrity of the selected lexical artifact does not prove execution-meter conformance or permit substituting an unpinned tokenizer or logarithm implementation.
The selected lexical profile MUST be exact canonical JSON with closed top-level
shape {version: "actql-text-rank-profile/0.1", unicode_version: "16.0.0", unicode_source_sha256, tokenizer_law, numeric_law, tokenizer_vectors_bytes_cid, numeric_vectors_bytes_cid}. unicode_source_sha256 MUST contain exactly
UnicodeData.txt, CompositionExclusions.txt, CaseFolding.txt,
NormalizationTest.txt and LICENSE.txt, each with a lowercase 64-hex SHA-256
of its original bytes. The two vector CIDs are SHA-256 CIDs of their exact
fixture bytes, not reserialized parsed values. tokenizer_law and numeric_law
are the complete closed law descriptors illustrated by the
selected Unicode 16 artifact:
their versions are actql-text-rank-tokenizers/0.1 and
actql-bm25-binary64/0.1, and their fields encode the normalization, tokenization,
statistics, binary64 arithmetic and ranking rules above. A different descriptor
does not select a Host-defined replacement law under the same version.
Check Environment selects the profile's byte CID independently. Admission MUST compare the original profile bytes to that pin, reject noncanonical bytes without repair, verify every required source/vector byte pin, and reject unsupported law descriptors before reporting that profile resolved. Required artifacts are locally provided within explicit input byte/node/depth ceilings; no ambient Unicode data or implicit download supplies a missing artifact. Matching hashes establish selected-byte integrity, not independent provenance or evaluator conformance. Profile resolution does not discharge tokenization, scoring, uniqueness, resource or publication obligations. These profile bytes and dependencies participate in Check Environment identity, not authored Program syntax; selecting or updating a profile never changes a source body implicitly.
External ranked acquisition: search_candidates
search_candidates consumes a finite query Relation. Its closed body requires
source, query_id, request, positive safe integer k, fields, and
outputs: {candidates: name, coverage: name}. Source names a declared
relation-valued search SourceView input used as an acquisition template, not
an additional initial read. Its Connector, Resource and view
resolve statically under ordinary SourceView resolution. The query_id field
is a non-null string candidate key of the query Relation. Request maps the
SourceView's dynamic request roles to common typed expressions over each
query row; remaining roles come from that input's static request. Together
they cover the exact request contract without duplication. Fields reuses
select's projection/rename grammar over the response schema.
The step has no as: outputs binds two distinct dataflow names in the ordinary
namespace. It does not change the implicit current Relation; a later consumer
selects an output explicitly with from. Both outputs are published together
only when the whole acquisition succeeds. They cannot refer forward or shadow
an input/preceding binding. This is the sole multi-output binding form here,
not a general fetch DAG or row-selected Connector mechanism.
The SourceView contract declares dynamic request roles, response candidate
identity, total ranking evidence, optional score contract, per-query extent,
completion scope, pagination and native batch capability. Exactness is
exact or approximate relative to that declared search contract, separate
from completeness of the external domain. ANN uses this operation with typed
vector request roles and pinned index/model/search configuration; it is not
an approximate implementation of nearest. k is a requested ranked prefix,
not a claim that the prefix enumerates every matching external entity.
Candidates output contains fixed query_id, candidate_id, rank and score fields plus the selected response fields. Candidate identity preserves its declared non-null key type; rank is a positive one-based safe integer; score is a nullable number, null exactly when the SourceView supplies no score. Projected names cannot collide with fixed roles. Candidate identities are unique per query; provider scores/order are validated against the declared contract and captured rather than recomputed by an unspecified local ranking algorithm. Canonical output is query identity then rank. Capture records the complete ordered response evidence needed to replay that prefix.
Coverage output contains exactly one row per input query, including zero-hit queries: query_id, returned_count (nonnegative safe integer), observation_completeness (complete|partial), exactness (exact|approximate), truncated (boolean), and observation_cid (string). Enum values are closed string contracts. Completeness is relative to the pinned request scope, not the whole Resource. The evidence binds that scope, requested k, actual request, pagination termination and response identities; partial prefixes are never reported as complete domain scans. Zero input queries yields two typed empty Relations and no provider dispatch.
The Host MUST admit request-value policy and egress before dispatch and bind row, request, response, attempt, cost, duration and byte ceilings to the Plan. Request expressions respect the common failure rules. Acquisition, schema, identity, authorization or envelope failure fails the operation rather than producing an empty candidate list or successful coverage row. Transport deduplication/batching preserves each logical query's request, ordering, coverage and accounting evidence, and follows the SourceView capability. Replay reuses the captured responses without issuing a fresh search. Checking and Planning perform no implicit provider search.
Local anti joins over these captured candidates remain ordinary Relation computations. They prove absence only from that finite captured set, not from the external domain. A consumer or Effect requiring external absence needs independent complete evidence; no-hit ranked search alone cannot satisfy it. This is not a blanket prohibition on subsequent joins or Effects.
Model acquisition: infer
The public operation name is infer; the module profile is
actql-infer/0.1. ModelTask readers retain actql.model_task/0.1 for
structured generation and admit actql.model_task/0.2 for the explicit
structured-generation or Decision discriminator.
Model inference is acquisition of an externally generated observation. It is
neither pure algebra nor a business-state mutation Effect.
- infer:
contract: model-task:ticket-triage.v1
input:
subject: {field: subject}
body: {field: body}
as: ticket_triageinfer MUST consume a Relation, declare a unique as result, and map every
and only the closed ModelTask input fields from type-compatible source fields.
input is an explicit model-egress manifest. It MUST NOT be inferred from
equal field names.
key is either one field or a duplicate-free field tuple containing at least
two fields:
InferKey := FieldName | [FieldName, FieldName, ...]Every key field MUST be non-null and scalar, and the complete tuple MUST equal
one compiler-proven candidate key. When the input has exactly one candidate
key, key MAY be omitted and the compiler MUST record its unique completion.
When zero or more than one candidate key remains, the author MUST select one
explicitly. Key fields correlate requests and results; they are sent to the
model only when also named by input.
The result is a new Relation containing the key fields in authored tuple order,
then outcome and value. Outcome is ok or failed: followed by exactly
rate_limit, timeout, provider_error, refusal, schema_invalid,
safety_rejected or truncated. Value is the ModelTask's non-null closed output
object on success and null on failure. Successful {summary: null} is distinct
from a failed null value. Filtering outcome = 'ok' refines value to the original
non-null success contract. Keys cannot collide with these fixed output roles.
There is no implicit source-row copy or join-back.
The resolved ModelTask contract MUST fix task and prompt identity, an authored
model selector, exact closed input and output contracts, kind-specific settings,
accepted data classifications and regions, forbidden training use, and
tool_policy: none, retrieval_policy: none, and streaming: false.
A Decision Task MUST bind the registered actql.decision_prompt/0.1 payload
through its existing Model prompt envelope and CID, and its declared output
contract MUST equal the closed answer object derived from every question.
Question type is exactly choice, score, or boolean; no kind alias or
Boolean criteria is admitted in this initial payload. Decision settings and
generation settings MUST NOT coexist or supply defaults for one another.
Instructions, option descriptions and level descriptions MUST be present String values, preserving empty Strings, Unicode and newlines; absence and null are not empty Strings. Question IDs, state-input selectors, type and format remain nonempty under the existing contract.
The generic actql.decision_answers/0.1 shape does not impose two decimal
places. The initial TypeSafe/Jev physical probability profile selects two
places, half-unit tolerance 0.005, arithmetic margin 0.000001 and scaled-grid
margin 0.0000000001. Other precision requires an explicit supported binding,
not a provider-reported tolerance. Profile selection is not evidence that a
provider implements Decision; unsupported execution profiles reject before
dispatch. See Decision contracts.
max_items is an author guard as defined in Author guards, resolved limits, and Plan facts, counting distinct input key
tuples. Its resolved-limit candidates are the Program-proven input bound,
ModelTask Contract ceiling, language ceiling, Host ceiling, and authored guard
when present. The runtime MUST materialize and count the distinct key tuples
before the first provider dispatch. A limit breach therefore causes zero
provider calls.
The compiler derives provider-call, request-byte, token, and duration bounds
from the resolved item limit, the ModelTask contract, Host policy, and checked arithmetic.
Physical bounds belong to the inference barrier. The default result mode is
outcomes; source may explicitly select on_non_success: fail for strict
publication. Omission normalizes to outcomes. A logical key owns one fixed
model-call slot, with finite Contract/Host retry bounds for classified transient
rate limits, timeouts and provider errors. Refusal, invalid schema, safety
rejection and truncation are not automatically retried. Request, model, prompt,
settings and schema do not change between attempts. No provider-text guessing
or hidden retry is admitted.
Every attempt MUST reserve and account for attempt, token, cost, duration and wire budgets, including failed or lost responses. Unknown usage retains its reservation rather than becoming zero. The first valid completion of the currently active attempt seals the slot; later obsolete responses cannot replace it and their costs remain evidence. Retry backoff is bounded Host work. Atomic publication follows capture of all slot outcomes: N failed slots emit N failure rows, not an empty successful observation; zero keys emit a typed empty Relation with zero calls. Strict mode publishes no Relation if a slot fails, while retaining private observations and cost evidence. Unattempted slots are not fabricated provider failures.
Authorization, egress, invalid request or artifact, duplicate identity, global capacity, engine failure and cancellation MUST fail the barrier, not become ordinary provider-failure rows. Local or global context-capacity violations are admission failures, not retriable model outcomes. Exact replay consumes the captured outcomes with no model dispatch, retry or wait.
All call slots, mapped requests, request bytes, exact adapter plans, tokenizer
results where text tokenization applies, and closed worst-case media/token
reservations MUST be preflighted before the first provider
dispatch. Total bounds are derived from the resolved item limit and per-item contract or
Host limits using checked safe-integer arithmetic.
The Connector and Host own source classification; an author may only strengthen
it with minimum_classification. Unknown classification denies model egress.
Before provider access, the runtime MUST reject input whose effective data classification is not admitted by the ModelTask data-handling contract. The input Relation is processed in canonical key order, while provider completion order has no semantic meaning.
Every successful call MUST emit versioned evidence binding the ModelTask, semantic request, call-slot identity, input-result artifact, input-row identity, authored and provider-reported model observations, provider request observation, attempt count, structured-response artifact, validated structured output, token observations, finish observation and Host observation time. Exact historical 0.1 evidence retains its required fields and original identity. New invocation evidence 0.2 records unavailable defined provider fields and each unavailable token quantity as explicit NULL, never guessed zero or an SDK-generated provider identity. Complete checked response evidence binds the selected response profile and its completeness assertion independently of optional finish reason. Raw response bytes and canonical structured-response artifacts are distinct identities. Exact replay reuses the captured response and its original evidence; a fresh model call creates new evidence and a new Invocation identity. New capture bundles MAY embed exact historical and current invocation evidence records together.
Each record's explicit version selects its own closed schema and its original self CID; the restoring Owner MUST bind each record to the original request, input and typed output. A bundle version does not relabel its embedded observations.
Historical bundles retain their exact historical schema and CID, and MUST NOT embed current evidence.
Unknown versions, discriminator mismatches, changed original CIDs and crossbound records MUST be rejected.
Additional provider metadata MUST be bounded opaque JSON object data, or NULL when unobserved. An empty object denotes an actually observed empty object. Preserve unknown fields and original string values; do not infer permissions, response completeness, answer precision or retries from them. Metadata shares the existing response/evidence byte, node, depth and funded-retention limits. Reuse original raw/structured Artifact custody where available; do not create a second body-sized copy or independent metadata Store. An unknown metadata field does not make an otherwise required answer malformed.
Effect Preview derived from an inference result MUST bind every actually
depended-on model_slot_capture CID, including failed outcomes. Each capture
binds exact ModelTask, fixed request/input identity, ordered attempts, adopted
outcome, accounting, authority, attachment admission and policy evidence.
An ok slot requires validated typed value, actual response artifact and response
issuer evidence (including the successful model-invocation evidence). A failure
slot requires classification evidence and no typed value; response_artifact_cid
and response_issuer_evidence_cid are null when no response arrived, not fabricated
empty artifacts. If a raw error response exists it is retained privately with
its real issuer evidence. Timeout-no-response may feed a failure-notification
Result/Preview in outcomes mode, retaining the original model policy/provenance.
Strict mode exposes no such Relation. Replay and notification use the original
capture; neither issues a replacement model call. Effect apply MUST NOT
rerun inference. Graph, vectors, or events enter infer only after an explicit
row bridge. The profile rejects resource_ref model inputs. It admits typed
content_ref inputs and bounded homogeneous arrays under the ModelTask media
contract: MIME, content identity, complete/truncated status, byte/page/frame
limits, transfer method, classifications, region and egress scope are explicit.
Issuer, current scope, permission, availability and exact content bytes are
verified before dispatch. Content equality does not erase Ref scope identity.
PDF, image, audio and video may be model inputs without an extraction registry;
this does not authorize hidden SourceView extraction. Raw bytes/base64 are not
Program values. Adapter uploads are bounded authorized model transport, not
hidden business mutations. Tool use, hosted search/retrieval, code interpreters,
streaming downstream, dynamic model or prompt selection, embedding generation,
and reranking are outside actql-infer/0.1. Cross-Run model cache is deferred;
same-observation replay and same-slot retry do not introduce one.
The installed complete-byte media profile resolves every role in
media_input_contracts to an actql-model-media-input/0.1 dependency. Its closed
envelope contains version, payload and self_cid; the CID hashes the
canonical envelope without self_cid. The closed payload contains
media_type, positive safe-integer max_bytes, explicitly nullable positive
max_pages and max_frames, complete: true, transfer_method: "inline_bytes",
nonempty duplicate-free classifications and regions, and
egress_scope_cid. NULL page/frame ceilings mean not applicable, not permission
for unlimited parsing of that medium. Missing bodies, substituted identities,
duplicate role bindings and unreferenced bodies are rejected; all dependencies
share the installed byte/node/work envelope. This initial profile does not
admit truncated content, author-supplied URLs or another transfer method.
Media roles are matched to the ModelTask's declared input fields, not observed rows. A role must be a direct ContentRef or a homogeneous ContentRef array with an explicit finite item bound. Its declared MIME restriction, when present, must match the dependency's MIME. A broad ContentRef type does not waive the dependency's runtime MIME restriction. Missing media bindings, unknown roles, scalar roles, nested Content containers and unbounded arrays are rejected by this installed profile. Nullable inputs and empty Relations do not remove these static obligations. This matching uses the existing bounded dependency admission envelope and retains the exact checked role/body/CID binding.
Dependency admission is not attachment admission. The physical Model preflight must additionally bind each original Ref and content identity, current issuer authority, exact retained bytes, applicable media limits and current egress permission to the same checked role and call. A syntactically valid body or ContentRef envelope is not such evidence. Request preparation checks the original opaque Ref's availability before emitting its input frame, including references inside arrays and objects; that check alone does not establish physical byte validation or replace the dispatch-time authority fence.
Physical preflight receives the smaller of the current Model quantum and its remaining work budget. It reports the actual consumed work on each advance, including failed validation; the common bounded Model owner adds it once. Exact-byte hashing charges one work per examined byte, with at most 256 bytes per byte-check update and no more than the admitted quantum across an advance. Completion/control transitions are separately charged; adapters cannot count a whole attachment as one poll. Invalid work reports stop without provider dispatch. Work exhaustion never resumes preparation or dispatch, and pending physical cleanup retains its existing reservation until release succeeds.
The physical owner issues an actql-model-attachment-admission/0.1 observation
for each call with non-NULL Content attachments. Its ordered inventory binds
each checked role and array ordinal (or -1 for a direct role), original Ref CID,
validated content CID and size, media dependency CID, effective policy,
classification, region, egress scope, applicable page/frame counts and complete
inline-byte transport. The record also binds the call slot, ModelTask, fixed
request and actual dispatch authority. Its CID enters that slot's
attachment_admission_cids; it is saved through the same private capture Store
before settlement is acknowledged. A scalar or all-NULL call has an empty
inventory. Saved restoration verifies the exact referenced record and its call
and authority binding. This observation neither extends byte custody nor
authorizes another provider dispatch.
A model slot identity binds the Program/Stage definitions, effective inputs, input artifact and key, ModelTask and fixed request. It excludes future response and Stage Invocation CIDs. Captured attempts and the adopted outcome then enter the ObservationSet, from which the Stage Invocation is computed.
An INFER with ON FAILURE FAIL MUST withhold successful publication of the
whole step when any row remains unsuccessful after its bounded retry policy,
while retaining original call evidence and costs and preventing partial
results from reaching later steps or Effects.
MAX ITEMS guards distinct admitted item keys under the common resolved-limit
rules. It grants neither permission nor additional retries. The text clauses
normalize into the existing inference barrier: ON FAILURE FAIL is
on_non_success: fail, and MAX ITEMS n is max_items.
External SourceView joins
lookup is not an author-visible operation. Authors declare a keyed
SourceView as an input and use the ordinary RelA join operation for both
local and cross-system relations:
inputs:
customers: {connector: crm/main, resource: customer, view: active}
risks: {connector: risk/main, resource: risk_record, view: by_customer}
from: customers
steps:
- top_k: {k: 100, by: [{score: desc}, {customer_id: asc}]}
as: bounded_customers
- join:
with: risks
kind: left
on: [customer_id]
null_key: fail
select:
customer_id: left.customer_id
risk: right.risk
as: customers_with_riskAn ActQL Program MUST NOT contain a lookup step. When the right input is a
keyed SourceView, Check MUST lower the logical join to an
actql-lookup/0.1 acquisition barrier followed by an ordinary RelA join. The
barrier MUST NOT copy source rows or perform the logical join itself. The
logical join kind, equality keys, null behavior, selected fields, the authored
join cardinality assertion, and any optional output guard remain author-visible.
Key deduplication, batching, paging, pushdown, materialization, and local join
strategy are compiler/runtime choices.
The external right input MUST resolve one pinned relation-valued search,
fetch, or follow SourceView. Its default read mode MUST be a bounded
snapshot or bounded changes mode. The left Relation MUST have a
compiler-proven positive finite row bound. The authored equality keys MUST
match every and only the SourceView's correlation response roles. The
SourceView MUST prove a complete response for each requested key. Its response
cardinality (zero_or_one | zero_or_many | one | one_or_many) and capacity
contract determine the possible response extent. max_output_rows is an author
guard as defined in Author guards, resolved limits, and Plan facts; the logical join's limit uses the transfer rules in
join, including any authored join cardinality assertion. The acquisition
response limit is resolved separately as specified below. The effective data
policy MUST be admitted before dispatch. The selected physical strategy MUST
fit the sealed request, byte, attempt, cost, and duration envelope.
If the left key set is unbounded, Check fails with
ACTQL_E_UNBOUNDED_EXTERNAL_JOIN. A truncating hard_limit_only response is
not a legal implementation of join semantics and fails with
ACTQL_E_EXTERNAL_JOIN_COMPLETENESS_UNPROVEN. Check MUST NOT require the author
to repeat a transport limit as max_items.
The selected SourceView's output.facts Contract Envelope of kind
carrier_facts carries an optional body.lookup_capacity component. Its
closed schema is contract_bodies.carrier_facts.properties.lookup_capacity in
actql-check-environment.json. When present, all five members are required:
version: "actql-lookup-capacity/0.1", completeness, max_rows_per_key,
max_pages_per_request and max_response_bytes_per_page. The three bounds
MUST be positive safe integers. completeness is exactly
complete_per_requested_key or hard_limit_only; only the former can support
a keyed join's completeness obligation. Missing capacity remains unknown;
null, malformed components and unknown component members MUST be rejected,
including on unused views. Other carrier-facts members are not prohibited by
this component schema and do not gain evidence meaning from passing it.
max_rows_per_key bounds the complete logical response for one distinct
requested correlation tuple across all pages, before the ordinary join.
max_pages_per_request bounds successful logical response pages for one
request tuple or one native batch; retries are not additional logical pages.
max_response_bytes_per_page bounds response-body octets after content
decoding for each such page, including the response envelope, not canonical
row bytes, headers or compressed transport octets. Host decoded-body limits
MUST accommodate this worst-case logical response. Separate transport and
attempt limits MUST cover retries, redirects and failed responses; this page
bound alone is not evidence for their wire-byte extent. Capacity does not
change response cardinality or permit truncating a complete response.
Capacity admission MUST retain the selected SourceView, its exact facts
reference and the containing Contract Envelope CID. The component has no
independent self-CID and is covered by that envelope's existing preimage.
An equal bound from another source is not interchangeable evidence. Neither
shape validation nor a declaration of completeness proves Catalog authority,
actual response completeness, identity uniqueness or successful enforcement.
A generic root completeness: "complete" claim MUST NOT be promoted to
complete_per_requested_key. Runtime acquisition retains its required
response validation and atomic failure obligations.
The SourceView owns relation schema, candidate keys, correlation
request/response roles, response identity, per-key completeness, response
cardinality, and admitted access paths. Static request fields outside the
correlation roles MUST be supplied under the SourceView input's request and
MUST type-check against the closed request contract. Correlation request fields
MUST NOT be supplied there because the join key owns them. Together, the join
keys and static request fields MUST consume every and only the SourceView
request fields.
Provider, Resource, SourceView, request roles, response roles, identity,
response cardinality, and pagination MUST NOT be selected by row data.
Check MUST compare the set of authored right-side equality fields with the correlation response roles, rejecting missing or extra roles without rejecting repeated equality pairs. Request tuple components follow the Catalog's correlation request/response pair order. For each response role, its request value comes from the left field of the first authored equality pair naming that role. All authored equality pairs, including subsequent uses of that response role with another left field, remain ordinary join predicates in their original order. The null-request policy examines every authored left join-key component, not just the selected request representatives. This mapping does not coerce values or prove their admission to the request contracts, response completeness, left bounds or successful acquisition.
inner, left, semi, and anti joins may use a complete keyed access path.
An anti join may treat no response as absence only because completeness is
proved for each requested key. Check MUST reject a full join against a
keyed-only SourceView because it cannot observe right-side rows outside the
left key set. Full-Relation semantics require a bounded complete-scan
SourceView.
The compiler canonical-deduplicates request tuples for transport only and then
joins responses according to the authored RelA semantics. Repeated left keys
therefore retain their authored multiplicity. With omitted null_key, a row
with any null join-key component MUST be excluded from the provider request and
remain available to the ordinary RelA join; left and anti therefore retain
it while inner and semi do not. For null_key: fail, discovery of such a
row MUST fail before provider dispatch. The Lookup Barrier binds this as
null_request_policy: exclude_from_request|fail, and its evidence binds both
the exact request-key-set CID and the number of excluded null-key rows.
Unrequested response keys, excess response cardinality, duplicate response
identity, and external non-success MUST fail the acquisition atomically. The
canonical Lookup Barrier MUST resolve missing requested keys from response
cardinality by exactly this rule:
zero_or_one and zero_or_many emit no acquired row for a missing key; one
and one_or_many fail on a missing key. These laws are explicit in the Lookup
Barrier and are not source options.
A native batch MUST bind distinct lookup_request_key,
lookup_batch_request, and lookup_response_key contracts, a positive
max_keys_per_request, and the claim preserves_per_key_semantics: true.
Every positive acquisition bound required by the actql-lookup-barrier/0.1
schema MUST be present in the canonical Lookup Barrier. The schema is
schemas.lookup_barrier in the
production-profile schema registry;
Typed barriers and Plan Documents defines its Plan binding. The compiler derives these bounds from the left
Relation row bound, SourceView batching and capacity facts, and pinned Host
execution policy.
max_distinct_keys is at most the left row bound. If the SourceView omits a
native batch capability, max_keys_per_batch is one. Checked arithmetic MUST
reject overflow, absent capacity evidence, or a Host byte cap smaller than the
Connector's worst-case logical response. No request, page, retry, byte, or
deadline arithmetic is Program vocabulary. The acquisition's
max_response_rows is derived independently of the logical join's
max_output_rows; a final semi/anti/output cap MUST NOT truncate the response
needed to prove per-key completeness.
The remaining request, batch, page, attempt, byte, and duration limits are physical execution constraints and MUST NOT appear in Program source.
The selected Check Environment's host_policy_profile.lookup_execution
component MUST follow policy_components.lookup_execution.schema in
actql-check-environment.json: a closed object with
version: "actql-lookup-execution-policy/0.1" and six positive safe-integer
ceilings: max_network_attempts_per_request, max_request_bytes_per_attempt,
max_total_request_bytes, max_total_response_bytes,
max_total_decoded_response_bytes and max_total_duration_ms.
Absence leaves lookup execution unresolved without defaults. A present null or
invalid component MUST fail component admission even when no lookup uses it.
Its identity is the canonical CID of the entire component with no exclusions;
admission retains its exact Check Environment membership.
Lookup execution MUST enforce these ceilings against the existing
lookup accounting counters: request octets
per attempt and in total, response_wire_bytes for max_total_response_bytes,
and response_decoded_bytes for max_total_decoded_response_bytes. Neither
response total substitutes for the other. Redirects, failed attempts, and
conservative reservations for unresolved usage remain charged by that protocol.
The attempt ceiling includes redirect network dispatches as well as initial
dispatch and retries for one logical request. max_total_duration_ms limits
the acquisition-wide elapsed deadline, including resumed waiting, not a sum of
parallel attempt durations or a fresh deadline on resume.
Static capacity resolution MUST check that the SourceView's worst-case complete logical decoded response fits the selected decoded-response ceiling using checked arithmetic. This does not prove the wire extent of a compressed response. Selected transport plans additionally fit their own request, response, redirect and timeout constraints and the aggregate envelope. In particular, the existing HTTP network policy retains its separate compressed/decompressed per-request bounds; this Host component does not replace them. Declaring a maximum retry count grants no retry capability and does not guarantee that all retries fit the remaining byte or deadline budgets. Dispatch and unresolved-usage reservations remain subject to those budgets before further work is admitted.
The capacity proof binds the left row bound, SourceView and carrier-facts CIDs,
Host-policy CID, and every resolved bound. complete concerns one successful
logical response for all requested keys. Redirect, retry, and failed-attempt
bodies count toward transport wire-byte and attempt limits, not as logical
result data. Upstream-candidate completeness and complete response for
requested keys are separate evidence axes; one MUST NOT be promoted from the
other. Canonical lowering merges exact identical acquisitions into one barrier,
using the following complete CSE identity; this is not an optional graph rewrite. Its CSE
identity includes the named SourceView input, effective response policy CID,
null-request policy, observed response fields, left Relation, pinned contract,
static request, and acquisition bounds. Different logical
join output caps do not change acquisition identity. Acquisitions with
different input aliases, response policies, null policies, or observed field
sets MUST NOT be fused.
The acquired Relation is ordered by canonical request tuple followed by
response identity. A keyed SourceView input may author only connector,
resource, view, non-correlated static request, and
minimum_classification, and meta (Authoring Context); it MUST NOT author scan. Local schema and scan
fields MUST be rejected rather than ignored. Check MUST resolve its response policy as the
maximum of the authoritative SourceView policy and the named input's minimum,
then resolve the barrier output policy from both request-input and acquired
response provenance. The barrier and downstream ResultDefinitions bind all
source policy CIDs. Compile time produces egress requirements, not an admission
claim. Concrete ContentRef admission follows Reference admission and availability. Immediately before
provider access, runtime MUST recheck sensitivity, completeness, MIME, bytes,
availability, provider-account boundary, region, and current Host policy
against the bound egress requirements. Failure performs zero provider calls.
The effective response policy MUST use the closed preimage in
actql-source-response-policy.json
/schemas/policy: {version:"actql-source-response-policy/0.1", source_ref, source_view_contract_cid, source_data_policy_cid, minimum_classification, classification}. source_ref is the selected concrete SourceView's admitted
data-policy source reference; both contract CIDs identify that exact selected
SourceView and its authoritative data-policy Contract Envelope. The minimum is
the authored input minimum or null when absent. Authoritative unknown remains
unknown; otherwise classification is the maximum in
public < internal < confidential < restricted, with absence preserving the
authoritative classification. The CID hashes the entire canonical JSON UTF-8
preimage using SHA-256, without a self-CID field.
This same response-policy projection applies to direct SourceView acquisition.
Input aliases are bound by the surrounding acquisition identity rather than
added here. Request-input provenance is combined separately for the barrier
output and egress requirements; it is not part of this response-only policy.
Observations, snapshot tokens, permissions, Host ceilings and the whole Catalog
are not response-policy preimage fields. Equal policy identities do not authorize
retargeting a request, substituting another named input, or skipping current
egress admission. Shape admission alone does not establish the selected-reference
correspondence or the classification calculation.
Request policy composition MUST retain the contributing value provenance and
its admitted policy references separately from the response policy; preserve
unknown when any required contributing classification is unknown; and otherwise
take the maximum known classification independent of traversal or operand order.
Being an authored literal, a scalar Parameter, a defaulted value or a successfully
admitted typed value is not by itself evidence of a public or internal classification.
Missing provenance is not repaired by assigning a default classification or by
attributing the value to every input in the Program. An unknown classification
does not by itself invalidate ordinary algebra; it does not confer permission to
dispatch either. Egress still requires admission under the applicable destination
data-handling contract and current Host policy, including the existing rejection
of unknown classification for model egress. This classification calculation does
not supply missing provenance or define a substitute effective-policy preimage.
The Host may bind verified policy evidence for the complete static value set of one checked Source Request. Such evidence MUST bind the original Program Definition CID, effective Parameter Values CID, Stage/input occurrence, selected SourceView contract CID, and the canonical identity of its exact bound static request values. It MUST retain the contributing admitted data-policy Envelope from the same selected Environment separately from the response policy. The Host MUST verify all of those value contributors; the response classification alone does not establish their provenance. Authored Programs cannot self-assert this evidence. Missing, duplicate, foreign or mismatched evidence MUST NOT resolve unknown provenance. Unresolved Host bindings cannot be attested as concrete request values. Correlated keyed values still require their own producer policy. Default versus supplied Parameter origin remains a separate correspondence even when the effective Parameter Values CID and request bytes are identical; an attestation for one origin cannot be reused for the other without verification.
Policy evidence for request values is not provider access or Model approval. Current egress, authorization, scope and approval checks remain independent, and an admitted policy whose classification is unknown remains unknown. Rechecking or recovery MUST establish the original correspondence before using the evidence; equal effective-policy CIDs do not permit transplanting it to another request.
An effective data policy MUST use the closed preimage in
actql-effective-data-policy.json at /schemas/policy, hashing the entire
canonical JSON UTF-8 object without exclusions. Its two reference arrays contain
ascending, unique full CIDs of the contributing admitted source data-policy
Contract Envelopes and response-policy objects respectively. A response reference
also requires its exact authoritative source data-policy reference in the source
array. Every reference must resolve to the policy actually selected for a
contributing value; unrelated Catalog entries are not contributors.
unknown_provenance MUST be true if any contributing value lacks admitted policy
provenance. Classification is unknown if that flag is true, any contributing
policy is unknown, or both reference sets are empty; otherwise it is the maximum
classification of all referenced policies. Known references remain present even
when the result is unknown. No public classification is inferred for an empty
set. A declaration minimum remains represented by the response-policy reference,
not by modifying the authoritative data-policy Envelope.
Policy-set deduplication MUST NOT discard the separate correspondence from each contributing value occurrence to its policy references or missing provenance. This preimage identifies the effective policy, not the value, Program, request, observation or permission. Those subjects bind the policy CID through their own contracts. Their owner handles, source ordinals, actual data, snapshot tokens and the whole Check Environment are not added to this preimage. Equal policy CIDs do not establish equal requests or permit exchanging provenance between owners. Schema admission checks shape; reference authority, exact contribution selection, array ordering, response-to-source correspondence and classification calculation require semantic verification before the policy can be used.
Static publication policy analysis for unpivot MUST retain input policy and
the exact authored column-reference sequence, including repetitions. The canonical
reference labels emitted into name_column are value contributions, not merely
structural output names. Without admitted value provenance their classification
remains unknown; authorship does not confer a public classification. Empty input
or a later projection hiding the label column does not remove these static
contributions. Repeated columns retain their occurrence identity and expansion
obligations without duplicating effective-policy CID set members. This fixes the
label contribution boundary, not a new effective-policy preimage or egress grant.
Static publication policy analysis for pivot MUST retain its input policy
provenance, the exact authored bucket-value sequence and any authored fill with
their value provenance. Bucket strings are comparison values as well as generated
column names; their role is different from index-field references or the aggregate
function's name. Missing admitted provenance for bucket or fill values remains
unknown, without discarding known input-policy references. An empty bucket list
does not invent a contributing selector, but it does not waive an authored fill's
validation or provenance. Null or undeclared input column values still contribute
index groups, so their input policy is not removed merely because they contribute
to no generated bucket. This fixes pivot's static contribution boundary; it does
not grant publication or egress authority or change the effective-policy preimage.
When the SourceView contract admits response projection, the compiler MUST bind
the observed response field set as the union of right-side fields published by
join.select, correlation fields, and response-identity fields needed for
evidence. The Lookup Barrier binds observed, correlation, and identity field
sets separately; the logical join binds the published right-side set. Fields
outside that union are not requested merely to make acquisition sharing easier.
Without a projection claim, the barrier declares the full SourceView response
schema as observed rather than pretending that provider egress was narrowed.
The public Plan summary MUST derive every external-join entry from the structured logical-join-to-acquisition binding, not from source order, physical node order, or normalization presentation text.
Content serialization: serialize
The public operation name is serialize and the module profile is
actql-content/0.1. It converts one explicitly ordered Relation into one
content-addressed Content and returns a one-row Relation carrying its
content; it is distinct from ordinary Result delivery. It MUST NOT upload
the Content implicitly.
The Content barrier MUST bind every input data-policy CID and the effective output data-policy CID. Content derivation preserves policy exactly: the barrier output policy, emitted ContentRef metadata policy, issuer-evidence policy, and derivation-evidence policy MUST be identical. A later Invocation receiving that ContentRef therefore cannot replace its original policy with a weaker local-input default.
- serialize:
format: csv
fields: [customer_id, risk, observed_at]
order_by: [{customer_id: asc}]
encoding: utf-8
line_ending: crlf
header: true
field_delimiter: ","
null_marker: ""
name: risk-export.csv
as: risk_exportformat is exactly csv or jsonl; encoding is utf-8 or utf-8-bom, and
line ending is lf or crlf. Authored UTF-8, UTF-8-BOM, LF and CRLF
are also accepted and resolve to those lowercase profile tokens through the
common checker in every source format. This finite option resolution does not
normalize names, field values or the NULL marker. All source formats resolve omitted encoding to
utf-8 and line ending to lf. name is a mandatory nonempty label, not a
destination. CSV requires explicit header and null_marker; its omitted
field_delimiter resolves to comma. The delimiter is exactly comma or semicolon,
and the NULL marker is exactly empty text or the two-character string \N.
JSONL rejects all three CSV-only options. The old nested csv object and
logical_name option are not alternative spellings.
CSV MUST use U+0022 as its quote character; this is a serialization-profile law, not an author field. Header cells are quoted exactly when they contain the selected delimiter, quote, CR or LF. String, date and timestamp data cells are always quoted, including ordinary strings, empty strings and a string equal to the NULL marker. Quotes are doubled. Number, Boolean and exact decimal cells use the existing common scalar spelling without quotes; NULL uses the unquoted marker. Structured and reference cells contain the existing canonical typed wire value inside one quoted CSV cell. References are not dereferenced. No Unicode normalization, newline conversion inside cells or spreadsheet formula escaping is applied. This Content framing does not change existing Result delivery's always-quoted headers, comma, LF or fixed NULL spelling.
CSV requires every selected column to have a Required presence declaration;
nullable values are independently allowed. A Conditional column is rejected
even if every sampled row happens to contain it. JSONL preserves a missing
Conditional field by omitting its key and an explicit NULL by emitting JSON
null. It uses the common typed wire representation, including exact decimal,
JSON-domain and reference representation, in the specified field sequence.
Each complete record ends with the selected line ending. A BOM, when selected,
is emitted once at the beginning, including an empty input. Empty CSV consists
of that optional BOM and optional header with its line ending; empty JSONL
consists only of its optional BOM.
fields MUST be the exact emitted field sequence. The operation MUST specify
order_by or preserve_order: true, not both. order_by requires a checked
total stable order; preserve_order requires an active upstream total-order
witness, not provider arrival order. bounds.max_rows and
bounds.max_bytes are author guards as defined in Author guards, resolved limits, and Plan facts, counting input rows
and serialized bytes respectively. The machine barrier MUST contain both
resolved limits, using Program proof, Host policy, and any authored guards.
A downstream Command Contract is checked independently during Effect preflight;
serialization does not infer future consumers by reverse dataflow. A breach
publishes neither a partial
Content nor a partial output Relation.
Before a Content serialization barrier is admitted, the input Relation MUST
have either a compiler-proven candidate key or a total stable ordering witness
from an algebra contract. When a candidate key closes authored ordering ties,
its omitted fields are appended ascending and journaled. Authored order_by
alone cannot prove that ties are impossible.
The output fields, in order, are exactly name, content, media_type,
size_bytes and row_count. Even an empty typed input produces one output row.
The barrier input CID is derivation evidence,
not business data. Only the host
Content Store may issue the content_ref. ActQL source MUST NOT contain
bytes, base64, storage locators, or an author-supplied content CID.
Object/file/stage persistence uses the Connector's typed store Effect over
the resulting content_ref. Attachment, send and other declared operations
use typed command Effects; neither kind serializes rows implicitly.
serialize creates an Invocation-scoped immutable Content. Workspace
persistence, retention, and later deletion are not serialization semantics;
they require an explicit typed store Effect or Run Graph policy. CSV
serialization is byte-faithful and performs no implicit spreadsheet formula
escaping. Authors apply any business transformation before serialization.
Artifact-backed acquisition
ActQL has no media profile and no extract_text, transcribe, ocr, or
provider-feature operation family. A captured PDF, image, or audio object is an
content_ref; already extracted information may be read as a typed Relation.
A keyed SourceView accepting an ContentRef is subject to exactly the same
observation classification as a direct SourceView (Connector SourceViews). It may read existing
structured results or capture bytes. Newly interpreting a document requires a
registered extraction profile; SourceView transformation profiles currently registers none. Naming a
View text or transcription does not authorize that work.
Accepted MIME types, full-artifact requirements, byte limits, schema,
completeness, data policy, and egress scope remain explicit Contract terms.
An asynchronous provider flow that requires submit, wait/poll, and result fetch is not one hidden acquisition. Submission is a typed Command, waiting and outcome routing belong to the Run Graph, and the completed bytes or result are captured by a later SourceView. Model-based extraction uses an explicitly admitted ModelTask, not a hidden SourceView callback. Local byte transformations likewise require a registered profile. Modality alone MUST NOT select execution semantics.
Named Results and Effect declarations
Named Results
This subsection defines stage-local ResultDefinition values. Program-root
Results instead use the explicit Stage Result export grammar in Stages.
They inherit this closed Result contract and cannot reselect its fields.
Every ActQL Program MUST declare a map of closed named results. An empty
Program-root results map is permitted only when the Program contains at least
one Effect validated by the common Checker, including its input, target,
Mapping and Capability requirements. A stage with no local Results likewise
requires an Effect. A Program with neither public Results nor Effects MUST be
rejected. SQL spelling, Connector names and self-declared flags do not establish
this exception.
Checked Effects are execution-plan roots, including when there are no public Results. Execution, approval, recovery and release MUST retain their ordinary owner contracts; no hidden public Relation is generated. Unapproved, unknown commit and unresolved Effect states MUST NOT be treated as successful completion. Recovery reconciles the original operation and its saved receipt; it MUST NOT automatically resend an unknown operation. Private Table capture preserves absence separately from NULL without relaxing the normal Named Result all-Required contract. Releasing or cancelling the origin Run does not delete a committed persistent Table.
Each member is a logical publication boundary and may be consumed by an Effect or retrieved independently after the Run has materialized it:
steps:
- top_k:
k: 20
by: [{score: desc}, {product_id: asc}]
as: ranked_candidates
results:
response:
from: ranked_candidates
fields: [customer_id, product_id, score]
order_by:
- {customer_id: asc}
- {score: desc}
- {product_id: asc}A named Result is a Relation. A ResultDefinition.from is REQUIRED and MUST
name an authored dataflow binding: a declared input, an explicit step as,
or an explicitly named multi-output port in the completed pipeline.
Compiler-generated AoA step IDs are not ActQL source names and MUST NOT be
accepted there. fields is REQUIRED. It
closes the data published by the Result, including delivery and Effect
consumption; the compiler does not widen that boundary when an upstream
Relation gains a field.
Optional order_by belongs to the named Result, not to serialization.
A Result MAY select a raw join output using exact provenance-qualified names
such as fields: [left.id, right.id]; its order_by uses the same reference
rules against the source before projection. The Result schema and published
column names MUST retain the selected qualification and provenance without
implicitly stripping prefixes, merging same-named fields, or reinterpreting
them as authored flat names. This does not permit dotted authored source fields
or nested-property selection through fields. Result fields introduces no
alias syntax. A consumer that requires flat fields MUST be supplied a Result
whose required fields have been explicitly flattened upstream with join
select, a select alias, or rename; it cannot silently flatten them at the
consumption boundary. All ordinary Result type, order and publication checks
still apply.
If present, result order_by is applied before its field projection and lowers to
REL.orderByStable. fields lowers to REL.select. The compiler MUST verify
that every ordered and projected field exists and reject an authored typed
order key unless its type is AuthorOrderable (Lexical and ordering rules).
It MUST report the exact Result schema when statically known, including
for empty runtime results. Columns-only evidence cannot prove an order-key type; such
inputs retain the deferred AuthorOrderable obligation of Lexical and ordering rules, while generated or typed
non-scalar order keys are rejected during planning.
Every named Result MUST have a proven total deterministic order at its publication boundary. This rule applies even when no delivery is requested or the Result is consumed only by an Effect, because later delivery selection is not part of the checked Program. Relation identity is a set of independently proven candidate keys; ordering is a separate witness. When a candidate key closes authored ties, missing key fields are appended to the Plan's ordering, not authored source (Deterministic completion). Result order is resolved in this priority:
- Apply authored
order_by, completing ties under Deterministic completion. - Otherwise preserve an existing total stable ordering witness, including after a projection that hides its original order fields.
- Otherwise order by the eligible candidate key selected by Deterministic completion.
- Otherwise order ascending, nulls last, by all projected fields in declared
field order, if every field is
CanonicalOrderable(Lexical and ordering rules). - Otherwise reject the Result.
For authored Result order, checking MUST first apply eligible candidate-key completion. If no
eligible key is available, a retained total stable ordering witness MAY resolve
ties by stable sorting: authored terms remain primary and the witness orders
only rows equal on all those terms. This does not replace authored order with
the inherited sequence. If neither is available, a retained, checked Graph edge
identity from edge_rows MAY complete authored Result order. All three identity
components (src, dst, rel), or their provenance-preserving aliases, must
remain available. Select and append missing components by the canonical
completion algorithm, ascending with nulls last; authored terms remain primary.
The nullable label is part of Graph identity, not a non-null Relation candidate
key. This evidence is conditional on the exact Graph producer's successful
admission and whole-edge identity uniqueness; a row schema or physical edge
enumeration cannot supply it. Projection that loses an identity component loses
this completion evidence. This rule does not apply to Results without authored
order, scalar identity selection or omitted infer.key.
Without any of these witnesses, reject; projected-field fallback
applies only when authored order_by is absent. Retained execution and admission
obligations of the supporting witness still apply.
No lower-priority rule overrides a higher-priority one. Physical input enumeration order MUST NOT break ties.
If any Effect consumes a Result, checking MUST derive a machine-only evidence
policy with sensitivity: private, retention class authority-evidence, and
encryption required. The effective data and retention policies MUST satisfy
that evidence policy and every constraint inherited from the Result's fields
and provenance; where constraints on one dimension are ordered, the more
restrictive value applies. This rule is not an author-visible visibility or
retention field.
Private Table inputs
A stage may declare private table_inputs alongside its ordinary results.
For example, table_inputs: {material: {from: rows, fields: [id, p]}}
closes an authored Relation binding for one whole-Table operation, and that
stage's Table store refers to it with from: {table_input: material}.
This is a structured Program boundary; it adds no SQL syntax or public delivery
format. Ordinary Result-backed all-Required Table inputs remain valid.
Checking MUST resolve a Table input's authored from binding and nonempty,
duplicate-free fields against the exact stage pipeline, reject unknown names
and surplus declaration keys, and derive a closed schema from those selected
fields including independent presence and nullable contracts even for empty
input.
A private Table input MUST remain distinct from ordinary Named Results and be
consumed only by a Table-capable store in its declaring stage, with no Result
export, downstream Result import, mixed result/table_input source, or implicit
conversion to an all-Required Result.
Capture MUST retain missing, explicit NULL and present values, exact source occurrence, field projection and schema through the existing Relation, Store and Effect input owners, without NULL filling, inferred presence guarantees, or Table writes before approval and current authority.
The authored Table declaration and source choice MUST bind Stage/Program and operation identity; a consumer without the checked private input, completed capture and Table capability rejects it rather than treating it as a source-free Store or ordinary Result, while structural acceptance alone grants no execution, publication or commit authority.
Effect declarations
Effects are declarative, reviewable intent. Evaluating the computational part
of an ActQL program MUST NOT apply an Effect. Proposal and commit are separate
runtime phases; the Host MUST complete current preflight and any required
Checkpoint Resolution before commit. Effect declarations lower directly to
AoA EFFECT.plan as defined in AoA fragments and algebra lowering.
ActQL 0.1 defines these closed effect kinds:
create update upsert delete command storeThe full form is:
effects:
mark_for_review:
update:
target: connector://crm/main/Opportunity
mapping: mapping:crm.opportunity.v1
from: {result: crm_updates}
match:
target_field: Id
result_field: opportunity_id
set:
NeedsReview: {literal: true}
RiskScore: {field: risk_score}The nested effect-kind key is the only ActQL 0.1 spelling. A sibling kind
field is rejected rather than treated as an alias.
The kind determines its authority class:
createappends and MUST NOT address existing rows;updatechanges only addressed existing rows and MUST NOT create;upsertmay create or update against a declared mapping key;deleteremoves only addressed rows and carries no field changes;commandinvokes one Connector-owned typed provider operation against the ResourceRef values of its input Result (a named Result or a private capture); it is not arbitrary HTTP or generic CRUD;storewrites typed Content to a declared object, file or stage resource, or persists typed rows into a declared table resource through its Mapping. Object/file/stage inputs without acontentfield of typecontent_refare refused; serialization is an explicit preceding operation.
Every normalized data Effect MUST retain an explicit target and a
typed-qualified Mapping Contract reference in mapping, whether authored in
the long form or uniquely resolved from the published short-form binding. Every command MUST explicitly declare
a ResourceRef result field and Command Contract.
Effect max_items is an author guard as defined in Author guards, resolved limits, and Plan facts, constraining the
kind-specific scope units in Preview strength, scope, and freshness. Its resolved limit uses Result or literal
scope proof, Contract ceiling, Host policy, and any authored guard. Preview
MUST enforce that limit on the complete resolved scope before creating an
Effect Commit Checkpoint or dispatching a mutation. The runtime derives
deterministic apply and row idempotency keys from the exact Effect Preview,
resolved Contract, Effect member identity, target identity, operation, and intended change. The
author does not name an idempotency namespace.
Permissions are not Program vocabulary. The compiler and preview derive the
exact permission set from the resolved Mapping or Command Contract and expose
it in the checked Plan and review projection.
Approval policy is not Program vocabulary. Execution-account configuration
and, for delegated decisions, the Host decide Effect authority; Mapping and
Command Contracts declare exact technical permission requirements, not
approval policy (Account authorization routing). A Program MUST NOT author
an approval, review, allow, deny, or checkpoint key as Effect policy;
such a key is invalid rather than a request to weaken or strengthen authority.
on_item_failure, when present, MUST be continue; omission is the sole
canonical spelling for the fixed fail-fast behavior. The resolved failure
semantics MUST appear in the Plan and authority summary.
ActQL 0.1 treats the Effects within each stage as one unordered intent set derived from the same Stage Invocation. Member source order is not execution order; the enclosing stage array does guarantee success-dependent order. The set provides no cross-member or cross-Connector atomicity, effect-to-effect order, distributed transaction, compensation graph, or Saga execution.
Each member consumes at most one named result. A result-correlated member
(from: {result: name} with id, match, or the upsert key; create and
store from a result; every command) consumes exactly one. A target-addressed
member (ids, external_id, where, or create with payload) consumes
none. No member consumes another member's outcome within that same stage.
Within one member, on_item_failure applies to its stable row sequence: halt
stops that member after the first failed row and marks its remaining rows
unattempted; continue attempts the remaining rows. Failure of one member does not suppress
another independent member under the set's fixed attempt_all apply policy. A
host apply receipt MUST distinguish every member and every applied, accepted,
unknown, failed, or unattempted outcome. compensate is invalid in ActQL 0.1
rather than an underspecified promise.
Before preview succeeds, the runtime MUST derive a structured write footprint for every member. Exact targets in the same provider boundary and identity scheme cannot overlap, even when different fields of the same row are written. A create collection footprint may coexist with updates only with admitted proof that planned keys are disjoint or that allocation cannot reuse existing keys. Without such proof collection overlap remains a conflict. An opaque footprint cannot be combined without a contract proving independence. Different Command payloads alone are not such a proof; a declared append capability may be. An identity-scheme mismatch within the same provider boundary is unprovable and therefore rejected. Authors cannot provide free-form conflict domains or waive this check.
update and delete MUST use exactly one selector:
id: output_id_field
match:
target_field: Id
result_field: opportunity_id
ids: [literal_id_1, literal_id_2]
external_id: {field: external_key, value: ACME-1}
where:
- {eq: [{field: status}, {literal: archived}]}id is the preferred shorthand for reading primary-key IDs from one named
result field and therefore requires from: {result: name}; the resolved mapping
contract supplies the target primary-key name. match is not an alias for
id: it carries an additional auditable assertion naming the target field, so
its authority contract and ActQL CID remain distinct even when both forms emit
the same AoA effect plan. In ActQL 0.1 match.target_field MUST equal the
connector mapping contract's single declared primary key. The result key and target key MUST both be scalar,
unique, and non-null. A missing target, duplicate result identity, duplicate
target identity, non-primary target match, or identity-type mismatch MUST fail
before preview succeeds. ActQL performs no implicit string/number coercion for
identity matching; any permitted conversion must be explicit in the resolved
mapping contract.
where is a non-empty array of Predicates predicates combined by conjunction. Its
{field: name} operands address fields of the target Resource under the
resolved Mapping Contract; they do not refer to an input Relation or named
Result. Literal and parameter operands follow Predicates. Only a target row for which
the conjunction is strictly true is selected; false and three-valued unknown
do not select it. Arbitrary scalar expressions are not selector operands.
Literal ids, external_id, and where address the target directly and MUST
NOT declare from. The compiler MUST NOT infer a selector.
create and ordinary store consume from: {result: name} unless create uses an explicit
object payload; Table store may instead consume its stage's closed private
from: {table_input: name}. upsert requires from: {result: name}, an explicit
result-side key, and a non-empty set map. The mapping contract supplies the
possibly differently named target-side single primary key. The runtime MUST
correlate those two fields, MUST NOT assume they share a name, and MUST NOT copy
the remaining result row into the target implicitly. update requires a
non-empty set map. delete carries no field
changes. A step alias is an internal dataflow node; an Effect may consume it
only after stage-local results, or table_inputs for Table store, exposes
a closed contract for it.
A command consumes one named result. Its target.field MUST be a
value-level resource_ref<K> whose kind equals the Command Contract target
kind. Each input member is exactly {literal: ...} or {field: ...} and is
validated against the closed Connector-owned input contract. Unknown, missing,
nullable, vector-dimension, ContentRef-media, and ResourceRef-kind mismatches
fail during check or preview. The target field may be projected away only by
making the command invalid; receipt-only lineage cannot restore it.
An update or upsert change value is either a literal or a field from its
correlated result row:
set:
NeedsReview: {literal: true}
RiskScore: {field: risk_score}
Reason: {field: risk_reason}The complete payload of an Effect {literal: value} retains its source keys as
specified in Source representation. After the target
contract is checked, a JSON payload preserves those exact data keys; a typed
Object applies its existing field-name normalization, collision rejection and
closed-contract matching. Neither interpretation changes the already-bound
source identity. Member names within the payload do not select a type, and an
object containing $json remains authored data. This rule adds no
typed_literal alternative to EffectValue and does not expand Values types.
All literal values use the explicit {literal: value} form. Bare values are
rejected with a deterministic explicit replacement and MUST NOT be interpreted
from schema likelihood. A bare Effect value MUST be rejected before lowering.
Its diagnostic MUST expose only explicit {literal: ...} and schema-valid
{field: ...} repairs. Check MUST NOT guess between those meanings.
{field: name} requires
from: {result: name} and a result-correlated selector (id, match, or the
upsert key). Check MUST reject unknown result fields. Preview MUST reject missing row values, target fields not
declared by the resolved mapping contract, incompatible target types, target
primary-key mutation, and duplicate row identity. It MUST compute separate
strength-appropriate state evidence and a target guard for every correlated
row under Preview strength, scope, and freshness.
Checking and canonical planning
Validation
For a Program containing every required semantic choice, checking SHOULD succeed without an authoring repair. If one complete repair is unique and semantics-preserving, the checker MUST return that replacement. If several meanings remain possible, it MUST return stable explicit candidates rather than select a meaning. Diagnostic representation is specified in Diagnostic documents and Diagnostic compatibility profiles.
Checking and planning MUST NOT execute source text, import modules, access the network, or resolve external connection authority. A source builder is subject to the same validation and canonical lowering contracts as textual source; it cannot bypass them with callbacks or embedded code.
For supported profiles, planning MUST parse, elaborate, validate, and lower far enough to detect source, Carrier compatibility, arity, field, identity, ordering, and effect-scope errors without running the computation. Unsupported-profile rejection follows Conformance profiles.
Machine-readable diagnostics MUST include:
code
path
messageand SHOULD include, when known:
current_shape
available_results
replacement
candidates
allowed_keys
operation_signatureA replacement is permitted only when one complete repair is semantically
unique. When multiple repairs are possible, candidates MUST contain stable,
complete alternatives and no replacement may be applied automatically.
Deterministic completion
The compiler MAY insert structure only when the result is type-unique and semantics-preserving. ActQL 0.1 permits:
- previous-result wiring in a linear pipeline;
- sole-input selection when exactly one input exists;
- sole-SourceView selection under Connector SourceViews;
- generated AoA step identifiers;
- object-predicate expansion;
- relation/carrier bridges required by
row_window,window_events,graph,reach, andnearest; - graph branch projection and explicit distinct;
- restoration of authored graph/vector identity names;
- Result ordering and projection declared by each ResultDefinition;
- the sole compiler-proven
infercandidate key whenkeyis omitted; - a retained declared identity as a stable tie-break or carrier identity.
Whenever a rule permits choosing a candidate key, use this canonical algorithm:
retain only keys whose fields remain available and satisfy the use's type and
nullability requirements; sort each key's fields in canonical code-unit order;
deduplicate identical tuples; then order tuples by field count, followed by
lexicographic field sequence, and select the first. Append missing order fields
ascending with nulls last. This selection applies to Result order, tie-break
completion, eligible carrier identity completion, and preferred-identity
summaries. A rule requiring a sole candidate (such as omitted infer.key)
still rejects multiple candidates; canonical selection does not waive that rule.
Reference fields are eligible for canonical ordering completion. Whenever
ordering completion is required, the compiler MUST apply the algorithm above without
excluding a key solely because it contains reference fields.
Author-order restrictions apply to source before completion. Generated reference ordering belongs in the Plan and completion evidence, not an authored order key. Source printers, repair, and migration MUST NOT write it back as an author-selectable reference order or expose a source flag to bypass the rule.
The compiler MUST NOT infer:
- join kind or business join key;
- fixed versus sliding versus session windows;
- event-time versus processing-time fields;
- an approximate external search in place of exact VecA;
- vector metric;
- traversal direction;
- a business tie-break;
- effect kind, effect scope, or approval policy;
- one of multiple structurally compatible inputs.
Fields with exactly one admitted meaning are not authoring vocabulary. Their values are language laws, not choices for the checker to infer. Optional validation assertions, including join cardinality, remain author choices even when existing evidence already entails them.
Grammar decoding, including expression text (Decoding, identity, and printing), precedes these rules and is not a source completion. Every semantic source completion or macro expansion MUST appear in a stable normalization journal with source path, authored form, resulting canonical form, and rule identifier. Fixed operation semantics—such as SQL-style null non-match for an ordinary join, fail-fast Effect member execution, directed graphs, exact nearest-neighbor search, closed-open windows, first-row deduplication, fail-on-tie behavior, and lexicographic shortest-path ties—are language laws, not source completions. They MUST appear in the canonical Plan node and MUST NOT produce normalization entries. Facts derived from type, identity, or an external Contract are Plan proofs and MUST bind the evidence CID on which they depend; they likewise MUST NOT appear as source normalization.
Normalization Journal
The identity-bearing journal has version actql-normalization-journal/0.1 and
contains rule_catalog_cid, canonical entries, and journal_cid. Every entry
has exactly path, rule, authored, and canonical. Paths use the declared
RFC 9535-compatible JSONPath subset; rule IDs MUST exist in the frozen
actql-normalization-rule-catalog/0.1. Entries are ordered by path, rule,
authored bytes, then canonical bytes using the ActQL code-unit order. The
journal CID is computed over {version, rule_catalog_cid, entries}.
Compiler-specific notes or prose may accompany a journal only as non-semantic presentation and are excluded from its CID. Two implementations claiming the same normalization-rule catalog and check evidence MUST emit byte-identical semantic entries and journal CID. An implementation with different internal pass names maps them to the normative rule IDs rather than exposing those names as language semantics.
Generated identifiers
AoA steps generated from ActQL source receive deterministic identifiers based on document order and preferred operation names. Generated identifiers are not part of the ActQL authoring surface. There is no alias-equivalence class in ActQL source: operation, parameter, expression, and binding names use exactly their registered spelling.
AoA fragments and algebra lowering
ActQL source vocabulary, AoA registry vocabulary, and Executable Plan node vocabulary are three disjoint namespaces:
ActQL algebra form --lowers to--> AoA operation in an AoA Fragment
ActQL boundary form --compiles to-> typed barrier node
AoA Fragments + typed barriers ----> ActQL Executable PlanAn ActQL operation name describes the author-visible action. An AoA operation
identifier names the canonical computation primitive selected by lowering.
Lexical equality between those names is neither required nor semantic: for
example, ActQL filter lowers to AoA REL.where. AoA identifiers such as
REL.where are not ActQL source spellings and are rejected as unknown ActQL
operations. The normative mapping below is the only connection between the two
namespaces. Source names express author-visible actions rather than IR wiring;
the generated operation inventory exposes their canonical mappings.
An ActQL compiler lowers every algebra form into one or more typed AoA Fragments, including separately identified effect-planning fragments. Every fragment MUST satisfy the pinned AoA registry's sort compatibility, arity, cycle, dangling-reference, operation-parameter, and output validation contracts. A program without a boundary form executes those validated AoA Fragments directly.
When an acquisition or Artifact boundary form is present, the compiler instead
binds the validated fragments and content-addressed typed barriers into one
actql-executable-plan/0.1 envelope. The envelope is an ActQL protocol
artifact, not ActQL source, not AoA IR, and not another authorable language. It
has no authorable control flow and does not reinterpret AoA semantics.
The required mapping from ActQL algebra forms to canonical AoA registry operations is:
| ActQL surface form | Canonical AoA registry operation |
|---|---|
filter | REL.where |
select | REL.select |
rename / drop | REL.select |
derive | REL.derive |
sort | REL.orderByStable |
take | REL.limit |
top_k | REL.topk |
top_k_by | REL.topk_partitioned |
fixed/sliding row_window | REL.window.fixed / REL.window.sliding |
window_group | REL.window.project |
lag | REL.window.lag |
watermark_rows | REL.watermark |
pivot | REL.pivot |
unpivot | REL.unpivot |
json_pick | REL.json.pick |
unnest | REL.unnest |
distinct | REL.distinct |
dedupe_rows | REL.dedupeRows |
assert_rows.where / assert_rows.unique_by | REL.assert_all / REL.assert_unique |
group | REL.groupBy |
join | REL.join |
nest | REL.derive / REL.join / REL.select / REL.orderByStable / REL.groupBy / REL.where |
append | REL.unionAll |
values | REL.values |
except / intersect | REL.except_rows / REL.intersect_rows |
cross_join | REL.cross_join |
analytic | REL.analytic |
normalize_graph | SGA.normalize |
reach | SGA.reachable |
match_nodes | SGA.match_nodes |
match_edges | SGA.match_edges |
traverse | SGA.traverse |
shortest_path | SGA.shortest_path |
components | SGA.components |
graph_bounds | SGA.bounds_check |
degree | SGA.degree_metrics |
graph_diff | SGA.diff |
graft_graph | SGA.graft |
union_graphs | SGA.union_structural |
subtract_graphs | SGA.diff_structural |
subgraph | SGA.subgraph |
graphs_equal | SGA.equal |
apply_graph_delta | SGA.apply_delta |
mean_graphs | SGA.mean_structural |
fixed/sliding window_events | TEA.window.fixed_label / TEA.window.sliding_assign |
watermark_events | TEA.watermark.label |
session | TEA.window.session_assign plus relation bridge when required |
dedupe_events | TEA.dedupe |
asof_join | TEA.join.asof |
sequence | TEA.sequence.match |
time_series | TEA.time_series |
interval_join | TEA.join.interval |
calendar_bucket / calendar_series | TEA.calendar.bucket / TEA.calendar.series |
normalize_vectors | VEC.normalize_l2 |
score_vectors | VEC.score_sort |
nearest | VEC.knn_join |
pool_vectors | VEC.pool |
merge_rankings | VEC.merge_rankings |
text_rank | REL.text_rank |
edge_rows | BRIDGE.graph.to_edge_rows |
node_rows | BRIDGE.graph.to_node_rows |
graph | BRIDGE.rows.to_graph |
events | BRIDGE.events.to_rows |
to_events | BRIDGE.rows.to_events |
vectors | BRIDGE.vectors.to_rows |
to_vectors | BRIDGE.rows.to_vectors |
nearest may emit vector bridges for relation inputs;
graph may emit relation branch operations before its graph bridge.
Output-correlated effect declarations emit separately identified AoA pipelines
terminating in EFFECT.plan. They do not introduce an
ActQL effect IR or another executable language layer.
No ActQL algebra operation may lower to an unregistered AoA operation. Typed
barriers instead resolve exact versioned task, SourceView, or serialization
contracts plus validated request fragments.
Failure to lower completely is a validation failure; a compiler MUST NOT emit
a partially executable plan.
Canonical binding of additional operations
The added mappings specify required registry contracts, not permission to invoke unregistered kernel exports. The admitted registry and expression extension identities in the Check Environment bind these operations and their complete parameter, type, numerical and failure laws. An implementation without those contracts cannot issue a runnable Plan for the operation. Registration/implementation status is not a different source meaning.
Each added single-output operation lowers to exactly one node with the mapped operation ID, the usual generated step ID, ordered input references and closed parameters. Finite-list operations retain every authored input position in that one occurrence. Repeated references are not deduplicated. A kernel may fold or parallelize the ordered inputs only when it preserves the specified result, order, representative failure, numerical law and accounting. No physical indexes, hash tables, runtime batch partition or evaluator caches appear in this graph. Existing bridge lowering remains as specified above.
Input references are current input first, then with for binary operations;
mean_graphs uses its declared graph list; RRF uses current then its with list;
text_rank uses corpus then queries; subgraph and shortest_path append named
selector Relations in nodes/source/target slot order when present. Generators
without referenced inputs have an empty input list. Repeated references keep
their list positions. Fixed operands occur only in graph.in; their source
reference members are removed from parameters. A Relation selector instead
replaces its from member with input_port, a zero-based number, retaining
its id field. It never retains a separately resolved source name.
Canonical parameters retain the operation body's keys and structural values, excluding input references, step from/as/meta and publication names. Object keys use canonical field-name order; semantic lists retain declaration order. Insert fixed defaults: take offset=0; unnest outer=false; omitted join null_key=no_match; lag offset=1; single-ID shortest_path selection=each_pair; unweighted shortest_path weight=null; absent subgraph edges=literal true. Normalize a single union/RRF with to a one-element list. Values retains an ordered array of complete rows; it never derives their order from object keys. Bind parameters using the common parameter contract before lowering; literal business objects remain data, not expression syntax. For values, the checked schema and unwrapped bound cells follow the execution contract below rather than retaining author-type defaults or cell operators. Checked output contracts supply empty collection item types and nullable outer results.
The following table closes input wiring for added operations only; existing
operations retain their specified lowering contracts. C means the selected current input;
names in brackets are ordered graph.in entries, including repeated names.
All non-reference body members remain, except the defaults and publication
members specified above. Selector where and ids variants remain unchanged.
| Operation | Source reference slots | Ordered inputs | Reference members in canonical params |
|---|---|---|---|
| except, intersect, cross_join, graphs_equal, apply_graph_delta, interval_join | with | [C, with] | remove with |
| mean_graphs | with list | declared list | remove with; retain weights/support/properties |
| merge_rankings (RRF) | with list | [C, ...with] | remove with; retain ordered weights in policy |
| text_rank | queries | [C, queries] | remove queries |
| subgraph | nodes.from when present | [C, nodes.from] or [C] | nodes becomes {input_port: 1, id} for Relation selector |
| shortest_path | source.from, target.from when present | C, then source Relation, then target Relation | replace each Relation selector with {input_port: assigned index, id} |
| union_graphs list | with list | [C, ...with] | remove with |
| values, time_series, calendar_series | none | [] | retain remaining body |
| analytic, calendar_bucket | none beyond current | [C] | retain body |
Non-RRF ranking merge retains its existing binary lowering. Search is a typed barrier, not a node in this table: source resolves the static acquisition template, query_input binds the selected current/query Relation, and outputs binds publication ports rather than algebra inputs. The scalar and singleton-list union forms have the same one-node occurrence shape. The complete ordered input list, rather than an implementation's fold tree or scheduling choice, participates in canonical identity.
Expression pointers are relative to the canonical node's params: analytic
/functions/<escaped-name>/expr and /functions/<escaped-name>/filter use the
input row schema; subgraph /edges uses edge schema and /nodes/where uses
node schema; shortest_path /source/where and /target/where use node schema.
Search /request/<escaped-role> is relative to the barrier request expression
map's enclosing object and uses the query row schema. No pointer targets a
removed input-reference member. Other added operations above have no expression
roots beyond the shared calls explicitly admitted in their definitions.
Independent wiring and value/proof examples are recorded in
operation-integration-vectors.json.
These are expected specification outputs, not execution conformance claims.
Wiring examples omit resolved type/resource envelopes and the ordinary node ID
unless ID allocation is the subject of the example. Defaults inside analytic
lag/lead calls are offset=1; every other optional member remains absent unless
its operation explicitly specifies a normalization default.
Expression roots MUST use the common checked-node lowering and retain their canonical parameter paths: analytic functions//expr or filter slots where admitted; subgraph edges and selector where slots; search request/; existing aggregate expr/filter slots; and vector function argument positions. Enumerate roots by canonical JSON Pointer order, escaping ~ and / as ~0 and ~1. Structural field names, metrics, frames, literal values rows, graph deltas and profile configuration are not expression roots. Generated typed NULL/empty nodes use checked contracts, never untyped machine literals. Common expression admission rederives each root, type, demand path and envelope against its consumer and bound inputs.
Analytic filters/arguments have the frame/partition demand specified by analytic, not an unconditional preceding derive. Scalar vector nodes retain the tags vector_score/vector_concat in the common expression tree and lower to the corresponding op/body nodes, with metric a structural slot. They bind the metric profile and component limits through the same root envelope mechanism as other profiled pure expressions. Aggregate nodes retain their function tag, filter, order, distinct and extra arguments; added aggregates do not lower through arithmetic macros that change demand or fold order.
Every added node MUST bind resolved limits by measurement and logical consumer: input admission, expression evaluation, pair/frame/path exploration, token/statistic computation and output materialization are distinct scopes. Enumerate bindings by node ID, consumer JSON Pointer and measurement name; duplicate keys fail. Numerical/profile contracts bind separately from Host physical resource ceilings. Optimizations preserve logical cardinalities, types, values, demand, representative failure and enforced logical limits; they need not reproduce physical allocation counts or elapsed time. Validators reconstruct canonical nodes, input positions, roots, types and limit bindings from the checked request rather than accepting a rehashed graph as evidence.
For each result-correlated update, upsert, or delete, the compiler MUST
emit a separately identified AoA pipeline terminating in EFFECT.plan.
selector_field is the declared result identity field and field_map contains
only {field: result_field} changes; literal changes remain in the authority
contract. The AoA effect-plan logical CID and the authority contract are both
bound by the ActQL CID. Effect-plan execution materializes reviewable intent
rows only and grants no write authority.
REL.values execution parameters
The executable contract is actql-rel-values-execution/0.1, owned by
actql-aoa.json#/execution_contracts/REL.values; its signature and complete
contract CID are required by target_registry_requirements["REL.values"].
Its params_schema is a standalone JSON Schema resource. This is not the
Program authoring values schema and is not an in-memory checked-cell enum.
REL.values parameters MUST contain exactly schema and rows, with no optional
or unknown members: schema is a nonempty field-name map of complete checked
value contracts, and rows is an ordered array of objects. Checked contracts
include explicit nullable, object additional_properties: false and vector
element_type: f32; recursive opaque JSON and reference domains are excluded
by constructor eligibility, even for empty rows. Existing safe-integer limits
on string/array extents apply. No schema inference or author-type defaulting
occurs at this boundary.
Before constructing execution parameters, the compiler MUST resolve each
authored cell-root Parameter hole against the selected checked bindings, and
remove exactly one authored singleton literal quotation while retaining its
payload unchanged. An ordinary literal cell passes through as data. Resolution
does not descend into literal or supplied business values. There are no holes
or quotation operators in the execution params grammar: every row member is
a typed wire value, including objects whose keys are $param or literal.
Missing bindings or incompatible declared/bound types fail before lowering;
parameters are not evaluated or substituted by the REL.values executor.
The execution boundary MUST verify each row's exact declared field set and recursive value/type compliance without implicit casts, filling, normalization repair or rounding, before publishing any output. Nullable permits null, not omission. String values are well-formed Unicode and are retained exactly; object member names satisfy the separate NFC-then-LF identifier contract. Numbers are finite binary64 and not negative zero. Decimal is canonical fixed-scale JSON string text under its declared precision/scale, not a number, coefficient object or typed-identity projection. Date and timestamp use their their existing domain-specific canonical text, arrays are ordered wire arrays, objects are exact field maps, and vectors retain admitted finite numeric components without an extra quantization step here. Schema validation covers closed shape and checked type syntax; schema-to-row agreement, canonical text, numeric domains, calendar validity, decimal precision/scale spelling and resolved resource limits require semantic validation. Duplicate JSON keys are rejected before map construction.
REL.values MUST preserve the declared schema for zero rows, every row occurrence in list order, and duplicate rows. Object keys use canonical UTF-16 ordering for serialization and do not determine row order. Neither deduplication nor reordering is authorized by canonicalization. The internal occurrence ordinal is not an extra output field or evidence of unique field values. Full value validation and resolved row/byte/recursive-value limits precede publication; validation can be bounded or streamed but cannot expose a partial successful Relation.
A runnable Plan using REL.values MUST bind a registry entry implementing the
signature and pinned execution contract, its checked-type dependency and the
existing canonical JSON, typed-value and failure laws. A matching operation
name or input/output signature alone is insufficient. A backend unable to
preserve typed decimals, empty schema evidence, order, multiplicity or required
validation/limits rejects executable compilation, without casts or alternate
encodings. The selected registry is the complete JSON body at
pipeline_registry_file (repository-root-relative) in the AoA contract; its
canonical CID is pipeline_registry_cid. The REL.values entry's
execution_contract.file is repository-root-relative and its pointer resolves
within that file; its cid identifies the complete selected subtree, not just
the params schema. Registration resolution verifies the registry body CID,
signature, reference and execution-contract CID against the target requirement.
The selected registry registers this extension; registration does not assert
backend implementation, authorize a Program, or discharge other Program and
runtime obligations. Other target requirements remain separate from actual
registrations and are not implicitly added to this registry.
Typed barriers and Plan Documents
Boundary forms MUST NOT lower to AoA operations. They compile to the following closed Executable Plan nodes:
| ActQL boundary form | Executable Plan node | Barrier contract |
|---|---|---|
infer | acquisition_barrier | actql-inference-barrier/0.1 |
keyed SourceView join acquisition | acquisition_barrier followed by RelA join | actql-lookup-barrier/0.1 |
search_candidates | one acquisition_barrier with candidates and coverage output ports | actql-search-barrier/0.1 |
serialize | content_barrier | actql-content-barrier/0.1 |
binary64 sum / avg | compiler-inserted numeric_observation | numeric capture contract; exact lanes remain AoA computations |
The barrier contract, profile contract CID, input and output port contracts, and adjacent AoA Fragment CIDs are all part of Executable Plan identity. Pure text-time evaluation is a registered expression within AoA Fragments, with per-expression contracts and envelopes, not an acquisition node. Profile admission precedes evaluation; unsupported execution stops before input/provider access (Text, time, and regular expressions).
A numeric observation node binds the logical aggregate slot, input/contributor contract, numerical law, limits and typed output. Local reduction and remote pushdown both return a captured observation to that same boundary. The Plan does not include the future adopted value. The infer node binds model execution/retry/media contracts and outcomes versus strict mode.
The Executable Plan fixes the canonical computation and admitted execution envelope, not a Host's physical execution strategy. Hosts MAY choose different placement, batching, pushdown, scheduling, or execution algorithms wherever the checked contracts leave those choices open. Limits, access constraints, and evidence requirements already bound into the Plan remain binding; calling a choice physical does not exempt it from those requirements.
Physical optimization MUST NOT change a bound Plan's semantic identity or the artifact covered by an approval. Choosing a physical strategy within the checked envelope does not authorize rewriting that envelope under an existing approval.
A complete Plan closes the permitted computation and external boundaries; it does not require all observed values or exact row counts to be known before execution. A Host MAY use separately supplied statistics and historical Run measurements to estimate scale and select an admitted physical strategy. Estimates MUST NOT substitute for proven bounds, enforced limits, or concrete scope evidence required for approval. Planning itself does not acquire new provider observations.
Physical strategy revisions may change only unexecuted work and must preserve the canonical computation, failure rules, captured observations, approved artifacts, and remaining limits. Estimates may guide placement, batching, parallelism, spill, and equivalent algorithms, but never widen a SourceView, egress destination, or Effect scope. Unknown statistics are not zero estimates. The Host may acquire additional statistics only as declared, authorized, budgeted work; compiler evidence resolution performs no hidden acquisition. Observed statistics carry their subject, authority scope, method, freshness, snapshot applicability, and uncertainty. Statistics containing source-derived values retain the source's data-policy protections.
The Host records adopted strategy revisions and the evidence used to select them separately from canonical Plan identity. Adaptive optimization and a particular cost model are not required for conformance. Quota observations are not reservations; waiting or rescheduling does not waive dispatch-time checks.
The required module mappings are bound by target_registry_requirements in
ACTQL_AOA_CONTRACT.
An independent implementation that does not supply and validate one of those
operations MUST reject the corresponding source and MUST NOT claim that module.
AoA IR is the canonical identity of each computation fragment. Its logical CID is computed using the AoA canonical serialization and hash contract. Every staged program additionally has an Executable Plan CID binding every fragment and barrier CID. An ActQL source hash MAY be stored as provenance, but it is not interchangeable with either executable identity.
Planning MUST NOT acquire provider data, create Run state or Checkpoints, or capture private invocation inputs.
Planning an otherwise valid Program with unbound scalar parameters or opaque
bindings MUST return needs_bindings without issuing a runnable Plan Document.
Writing a runnable Plan requires every declared value to be bound.
Evidence propagation
The machine-readable portability envelope containing scalar, external-call,
aggregate, join-order, operation-propagation, and atomicity laws is
ACTQL_PORTABILITY_CONTRACT, identified by ACTQL_PORTABILITY_CONTRACT_CID.
The ACTQL_OPERATION_SEMANTICS table inside that envelope and the propagation
laws in this specification MUST agree. Every
public operation has exact identity, order, completeness, and schema
rules. The central propagation laws are:
| Operation | Identity | Stable order | Completeness |
|---|---|---|---|
filter / scalar-preserving derive | preserve | preserve | preserve |
select / rename / drop | retain every fully and uniquely mapped candidate key | preserve | preserve |
derive array | retain unchanged keys or proved complete exact-copy aliases; computed key shadow loses proof | retain row positions and input column positions; append new columns in slot order; old sort-key proof does not apply to shadowed values | value-version policy and input provenance; no completeness upgrade |
non-outer unnest | source key + exposed/retained non-null index | source then index | preserve |
outer unnest | no key from nullable index; parent key only with proven at-most-one expansion | internal source/element occurrence order | preserve |
group | group keys | canonical group-key order | preserve |
join | join-kind × cardinality table in join, then projection | kind-specific join order | complete only if both complete |
append | discriminator + local key only when literal domains prove disjointness | left then right | complete only if both complete |
dedupe_rows | by fields | canonical key order | preserve |
assert_rows.where | preserve | preserve | preserve on success; atomic failure otherwise |
assert_rows.unique_by | preserve and add the checked non-null unique tuple | preserve | preserve on success; atomic failure otherwise |
pivot | index fields | canonical index order; empty values with nonempty index uses first-occurrence order under reshaping; empty index emits one global group | preserve |
take / top_k | preserve | requires/provides total stable order | bounded-query completeness |
| SGA query | node/edge keys where exposed; path rows need no synthetic scalar ID | canonical graph/path order | atomic bound failure |
| TEA session/sequence | record/session IDs; sequence(disallow) label IDs | canonical event/match order | atomic bound failure |
| VecA nearest/merge | query + candidate | query, score desc, candidate | complete only if all inputs complete |
A compiler MUST NOT report stronger evidence than these laws. Internally it
represents candidate keys as a set and ordering as a distinct proof. An empty
field-key list does not stand for operation-defined ordering. Concrete
field schemas and renamed keys are inferred from the checked schema bundle,
but loss or preservation cannot vary by host. Successful check reports the
inferred schema, one preferred identity when proven, stable-order status,
completeness requirements, and generated-field collisions.
For window operations, a WindowResult completeness requirement denotes the
selected window's result extent and retains the operation's input and coverage
requirements. It does not assert completeness of the entire source or observed
completion merely because window boundary fields are present.
An assertion MUST execute before any operation that relies on its proof.
Author guards, resolved limits, and Plan facts
An author guard is an OPTIONAL positive safe-integer constraint that fails
protected work when its measured scope exceeds the guard. It expresses an
independent safety choice, not a request to select the first N items. The
operation-specific sections define its scope unit and enforcement boundary.
Source may supply a literal or {$param: name} under Parameters and binding; the resolved limit
uses the bound value and retains the author's guard provenance.
A compiler MUST retain an explicitly authored guard and its provenance even
when a Program proof or current Contract/Host ceiling is equally or more
restrictive. The availability of a derived bound does not require its value to
be repeated in source.
Every runnable capacity limit MUST be represented by a canonical
actql-resolved-limit/0.1, built from one or more
actql-bound-witness/0.1 values. A witness identifies its unit, numeric value,
role, governing law, dependency CIDs, and one of these sources: Program proof,
author guard, pinned Contract, language law, or Host policy. Equal numeric
values with different evidence remain different witnesses. Candidate order is
canonical, and checked addition or multiplication MUST reject safe-integer
overflow rather than saturate.
Every non-source witness carries a closed derivation (preserve, min, add,
multiply, clamp, or an enforced preflight ceiling). Plan validation resolves
its dependency witnesses and recomputes the numeric value. Role/source pairs,
known law IDs, and dependency closure are checked rather than accepted as
self-described metadata.
The effective maximum is the minimum of every applicable available candidate:
Program-proven upper bound, author guard, Contract ceiling, language ceiling,
and Host ceiling. At least one finite candidate is REQUIRED for a runnable
capacity limit. A proven upper bound is evidence about the data; a ceiling is
a restriction requiring enforcement, not such a proof before its check.
An author guard is binding only when it is stricter than all non-author
candidates; otherwise it is preserved as currently_non_binding. Every breach is fail:
it MUST NOT truncate a Relation, external acquisition, Effect Set, or
Artifact. The resolved limit records whether enforcement is static, unit
preflight, before provider dispatch, before Effect commit, streaming atomic,
or engine atomic. An absent author guard is recorded as absent, not as an
inferred source field. Host policy changes affect limit resolution, not whether
an authored guard is legal or redundant source syntax.
actql-bound-transfer-laws/0.1 classifies every public operation. An operation
MUST either provide a versioned upper-bound transfer law or explicitly require
a finite Contract/Host/author ceiling. Row-preserving operations retain their
input upper bound; take and top_k clamp it; append adds; bounded expansion
uses checked multiplication; reductions use their operation-specific bounds; joins,
graphs, vectors, temporal operations, inference, and serialization use their
carrier-specific laws. A public operation absent from this registry cannot
produce a runnable Plan.
The pinned transfer registry MUST cover exactly the public operation inventory.
Its classifications describe upper-bound transfer, not exact output counts or
permission to execute. preserve permits retention of an applicable input upper
bound even when rows are removed; for nest, except, and intersect that
input is the parent/left operand. clamp, add, and multiply select checked
numeric transfer using the operation's actual limit, operand occurrences or
expansion factor. reduce requires the operation-specific reduction law,
including its empty-input behavior; it does not assert that every reduction
emits at most the input count. carrier_specific requires the law for the
selected Carrier, output port and quantity, rather than a generic row formula.
explicitly_unknown_requires_ceiling supplies no derived finite upper bound:
execution requires an applicable finite ceiling and its enforcement. Registry
coverage alone establishes neither a governing witness-law application nor the
validity of its source evidence. Unknown inputs or unresolved factors are not
zero, and none of these classifications waives validation or enforcement.
Resolved limits, fixed language laws, and pinned Contract facts belong to the
content-addressed actql-plan-fact-manifest/0.1, not the Normalization Journal.
Changing Host policy leaves Program Definition identity unchanged but changes
the checked Plan whenever a Host witness changes. Adding or changing an author
guard changes Program Definition identity. A checker MAY validate semantics
with incomplete execution-policy evidence. It MUST NOT issue a runnable Plan
whose finite capacity remains unresolved.
Every resolved machine limit is also bound to the exact barrier field, AoA step field, or input preflight it governs. Validation requires the execution value to equal the resolved maximum, rejects missing or duplicate bindings, and rejects resolved limits that are not consumed by executable semantics. Fixed language laws remain Plan facts and do not masquerade as authoring normalization.
A Result publication limit MUST identify its protected subject as
unit:<unit_definition_cid>#/results/<result_name_token>/<scope>, where
unit_definition_cid is the owning Unit Definition CID, result_name_token
is the canonical Result name escaped as one JSON Pointer token (~ becomes
~0, then / becomes ~1), and scope is rows or bytes. These scopes
distinguish the published Result row list from the artifact-byte measurement
specified by the governing limit law; the reference does not redefine that
law's byte representation. It is not a file path or an observed Result CID.
Validation MUST bind it to that Unit's actual Result port and the corresponding
measurement scope; a matching numeric limit or a valid self-CID is insufficient.
Internal stage/step indices and the enclosing Program revision MUST NOT replace
the Unit identity in this reference. This subject convention does not replace
candidate selection, proof application, canonical limit validation or atomic
failure on breach, and publication limits do not enter Unit Definition identity.
A persistent Program upper-bound proof for an authored Unit operation's output
MUST use unit:<unit_definition_cid>#/steps/<ordinal> as its semantic_ref,
where ordinal is the canonical nonnegative decimal array index in that Unit's
source steps, verified against the actual checked source occurrence. It is
not a producer traversal index, generated backend node ID or unscoped internal
handle. The proof's law and quantity identify the governed output-bound rule;
this convention does not redefine expression, field or barrier subscopes.
The proof preimage MUST be the complete canonical object with exactly
version: "actql-bound-proof/0.1", semantic_ref, law_id, unit, value
and inputs, where inputs contains the persistent input-witness CIDs in
canonical sorted order while retaining repeated operand occurrences. The
owning Unit Definition excludes concrete capacity proofs and limits, so this
reference does not introduce an identity cycle. Effective bindings and actual
input correspondence remain separately validated conditions of the proof;
numeric agreement does not discharge them.
Conversion from checked internal evidence MUST rebuild the proof CID, witness source, dependency set and derivation references, and every dependent witness and resolved-limit CID, using the verified source mapping and converted input evidence. Changing a reference string while retaining an old CID is invalid. Unchanged Host/Contract evidence retains its own identity. Cross-Unit operands retain their producer Unit reference rather than acquiring the consumer's namespace. This conversion preserves law, quantity, value and all prerequisite checks; it does not establish runtime truth or backend support.
A components operation's persistent resolved limits MUST use
unit:<unit_definition_cid>#/steps/<ordinal>/components/nodes for quantity
nodes, and the same prefix ending in /components/output for quantity rows.
Both identify the actual authored operation in its producer Unit and retain
engine-atomic enforcement. Input-node capacity is not an output-row ceiling,
and neither limit proves that Graph data admission or component computation has
completed. The atomic output gate does not permit delaying input-node enforcement
until after the component algorithm.
Conversion MUST preserve every admitted candidate occurrence, its law, quantity, value, prerequisites and selected policy. Program candidates and their dependency graphs MUST be rebuilt using the Unit operation proof references above; imported dependencies keep their producer Unit. Unchanged Host witnesses keep their CIDs. Sort the converted candidate occurrences by their new witness CIDs, without deduplication, and rebuild both resolved-limit CIDs from their complete canonical bodies without self CIDs. Preserve the effective minima and author-guard status; conversion does not add, remove or waive an authored guard. The two limits remain outside Unit Definition identity. Equal ordinals or maxima do not make different Units or measurement scopes interchangeable.
A traverse operation's persistent resolved limits MUST use
unit:<unit_definition_cid>#/steps/<ordinal>/traverse/paths for quantity
paths, and the same prefix ending in /traverse/output for quantity rows.
These identify the authored operation in its producer Unit, not a consumer's
import occurrence. Path-exploration capacity and emitted-row capacity are
independent engine-atomic requirements. Exploration must enforce its selected
path limit during traversal; the atomic output gate does not defer it until
after producing rows. Neither limit proves completed admission, membership or
traversal. Node emission and an empty selection do not waive selected limits.
Conversion MUST preserve every admitted candidate occurrence, law, quantity, value, prerequisite, effective minimum, author-guard status and selected policy. Program candidates and their dependency graphs use the Unit operation proof references above; imported dependencies keep their producer Unit. Unchanged Host witnesses keep their CIDs. Sort converted candidate occurrences by their new witness CIDs without deduplication and rebuild both resolved-limit CIDs from their complete canonical bodies without self CIDs. Conversion does not add or waive an authored guard. Both limits remain outside Unit Definition identity; equal ordinals or maxima do not make different Units or scopes interchangeable.
When traversal construction starts from admitted non-author candidates and a
separately bound max_output_rows, it MUST add the owning Unit's guard witness
only to the output-row limit. Its law is actql.guard.traverse-output, quantity
is rows, and source path is $.steps[<ordinal>].traverse.max_output_rows.
The witness's program_definition_cid identifies that Unit Definition. The
operation, selected policy and effective Parameter binding must agree with the
retained capacity application. The final maximum is the minimum of all candidates;
a guard below the non-author minimum is binding, otherwise it is
currently_non_binding and remains present. Omission stays absent.
The original non-author claim is not the final output claim when a guard exists.
The path-exploration limit is unchanged. Canonical candidate sorting and CID
recomputation follow the traversal conversion rule above; this does not certify
that runtime enforcement or traversal has occurred.
A reach operation's persistent resolved limits MUST use
unit:<unit_definition_cid>#/steps/<ordinal>/reach/paths for quantity paths
and the same prefix ending in /reach/output for quantity rows. Its bounded
node-emitting traversal expansion requires both the selected path-exploration
ceiling and output-row ceiling. The authored maximum depth does not replace either
ceiling. Limits identify the original reach operation in the producer Unit, not
an invented authored traverse step or an importing consumer. Enforce the path
ceiling during exploration and reject the entire operation on either breach.
Conversion MUST preserve the admitted applicable candidates, quantities, minima, selected policy and producer dependencies, following the traversal conversion rules for rebuilding Program proofs, retaining unchanged Host witnesses and sorting repeated candidate occurrences without deduplication. No output guard is synthesized for the closed reach body. Rebuild the limit CIDs from their canonical bodies without self CIDs; both limits remain outside Unit Definition identity. Expansion journaling, Graph admission, start membership and actual traversal remain separate requirements, not guarantees supplied by these limits.
A shortest_path operation's persistent resolved limits MUST use
unit:<unit_definition_cid>#/steps/<ordinal>/shortest_path/paths for quantity
paths, and the same prefix ending in /shortest_path/output for quantity
rows. Both identify the authored operation in its producer Unit, even when
consumed through imports. Search-path and output-row ceilings are independent;
neither max_hops nor a bound on the selected output count substitutes for the
search-path ceiling. Enforce search capacity during exploration and reject the
operation atomically on either breach. Empty selection, zero-hop paths and
unreachable endpoints do not waive selected capacity contracts.
Conversion MUST preserve every applicable admitted candidate occurrence, its law, quantity, value, prerequisite and selected policy. The shortest-path output bound law contributes only to the row scope. Rebuild Program proofs and their dependencies using their producer Unit references; preserve unchanged Host witness CIDs. Sort converted candidates by their new witness CIDs without deduplication and rebuild each limit CID from its complete canonical body without a self CID. Limits remain outside Unit Definition identity. Neither limit proves completed Graph admission, weight validation or path selection.
When shortest_path construction starts from admitted non-author candidates and
a separately bound max_output_rows, add the owning Unit's guard witness only
to the output-row limit. Its law is actql.guard.shortest-output, quantity
rows, source path $.steps[<ordinal>].shortest_path.max_output_rows, and
program_definition_cid is that Unit Definition. The exact operation, selected
policy and effective Parameter binding MUST agree with the retained application.
Take the minimum of all candidates. A guard strictly below the non-author minimum
is binding; an equal or larger guard is currently_non_binding and remains
present. Omission remains absent. The path limit is unchanged. The original
non-author output claim is not the final claim when a guard exists. Canonical
candidate ordering and CID reconstruction follow the preceding rule; no guard
is silently waived or invented.
A sequence's persistent resolved limits MUST use
unit:<unit_definition_cid>#/steps/<ordinal>/sequence/matches-per-key for
quantity matches_per_key, and the same prefix ending in /sequence/output
for quantity rows. Both address the actual authored sequence in its producer
Unit and retain engine-atomic enforcement. The per-key limit is not a total-row
bound or a search-work budget. Conversion MUST preserve the admitted candidate
occurrences and their order, minima, applicable bindings and selected policy;
unchanged Host witnesses retain their CIDs. Recompute each resolved-limit CID
from its complete canonical body without its self CID after changing the subject.
Equal step ordinals or maxima in different Units do not make the subjects
interchangeable. Imported prerequisites retain their original producer Unit.
These references assert neither completed matching nor permission to publish
partial results after either limit is exceeded.
When construction starts from an admitted Host-only sequence limit and a
separately bound authored restriction, it MUST retain the Host candidate
occurrences in their original relative order and insert the owning Unit's authored
guard witness in canonical witness-CID order. The effective maximum is the minimum of all these candidates. A guard
below the Host minimum is binding; an equal or larger guard is
currently_non_binding and MUST still be retained. Omission stays absent and
does not synthesize a guard. The scope-specific guard law, authored source path,
Unit Definition and effective Parameter binding MUST correspond to the same
checked sequence. Recompute the limit CID from this complete candidate body;
the original Host-only limit is not the final claim when a guard is present.
An ordinary group aggregate's persistent resolved limit MUST use
unit:<unit_definition_cid>#/steps/<ordinal>/group/aggregates/<name_token>/<scope>
as its subject_ref. The ordinal identifies the actual authored step in the
owning Unit, and name_token is its canonical aggregate output name escaped
as one JSON Pointer token (~ to ~0, then / to ~1). Scope items applies
only to array_agg with quantity array_items; scope bytes applies only to
string_agg with quantity bytes, measuring the specified UTF-8 result. The
scope is not an array of all groups, the input row list, or artifact wire bytes.
Validation MUST bind the limit to that exact checked call, effective Parameter
bindings, selected policy and applicable input proofs. Equal maxima, input
types or expression bodies do not permit substituting another occurrence.
This reference does not authorize truncation or replace atomic enforcement.
Window groups, pivot and analytic calls retain their separate surrounding scopes;
they MUST NOT masquerade as an ordinary group occurrence.
A window_group aggregate's persistent resolved limit MUST instead use
unit:<unit_definition_cid>#/steps/<ordinal>/window_group/aggregates/<name_token>/<scope>.
The name escaping, items/array_items and bytes/bytes measurement rules,
exact checked-call correspondence, effective bindings, policy and atomic failure
requirements are the same as for ordinary group aggregates. The call's group
includes its checked window-boundary prefix exactly once. Its limit applies to
one aggregate result per such group, not to the number of windows, the whole
input, or all groups' retained contents. Authored guard fields use
window_group.aggregates[<JSON-string-name>].max_items or .max_bytes under
the owning operation reference, never the ordinary group.aggregates field.
An actql.bound.array-aggregate-input proof for this call uses this window-group
items subject, bounding contributing items by the actual input membership rows;
pre-window row counts require the applicable membership expansion first.
The ordinary group and window-group namespaces are not interchangeable even
when input schemas, aggregate expressions or selected maxima agree.
An actql.bound.array-aggregate-input proof MUST use the same items reference
as its semantic_ref, quantity array_items, and the complete canonical proof
preimage defined above. Its input is the persistent witness for an applicable
upper bound on that group's actual input rows, and its value equals that bound.
Filter, NULL exclusion and distinct cannot increase the contributed item count.
The proof bounds each group's array, not the number of groups or total retained
array items across groups. It MUST preserve the input proof's dependencies and
required checks. Conversion MUST rebuild dependent identities; a rows witness
MUST NOT be relabeled as an array_items witness. This proof does not establish
observed counts or discharge the aggregate's order and value-admission obligations.
Persistent nest resource limits MUST address the original authored operation
with prefix unit:<unit_definition_cid>#/steps/<ordinal>/nest/ and these suffixes:
| Suffix | Quantity and protected measurement |
|---|---|
child-arrays/items | array_items, each matched correlation group's child array |
empty-array/items | array_items, the typed empty-constant aggregation, fixed upper bound 1 |
child-arrays/bytes | bytes, the sum of emitted child-array value encodings after grouping |
attachment/bytes | bytes, all retained top-level object/array values after parent attachment, including repeated parent copies |
These names identify the existing canonical expansion's measurement sites, not
generated AoA node IDs, additional authored aggregate calls, or future observed
values. The two byte sites remain distinct even when their selected ceiling is
equal. The empty constant's item ceiling is not a row-count witness, and neither
item scope denotes total expanded child elements. The nest definition fixes
the fields and canonical value-byte measure at each byte site; no row wrapper,
artifact bytes, or physical sharing substitution is implied.
For the generated child-array aggregation, an actql.bound.array-aggregate-input
proof MUST use the nest/child-arrays/items subject and an applicable persistent
upper bound on the actual child input's rows. Its value equals that row bound;
the matching semijoin and each correlation group can only remove child
occurrences. Repeated correlation pairs do not replicate them. This is a
per-array bound, not a bound on total copies after parent attachment, and requires
no uniqueness assertion about parent correlation keys. It preserves the input's
dependencies and required checks under the same canonical proof/witness rules
as other array aggregations; a row witness is not relabeled as array items.
The fixed empty-constant item limit MUST retain an input-free Program proof with
law actql.bound.nest-empty-array, its nest/empty-array/items semantic reference,
unit array_items, value 1 and an empty inputs array. The source witness
depends on that proof CID under the ordinary bound-witness preimage. This fact
bounds the typed aggregation of the known false-filtered child input; it does
not imply that upstream acquisition, admission or failure propagation may be
omitted, and is neither a Host ceiling nor a guard on matched child arrays.
Each limit MUST retain its exact checked nest occurrence, producer Unit, effective bindings, selected policy and prerequisite ownership. Imported uses retain the producer Unit's subject. Ordinary group, window-group and analytic subjects cannot substitute for these generated nest scopes. Conversion rebuilds dependent identities under the existing canonical preimages and preserves unchanged Host witnesses. Static selection does not discharge output admission, order, input-failure propagation or atomic resource enforcement.
An analytic aggregate's persistent resolved limit MUST use
unit:<unit_definition_cid>#/steps/<ordinal>/analytic/functions/<name_token>/<scope>
as its subject_ref, binding the actual producer Unit, authored step ordinal and
canonical call name escaped as one JSON Pointer token. Scope items is the
array_items limit on each array_agg frame result; scope bytes is the raw
UTF-8 payload limit on each non-null string_agg frame result. This reference
names a universally enforced per-frame call limit, not one future output-row
observation, the total across frames, or the analytic row-array wire budget.
Equal maxima do not permit substituting another call, Unit, policy or bound
Parameter selection. Imported limits keep the original producer Unit.
An actql.bound.analytic-array-input proof MUST bind that exact items
reference as semantic_ref, quantity array_items, and the complete canonical
bound-proof preimage. Its sole input is a persistent applicable upper-bound
witness on this analytic operation's actual input rows, and its value equals
that bound. Each partition is a subset of input occurrences, a ROWS frame is
a subset of its partition, and filter, NULL exclusion and distinct cannot
increase contributors. Thus the proof bounds each frame's array even when frames
overlap; it does not bound the sum of frame memberships or retained array items.
It preserves input dependencies and prerequisite checks and grants no observed
count, position-order witness or completed value admission. A rows witness is
not relabeled as array_items, and the ordinary group input law is not used.
Conversion MUST rebuild the analytic proof, witness, dependencies and resolved
limit CIDs from the verified Unit/step/call mapping and converted input witness,
preserving unchanged Host/Contract identities. The proof body remains exactly
{version, semantic_ref, law_id, unit, value, inputs} under the existing canonical
bound-proof rules. Independent Host or author item ceilings remain separate
candidates; all applicable candidates participate in minimum selection.
Enforcement rejects excess atomically without truncation. Registration of the law
does not establish correct application, runtime truth or backend support.
An analytic call's author-guard witness MUST bind the owning Unit Definition
and exact local path $.steps[n].analytic.functions[<json-name>].max_items or
.max_bytes, retaining its effective bound value and existing
actql.guard.array-aggregate or actql.guard.string-aggregate law respectively.
json-name is the canonical call name encoded as a JSON string, not a JSON
Pointer token. Omission supplies no author guard. The minimum applies per frame
result; delimiter bytes follow string aggregation's existing output rule.
A persistent Graph input preflight limit MUST use
unit:<unit_definition_cid>#/inputs/<input_name_token>/<measure> as its
subject_ref, where the token is the actual canonical Unit input name escaped
as one JSON Pointer token and measure is nodes or edges. Validation binds
the reference to that Unit's checked Graph input, selected policy and matching
quantity, not merely to a matching input name in another Unit. The limit retains
unit_preflight enforcement and failure on breach; unchanged Host candidates
retain their identities. This is an input-boundary scope, not an authored
operation output or an observed input identity.
The corresponding enforced upper-bound witness MUST use
unit:<unit_definition_cid>#/inputs/<input_name_token> as its Program
semantic_ref, the matching actql.bound.graph-input-nodes or
actql.bound.graph-input-edges law, and the limit's quantity and effective
maximum. Its source.proof_cid, derivation.resolved_limit_cid and sole
dependency are the rebuilt resolved-limit CID; its derivation is
enforced_ceiling. There is no separate derived-input proof object for this
witness. Conversion rehashes the limit, this witness and dependent operation
proofs while retaining the exact checked input/preflight ownership. It does
not assert that preflight has run, authorize input acquisition or insert
capacity policy into Unit Definition identity.
Canonical identities
ActQL uses distinct identities rather than overloading one CID. Stage Definition CIDs bind stable stage IDs and local semantics without the enclosing Program CID. Program semantics bind their ordered descriptors, parameters, imports and exports; Program Definition adds the root ID. Node-local semantic identities never include the enclosing Program revision, transitively or directly. The graph membership envelope binds both sides separately. No definition hashes its containing Program or future observations. The following invocation/plan/manifest identities apply to an extracted single-unit Program; its Program Definition is the local Unit Definition, not the enclosing multi-node Program revision. The graph output index aggregates node history without new Invocations:
source_text_hashis non-semantic provenance over authored bytes;program_semantics_cidis the CID of the canonical unbound ActQL semantic envelope, excluding authoredidand every allowed presentation-onlymetaobject;program_definition_cidbinds the authored authority-bearingidto that semantics CID;authoring_context_cididentifies the separately extracted metadata sidecar bound to that Program Definition;check_environment_cidbinds the exact evidence bundle used by check;parameter_values_cidbinds one canonical typed value map using the Parameter value identity projection;invocation_template_cidbinds Program Definition CID, Parameter Values CID, Check Environment CID, and the fact that runtime sources remain unbound;executable_plan_cidbinds the complete graph and execution material (Typed barriers and Plan Documents), including fragments, barriers/wiring, Results, effects, governing facts, resolved limits, source-state requirements, and semantic contract identities;compiled_plan_cididentifies the compilation envelope below, includingexecutable_plan_cid; it is not an alias for the main AoA logical CID;prepared_invocation_cidbinds one bound Invocation Template and Compiled Plan to an authenticated execution context, current Host policy decision, and expiration without restating their execution details;run_authorization_cidbinds the Host's current allow or approved decision for that exact Prepared Invocation;run_definition_cidbinds the immutable definition of one durable Run, including its initial captured input artifact set (Run admission and identity);invocation_cidbinds Program Definition, parameter-values, and Check Environment identities to the exact captured observation evidence (Capture, execution, and replay);result_manifest_cidbinds the complete name-to-Result-Artifact map produced by one Run without embedding Result rows (Named Results and Effect declarations);effect_preview_cidbinds the concrete invocation and authority preview; and- Run/apply receipt CIDs bind the exact published artifacts and outcomes, while a Result delivery receipt CID additionally binds one selected canonical byte representation (Result delivery).
The actql-compiled-plan/0.1 compilation envelope is a closed canonical
contract. It binds compiler_version, aoa_ir_version,
executable_plan_cid, program_definition_cid, program_semantics_cid,
aoa_contract_cid, and aoa_logical_cid; compiled_plan_cid hashes the
complete body without only that self member. A Prepared Invocation refers to
this envelope. Run admission checks the envelope against the exact Program
Definition before execution, and later Receipt issuance derives its compiler,
AoA, and semantic fields from the already admitted envelope. A publisher
cannot supply replacement version or logical-identity strings.
Local semantic identity and graph membership
Unit namespace is the typed pair of stable authored Program id and node id. Unit Definition binds this namespace, the complete local body, used parameter and binding declarations, producer namespace/definition/export/port/type and correlation contracts, and incoming success/freshness declarations. It excludes parent revision CID, sibling bodies, unrelated exports, physical paths and array position. Renaming a node or changing its incoming dependency changes local meaning. Canonical extraction reuses a single-unit Program Definition and typed namespace encoding; ambiguous concatenation and duplicate artifact families are not permitted.
The compiler MUST derive local parameter, binding, contract and review projections by the pinned law, including domain, guard, permission, scope and failure dependencies, not an optimizer's optional slice. Whole values and lock remain graph admission evidence. Local contract closure contains all interpreted schema, Mapping, Command, ModelTask, capacity, policy and numeric laws. Catalog membership, uniqueness of resolution and whole-lock authenticity remain checked; this is not automatic compatible-lock renewal.
An exact Preview MUST bind Unit Definition, used effective values, local contract closure, exact admitted inputs/imports and observations, required issuer, order, completeness, policy/provenance, targets, payloads, guards, management-write recipe and resolved limits. Type equality alone cannot substitute a different producer receipt. Full ContentRef scope remains material. Global Program/lock/manifest CIDs, unrelated metadata, physical Run ids and the dispatch key itself are excluded, including indirect ancestry through Invocation, Result, receipt or parameter envelopes. Projected evidence has its own CID, never its parent's CID.
The local_manifest_cid and semantic_outcome_cid reference roles resolve only to the registered semantic projections, not the Run-bound Manifest or full apply receipt. Their registered reference graph includes Result Artifact and local Invocation descendants. Required source-audit correspondence, signatures and membership are verified through separate projection attestations; retaining the entire Captured Run as evidence does not put its wrapper CID into Preview.
Authenticated membership binds current graph revision/run, namespace, local definition and local artifacts outside their semantic hash preimages. Admission checks both local semantics and membership/producer authority. A same-shaped raw JSON value without evidence does not grant execution rights. Local review binds annotations actually reviewed for the unit and applicable shared root explanation; unrelated stage metadata and absolute UI positions are excluded. Browse-only context is distinct from signed review. Changing relevant explanation requires a new review; changing only node 3 leaves fixed node 1/2 templates unchanged. New graph launch still needs composition admission; reuse of earlier successful work requires an explicit recovery link, not silent replay of an old Resolution.
The normative actql-program-semantics/0.1 canonical envelope is:
{
"version": "actql-program-semantics/0.1",
"actql_version": "actql/0.1",
"expression_contract_cid": "sha256:...",
"parameters": {},
"parameter_references": [],
"bindings": {},
"binding_references": [],
"program_template": {}
}For the staged grammar, program_template is exactly
{stages: [{id, stage_semantics_cid, after, imports}, ...], exports}.
Descriptors retain stage order; after is initial for the first descriptor and
the completed/accepted normalized law thereafter. Imports is a map from local
input name to its explicit Result/receipt reference; exports is the root results
map. No typed runtime artifacts or full source body are embedded here.
Each stage_semantics_cid hashes the closed envelope
{version: "actql-stage-semantics/0.1", expression_contract_cid, parameters, parameter_references, bindings, binding_references, local_body}. Local_body contains decoded inputs, steps,
results and normalized effects (empty map when omitted), plus from only when
authored; imported inputs remain explicit source references, not captured values.
Parameters contains only declarations used by that stage, and parameter_references
uses the common canonical reference enumeration relative to local_body.
Each reference is exactly {name, path, type}, with the annotation-free
declaration as type and an RFC 6901 JSON Pointer as path. Sort by path then name
in canonical UTF-16 order, rejecting duplicate locations. Local paths start at
local_body; root paths start at the decoded source document, not the descriptor
array. Structural reference discovery never descends into opaque literal data.
Bindings and binding_references apply the same used-only projection to opaque
binding declarations, never their concrete runtime values. Empty maps/lists
remain explicit. The registered envelope schemas admit source-body containers;
the pinned structural grammar and semantic admission MUST validate their contents.
References remain unbound. Stage id, after and every allowed annotation are
excluded from local_body. The Stage Definition binds its stable stage ID and
this semantics CID; Unit Definition adds the namespace and incoming contracts
specified in Local approval dependencies. Neither hashes the
parent Program. Root parameters/references cover the entire Program and use
source stage-relative pointers; they do not enter unrelated Unit Definitions.
The Program template MUST use the stage descriptors defined above, not the
complete source JSON as its template. Parameter declarations in both
envelopes exclude annotations. The authority-bearing definition envelope is:
{
"version": "actql-program-definition/0.1",
"id": "crm.update_risk",
"program_semantics_cid": "sha256:..."
}The Stage Definition MUST hash exactly the closed envelope
{version: "actql-stage-definition/0.1", id, stage_semantics_cid}, where id is
the normalized authored stage ID and stage_semantics_cid identifies that same
stage's envelope above. There is no self-CID member in this preimage. The parent
Program ID/CID, stage position, after, annotations and concrete values are not
additional members. Changing the stage ID changes this definition even when its
semantics are unchanged. This envelope is registered as schemas.stage_definition
in the stage contract registry. It is not the Unit Definition: the typed
Program/node namespace and incoming contracts remain separate local extraction
requirements. Computing either source identity grants no execution authority.
For an extracted stage, used_parameter_declarations_cid MUST hash exactly the
canonical JSON map in that stage semantics envelope's parameters member.
The map uses the same annotation-free declaration encoding and used-name
selection already specified above; it is not the enclosing Program's parameter
map, a map of concrete values, or a reference-occurrence list. An empty selection
hashes {}. There is no additional version, namespace or self-CID wrapper.
Defaults and constraints remain declaration semantics, while supplied values,
binding origin and annotations do not enter this projection. Opaque Binding
declarations remain in the separate stage semantics bindings member; this
Parameter-only CID does not discharge their local dependency requirements.
The existing stage-semantics parameters schema and semantic declaration
validation govern the map; no additional artifact family is introduced.
For a Unit extracted from a source stage, local_body_cid MUST identify that
stage's complete actql-stage-semantics/0.1 envelope, not only its local_body
member and not its Stage Definition or the enclosing Program. This reuses the
existing semantic envelope so that used Binding declarations, reference
locations and the expression-contract pin remain part of the local meaning.
The separately referenced used Parameter declaration map is the projection
defined above, not an independently chosen declaration set. Unit namespace and
incoming contracts remain separate members of the local dependency envelope;
the stage semantics CID cannot replace either. This defines the
external:local-body reference role for extracted source stages without adding
another artifact family or weakening dependency/authority admission.
Every self-CID is computed over the same envelope without its own CID field.
Normalization journal and schema/capability/mapping evidence
are not Program members. Evidence-dependent completion (for example expanding
drop using a complete checked schema) appears in the journal and AoA plan,
therefore changes Check Environment/Compiled Plan identity without changing
Program identity. The separate AoA logical CID identifies only the main
computation plan; each generated effect plan has its own AoA logical CID.
The Run Receipt MUST bind all applicable identities, concrete input logical
identities and evidence, the Result Manifest CID, and every published Result
Artifact identity and completeness claim. It does not select a Result format or
bind delivered bytes. Result byte hashes belong to the delivery receipt defined
in Result delivery.
ActQL semantic identities use that pinned AoA Kernel canonical JSON contract: Unicode string values are preserved and object member names are normalized at the boundaries specified in Source representation, with the quoted Effect literal source-key exception defined there. Exact decoded keys in those payloads participate in Stage and Program semantic preimages before external target contracts are resolved. Composed and decomposed keys, or keys differing in CR/LF, therefore give different source identities even when later typed Object admission yields the same field. This does not make Program identity depend on a Mapping contract. Object keys are ordered by canonical code units, finite numbers use the AoA normalized number spelling, and negative zero is invalid. The ActQL CID is SHA-256 over the UTF-8 canonical serialization of its semantic envelope. Formatting, source key order, and comments do not affect this identity. A source-text hash MAY be retained separately as provenance.
Local Result import interface
For each declared Result import, the local import_interface_cids entry MUST
identify the closed schemas.result_import_interface envelope registered in the
stage contract: {version: "actql-result-import-interface/0.1", input_name, producer_namespace: {program_id, node_id}, producer_unit_definition_cid, producer_result, value_contract_cid, availability: "stage_success", self_cid}.
Its self CID hashes canonical JSON of every member except self_cid. Names are
source-normalized names. input_name is the consumer's local input name;
producer_result is the producer's local Result name, not a root export alias
or temporary dataflow name. The namespace and Unit Definition identify that exact
producer. A Stage Definition, parent Program revision, local source handle or
array index MUST NOT substitute for the producer Unit Definition.
The value_contract_cid MUST resolve to the complete checked, nonnullable object
value contract of the selected Result row schema, using the existing canonical
value-contract encoding. This is a declared port contract, not a hash of rows,
an observed Result artifact or a Host execution ceiling. Port ownership, field
types and the producer definition MUST agree before constructing the interface;
type equality alone does not prove producer identity. Unknown type evidence or
an unresolved producer Unit Definition prevents construction. No observed value,
Invocation, receipt, physical path or whole Catalog is added to this preimage.
Selected contract semantics remain dependencies of the corresponding Unit;
concrete policy and observation evidence remain execution admission material.
Every declared Result import MUST have its own interface, including unused
imports and distinct input names referring to the same Result. The consumer's
interface list is ordered by normalized input name in UTF-16 code-unit order;
source map enumeration order is not identity. A Result import requires successful
producer computation and publication of that selected Result, independently of
the consumer's predecessor-success gate. after: accepted does not waive data
availability or required Ref availability. Zero rows retain the declared type.
Receipt imports require their separate success-view and correlation contracts;
they MUST NOT be encoded as this Result interface or silently omitted when their
interpretation is unsupported. This static interface is neither proof of
successful publication nor permission to activate the consumer.
Local contract selection and identity
The compiler MUST select the exact transitive contract dependencies of the complete source-owned Unit inventory. Roots include the governing contracts of its declarations, operations, expressions, publication, imports and incoming gates, including domain, guard, permission, scope and failure rules. Selection does not perform liveness or branch pruning: unused declared inputs, authored no-ops and unselected expression branches remain in the inventory. A selected SourceView contributes its declared contract references, including the advertised read-mode contracts; unrelated Catalog members do not become local dependencies. Whole-Catalog membership, resolution uniqueness and whole-lock integrity are still validated outside this projection.
Dependency traversal follows contract-bearing positions specified by each reached contract's registered governing grammar, not arbitrary CID-looking strings, business data or metadata. Unknown dependencies, wrong kinds, conflicting pins and unsupported dependency grammars prevent closure. Finite reference cycles retain every reached member once only where the governing grammar permits those cycles; cycle prohibitions and execution-prerequisite acyclicity remain unchanged. Existing contract identity rules determine the referenced CIDs without ad hoc metadata stripping or rehashing.
A Unit Definition MUST distinguish policy/capacity interpretation contracts from concrete policy decisions, measured quantities and resolved limits. The former are semantic contract dependencies; the latter belong to their governing Plan or Invocation evidence and do not enter Unit Definition merely because Check consulted them. Authored guards remain part of the local body. This preserves the rule that changing Host policy values does not change Program Definition.
The local contract closure CID MUST hash canonical JSON of the following whole
envelope, registered as schemas.local_contracts in the stage contract registry.
{version:"actql-local-contracts/0.1", contracts:[contract_cid, ...]}The array contains exactly the reached contract CIDs, each once, sorted in
ascending UTF-16 code-unit order. Its entries have the canonical lowercase
sha256: form. There is no self-CID field. The empty set uses the same versioned
envelope with an empty array; it does not assert that a particular Unit has no
governing laws. Source locations, traversal order, reference multiplicity and
whole environment/Catalog identities are not part of this set preimage.
Occurrence roles and repeated uses remain in the source-bound selection evidence
and the Unit body/interfaces. Equal contract-set CIDs alone do not establish
equal Unit Definitions or Invocations. Schema validation of a sorted CID set
does not establish complete dependency selection or authority.
Structural grammar (normative)
The following JSON-model grammar is normative. ? marks an optional member,
* marks a string-keyed map, and + marks a non-empty string-keyed map.
| separates alternatives; & merges members into one closed object.
Object-form Input, Parameter, and Binding declarations additionally admit
meta?: Meta as defined in Authoring Context. Their execution fields are defined in Inputs and external contracts;
expressions in Expressions and predicates; operation bodies in Operations; and Effect bodies in Effect declarations.
Expression and Predicate positions accept either a structured tree or a
string decoded under Expression text. This alternative does not apply to other string
fields or to the restricted EffectValue grammar below.
Named productions resolve as follows: name classes and InputOrStepName in
Pipeline names and references–Field names and nested paths; JsonValue, NonEmptyString, and PositiveSafeInteger in
Source representation and Author guards, resolved limits, and Plan facts; Input, ParamDeclaration, and BindingDeclaration in Local typed inputs–Parameters and binding;
OperationName and OperationBody in Operations; ResultDefinition in Named Results and Effect declarations;
MappingRef, CommandContractRef, ResultField, TargetPrimaryKey, ScalarId, and
Field in Effect declarations. Parameter substitution applies only at the value positions
admitted by Parameters and binding, never at a name or structural production.
InputOrStepName denotes any authored dataflow binding, including explicit
multi-output port names, with the position-dependent scope above. values
cells use the closed CellValue alternatives defined in values, not
Expression. Search output registration is simultaneous and rejects collisions
with either output, any input or preceding binding, and self/forward references.
ActQLDocument := {
version: "actql/0.1",
id: NonEmptyString,
meta?: Meta,
params?: { ParamName: ParamDeclaration }+,
bindings?: { BindingName: BindingDeclaration }+,
stages: [Stage, ...],
results: { ResultName: {from: {stage: StageId, result: ResultName}} }+
}
Stage := {
id: StageId, meta?: Meta, after?: "completed" | "accepted",
inputs: {InputName: StageInput}*, from?: InputName,
steps: [Step, ...] | [], results: {ResultName: ResultDefinition}+,
effects?: {EffectName: EffectDeclaration}*
}
StageId := NonEmptyString unique within this Program
StageResultRef := {stage: StageId, result: ResultName}
StageInput := Input | {result: StageResultRef, meta?: Meta}
| {receipt: {stage: StageId, effect: EffectName}, meta?: Meta}
StageConstraints := prior-stage imports only; first stage rejects after;
noninitial omitted after normalizes to completed; no implicit cross-stage current;
Stage.from references only materialized inputs, never a search template.
InputDuplicatePolicy := "reject" | {first_by_order: {order_by: [OrderKey, ...]}}
MaterializedInputExtension := {duplicates?: InputDuplicatePolicy}
InputConstraints := Input forms defined in Local typed inputs and SourceViews;
only materialized relation inputs admit MaterializedInputExtension;
keyed/search acquisition templates and authoritative uniqueness claims are not weakened.
ParameterConstraints := recursive closed scalar/object/array value contract;
nullable defaults false at every recursive Parameter type position;
number integer/minimum/maximum, string format: duration, and array length/max_items
apply at their respective type positions recursively;
default and annotation meta are named-declaration-only;
default is a typed literal applied only for missing values.
DeriveBody := DeriveMap | OrderedDeriveSlots
DeriveMap := {OutputFieldName: Expression}*
OrderedDeriveSlots := [DeriveSlot, ...]
DeriveSlot := {OutputFieldName: Expression} # exactly one member
DeriveConstraints := array is nonempty; NFC-unique slot outputs;
map is input-snapshot/add-only; array is prior-slot/input-preserving shadow;
shape and array order survive normalization; only RHS is Expression.
Meta := non-empty JSON object subject to Authoring Context (#authoring-context)
EventsBody := true
VectorsBody := true
NormalizeGraphBody := true
ReachBody := {start: WireValue | GraphNodeSelector, direction: in | out | both,
max_depth: positive finite integer-valued number}
MatchNodesBody := {ids: [WireValue, ...], fields: [Field, ...]}
| {where: Expression, fields: [Field, ...]}
MatchEdgesBody := {types?: [NonemptyString, ...] | [], where?: Expression,
fields: [Field, ...]}
MatchConstraints := fields are nonempty with no repeated reference;
ids are nonempty typed identities; repeated IDs/types do not repeat result rows.
GraphNodeSelector := {ids: [WireValue, ...]}
| {from: InputOrStepName, id: Field} | {where: Expression}
SubgraphBody := {nodes: GraphNodeSelector, edges?: Expression}
GraphSelectorConstraints := exactly one closed selector form; selected identities
are admitted, deduplicated and required to exist; empty Relation/predicate is valid.
TraverseBody := {start: GraphNodeSelector, emit: nodes | paths,
direction: in | out | both, min_hops: nonnegative integer-valued number,
max_hops: positive integer-valued number, edge_types?: [NonemptyString, ...] | [],
max_output_rows?: PositiveIntegerValue}
| {start: GraphNodeSelector, emit: nodes | paths, hops: [TraversalHop, ...],
max_output_rows?: PositiveIntegerValue}
TraversalHop := {direction: in | out | both, edge_types?: [NonemptyString, ...] | [],
edge_where?: Expression, node_where?: Expression}
ShortestPathBody := {source: PathEndpoint, target: PathEndpoint,
selection?: each_pair | nearest_per_source | nearest_pair,
direction: in | out | both, max_hops: positive integer-valued number,
weight?: Field, edge_types?: [NonemptyString, ...] | [],
max_output_rows?: PositiveIntegerValue}
PathEndpoint := single typed identity | GraphNodeSelector
ShortestPathConstraints := selection is required with any selector and forbidden
with two single identities; weight names a known non-null number edge field.
NearestBody := {queries: InputOrStepName, metric: cosine | dot | l2,
k: positive safe-integer literal, candidate_id?: Field, candidate_vector?: Field,
query_id?: Field, query_vector?: Field}
PoolVectorsBody := {vector?: Field, by: [Field, ...], method: mean | sum | max,
output_id?: OutputFieldName}
PoolVectorsConstraints := by is nonempty and duplicate-free; output_id requires
exactly one grouping field; Relation input requires vector and a scalar candidate
key, while Vectors input retains its intrinsic member identity.
MergeRankingsBody := {with: InputOrStepName, by: [Field, ...] | [],
candidate: Field, score: Field, score_policy: max | {weighted_sum: {left: number, right: number}},
score_contract: ScoreContract, k: positive safe-integer literal, tie_break?: [Field, ...] | []}
| {with: InputOrStepName | [InputOrStepName, ...], by: [Field, ...] | [],
candidate: Field, rank: Field, k: positive safe-integer literal,
score_policy: {rrf: {rank_constant: positive safe-integer literal,
weights: [nonnegative finite number, ...], rank_window: positive safe-integer literal}}}
ScoreContract := {domain: NonemptyString, range: [finite minimum, finite maximum],
normalization: NonemptyString, higher_is_better?: true}
MergeRankingsConstraints := minimum <= maximum; weighted_sum weights sum to one;
RRF weights include the current input and have positive total weight.
TextRankBody := {queries: InputOrStepName, query_id: Field, query_text: Field,
candidate_id: Field, candidate_text: Field, tokenizer: unicode_words | char_bigram,
scoring: {bm25: {k1: positive finite number, b: finite number in [0, 1]}},
k: positive safe-integer literal}
MeanGraphsBody := {with: [InputOrStepName, ...], weights: [nonnegative number, ...],
support: {nodes: number in (0, 1], edges: number in (0, 1]},
properties: {nodes: {Field: MeanPropertyStrategy}*, edges: {Field: MeanPropertyStrategy}*}}
MeanPropertyStrategy := exact_equal | {mean: {missing: exclude | zero, nulls: exclude | zero | fail}}
MeanGraphsConstraints := nonempty graph list; weights match list length with positive
exact total; complete property coverage; generator requires as and rejects step from.
GraphBody := {nodes: GraphNodes, edges: GraphEdges}
| {source: Field, target: Field, label?: Field}
GraphNodes := {from: InputOrStepName, id: Field, fields?: [Field, ...] | []}
GraphEdges := {from: InputOrStepName, source: Field, target: Field,
label?: Field, fields?: [Field, ...] | []}
GraphConstraints := directed only; explicit branches require step as and reject
step from; fields lists have no repeated reference; label completion is concise-only.
DegreeBody := {direction: in | out | both}
ComponentsBody := {kind: weak | strong, max_output_rows?: PositiveIntegerValue}
GraphBoundsBody := {max_nodes?: NonnegativeIntegerValue,
max_edges?: NonnegativeIntegerValue, max_degree?: NonnegativeIntegerValue}
GraphBoundsConstraints := at least one bound; diagnostic thresholds, not resource ceilings.
GraftGraphBody := {with: InputOrStepName}
StructuralGraphBody := {with: InputOrStepName, merge?: GraphMerge}
UnionGraphBody := {with: InputOrStepName | [InputOrStepName, ...], merge?: GraphMerge}
GraphMerge := {node_properties?: {AuthoredFieldName: GraphFieldStrategy}*,
edge_properties?: {AuthoredFieldName: GraphFieldStrategy}*}
GraphFieldStrategy := exact_equal | set_union | set_difference | number_sum
| number_mean | number_min | number_max
StructuralGraphConstraints := union_graphs uses UnionGraphBody with a nonempty list;
subtract_graphs uses StructuralGraphBody; union folds left in authored reference
order, retaining repeated references and treating scalar with as a singleton;
empty maps are retained; strategies refer to existing properties of compatible type.
ParamRef := { $param: ParamName }
PositiveIntegerValue := PositiveSafeInteger | ParamRef
FractionValue := finite number in [0, 1] | ParamRef
GroupBody := {by: [Field, ...] | [], aggregates: {OutputFieldName: AggregateCall}*}
ValuesBody := {schema: {OutputFieldName: ConstructibleValueContract}+,
rows: [{OutputFieldName: CellValue}*, ...] | []}
ConstructibleValueContract := AuthorValueContract excluding json/resource_ref/content_ref
at every recursive type position
ValuesConstraints := each row has exactly the declared field set;
cells use CellValue, not expressions; schema is nonempty even for zero rows.
SetBody := {with: InputOrStepName, duplicates: distinct | all}
SetConstraints := body of except/intersect; compatible aligned input contracts required.
CrossJoinBody := {with: InputOrStepName, right_rows?: one,
max_output_rows?: PositiveIntegerValue,
select?: {OutputFieldName: QualifiedJoinField}+}
QualifiedJoinField := left.Field | right.Field
IntervalJoinBody := {with: InputOrStepName, kind: inner | left | semi | anti,
mode: point_in_interval | overlap, on: [{left: Field, right: Field}, ...] | [],
left: {time: Field} | {start: Field, end: Field},
right: {start: Field, end: Field},
select?: {OutputFieldName: QualifiedJoinField}+,
max_output_rows?: PositiveIntegerValue}
IntervalJoinConstraints := left is exactly {time} in point_in_interval mode,
exactly {start,end} in overlap mode; all temporal fields have the same non-null
timestamp or safe-integer UTC-millisecond domain; on pair occurrences are retained.
AsofJoinBody := {with: InputOrStepName, kind: left | inner,
by: [{left: Field, right: Field}, ...] | [], left_id?: Field,
left_time: Field, right_time: Field, direction: backward | forward | nearest,
tolerance: MillisecondValue, right_tie_break?: [Field, ...] | [],
select: {OutputFieldName: QualifiedJoinField}+}
AsofJoinConstraints := Relation left_id omission requires a complete retained
candidate key; Events retain their intrinsic identity; comparison time fields
are non-null UTC timestamps or safe-integer milliseconds; key occurrences and
ascending tie-break occurrences are retained; null equality keys do not match.
SequenceBody := {by?: [Field, ...] | [], id: Field, time: Field,
pattern: [{as: nonempty string, where: Predicate}, ...],
within: MillisecondValue, overlap?: allow | disallow,
contiguity?: adjacent | subsequence,
max_matches_per_key?: PositiveIntegerValue, max_output_rows?: PositiveIntegerValue}
SequenceConstraints := at least two uniquely labeled pattern elements; generated
output names are valid and collision-free; Events id names the intrinsic role;
clocks on Relation and Events accept timestamps or integer milliseconds;
canonical completion inserts by: [], overlap: disallow, contiguity: adjacent.
CalendarUnit := day | week | month | quarter | year
CalendarTimestamp := canonical UTC millisecond timestamp in years 0001..9999 | ParamRef
CalendarBucketBody := {time: Field, into: OutputFieldName, unit: CalendarUnit,
timezone: nonempty string, week_start?: monday | sunday}
CalendarSeriesBody := {start: CalendarTimestamp, end: CalendarTimestamp,
unit: CalendarUnit, timezone: nonempty string, week_start?: monday | sunday}
CalendarConstraints := week_start is required for week and forbidden otherwise;
literal profile selectors only; the selected pinned TZDB determines zone membership.
MillisecondValue := PositiveSafeInteger | PositiveDurationToken | ParamRef
SignedMillisecondValue := integer number in [-9007199254740991, 9007199254740991]
| PositiveDurationToken | ParamRef
PositiveDurationToken := ASCII digits followed by ms | s | m | h | d,
ASCII case-insensitive; positive safe-integer milliseconds after multiplication
RowWindowBody := {kind: fixed | sliding, time: Field, size: MillisecondValue,
step?: MillisecondValue, offset?: SignedMillisecondValue}
WindowEventsBody := {kind: fixed | sliding, size: MillisecondValue,
step?: MillisecondValue, offset?: SignedMillisecondValue}
WindowConstraints := sliding requires step; offset defaults to numeric zero;
fixed admits and retains an explicit step without using it in window calculations;
omitted fixed step is not inserted; supplied step retains validation obligations;
Parameter declarations are non-null number or duration string;
bound numbers satisfy the corresponding signed/positive safe-integer domain.
WindowGroupBody := GroupBody
LagBody := {partition_by: [Field, ...] | [], order_by: [OrderKey, ...],
field: Field, output: OutputFieldName, offset?: PositiveIntegerValue}
LagConstraints := order_by nonempty; offset defaults to one;
partition-key occurrences retained; output is fresh and nullable.
WatermarkRowsBody := {time: Field, late_policy: drop | mark,
watermark_ms: FiniteNumberValue} | {time: Field, late_policy: drop | mark,
max_skew: MillisecondValue}
FiniteNumberValue := finite number | ParamRef
WatermarkRowsConstraints := exactly one threshold source; fixed threshold permits
fractional and negative values, but not negative zero; number Parameter non-null.
TopKBody := {k: PositiveIntegerValue, by: [OrderKey, ...]}
TopKByBody := {partition_by: [Field, ...] | [], k: PositiveIntegerValue,
by: [OrderKey, ...]}
TopKConstraints := by nonempty; keys repeated without deduplication;
partition keys KeyComparable; authored order keys AuthorOrderable;
total-order completion and selection bounds are separate required evidence;
no expression-valued k or implicit global/partitioned operation rewrite.
PivotBody := {index: [Field, ...] | [], column: Field,
values: [OutputFieldName, ...] | [], value: Field,
aggregate: sum | avg | count | min | max | first, fill?: CellValue}
PivotConstraints := repeated index references retained; values NFC-unique;
generated columns canonical string order; no generated/index name collisions;
column string or nullable string; fill whole-value literal or Parameter;
empty axes preserve the specified global-group and index-only semantics.
AggregateCall := {fn: AggregateFunction, field?: Field, expr?: Expression,
filter?: Expression, distinct?: Boolean, order_by?: [OrderKey, ...],
max_items?: PositiveIntegerValue, max_bytes?: PositiveIntegerValue,
delimiter?: String, fraction?: FractionValue}
AggregateFunction := count | count_if | sum | avg | min | max | array_agg |
string_agg | first | last | percentile_disc | percentile_cont |
var_pop | var_samp | stddev_pop | stddev_samp
AnalyticBody := {partition_by: [Field, ...] | [], order_by: [OrderKey, ...],
functions: {OutputFieldName: AnalyticCall}+, frame?: RowsFrame}
AnalyticCall := {fn: row_number | rank | dense_rank}
| {fn: lag | lead, expr: Expression, offset?: NonnegativeIntegerValue}
| {fn: first_value | last_value, expr: Expression} | AggregateCall
RowsFrame := {start: FrameBoundary, end: FrameBoundary}
FrameBoundary := unbounded_preceding | current_row | unbounded_following
| {preceding: PositiveIntegerValue} | {following: PositiveIntegerValue}
NonnegativeIntegerValue := NonnegativeSafeInteger | ParamRef
AnalyticConstraints := fresh outputs, all expressions use input snapshot;
aggregate-local order_by forbidden; frame required exactly when any frame call exists;
rank/navigation/value calls reject filter/distinct; offset only lag/lead;
start not unbounded_following, end not unbounded_preceding, start <= end;
no RANGE/GROUPS/exclusion; peer order, position order and publication order separate.
AggregateConstraints := exactly one field/expr except optional row-count argument;
distinct follows the closed group matrix; no other modifiers are admitted;
array_agg/string_agg/first/last require nonempty order_by outside analytic;
analytic supplies their order and rejects aggregate-local order_by;
max_items only array_agg; max_bytes and required literal delimiter only string_agg;
required fraction only percentile_disc/percentile_cont;
numeric guards/fractions admit a literal or whole ParamRef, not Expression;
bound parameters satisfy the same finite/range constraints before execution.
NonnegativeSafeInteger := integer number in [0, 9007199254740991]
WireValue := canonical JSON data-model value, opaque to expression/binding traversal
UnreservedCell := WireValue excluding singleton objects with key literal or $param
CellValue := UnreservedCell | { literal: WireValue } | ParamRef
CanonicalCellValue := { literal: WireValue } | ParamRef
CellConstraints := singleton $param requires ParamName; literal payload is opaque;
no schema-dependent dispatch; wrap reserved-shape business data with literal.
Step := {
from?: InputOrStepName,
as?: StepName,
meta?: Meta,
OperationName: OperationBody
}
StepConstraints :=
generator => from absent, as required;
search_candidates => as absent, body.outputs required;
otherwise => ordinary current-input and optional as rules.
SortBody := [OrderKey, ...] | []
JsonPickBody := {column: Field, path: [String, ...], output: OutputFieldName}
AppendBody := {with: InputOrStepName}
UnpivotBody := {
columns: [Field, ...], # nonempty; repeated references retained
keep?: [Field, ...] | [], # omitted means []; no repeated output fields
name_column: OutputFieldName,
value_column: OutputFieldName
}
UnnestBody := {
column: Field,
as: OutputFieldName,
with_index?: true | OutputFieldName,
outer?: Boolean
}
DistinctBody := true | [Field, ...] # nonempty, no repeated references
DedupeRowsBody := {
by: [Field, ...] | [],
order_by: [OrderKey, ...] # nonempty
}
DedupeRowsConstraints := repeated by references are retained;
empty by denotes one global partition; keep first, fail on top tie;
keep/on_tie are language invariants, not authored members.
Take := PositiveIntegerValue | {
count: PositiveIntegerValue,
offset?: NonnegativeSafeInteger | ParamRef
}
Nest := {
with: InputOrStepName,
on: [{left: Field, right: Field}, ...],
into: AuthoredFieldName,
fields: SelectProjection,
order_by?: [OrderKey, ...],
max_items?: PositiveIntegerValue
}
SelectProjection := non-empty select projection defined in select (#select)
RenameBody := {ExistingFieldName: AuthoredFieldName}*
DropBody := [Field, ...] | []
ProjectionConstraints := rename/drop require a complete input schema;
empty rename/drop retain all input fields; select remains nonempty;
unknown fields, duplicate output names, and removing all fields
from a nonempty input fail check.
EffectDeclaration :=
({ meta?: Meta } & { create: CreateBody })
| ({ meta?: Meta } & { update: UpdateBody })
| ({ meta?: Meta } & { upsert: UpsertBody })
| ({ meta?: Meta } & { delete: DeleteBody })
| ({ meta?: Meta } & { command: CommandBody })
| ({ meta?: Meta } & { store: StoreBody })
EffectKind := "create" | "update" | "upsert" | "delete" | "command" | "store"
DataAuthority := {
target: ConnectorRef,
mapping: MappingContractRef,
max_items?: PositiveIntegerValue,
on_item_failure?: "continue"
}
MappingContractRef := ASCIIString matching
^mapping:[A-Za-z0-9][A-Za-z0-9._/-]*\.v[0-9]+$
ResultDefinition := {
from: InputOrStepName,
fields: [ResultField, ...],
order_by?: [OrderKey, ...],
meta?: Meta
}
OrderKey :=
{ Field: SortDirection }
| { field: Field, dir: SortDirection, nulls: "first" | "last" }
SortDirection := "asc" | "desc"
FromResult := { from: { result: ResultName } }
CreateBody := DataAuthority & (FromResult | { payload: JsonObject })
UpdateBody := DataAuthority & UpdateSelector & { set: { TargetField: EffectValue }+ }
UpsertBody := DataAuthority & FromResult & { key: ResultField, set: { TargetField: EffectValue }+ }
DeleteBody := DataAuthority & DeleteSelector
StoreBody := DataAuthority & FromResult
CommandBody := {
from: { result: ResultName },
target: { field: ResourceRefResultField },
contract: CommandContractRef,
input: { InputField: ({ literal: JsonValue } | { field: ResultField }) }*,
max_items?: PositiveIntegerValue,
on_item_failure?: "continue"
}
UpdateSelector | DeleteSelector :=
(FromResult & { id: ResultField })
| (FromResult & { match: {
target_field: TargetPrimaryKey,
result_field: ResultField
} })
| { ids: [ScalarId, ...] }
| { external_id: { field: Field, value: ScalarId } }
| { where: [EffectPredicate, ...] }
EffectValue := { literal: JsonValue } | { field: ResultField }
EffectPredicate := SelectorPredicate<SelectorOperand> (see [Predicates](#predicates); target-field scope in [Effect declarations](#effects))The grammar alternatives are exclusive. create accepts exactly one of a
named result or payload; update and delete accept exactly one selector. A
command consumes named-result ResourceRefs and its Connector-owned typed
contract; it has neither a mapping nor an arbitrary URL/method/body escape.
Predicates defines the shared predicate vocabulary; Effect declarations defines Effect
selector scope.
Every literal in set MUST use {literal: ...}; an object containing exactly
field is a result-row reference. Bare JSON values are invalid.
ActQL does not assign field or literal meaning from the surrounding context.
The map is an unordered Effect Set; see Effect declarations.
Complete example (informative)
Additional complete Programs are in SCENARIOS.md.
This example is informative. The preceding normative definitions determine
its meaning. It ranks one candidate per customer/product, using that pair's
latest day of activity. The length-prefixed candidate ID uses Unicode scalar
counts, so / or : in a source ID cannot collapse different pairs.
version: actql/0.1
id: renewal_risk.rank_evidence
stages:
- id: main
inputs:
activities:
carrier: relation
schema:
event_id:
type: string
nullable: false
customer_id:
type: string
nullable: false
product_id:
type: string
nullable: false
occurred_at:
type: timestamp
nullable: false
primary_key:
- event_id
customers:
carrier: relation
schema:
customer_id:
type: string
nullable: false
name:
type: string
nullable: false
segment:
type: string
nullable: false
primary_key:
- customer_id
product_vectors:
carrier: relation
schema:
product_id:
type: string
nullable: false
embedding:
type: vector
nullable: false
dimensions: 3
primary_key:
- product_id
query_vectors:
carrier: relation
schema:
request_id:
type: string
nullable: false
embedding:
type: vector
nullable: false
dimensions: 3
primary_key:
- request_id
from: activities
steps:
- row_window:
kind: fixed
time: occurred_at
size: 1d
- group:
by:
- customer_id
- product_id
- window_start
aggregates:
activity_count:
fn: count
as: activity_counts
- from: activity_counts
join:
with: customers
kind: inner
on:
- left: customer_id
right: customer_id
select:
customer_id: left.customer_id
product_id: left.product_id
activity_count: left.activity_count
window_start: left.window_start
customer_name: right.name
as: candidates
- from: candidates
join:
with: product_vectors
kind: inner
on:
- left: product_id
right: product_id
select:
customer_id: left.customer_id
product_id: left.product_id
customer_name: left.customer_name
window_start: left.window_start
embedding: right.embedding
- derive:
candidate_key: to_string(len(customer_id)) || ':' || customer_id || to_string(len(product_id)) || ':' || product_id
- dedupe_rows:
by:
- customer_id
- product_id
order_by:
- window_start: desc
- assert_rows:
unique_by:
- candidate_key
as: candidate_vectors
- from: candidate_vectors
nearest:
queries: query_vectors
candidate_id: candidate_key
candidate_vector: embedding
query_id: request_id
query_vector: embedding
metric: cosine
k: 20
as: ranked_candidates
- from: ranked_candidates
join:
with: candidate_vectors
kind: inner
on:
- left: candidate_id
right: candidate_key
null_key: fail
select:
request_id: left.query_id
customer_id: right.customer_id
product_id: right.product_id
score: left.score
as: ranked_evidence
results:
main:
from: ranked_evidence
fields:
- request_id
- customer_id
- product_id
- score
order_by:
- request_id: asc
- score: desc
- customer_id: asc
- product_id: asc
results:
main:
from:
stage: main
result: mainThe author writes semantic operations and explicit semantic choices. The compiler supplies linear wiring, carrier bridges, generated step IDs, and identity restoration. It lowers algebra forms to validated AoA Fragments and, when a boundary form is present, binds those fragments and typed barriers into the closed ActQL Executable Plan.
Machine contracts
These exact schema/law references are owned here. Their identities do not include this document or another owner's aggregate contract.
The expression 0.1 contract supplies common expression and machine node schemas. Unicode/regex/time data and bound laws are imported through its explicit base-profile CID. Value laws and demanded evaluation envelopes are resolved through the pinned expression contract.
../../spec/contracts/actql-aoa.json# sha256:e98fc21c3b3e22d9023f95014c459e444a469c77eadaa04d5b448c1c2e34e766
../../spec/contracts/actql-bound-transfer-laws.json# sha256:014682262d00c8435f300731e823e5b267ec66b78f980c765c4fd3772c68c911
../../spec/contracts/actql-protocol-schemas.json#/schemas/resource_ref sha256:f88141c15be5ac040af593008fdca7c85214fcb4ba60ea7110160b8fe8ee25ac
../../spec/contracts/actql-protocol-schemas.json#/schemas/content_ref sha256:352b114babc0040a2b5c44bc1894823d6cb79e351ac3916d48967ecfe32bd37d
../../spec/contracts/actql-protocol-schemas.json#/schemas/author_value_contract sha256:7deb73a773cf91e861d73dfc7b7b5331d77ee19605934e0198eb608eac69144d
../../spec/contracts/actql-protocol-schemas.json#/schemas/canonical_value_contract sha256:08c311d9a05b323ea6fde184981a965773ee71059c61bf3a830ab0e295c21947
../../spec/contracts/actql-production-profile-schemas.json#/schemas/text_time_expression sha256:798f8d7018ef0aa0acefa8bf2366ce0f0fcd42dcddea11bd1da11c310179bebd
../../spec/contracts/actql-production-profile-schemas.json#/schemas/regex_replacement_ast sha256:92c3fc4fc790cdf8f985b634dfe513f85b6cd5c904bb673de7330d18f577aeed
../../spec/contracts/actql-regex-ast.json# sha256:9c4e0107ad1e6a1eb374e8365c07615c74773416b0b43f9a1e02db5e551efc78
../../spec/contracts/actql-expressions.json# sha256:e7645d18471d4436b7542302adce28303a8a634ce29e9f5fd849f5cee8d1159e
../../spec/contracts/actql-expressions.json#/schemas/expression sha256:13d2c700aef09d7fd33d3eba9705f89541432b766b328ad394f9c2acd4686bc2
../../spec/contracts/actql-expressions.json#/schemas/expression_node sha256:d88c7ba0e4be6e1ea2519468f82303012dcdd8d8daf48af9e9a5889411659cb1
../../spec/contracts/actql-expressions.json#/schemas/evaluation_envelope sha256:68862bad556419047c4ded99896b14ad0993ffa453d4f67580a546aeeeb2fd9f
../../spec/contracts/actql-expressions.json#/laws sha256:bb8faf6038bb2b548b25b994d5baf926d2052a4611d1608191bab2d296251725
../../spec/contracts/actql-expressions.json#/schemas/fragment sha256:e07531aa4e844069941baae63c624996db3c0d5ed4a2eddfb26b7030816da542
../../spec/contracts/actql-expressions.json#/schemas/predicate_consumer sha256:067f0a0017116bd31143765743e5f7251b2550f18cb91b321faae296b240ff01../../spec/contracts/actql-stage-contracts.json#/source sha256:a55a1f56a5f19d0d118a6ff6d161f34c6d239d54f473ff84a95592a7c03e31eb
../../spec/contracts/actql-stage-contracts.json#/schemas/derive_binding sha256:d74bb697d91a2daf2c51e0d6c492f4bb35fdea5e542d606318683b1c9c5f06f9
../../spec/contracts/actql-stage-contracts.json#/schemas/local_dependencies sha256:22a2636742d270464988e350adc168af898987e83f620cba893efb091d9f49e8
../../spec/contracts/actql-stage-contracts.json#/schemas/local_contracts sha256:a1dcd5bf20a7d6dceedd4a298b684f439101d391063ae13bb61359bd10d30621
../../spec/contracts/actql-stage-contracts.json#/schemas/result_import_interface sha256:81898e3b8eb76b16e59e0de4b38ed5d948e7752ae127b45ae432f39e0afd2431
../../spec/contracts/actql-stage-contracts.json#/laws/lifecycle sha256:da02951b68ca4255a25ac5b3f60288dbde2a8f7b252f518d73247c9feca09c54
../../spec/contracts/actql-stage-contracts.json#/laws/identity sha256:090d1c81f41bc0cbda07b62ade75d2fbd450d0a333fcf6a23d1c2483b43e213f
../../spec/contracts/actql-stage-contracts.json#/schemas/stage_semantics sha256:c9fdde04da54089003a628cae27f01c118f59e359dff7cacdd09f6eb86c9f2e3
../../spec/contracts/actql-stage-contracts.json#/schemas/stage_definition sha256:4a3827b54a953f84ae64992a8ba6ac0b0c6c55025665ae00c5af7e95fa368c79
../../spec/contracts/actql-stage-contracts.json#/schemas/program_semantics sha256:89152a5f20ffdf17f321f40cf21d57c691ba5c30772d0aac2545888786a98ba0Source: actql/actql/docs/spec/language.md at d904416674e4 · Edit on GitHub