8.4 KiB
Semantics
Table of contents
- Overview
- Statement semantics
- Trigger semantics
- Qualifier scope
- Time trigger semantics
- Condition and expression semantics
Overview
This page explains how ruki statements, triggers, conditions, and expressions behave.
Statement semantics
select
selectwithoutwheremeans a statement with no condition node.select where ...validates the condition and its contained expressions.select ... order by <field> [asc|desc], ...specifies result ordering.- A subquery form
selectorselect where ...can appear only insidecount(...). Subqueries do not supportorder by.
order by
- Each field must exist in the schema and be an orderable type.
- Orderable types:
int,date,timestamp,duration,string,status,type,id,ref. - Non-orderable types:
list<string>,list<ref>,recurrence,bool. - Default direction is ascending. Use
descfor descending. - Duplicate fields are rejected.
- Only bare field names are allowed —
old.andnew.qualifiers are not valid inorder by.
limit
- Must be a positive integer.
- Applied after filtering and sorting, before any pipe action.
- If the limit exceeds the result count, all results are returned (no error).
create
createis a list of assignments.- At least one assignment is required.
- The resulting task must have a non-empty
title. This can come from an explicittitle=...assignment or from the task template. - Duplicate assignments to the same field are rejected.
- Every assigned field must exist in the injected schema.
id,createdBy,createdAt, andupdatedAtare immutable and cannot be assigned.
update
updatehas two parts: awherecondition and asetassignment list.- At least one assignment in
setis required. - The
whereclause and every right-hand side expression are validated. - Duplicate assignments inside
setare rejected. id,createdBy,createdAt, andupdatedAtare immutable and cannot be assigned.
delete
deletealways requires awherecondition.- The
wherecondition is validated exactly likeselect where ....
Trigger semantics
Triggers have the shape:
<timing> <event> [where <condition>] <deny-or-action>
Rules:
beforetriggers must havedeny.beforetriggers must not have an action orrun(...).aftertriggers must not havedeny.aftertriggers must have either a CRUD action orrun(...).- trigger CRUD actions may be
create,update, ordelete, but notselect
Examples:
before update where new.status = "done" and dependsOn any status != "done" deny "cannot complete tiki with open dependencies"
after create where new.priority <= 2 and new.assignee is empty update where id = new.id set assignee="booleanmaybe"
after delete update where old.id in dependsOn set dependsOn=dependsOn - [old.id]
At runtime, triggers execute in a pipeline: before-triggers run as validators before persistence, the mutation is persisted, then after-triggers run as hooks. For the full execution model, cascade behavior, configuration, and runtime details, see Triggers.
Qualifier scope
Qualifier rules depend on the event:
createtriggers:new.is allowed,old.is notdeletetriggers:old.is allowed,new.is notupdatetriggers: bothold.andnew.are allowed- standalone statements: neither
old.nornew.is allowed
Examples:
before create where new.type = "story" and new.description is empty deny "stories must have a description"
before delete where old.priority <= 2 deny "cannot delete high priority tikis"
before update where old.status = "in progress" and new.status = "done" deny "tikis must go through review before completion"
Important special case:
- inside a quantifier body such as
dependsOn any ..., qualifiers are disabled again - use bare fields inside the quantifier body, not
old.ornew.
Example:
before update where dependsOn any status = "done" deny "blocked"
Time trigger semantics
Time triggers have the shape:
every <duration> <statement>
Rules:
- the interval must be a positive duration (e.g.
1hour,2day,1week) - the inner statement must be
create,update, ordelete— notselect run()is not allowed inside a time triggerold.andnew.qualifiers are not allowed — there is no mutation context for a periodic operation- bare field references in the inner statement resolve against the tasks being matched, exactly as in standalone statements
Examples:
every 1hour update where status = "in_progress" and updatedAt < now() - 7day set status="backlog"
every 1day delete where status = "done" and updatedAt < now() - 30day
every 2week create title="sprint review" status="ready" priority=3
Condition and expression semantics
Conditions:
- comparisons validate both operand types before checking operator legality
is emptyandis not emptyare allowed on every supported typeinandnot inrequire a collection on the right sideanyandallrequirelist<ref>on the left side
Expressions:
- field references resolve through the injected schema
- qualified references use the same field catalog, then apply qualifier-policy checks
- list literals must be homogeneous
emptyis a context-sensitive zero value, resolved by surrounding type checks- subqueries are only legal as the argument to
count(...)
Binary + and - are semantic rather than purely numeric:
- string-like
+yieldsstring int + intandint - intyieldintlist<string> +/- string-or-list<string>yieldslist<string>list<ref> +/- id-ref-compatible valuesyieldslist<ref>date + durationyieldsdatedate - durationyieldsdatedate - dateyieldsdurationtimestamp + durationyieldstimestamptimestamp - durationyieldstimestamptimestamp - timestampyieldsduration
For the detailed type rules and built-ins, see Types And Values and Operators And Built-ins.
Pipe actions on select
select statements may include an optional pipe suffix:
select <fields> where <condition> [order by ...] [limit N] | run(<command>)
select <fields> where <condition> [order by ...] [limit N] | clipboard()
| run(...) — shell execution
Evaluation model:
- The
selectruns first, producing zero or more rows. - For each row, the
run()command is executed with positional arguments ($1,$2, etc.) substituted from the selected fields in left-to-right order. - Each command execution has a 30-second timeout.
- Command failures are non-fatal — remaining rows still execute.
- Stdout and stderr are fire-and-forget (not captured or returned).
Rules:
- Explicit field names are required —
select *and bareselectare rejected when used with a pipe. - The command expression must be a string literal or string-typed expression, but field references are not allowed in the command string itself.
- Positional arguments
$1,$2, etc. are substituted by the runtime before each command execution.
Example:
select id, title where status = "done" | run("myscript $1 $2")
For a task with id = "TIKI-ABC123" and title = "Fix bug", the command becomes:
myscript "TIKI-ABC123" "Fix bug"
Pipe | run(...) on select is distinct from trigger run() actions. See Triggers for the difference.
| clipboard() — copy to clipboard
Evaluation model:
- The
selectruns first, producing zero or more rows. - The selected field values are written to the system clipboard.
- Fields within a row are tab-separated; rows are newline-separated.
- Uses
atotto/clipboardinternally — works on macOS, Linux (requiresxcliporxsel), and Windows.
Rules:
- Explicit field names are required — same restriction as
| run(...). clipboard()takes no arguments — the grammar enforces empty parentheses.
Examples:
select id where id = id() | clipboard()
select id, title where status = "done" | clipboard()
For a single task with id = "TIKI-ABC123" and title = "Fix bug", the clipboard receives:
TIKI-ABC123 Fix bug