RUNNING
The rl command line
rl is a client of the same API the console uses: the console, the API and the command
line are one path, so what one of them can do, the others can. There is no public download
yet. A release carries it as bin/rl, beside the capabilities/ directory it reads.
Verbs
| Verb | What it does | Needs |
|---|---|---|
rl diff [-C dir] [-capabilities dir] |
Validates the working tree offline, exactly as the control plane would: every file decoded, every pipeline checked against the constraint bundle, every environment’s overlays merged over the base and checked again. Sends nothing. With -project p -env e it is a preview on the control plane instead — rl diff -project p -env e [-C dir] [-server url] [-no-files] [-o json] — which prints the control plane’s own diff of what the tree would change there, with the files rl plan would send, and stores nothing. See Previewing on the control plane. |
a constraint bundle; for the preview RL_SERVER, RL_TOKEN, config.propose |
rl plan -project p -env e [-C dir] [-server url] [-capabilities dir] [-no-files] |
Plans the working tree as one change set against an environment: what the tree adds is a create, what it keeps is an update, what it dropped is a delete, for the base and for this environment’s overlays. Sends the files in each pipeline’s folder that differ from what the control plane already holds, with its declaration, and removes none: see Sending a pipeline’s files; -no-files sends the declarations only. Runs the offline gate first when a bundle is at hand. A tree that changes nothing, because the environment already has everything it declares, says nothing to plan: <environment> already has everything the working tree declares and names no change set: the control plane stores nothing for a plan that does nothing. |
RL_SERVER, RL_TOKEN |
rl deploy -project p -env e [-key k] [-no-files] [...] |
rl plan, with the same files, then submits the change set under an idempotency key (random unless -key). Reports the commit, the pull request, or that the change awaits approval. When a gate blocks the change set (a gate failed, could not run, or has not finished) the control plane refuses the submit: it prints the server’s sentence naming the gate, then run `rl gates <id>` to see which, and exits 1; nothing was submitted. On an environment that follows approvals, a tree that commits nothing but leaves the environment behind the branch is submitted as a catch-up — even when the caller cannot see all of what it brings; a member who can approves it. |
as plan |
rl lake seed [-C dir] [-dry-run] |
Declares the layers and schemas the working tree’s tables already use, as files — layers/<name>.yaml and schemas/<layer>-<name>.yaml, with a generated-by annotation and nothing opinionated — so the repository becomes self-describing. Offline; it sends nothing. The seed reaches the control plane only the ordinary way: review the diff, rl diff, rl deploy. Declaring the first layer also wakes the rule for a file an asset names, and rl diff is a working copy that cannot know which files the control plane holds, so the closing line says to run rl plan against the environment before pushing: seeded N layer(s) and M schema(s); review the diff, then `rl diff` and `rl deploy`; then `rl plan` against the environment before pushing: `rl diff` cannot know which files the control plane holds. One-time: a tree that already declares a layer has opted in. Refuses a tree whose tables use a namespace level no layer may be named, because declaring the first layer activates the reference rules and that tree could not pass them. |
— |
rl approve [-key k] [-state s] <change-set-id> |
Lands a proposal awaiting review in the control plane. Requires config.approve; the author may approve their own only when nobody else can (no other member who is not banned holds config.approve on the project). When a gate blocks the change set it prints the server’s sentence naming the gate in place of the approve command (the control plane would refuse it), then run `rl gates <id>` to see which, sends nothing and exits 1; TWO EXCEPTIONS: a change set whose verdict is a gate that could not run (checks_cannot_run, a parser that was down) or that has not given an answer (checks_pending, still running or not yet run) is still sent, because the control plane judges that gate again at the approval and the refusal, if it comes, is the answer. An approval the control plane refuses for a gate is reported the same way. On an environment that follows approvals (the review policy) it approves nothing without -state: it prints the changes approving would make there and the rl approve -state <token> <id> command that approves exactly those, and that command is refused, printing the current changes and a new command, if they have moved since. When the approver cannot see all of some of those changes, it shows only the ones they may see, says there are others (never how many) and prints no command: only a member who can see all of them can approve. Exit 0 when it was approved; 1 when the control plane refused, or when the verb approved nothing on purpose (the gate and the review-phase cases above), each with the sentence that says what to do (a gate refusal is a 1 even when a later attempt can clear it, as checks still running do; a change set that turns AI prompt text on needs a person’s fresh sign-in, which a token cannot give, and is a 1 that says to make it in the console); 2 when nothing was attempted or decided: a usage mistake (a flag after the id is named), no server or credential, a credential that was not accepted, a call it does not serve, an answer that holds no change set or one with no status, or no answer at all (a control plane that cannot be reached, a timeout, a fault). A 2 after the request was sent may be an approval that landed: the sentence says so and names the -key to repeat the same command with, which returns the placement the first approval made (see Exit codes). Before 2026-11-04 every failure was a 1, an outage included. Flags go before the id. |
RL_SERVER, RL_TOKEN |
rl reject [-reason why] [-o json] <change-set-id> |
Closes a proposal without committing anything. The audit log records the rejection under the token rl signed in with (a console session is recorded as the person), with -reason beside it (at most 500 characters, refused and never cut), not as the person who planned the change set. -o json prints the control plane’s answer as one JSON document (see Output). Exit 0 when it was rejected; 1 when the control plane refused, in its own sentence, with no document; 2 when nothing was attempted or decided: a usage mistake (a flag or a form -o does not know, a flag after the id, no id or two), no server or credential, a credential that was not accepted, a control plane that cannot be reached, a call it does not serve, a profile that cannot be read, or an answer that is not a rejection (no change set, an empty one, or one with another status: nothing is printed and nothing here says it was rejected; before 2026-11-04 the first printed rejected : unspecified and exited 0). A 2 after the request was sent may be a rejection that was made: the sentence says so, and repeating the same command is safe, since a change set already rejected answers with the rejection. A change set that needs a person’s fresh sign-in is a 1 that says to make it in the console. See Exit codes. Before 2026-11-04 a control plane that could not be reached, and a missing server or credential, were a 1. Flags go before the id. |
RL_SERVER, RL_TOKEN |
rl withdraw [-reason text] [-server url] [-o json] <change-set-id> |
Takes back a change set you planned. Only its author can: a reviewer who is not the author closes it with rl reject, and a change set an agent planned has no author, so it is closed that way too. It asks nothing first. On success it prints the id, withdrawn, the author and the time it was closed. -reason (optional, at most 500 characters) is sent with the call and printed back as reason sent; the answer does not carry a reason, so rl never says it was recorded. A change set that is already withdrawn answers with the same success, and the reason a repeat call sends is dropped. When the control plane refuses — not yours, already past the point where it can be taken back, not found — rl prints its sentence unchanged and exits 1. An open change set (its pull request is on GitHub) is withdrawn by closing its pull request first: the control plane closes it, and only then records the withdrawal. After the usual lines rl adds pull request <url> is closed when the answer reads the pull request closed (a state that was read, not a claim that this call closed it: one already closed on GitHub reads the same), or pull request <url> was not found on GitHub, so it was not closed when GitHub did not show it and the withdrawal went ahead without closing it; a draft or a proposal has no pull request and prints neither. A pull request that is already merged settles the change set as merged and the withdrawal is refused, in the control plane’s sentence, exit 1. A close that failed changes nothing: when GitHub did not answer the control plane says so and rl exits 2 (nothing was decided); when no answer came from the control plane itself (a timeout, an outage, a fault) rl exits 2 and says the withdrawal may have been made, and repeating the same command is safe, since a change set that is already withdrawn answers with the same success; when no retry clears it (GitHub refused the App’s access, the repository’s address is out of date) it exits 1. Nothing here has run against a real GitHub yet: RunningLake’s GitHub App is not registered. Flags go before the id. |
RL_SERVER, RL_TOKEN, config.withdraw |
rl env rename [-project p] [-server url] [-o json] <old> <new> |
Renames an environment that holds nothing yet: no data plane bound, no project resource, no overlay for it in the repository and no live change set. The control plane decides, and when it refuses rl prints its sentence unchanged and exits 1. When no answer came (a timeout, an outage, a fault) it exits 2 and says the rename may have been made: read the project’s environments before repeating it, because a repeat of a rename that was made is refused as not found. On success it prints the old and the new name and the project, and says that profiles and Terraform configuration that name the old environment must be changed to the new one: rl edits neither. Flags go before the names. There is no rl project delete: the command line has no way to give the fresh sign-in that deleting a project is meant to need, so it does not offer one. |
RL_SERVER, RL_TOKEN, env.rename (owner or admin; not a token confined to one project) |
rl spendcap held [-project p -env e] [-all] [-server url] [-o json] |
The changes to an environment’s work cap that wait for an owner or an admin. A pushed change to the SpendCap file that would loosen the cap (raise the amount, relax hold to alert, change the currency or the period) or take it away is not applied on its own: the environment keeps the cap it has until it is confirmed. Each change prints its id and status, the cap in force and the cap pushed (both exact, in the currency the control plane names, never rounded to look tidy; a cap the platform could not read is not readable, and a missing currency prints no amount), why it is held, the commit that proposed it, when it was first seen, and whether it can be confirmed. The default is the open changes; -all adds the confirmed and the cleared. It follows every page. A held change is a record: the output says recorded and never refused, a work cap limits metered work, not the bill, and whether a cap refuses statements is not this verb’s to say (it points at rl spendcap status). Nobody is notified. A reason this build does not know prints as it may weaken the cap. |
RL_SERVER, RL_TOKEN, config.read |
rl spendcap confirm [-project p -env e] [-server url] <held-change-id> |
Shows one open held change (the same lines as held) and sends you to the console. It never confirms and always exits 2: confirming needs env.spendcap.set (owner or admin) and a fresh sign-in, and a token carries no sign-in time, so the control plane refuses a token by design (a token may lower a cap and may never raise one). rl therefore does not make the call, which would only write a refusal into the audit log. Open the console’s Cost page for the environment and press Confirm there: it sends the two amounts shown, and the control plane refuses the confirmation if either has moved since. An id that no open change has is a refusal, exit 1, and says to list with held -all. |
RL_SERVER, RL_TOKEN, config.read |
rl spendcap status [-project p -env e] [-server url] [-o json] |
An environment’s work cap in one read, the rest of the Cost card (ConfigService.GetWorkCap): the cap in force, this UTC month’s metered work, what the shadow evaluator observed, and how many statements finished with no price yet. The cap is no cap in force only when the platform holds none; a cap it holds but cannot read is not readable, which is not the same. Metered work is the receipt at list prices, not the bill, which supersedes it; a month priced in several currencies has no total, and a month priced in another currency than the cap’s is said not to be compared. Amounts are exact, the cap and the month at the one precision either needs, never rounded to look tidy. A part the control plane could not read prints as not read, never as a zero. The shadow line counts statements judged at admission (would be refused, would raise an alert, could not be compared by currency) and is best effort: it can be lower than the statements that ran. Whether the cap refuses is the platform’s word, read by state: enforced (a statement a cap of hold has no room for is refused at the SQL window only; a statement already admitted is never cancelled and a pipeline run’s statements are not judged yet; an enforcement row says so and a refusals row counts this month’s refusals as attempts, not statements, not read when the control plane could not read them), not enforced because the organisation is on a trial that has not converted or the platform writes no reservations, or not shown when the platform could not say. A cap of alert that is enforced refuses nothing. With no enforcement the line says nothing enforces the cap yet only beside a shadow record (mode shadow); with neither it claims nothing. Needs cost.read, because the month’s metered spend is in it. |
RL_SERVER, RL_TOKEN, cost.read |
rl query -project p -env e "<sql>" (or -file q.sql) [-engine trino|duckdb] [-download path] [-o json] |
Runs one statement on the data plane the environment is bound to and watches it to the end: prints the id and status, every change of status as it happens, the engine with the reason the record stores for it, and at the end the record — rows out, bytes scanned, CPU seconds, peak memory, elapsed, and the error class if any, with the sentence that says what the class means. -engine asks for that engine and nothing else: left out, the SQL window chooses (Trino where the plane serves it and the credential names a person, DuckDB otherwise), and the record’s engine reason says chosen when you named one. Naming an engine is an instruction, not a hint: a plane that does not serve it, or Trino for a credential that names no person (an agent, or a token with no owning user), is refused in the control plane’s own words and the statement runs on no other engine, exit 2. A word that is not one of the two is a usage mistake, exit 2, and nothing is sent. -no-wait returns after the submit. -timeout (default 30m; 0 for no limit) bounds the watch: when it runs out with the record not terminal, the last record is printed and rl exits 3. Never prints rows: results stay on the data plane. When the plane published a result, the record’s last line is its link, size and expiry, and -download <path> fetches it — whole or not at all, with nothing written and no temporary file left if it cannot complete. An expired link is refused before any request is made, and a refusal names the status and never the presigned URL. A download that fails after the query succeeded exits 4, not 1: the statement ran, so the thing to retry is the fetch. |
RL_SERVER, RL_TOKEN, query.execute |
rl glossary list [-status draft|approved|deprecated|rejected] [-domain slug] [-q text] [-server url] [-o json] |
The organisation’s glossary, one line per term: its slug, its name, its domain, its status, its version, who stewards it and the start of its definition. A term belongs to the organisation and is the same in every project, so these verbs take no -project and no -env; a script that passes one gets “flag provided but not defined” and exit 2, and nothing is sent. A draft’s steward column reads who proposed it. A version waiting for approval over the one in force is shown as 2 (3 waiting). A term with no steward in the answer reads none. -status and -domain narrow the list, and -q keeps the terms whose name, a synonym or definition holds the text, ignoring case. Every page is followed, and -o json merges them into one response. There is no column of linked columns or tables. |
RL_SERVER, RL_TOKEN, catalog.read |
rl glossary get [-version n] [-server url] [-o json] <term> |
One term by its slug: its name, status, version, domain and steward, the version in force with its definition, every version waiting for approval in full (the person who decides reads what they decide on, and the line says it is approved by someone other than whoever proposed it), and a table of all its versions with who proposed and who decided each, the reason a rejection gave, and the term a deprecated version points to. -version n prints that one version in full. A version nobody has decided reads waiting. |
RL_SERVER, RL_TOKEN, catalog.read |
rl glossary propose -name text -domain slug (-definition text | -definition-file path|-) [-synonym text]... [-key k] [-server url] [-o json] <term> |
Proposes a term: a draft that changes nothing until someone else approves it. The term is named by a slug (lowercase letters, digits, - and _, beginning with a letter, at most 63 characters) that never changes. -name is the display name a person reads (1 to 100 characters, not used by another term in any status), -domain is the slug of a data domain that is not archived, and both are fixed once the term exists. The definition is up to 4 KiB, given with -definition or read with -definition-file from a file, or from standard input when it is -: a file over 16 KiB, text that is not valid UTF-8 or a file with nothing in it is refused before anything is sent, and the text is sent exactly as read, nothing trimmed. -synonym may be repeated (up to 20, each up to 100 characters). A value over its limit is refused by the control plane in its own sentence, never cut. Prints the term and the version as stored and the idempotency key; a retry with the same key proposes one term. Every member except a viewer may propose (glossary.propose); an agent may not. |
RL_SERVER, RL_TOKEN, glossary.propose |
rl glossary update [-definition text | -definition-file path|-] [-synonym text]... [-no-synonyms] [-key k] [-server url] [-o json] <term> |
Proposes the next version of a term’s definition and synonyms. The name and the domain cannot be changed, so there is no -name and no -domain here. At least one of the definition, -synonym and -no-synonyms is required; the synonyms you give replace the list, and -no-synonyms removes them. rl first reads the term and sends the version it read: a version made in between is refused by the control plane in its own sentence (exit 1) and nothing is replaced silently. The version in force stays in force until the new one is approved, and a term with a version already waiting takes no second one until that is decided. Prints the new version’s number and the idempotency key. Same members as propose. |
RL_SERVER, RL_TOKEN, glossary.propose |
rl glossary approve -version n [-key k] [-server url] [-o json] <term> |
Approves a version, by someone other than the person who proposed it: the control plane refuses the proposer’s own approval in its own sentence (exit 1), except in an organisation of one member, where the answer says the version was approved by its own proposer. -version is required and nothing is chosen for you: it is the number of the version you read with rl glossary get, and if a newer one has been made since, the control plane refuses and nothing is approved. Owners and admins may approve (glossary.approve). rl signs in with an API token, and a token is judged by its own role, so a steward who holds only an assignment (and is neither an owner nor an admin) decides in the console as themselves, in a signed-in session. An assignment counts only for a person’s own signed-in session; the control plane decides that by the credential it is sent, and rl checks nothing of the kind. The person behind a token that proposed a version is still the proposer. Prints the version, who decided it and when, and the idempotency key; a retry with the same key is the same approval. |
RL_SERVER, RL_TOKEN, catalog.read, glossary.approve |
rl glossary reject -version n -reason text [-key k] [-server url] [-o json] <term> |
Rejects a version, with the reason its proposer will read (1 to 500 characters, required). The version stays readable, its name stays taken, and the version in force, if there is one, stays in force; a term with nothing approved takes a new version from rl glossary update. -version is required, as for approve. Who may: as approve, including the rule that rl signs in with an API token, and a token is judged by its own role, so a steward who holds only an assignment decides in the console as themselves, in a signed-in session. If the answer does not carry the reason, rl says so and does not say it was recorded. |
RL_SERVER, RL_TOKEN, catalog.read, glossary.approve |
rl glossary deprecate -version n -replacement term [-key k] [-server url] [-o json] <term> |
Retires the version in force and names the term to use instead (-replacement, required: another term whose version in force is approved). A deprecated term stays deprecated, and old references read “Deprecated: use” the replacement. It is refused while a version waits for a decision. -version is the version in force you read, required as for approve. Who may: as approve, including the rule that rl signs in with an API token, and a token is judged by its own role, so a steward who holds only an assignment decides in the console as themselves, in a signed-in session. |
RL_SERVER, RL_TOKEN, catalog.read, glossary.approve |
rl glossary export [-out file] [-server url] [-o json] |
The terms in force and the domains as YAML, exactly the bytes the control plane made: to standard output with nothing added (not even a final newline), or with -out to a file that is written whole or not at all, with mode 0600 because it holds the organisation’s definitions. A destination that cannot be written fails before anything is asked. The file holds the version in force of every term, its domain, status and replacement, and every domain; it holds no draft, no rejected version, no person and no asset. An empty answer is a fault (exit 2) and writes nothing. -out with -o json is a usage mistake. Every member may read it; an agent may not, and each export is recorded in the audit log. |
RL_SERVER, RL_TOKEN, catalog.read |
rl query get -project p [-download path] [-o json] <query-id> |
One query’s record, as the control plane last heard of it; -download fetches its result on the same terms as above, and exits 4 if the fetch fails. |
RL_SERVER, RL_TOKEN, run.read |
rl query list -project p [-env e] [-limit n] [-mine] [-o json] |
The project’s queries, newest first, one line each with the SQL collapsed to one line; -env narrows to one environment, -limit defaults to 50. -mine keeps only the queries this token ran itself. rl always signs in with an API token, and the control plane hands a token the results of what that token ran and nothing else, so the queries the same person ran in the console, or with another token, are not in it. The control plane decides which queries those are and rl filters nothing itself; an empty answer reads this credential has run no queries in <project> (or <project>/<environment>). |
RL_SERVER, RL_TOKEN, run.read |
rl query cancel -project p [-o json] <query-id> |
Asks for one query that has not ended to stop. It is a request: the control plane tells the data plane and records who asked and when, and the query ends when the data plane reports it. rl prints query <id> is running; cancel requested by <who> at <time> and the command that reads the record back, and exits 0 when the request is recorded, not when the query is over; a script that needs it over reads it with rl query get until the status is terminal. A statement still waiting for a free slot on the plane’s engine was never sent, so there is nothing for the data plane to report: it is cancelled at once and the answer is query <id> is cancelled; cancel requested by <who> at <time>. A query that had already ended is an answer and not an error: query <id> is already succeeded; nothing was cancelled, exit 0, and nothing was sent. A token may cancel a query that same token ran. Cancelling any other query, including one the same person ran in the console or with another token, needs the owner or admin role on the token, and the refusal ends with (it needs <permission>), naming the one that is missing, for example query.cancelothers (exit 1). When the control plane could not hand the request to the data plane it records nothing and says so (exit 1): ask again. A control plane that cannot dispatch queries at all exits 2. |
RL_SERVER, RL_TOKEN, query.execute, query.cancelothers (any query this token did not run: owner or admin) |
rl query logs -project p -list [-o json] <query-id> |
The query’s job-log streams, one line each: STREAM (stdout, stderr, profile, Query), STATE, LINES, SIZE (stored, compressed) and FIRST ERROR (the plane’s own line number, or a dash when it reported none; a DuckDB query’s stream always has a dash, because the engine’s message is not kept in its log and the failure class on rl query get is its first error). -list takes nothing else: the lines are the next row’s forms, and -list with any of their flags exits 2 and sends nothing. Who may read: only the person who ran the query; anyone else, and a query that does not exist, get the same answer (query <id> was not found in <project>, or it was run by someone else), so the call does not say a colleague’s query exists. A job with no readable log prints no table and says why on stderr, in the console’s words: This plane does not keep Trino's logs yet., Removed by retention: this project keeps job logs 30 days, and this query ended on 2 Sept 2026.; the engine and the day come from one read of the query’s record, and if that read fails the sentence loses only that clause. The table is stdout and every sentence is stderr, so a pipe sees only the table. Exit: 0 the list was read (including a stream that cannot be read, which has its reason beside it, and a query with no streams and no reason); 1 the job has no readable log, or the caller may not read it; 2 a usage mistake, no server or credential, Not built yet (the control plane declares the call and has not built it) or Not on this control plane (it does not declare it). |
RL_SERVER, RL_TOKEN, run.read |
rl query logs -project p [-stream s] [-errors] [-line n] [-download path] <query-id> |
A stream’s lines, raw on stdout, one LF-ended line each, exactly as the plane stored them (a removed secret’s marker included; the verb strips nothing, so piping them through cat -v is the reader’s to choose). The control plane signs five-minute links and carries no line; rl fetches each segment from the link and refuses one that is not the bytes the plane’s seal writes, whole, printing nothing of it. Give at most one of -errors, -line and -download. -stream names a stream by the label -list prints (stdout, stderr, Query; any case) or by its id, and may be left out when the query has one stream; two or more is a usage mistake that lists them, never a guess. Bare, it prints the whole stream. -errors is from the line the plane marked as the first error to the stream’s last line, not "the error lines": the plane reports one line and this verb does not decide which others are errors; a stream the plane marked none in (a DuckDB query’s, always) exits 1. -line n prints that one line. -download path writes the whole stream as the plane stores it (gzip), whole or not at all (a temporary file beside the path, renamed into place), when the control plane offers a link for it (it does once the plane has written the stream’s last line, and a control plane that offers none exits 1 and says so, whatever the stream’s state; print the lines and redirect them instead). The status is stderr: stderr · 2,150 lines and The log is complete., stderr · line 2,001 of 4,100, stderr · from line 4,090 of 4,100, wrote ./stderr.log.gz (6.8 MiB). A link that does not answer is signed again once; a fetch that fails after that exits 4, and the lines before the failed segment have already been printed, so the message says the first line not printed is 2,001. -o json is refused with lines (log lines are text: -o json is valid only with -list). The same read rule as -list. Exit: 0 the read happened; 1 no readable stream, the caller may not read it, the line is past the end, no error line was marked, or no download link; 2 a usage mistake (-list with a reading flag, -line 0, -download with no path, two of -errors, -line and -download, -o json, a stream that must be named), no server or credential, Not built yet or Not on this control plane; 4 a fetch from the plane failed, even with a fresh link, or a segment was not one rl can read. |
RL_SERVER, RL_TOKEN, run.read |
rl project create -name n [-slug s] [-cloud c] [-region r] |
Creates a project with a dev and a prod environment. -cloud is the cloud the project’s data lives in (aws, gcp or azure; aws when empty), and a cloud whose data plane is not built yet is refused by name, with where it stands, and nothing is created. -region is where the project’s data lives, and it is the one thing here that is hard to change later: a data plane can only be bound to an environment in the same cloud and region, so a project created in the wrong one cannot be served by the cluster the customer then builds. Empty means the organisation’s home region, which the control plane resolves from the organisation’s own row. The slug is derived from the name by the server unless -slug is given. |
RL_SERVER, RL_TOKEN, project.create (owner or admin; a token confined to one project may not create another) |
rl project list [-o json] |
Every project this credential can see, with the cloud and region each lives in and its prj_ id. -o json prints the response as one JSON document instead (see Output). |
RL_SERVER, RL_TOKEN, project.read |
rl plane preflight [-cloud aws|gcp|azure] [-profile p] [-project p] [-subscription id] [-region r] [-subnets ids] [-data-bucket name] [-principal-arn arn] [-cidr block] [-express-az az] [-pods n] [-kubernetes-version v] [-train t] [-catalog-engine-version v] [-no-egress-check] [-plane id] [-server url] |
Read-only checks to run before tofu apply, and the tfvars a customer cannot guess. On AWS: the current AL2023 AMI id for their region and cluster version, their egress address as a /32, and a freshly minted external id; two more are named and cannot be produced yet, with the reason. For file events (a rule that reacts to a file landing) it also warns, never fails: with -data-bucket name (a bucket the plane will adopt, not one the module creates) it reads whether the bucket sends events to Amazon EventBridge, which an adopted bucket’s owner has to turn on, and with -subnets and no NAT route out it reminds you to name sqs in interface_services; both say in plain words what is lost and how to fix it, and the apply succeeds either way. The two extra reads are s3:GetBucketNotificationConfiguration on that bucket and ec2:DescribeVpcEndpoints for the subnets’ VPC; a read your credentials may not make is reported as unknown and names the permission. On GCP (-cloud gcp -project p -region r) and Azure (-cloud azure -subscription id -region location) it checks the project or subscription is active, the APIs or resource providers the module uses are enabled or registered, the quota the default plane needs and, on Azure, that the identity can create role assignments; it asks the cloud’s own CLI (gcloud, az) for a token, or reads GOOGLE_OAUTH_ACCESS_TOKEN or AZURE_ACCESS_TOKEN. Those two collectors have only run against API shapes read from documentation, never a real project or subscription, and the report says so on its first line. A cloud with no preflight at all is refused by name, before any credential is consulted, with where it stands. The AWS setup guide that walks through each flag where you run it is not published yet. |
the customer’s own cloud credentials -plane id also posts the report to the control plane (AWS only; see Posting the preflight report). |
rl status -project p -env e [-server url] |
Where the environment actually is, as opposed to what git says it should be: resources by phase, how far it is behind git, what is still being torn down, and — for an environment that follows approvals — whether it is holding back from its branch, since when and at which commit, or has an approval the next sync applies. When the control plane has read and refused the newest commit it says so (the commit, when it was read, where the environment stays) and does not call the environment settled; rl plan and rl deploy say it in their in flight: line. When some resource is not ready it then names each one, grouped by why: failed, waiting on the plane, converging, degraded on purpose, or not built in this build. Each line carries what the control plane recorded for that resource and nothing more: the error, the retries when the control plane counted them, the next attempt when one is queued, the conditions that are False for a resource held on purpose, and plain words for a kind this build cannot run (it is listed as a failure only when the control plane recorded a real error for it). The list is what the credential is shown, it reads at most ten pages of 200 resources and says when it stopped short, and an environment with nothing not ready makes no listing at all. If the totals printed and the listing then could not be read, it exits 1. rl plan saying “already matches” is not a claim about convergence; this is the read that is. |
RL_SERVER, RL_TOKEN, config.read |
rl schema <kind> |
Prints the fields a kind’s spec accepts, from the Go type the server decodes it into — the same type rl diff and the console use, so what it prints is what the gate accepts. Offline. |
— |
rl get <kind> <name> -project p -env e [-o json] |
Reads one resource back as the control plane holds it, status and conditions included. The kind is case-insensitive. The flags may stand before the kind and name or after them. -o json prints the response as protojson, the form a script should parse. Its exit code is 1 when the control plane answered no, in its own sentence (a resource it does not have, one this credential may not read, or a name, project or environment it refuses), and 2 when nothing was decided: a usage mistake (a flag, a kind or a form -o that it does not know, a missing name, project or environment), no control plane or credential to ask, a credential it does not accept, a call it does not serve, an answer with no resource in it, or a control plane that cannot be reached or answers with a fault. The same under -o json. Before 2026-11-03 only a flag or a form -o does not know, and -h, were 2, and every other case above was 1. |
RL_SERVER, RL_TOKEN, config.read |
rl gates [-o json] <change-set-id> |
The four checks a change set’s SQL passes: dialect, catalog, dry run and differential, each with the control plane’s verdict, its sentence and the evidence behind it. It decides nothing itself, and the change set names its own project. -o json prints the response as one JSON document (see Output). The dialect and catalog checks are decided now; the dry run and the differential are not built yet, so they read “not run” and the command exits 2 even when the first two pass. |
RL_SERVER, RL_TOKEN, config.read |
rl check -project p -env e [-server url] [-o json] <statement> |
The dialect and catalog checks over ONE statement, before there is a change set: the same judgement the console’s Check and the Gates tab hold, as you (a table you may not see reads as absent, exactly as a table that is not there). It decides nothing itself, stores nothing, audits nothing and charges nothing, and starts no dry run or differential, which belong to a change set, so only two gates are answered. Each prints its verdict, the control plane’s sentence and its evidence; a warning is printed and does not change the exit code. Needs config.propose on the project (engineer, admin, owner; a service principal is refused). -o json prints the response as one JSON document (see Output). Exit 0 when both ran and passed, 1 when one failed or the control plane refused in its own words, 2 when nothing was decided (a gate not reported or not run, or the call not built). |
RL_SERVER, RL_TOKEN, config.propose |
rl ai policy [-project p] [-env e] [-server url] [-o json] |
What AI may do and where each value came from, per scope: the organisation, each project and environment, each layer. It prints what the control plane sent for each scope, one value at a time with its setting, its source, the server’s detail and the chain it walked, and resolves nothing itself: there is no effective line of its own, because a minimum worked out here would be a second copy of a rule the control plane owns. A field the control plane left empty prints not reported, and a source or setting this rl has no words for prints as the control plane spelled it. A widening request that is waiting is printed under the scope it is on, with who asked, when, and when it expires (the control plane’s time; 14 days after it was asked today). A widening that nobody answers expires and is not printed, and one its requester withdrew is not printed either. With no project (none given and none in the profile) it reads every project the credential can see and says so on stderr; -env without -project is refused. It is read-only, and there is no flag that carries a key, a secret or a value: changing a setting is made in the console, where a widening needs a fresh sign-in and a second owner or admin. -o json prints the response as one JSON document (see Output). Exit 0 when the policy was printed, 1 when the control plane refused the read (it names the action), 2 when nothing was read (see Exit codes). |
RL_SERVER, RL_TOKEN, config.read |
rl ai access [-project p] [-env e] [-server url] [-o json] <table> |
What an agent acting for you may see of one table, kind of context by kind of context: names and types, descriptions, the profile’s numbers, lineage and row samples. It prints your grant on the table (the control plane names none yet, so it reads not reported), the table’s classification and, for each kind, the control plane’s verdict (included, excluded_classification, excluded_no_grant, sample_off or ai_off, with whose rule said so when AI is off) and the rule behind it when the control plane sends one (the scope that decided and the file it is written in, for a verdict a policy setting decides; the commit that last changed that file and the change set that made it, each when recorded; never a guess), and decides nothing itself: it works out no verdict from the classification or the policy. A verdict the control plane sent without its rule prints rule not reported, a field it left empty prints not reported, and a token this rl has no words for prints as the control plane spelled it. The table is named by its qualified name, layer.schema.table, inside a project and an environment, both required (the profile may supply them): a table found under another project would be answered under a role that was not yours there. A table that is not there and a table you may not see are the same answer. A trial’s answer has no row for the profile. Nothing it prints is a value, and there is no flag that carries a key, a secret or a prompt. The verdict is what the boundary would admit, not what the context store holds. -o json prints the response as one JSON document (see Output). Exit 0 when the verdict was printed, 1 when the control plane answered no (the read was refused and it names the action, or there is no such table for you), 2 when nothing was read (see Exit codes). |
RL_SERVER, RL_TOKEN, catalog.read |
rl model providers [-project p] [-env e] [-server url] [-o json] |
The AI provider connections a project declares, one line each: the provider, the name of the stored key it signs in with, its address, its region and how many models it offers. It prints a key’s name, never a key, and not a key’s state: a key is entered in the console behind a sign-in and its state (stored, tested, replaced) is read there, because a command-line credential is not given it (a key’s calls are held to a person’s fresh session). There is no flag that carries a key, a secret or a value. An environment that declares none prints a note on stderr and nothing on stdout. -o json prints the response as one JSON document (see Output). Exit 0 when the connections were printed, 1 when the control plane refused the read (it names the action), 2 when nothing was read (see Exit codes). |
RL_SERVER, RL_TOKEN, config.read |
rl model provider <name> [-project p] [-env e] [-server url] [-o json] |
One provider connection with its models and what each costs: the input, output, cache-read and cache-write price in dollars for a million units, as the file declares them and printed from whole micro-cents by integer arithmetic, never a float. A dash is a price left unset, which is not zero: an unpriced model is not a free one. The name is the connection’s name in the project’s configuration. A connection that is not declared in this environment is a read that found nothing (exit 2). -o json prints the response as one JSON document, with the prices in micro-cents as the control plane holds them (see Output). |
RL_SERVER, RL_TOKEN, config.read |
rl model spend [-project p] [-env e] [-server url] [-o json] |
What a project’s model calls have cost and what each budget that applies to it allows, in dollars and nothing else: the cap, what finished calls cost (SETTLED), the most the calls still running may cost (HELD) and what is LEFT. The figures are the whole micro-cents the control plane holds, printed by integer arithmetic. A budget with no cap prints no cap and a dash for its settled, held and left (the control plane reports no figure for it), never $0.00; the unset shadow budget is the exception, its default being a cap of nothing; and the server’s mark (within, near or at its cap) is printed as it was sent: rl works out no percentage. Only agents will stop at a cap; queries, runs and everything else carry on. -project and -env are both required (the profile may supply them). -o json prints the response as one JSON document (see Output). Exit 0 when it was printed, 1 when the control plane refused the read (it names the action), 2 when nothing was read (see Exit codes). |
RL_SERVER, RL_TOKEN, cost.read |
rl model budgets [-project p] [-server url] [-o json] |
Every model budget this credential may read, as rl model spend prints them: with no -project, the organisation’s own rows and every project’s (the profile’s project does not narrow it); with one, that project’s and the budgets inside it. It takes no -env. The organisation’s cap, the shadow budget and the trial’s grant are for a credential that is not confined to one project. Exit codes as rl model spend. |
RL_SERVER, RL_TOKEN, cost.read |
rl model budget set -scope organisation|project|agent|action|shadow [-project p] [-agent a] [-action x] [-server url] [-o json] <dollars> |
Lowers one budget’s cap, to $0.00 included, which stops every call that budget holds. The figure is dollars with at most two decimals (50, 12.50). -scope and the flags it needs address the budget: a project’s takes -project, an agent’s -project and -agent, an action’s all three, and the organisation’s and the shadow budget’s none. A budget is raised only in the console, with a confirmation from the last five minutes: rl reads the cap the control plane holds first, and a figure above it or a cap where there was none makes no change and sends nothing: it prints the sentence and exits 2. A budget the control plane lists no row for is said to be no such budget (a typo in an agent, or a first cap, which is set in the console), also exit 2 and nothing sent. The organisation’s and the shadow budget’s rows need no project, and the profile’s project is not sent for them. It takes no -env. If the control plane refuses a change as a raise anyway (the cap moved between the read and the change), the same sentence is said. A lowering prints the cap now held and the control plane’s own sentence for what it does. There is no flag that carries a key, a secret or a value. -o json prints the response as one document. Exit 0 when the cap was set, 1 when the control plane refused it (it names model.budget.set) or when the answer did not arrive, so it is not known whether the cap changed (read it with rl model budgets), 2 when nothing was changed. |
RL_SERVER, RL_TOKEN, model.budget.set, cost.read |
rl model calls [-project p] [-env e] [-agent a] [-outcome o] [-since t] [-until t] [-limit n] [-server url] [-o json] |
The model calls a project’s agents made, one line each and newest first, as the control plane sent them: when the call started (UTC), the agent, the model, the outcome in words, the cost and where the call was sent. The cost is dollars the control plane settled, or a dash: a call that has not settled, or was refused before it was sent, prints a dash and never $0.00, and a figure sent without its currency is a dash too. The list adds nothing together and sorts nothing. -project is required (the profile may supply it); -env narrows the list to one environment; -agent and -outcome are closed words as the control plane spells them, -since and -until are instants in RFC 3339, and -limit asks for 1 to 200 calls. There is no flag that carries a key, a secret, a value or a prompt, and what was typed in a refused flag is not repeated. An empty list and a list cut short are said on stderr and print nothing on stdout. A control plane that does not serve the read says so and rl exits 2: that is never an empty list, and it does not say no model was called. -o json prints the response as one JSON document (see Output). Exit 0 when the calls were printed, 1 when the control plane refused the read (it names the action), 2 when nothing was read (see Exit codes). |
RL_SERVER, RL_TOKEN, model.calls.read |
rl model call [-server url] [-o json] <id> |
One call’s receipt, by the id the list prints: its cost and what settled it (usage, expiry or reconciliation), where it was sent and processed, its origin and provider, the attempt and the latency, the task and the change set it belongs to, the call it shadowed, and what refused it when it was refused before it was sent. By reference and never a value, it names the context the agent was given (kind, declared name, version and where it lives), counts the sources this credential may not see without naming them, counts what was masked by class and where it was found, and says whether prompt text is stored. It never prints prompt text or context content, and a field this rl does not know is not printed at all. The provider’s counts of what it received and returned, by class, are worded as the provider counts them and sit beside the price card that priced them: they appear on a receipt only, never on the list. A call that is not there for you is exit 1. -o json prints the response as one JSON document (see Output). Exit 0 when the receipt was printed, 1 when the control plane refused the read (it names the action) or there is no such call for you, 2 when nothing was read (see Exit codes). |
RL_SERVER, RL_TOKEN, model.calls.read |
rl volume upload -project p -env e -schema <layer>-<name> [-as name] [-content-type t] FILE |
Puts one file into a schema’s reserved _files/ area (-volume names a declared volume instead). -as stores it under a different name than the one on disk, and -content-type sets the type sent with it — guessed from the extension when unset, which is what an object store serves it back as. The control plane signs a single-object URL and the bytes go straight to the plane’s storage, never through the control plane. |
RL_SERVER, RL_TOKEN, volume.write |
rl volume ls -project p -env e -schema <layer>-<name> |
What is listed there: names, sizes and times, never content. An empty listing does not show the place exists — an object store answers the same for an empty prefix and for one nothing ever wrote — and the command says so. -all follows every page. |
RL_SERVER, RL_TOKEN, config.read |
rl routing rules [-server url] [-o json] |
The rules of the router’s constraint bundle, one line each: the rule, its kind, its state, what it does to the engines of a job it matches (a hard-correctness rule permits some engines, removes one, or refuses the job; the other two kinds do not choose engines and print a dash), and the bundle version it came in. The bundle’s version and digest are printed above the table. The state is what the rules’ evaluator does with a rule (enforced applies it, shadowing works out what it would remove and removes nothing, declared does not evaluate it); it is not a statement that nothing enforces what the rule describes. The rules are a fact about the bundle the control plane loaded: no -project, no -env. |
RL_SERVER, RL_TOKEN, routing.read |
rl routing rule [-server url] [-o json] <id> |
One rule: every condition it names, the conditions the evaluator does not read (listed apart, so a rule is not read as broader than it was written), the engines it permits or removes, and its message. A declared rule’s message is a template whose placeholders nothing fills in yet. | RL_SERVER, RL_TOKEN, routing.read |
rl addon list [-project p] [-env e] [-server url] [-o json] |
What the environment’s add-ons are doing on the data plane it is bound to, one line each, in the console’s words: the add-on, its state (ready, starting, degraded: <the plane's reason>, off in this environment, planned, not applied, the plane is not connected, not on this plane’s train), its version, its train and its ready replicas. A figure the plane has not reported is a dash, never a zero. An add-on is turned on and off by a change set (an Addon file, then an environment overlay), so there is no verb to turn one on here: this says where that stands. An environment bound to no data plane runs no add-on and is refused by name. The read is organisation-wide and needs dataplane.read, so a credential confined to one project, and the analyst and viewer roles, are refused in the control plane’s own sentence; the rows of the plane’s other projects are fetched and then dropped, which is a convenience and not an access boundary. -o json prints the response, narrowed to this environment. |
RL_SERVER, RL_TOKEN, dataplane.read |
rl addon get [-project p] [-env e] [-server url] [-o json] <kind> |
One add-on in the environment: its name, state, why it stands where it does in a sentence, its version, its train, its ready replicas and, when it is planned, the change set that plans it. The kinds are kyuubi, spark-connect, jupyterhub, mlflow, feast and kserve; one the control plane has no row for is refused with the list. Exit 0 only when the add-on is ready; any other state prints the answer and exits 1, so a script can wait on it. |
RL_SERVER, RL_TOKEN, dataplane.read |
rl source list [-project p -env e] [-type t] [-state s] [-server url] [-o json] |
The Sources the environment declares, one line each, as the control plane reports them: the source, its type, its state (the token, so a script can match it), its lag, its last sync and where it lands. A figure the plane has not reported is a dash, never a zero, and the listing says so under the table: a lag of 0 s is a measurement and no lag yet is not one. A source whose state carries an error class prints it in parentheses beside the state. An environment on the shared trial plane lists none, and says the control plane’s reason. -type and -state keep the sources of one type or state; the control plane refuses a word it does not know, by name, and an empty -type or -state is a usage mistake rather than “no filter”. It follows every page, and -o json prints them merged into one response with no page token (see Output). A Source is added, changed and removed with rl plan and rl deploy of sources/<name>.yaml, not here; rl schema source prints what that file accepts. |
RL_SERVER, RL_TOKEN, config.read |
rl source get [-project p -env e] [-server url] [-o json] <name> |
One Source as the control plane reports it: its type, its state with the sentence that says what the state means, its lag, the log its sink has not confirmed (the slot lag, Postgres only, in binary units such as 18.4 GiB; -o json carries the byte count) and when its plane observed its newest checkpoint, its last sync, next sync and last change, the rows it landed in the last day and week, where it lands, the name of the SecretReference it reads with (never a value), the plane it runs on, its snapshot, its replication slot and the WAL the slot keeps in your database, and the statement that drops a slot, each as the control plane reports it. A figure the plane has not reported is a dash, and a source that streams (change capture or a stream) and has no checkpoint says so in a sentence under the figures; a source that reads on a schedule has no stream and prints neither the slot lag nor the last checkpoint line. <name> is the source’s own name in the environment the flags or the profile give, or the whole projects/…/environments/…/sources/… name; rl get Source <name> is its spec. A source that is not there, and one this credential cannot see, are worded alike. |
RL_SERVER, RL_TOKEN, config.read |
rl source kinds [-project p -env e] [-server url] [-o json] |
The types of Source and whether the control plane lists each as available in this environment (today PostgreSQL change capture, the jdbc type, object storage and MySQL change capture; the control plane records a Source of any of the four and no plane reads one yet, and none on the shared trial plane): the type, what it reads, yes or no, and a note in the control plane’s own words: for a type it does not list as available the sentence for why, and for each of the four types it lists as available, the sentence that the control plane records a Source of it and no plane runs it yet. The list is the control plane’s: rl keeps none of its own. |
RL_SERVER, RL_TOKEN, config.read |
rl source prereqs [-project p -env e] [-server url] [-flavour f] [-o json | -sql] (<name> | -f file) |
The steps, and the SQL, that prepare your database for a Source, as the control plane writes them: rl composes no SQL, so the console and the command line print one text for a database you run yourself. Each step names the check that verifies it, says when the database must restart before it takes effect, and shows its statements. No step contains a password: a comment says the password is chosen in your own store and set there. <name> is a saved source; -f file is a Source document that is not saved yet, such as sources/orders-db.yaml, and only its name, type, database user and tables (and, for the jdbc type, its engine) are sent: never its host, port, database or credential reference. Exactly one of the two. -flavour writes the steps for self_managed (the default), rds, aurora or cloud_sql. -sql prints only the SQL, each step’s statements as the control plane composed them and a blank line between steps, so it can be piped to your database’s client; it is not combined with -o json. A source of a type that is not read from a database has no steps, which is said. |
RL_SERVER, RL_TOKEN, config.read |
rl source drop-slot <name> |
Makes no call and drops nothing, and exits 2 every time. A replication slot lives in your own database, and rl does not drop one: it says what the control plane reports about a slot, where the statement that drops a slot a removed source left behind is meant to be shown (the source’s page in the console, and rl source get <name>), and to check first that the source is removed, because a paused source is meant to keep its slot. |
none |
rl code-step list [-project p] [-env e] [-server url] [-o json] |
The code steps the environment declares, one line each: a pipeline asset that names a pythonFile. For each, what its file declares and nothing else: the pipeline, the table it writes, the script, how many tables and volumes it may read (a list the file leaves out declares a script that reads nothing), how many connections it may call, the two declarations (retry-safe, deterministic; one the file does not set reads no) and where it runs (the platform picks unless the file pins function or pod). It says nothing about running: a line that did would be false the day a plane runs one, and nothing runs a code step yet. A pipeline of statements is not listed; a pipeline whose spec this rl cannot decode is counted in one sentence and not guessed at. -o json prints the response (see Output). Read-only; the script’s text is a pipeline file and is not read here. |
RL_SERVER, RL_TOKEN, config.read |
rl storage-rule list -project p -env e [-place layer.schema[.volume]] [-pipeline name] [-state s] [-o json] |
The rules that react to a file landing, one line each: the rule, its one state word, where it watches, its actions in the order they run, when it last fired, what it matched in the last 24 hours and how many dead letters it holds. A figure no plane has reported yet is a dash, never a zero, and the listing says so under the table: a zero would read as “the plane looked and found none”. “What it matched” is the files the plane RECEIVED that the rule’s filters passed (counted when the plane received them, so a backlog drains into the current hours as a spike; a file can match several rules; the figure saturates at 2,147,483,647), and, for a plane that records (it reports storage.record.v1), it is followed by what the control plane kept of them and the batches it recorded (1,200 matched, 1,180 recorded, 3 batches); for any other plane 1,200 matched stands alone and last fired stays a dash. A rule’s last fired is when its newest batch was confirmed by the plane, after the control plane recorded it (a batch still waiting for that confirmation is not counted): for now a firing is a recording, and the page says so. -state takes a rule state word, -place a layer.schema or layer.schema.volume, -pipeline a pipeline a rule starts. It follows every page, and -o json prints them merged into one response with no page token (see Output). Names no object key: a rule watches a place, and a place is a directory. |
RL_SERVER, RL_TOKEN, config.read |
rl storage-rule get [-project p -env e] [-o json] <rule> |
One rule’s status, as the control plane derives it: the state word and the sentence that says what it means, where the rule watches (and the directory that implies, labelled as derived from the declaration, not observed), its filters, its batching with the defaults filled in, its actions, and what the plane the environment is bound to reports about its event source: the state of that source when the plane reports one, or, when its last report is older than fifteen minutes, when the plane was last heard from; and, on a plane whose current report names the rule set it holds, a rule held line saying whether the plane holds this version of the rule (it holds this version of the rule (it reports rule set 5), or it does not hold this version of the rule yet). No line at all is the control plane saying it cannot tell, which is not a no, and, beside it, the plane’s digest key as the page says it: a digest key line (held, fingerprint kfp_…, since <time> only for a current report from a plane that holds the key now, unavailable from the plane’s own word, or a dash with its reason where a key was known and cannot be said now; nothing for a plane that never sent a fingerprint, and never held for one the control plane cannot vouch for) and a history line (the plane’s digest key changed on <date>; duplicate matching, when it exists, starts over, whenever the plane’s key has changed, because that is history; it is the plane’s date, the same for every rule of the plane, and it does not say a rule’s history was reset: it names the plane’s key, and the marker that would say it for one rule is read by nothing yet). Holding a rule is not watching it: the state word says watching only for a rule whose plane holds this version AND says it is receiving, and then it means the plane is polling its event queue with the rule in hand, not that a file arrived or that anything ran. The last 24 hours print what the plane counted (files matched, and the files a filter ignored) and, for a plane that records, what the control plane kept (events recorded, batches closed, repeat deliveries), each a dash for a plane that does not, and a dash for the actions done, failed and runs started on every plane, because nothing runs an action yet; the 7- and 30-day windows are dashes, because only the last 24 hours are read so far. A last fired that is a time is followed by what it means (for now a firing is a recording), and never is printed only for a plane that records and has recorded no batch for the rule. The rule is named by its own name in the environment the flags or the profile give, or by its whole projects/…/storagerules/… name; a name with any other slash in it is refused. rl get StorageRule <rule> stays the spec; this is the status the spec cannot carry. |
RL_SERVER, RL_TOKEN, config.read |
rl storage-rule test [-project p -env e] [-o json] <rule> (-object key [-size 12MiB] [-event created|deleted] | -event-file file|-) |
What one sample file would do under a rule, before one arrives, as the control plane evaluates it with the same matcher the plane compiles: every filter, matched or refused, with its reason; what batching would do; each action; and why nothing would fire. -object is a sample key, relative to the watched place or its whole path, with an optional -size (12MiB) and -event (created, the default, or deleted). -event-file is a pasted S3 EventBridge Object Created or Object Deleted document of at most 256 KiB, read from a file or from standard input when it is -, never from an argument; a document over the cap is refused before anything is sent. It writes nothing and starts nothing. It prints no key, on standard output or standard error: the answer speaks of the rule’s own patterns and limits, and a refusal names the document and not its content. -object is an argument, so it is in your shell’s history. Exit 0 when at least one action would fire, 1 when none would, 2 when nothing was evaluated; a rule exits 0 only on a plane that records (it reports storage.record.v1), holds the rule and is receiving, and only for the action record, which records the batch and does nothing else (every other action is refused at authoring as not built); on any other plane it exits 1, and the answer says which (rule_not_on_plane: the plane has not confirmed that it holds the rule; plane_not_receiving: it holds it and does not say it is receiving; plane_not_batching: it holds it and is polling its event queue but does not report that it records, so nothing groups what arrives into batches or runs its actions; a held rule that is switched off carries it too, beside off_in_config). |
|
rl storage-rule events [-project p -env e] [-outcome batched|already_processed] [-since t] [-until t] [-limit n] [-o json] <rule> |
What the control plane recorded for a rule, newest first, one event a line: when the plane received it (UTC), its id shortened to eight characters, its kind (created or deleted), its size, its outcome and its batch, and, under the table, the hours that were counted and not listed (what was recorded, what a filter refused, repeat deliveries). The control plane keeps no file name, so the verb says how to find a file instead: by its time and its size, against your own storage; it prints no key, no etag and no bucket, and the response carries none. A figure nobody reported is a dash, never a zero: a deleted file has no size, and an hour the plane did not count has no ignored figure. -outcome takes batched or already_processed (a repeat delivery is counted and never listed, so duplicate is refused); -since is included and -until is not, each an RFC 3339 instant, 2026-10-04 02:19 or a date, always read as UTC; -limit (default 100, at most 5,000) is the most events listed, and pages are followed until there are that many, with a word when more exist. A listing that is empty says which of three things is true (a filter emptied it, the plane does not record, or nothing has been recorded yet). -o json prints the pages merged into one response with no page token, ids whole. Events are kept 30 days (the control plane’s purge, 8.3.66), and under the table the verb says so, in the words Events are kept 30 days.; a control plane with no store to purge sends no number and the verb says nothing of a retention. |
RL_SERVER, RL_TOKEN, config.read |
rl asset list [-project p -env e] [-q text] [-kind K] [-layer l] [-tier t] [-server url] [-o json] |
Every layer, schema, table, view and volume the environment declares that this credential may see, one line each: its address (silver.sales.orders, the Data lake’s spelling), its kind, a table’s tier as its plane last reported it, and the description its kind declares in git, clipped to a line and labelled [Declared in git]. A table no plane has reported has no tier: it prints a dash, which is not a zero and not “unclassified”, and the listing says so under the table. An asset you may not see is absent, and nothing says how many are. It follows every page, and -o json prints them merged into one response with no page token (see Output). -kind takes Layer, Schema, Table, View or Volume, and -tier public, internal, confidential, restricted or pending; the control plane refuses any other by name. -q text searches instead of listing: it keeps the assets whose declared names (a layer, schema, table, view or volume, and the address they make), display name or the description git declares hold every word of the text, in any case, and adds a FOUND IN column that says where, in the console’s words (its name, its display name, its description). Words are split at spaces and dots, so silver.sales.orders is three words; a word is matched as typed and never read as a pattern; the control plane refuses an empty query, more than 8 words, a word over 64 characters or a query over 200 characters by name (exit 1, its own sentence), and an empty -q given to rl is a usage mistake (exit 2, nothing sent). -q takes -kind, -layer and -tier as the listing does. Names only, no model, no ranking: a description held in a plane is not searched, and the answer does not say how many assets matched that you may not see. The FOUND IN column exists only with -q. |
RL_SERVER, RL_TOKEN, catalog.read |
rl asset get [-project p -env e] [-server url] [-o json] <collection/id> |
One asset as the control plane holds it: where git declares it, a table’s declared columns (name, type, required) and what its plane last reported (rows, files, the last commit, the snapshot, the tier and who chose it, and how many columns are unresolved when any are: the catalog holds such a table back whatever its tier, and Coverage counts it pending). <collection/id> is layers, schemas, tables, views or volumes and the declaration’s own name, in the environment the flags or the profile give; the whole projects/…/environments/…/tables/… name is accepted too, and a pipeline’s name is refused before anything is sent. A table’s description is not here: git declares none for a table and nothing stores one yet. A table the plane has not reported says so and prints no figure, never a zero. A column the plane holds and git does not declare is never named. It also prints who owns and who stewards the asset, its data domain and, for a table or view, its certification, each with where it came from (set here, or from the schema silver.sales), as the control plane resolved them for you. no owner, no steward, no domain and, after certification, none mean the control plane looked and nobody is named; - (not reported yet…) means this control plane did not say, which is not the same thing. An asset you may not see reads exactly as one that is not there. |
RL_SERVER, RL_TOKEN, catalog.read |
rl asset coverage [-project p -env e] [-server url] [-o json] |
How much of the environment is described and classified, as the control plane counted it over what you may see: layers, schemas, views and volumes with a description in git, and tables classified (a person chose the tier and no column is unresolved, the rule the catalog gates with), with how many are pending (no tier a person chose, or a tier with columns still unresolved) and how many the plane has not reported at all. A figure nobody counts yet reads not reported yet, never 0 (a zero would say somebody counted and found none), and a facet counted over nothing reads none declared. The two description facets a plane counts (Tables described, Columns described) are over the tables the planes reported, and say so: 0 of 2 reported, 8 not reported yet is two tables reported and undescribed and eight nobody has counted, never 0 of 10; Columns described reads N of M, in the R reported (the columns of the reported tables), and each prints its count by label when any label holds something (1 Declared in git, 2 AI-generated, not reviewed, and so on, as the control plane split it). Before any plane has reported, both read not reported yet. Then the open work the server counted. There is no score and no total across facets, and nothing says how much is out of sight. |
RL_SERVER, RL_TOKEN, catalog.read |
rl migrate analyse -source S [-rate DOLLARS] [-server url] [-o json] <file> |
Sends one usage export (the CSV the control plane’s export query for that source produces) and prints the analysis it makes: the analysis id, the source, the period the export covers as UTC dates with the last day included, today’s side by component with where each figure came from, the covered work and the work kept out of both sides, what today’s side leaves out, the assumptions, and what the RunningLake side says. -source is required and is sent exactly as typed: the control plane judges it, and rl keeps no list of sources. -rate is what you pay per unit the export counts (a DBU, a credit), from your own contract, in dollars: a plain number such as 0.55, with a leading $ allowed and at most 8 decimals. A comma, a sign or an exponent is refused, so is a ninth decimal (it is never rounded), zero, and a price above $1,000,000 a unit. Leave -rate out to analyse without one: it is then unset, which is not zero, and units the export counts without dollars stay in that unit. The file is read in memory by the control plane and is not kept; rl reads it once, never prints a byte of it or its name, and sends the bytes alone, with no name. It is at most 16 MiB, and an empty file or one over the limit is refused before anything is sent. Flags go before the file. A figure the control plane did not send reads no figure, never $0, and a count in the export’s own unit is printed with that unit and no dollar sign. Today’s figures are in dollars a month, cut to the cent or the fewest decimals that show a figure and never rounded up. Until a RunningLake price exists the other side reads not priced, with the control plane’s own sentence and no range; when one does, rl prints the rate card that priced it (with the console’s caution, A newer rate card exists. This analysis keeps the card it was priced with and is not repriced., on the line under it when the control plane holds a newer one) and the range as low to high a month, each end rounded up to the whole dollar, then the lines by component (compute, the floor, and the management fee as a line of its own), each workload with its engine, its range and its confidence and the reason for it, and what drives the range, every component, engine, confidence and driver in words and not as its token. A range whose two ends are equal, as the floor’s are, is still printed as low to high. An analysis priced in part has no total: rl prints the lines and the workloads that were priced, no figure and the reason for each that was not, and the control plane’s own words for why there is no total. One engine, Trino, is measured, so only work it would run and whose export states the bytes it scanned is priced, at low confidence: a Snowflake export that states the bytes scanned gets a range for its SQL warehouse work. Every other workload reads no figure, with its reason, among them all of a Databricks export’s (it states no bytes scanned) and scheduled work, which would run on DuckDB, an engine that has not been measured. rl adds, subtracts and compares nothing: it prints the two sides beside each other, each with what it leaves out. The call is made once and never retried, because a second call would be a second analysis and would use a second of the organisation’s places for analyses; after a failure that does not say what became of the file, look with rl migrate analyses before sending again. The analysis is then read back with a second call, because the control plane answers the first with only its id; if that read fails the analysis exists, and rl prints its id and the command that reads it. A file the control plane read and refused is printed as This file was refused., the control plane’s own sentence, and Nothing from this file was kept. |
RL_SERVER, RL_TOKEN, migration.analyse |
rl migrate analyses [-server url] [-o json] |
The analyses your organisation keeps, newest first, one line each: its id, its source, the period it covers, when it was sent (UTC), the rate card it was priced with (not priced when none was), and today’s covered figure. A dash in the last column is a covered figure the control plane left unset, and it is not a zero: it is unset unless every covered workload had dollars in its export, so open the analysis to see which had none; rl says so on stderr under the table. There is no column for who sent one: that is an opaque id, which the console names through the member list. It follows every page, and -o json prints them merged into one response with no page token (see Output). |
RL_SERVER, RL_TOKEN, migration.read |
rl migrate analysis [-server url] [-o json | -csv] <id> |
One analysis as the control plane holds it, printed exactly as rl migrate analyse prints one (including a Note: line, after Period:, for each thing the server says about the file’s shape: rows that repeat an earlier row, and runs of more than a week with no row; a script that parses the labelled lines ignores an unknown label), with the line that says how to read it again. An id that is not there is refused in the control plane’s own sentence, and one that is not an analysis in rl’s own. -csv takes the analysis as a file instead: the CSV the control plane makes, written to standard output exactly as it came, with nothing else on standard output, so rl migrate analysis -csv <id> > analysis.csv keeps it. rl makes one call for it, reads nothing first, and changes nothing in the file: no byte is decoded, re-encoded or added, and every line of it, the last included, ends with a carriage return and a line feed. The file begins with lines that start with # (what the analysis is, the period it covers, that it is an estimate from a usage export and not a quote, that every figure is a month averaged from the export, and what each side leaves out; a tool that wants them out reads the file with a comment prefix of #), then one table with a row for each workload, covered work first and work that is not covered after it. Amounts are exact decimal strings to eight places in US dollars, an empty cell means the export gave no figure and is not a zero, and a text cell that would start with =, +, - or @ carries a leading apostrophe added for a spreadsheet and not part of the id. Every file is recorded in the audit log, and rl says Recorded in the audit log. on standard error after it has written one. The call is made once and never retried, because a second call would be a second entry. Taking the file needs migration.export, which owners, admins and engineers hold; a role that may read an analysis and not take its file (an analyst, a viewer) reads the same figures with rl migrate analysis <id>, and is refused the file in the control plane’s own sentence with the permission named. -csv cannot be combined with -o json: that is a usage mistake, and nothing is sent. Flags go before the id, and -csv is a flag of this verb only, not of rl migrate analyse. |
RL_SERVER, RL_TOKEN, migration.read; with -csv, migration.export |
rl migrate delete [-server url] [-o json] -yes <id> |
Erases an analysis or a migration: for a migration its plan, its items and every table, column and role name they hold. Without -yes it deletes nothing: it reads the plan, says on standard error what a delete would erase (the kind, the source and the number of items you may see, never a name) and exits 2, because erasing cannot be undone. With -yes it makes one call and says Deleted <id>. with how many items went with it, and Recorded in the audit log.; the log keeps that it was deleted with the plan’s id, its kind and source and the item count, and no name. A change set already made from a migration’s items is not changed, and the control plane refuses to delete a migration while one is still a draft, proposed or open, in its own sentence naming that change set: Nothing was erased. follows. A delete is never retried: a second one finds no plan. A failure that does not say whether the plan was erased (unreachable, a deadline, a fault, a proxy’s answer) is a 2, claims neither, and names rl migrate analysis <id> to look. A credential scoped to one project is refused (the call belongs to the organisation). -o json prints runninglake.v1.DeleteMigrationPlanResponse. |
RL_SERVER, RL_TOKEN, migration.manage |
rl migrate principal [-server url] [-o json] (-clear | principal) <plan-id> <item-id> |
Chooses who a Snowflake role’s grants are for. A role of an inventory waits for a person, because a Snowflake role is not a principal here: the import proposes a team, and a team matches nobody until teams exist. The principal is in a Grant’s own grammar: user:<email>, role:<owner, admin, engineer, analyst or viewer> or team:<name>, held to the stricter form a typed value needs (an address made of the characters an address is made of, a team name a customer may declare). The role is then mapped and can be planned, and its grants go to that principal and not to the proposal. Nothing is granted by it: a change set carries the grants, and a person approves that. -clear takes the choice back and the role waits again. The call is safe to repeat (the same choice again changes nothing and writes nothing), so a failure is just asked again. The audit log records that a choice was made and its kind (user, role or team), never the principal, which can be a person’s address. The role must have a grant to propose and must not be planned by a change set still in play (one that was withdrawn, rejected, failed or expired no longer holds it, so a typo can be corrected). A credential scoped to one project is refused (the call belongs to the organisation). Exits: 0 done, 1 the control plane said no (its sentence is printed), 2 a usage mistake or a failure that is not an answer. -o json prints runninglake.v1.SetMigrationItemPrincipalResponse. |
RL_SERVER, RL_TOKEN, migration.manage |
rl migrate handled-outside [-server url] [-o json] (-note text | -clear) <plan-id> <item-id> |
Says that an item was done some other way (a table made by hand, a grant given in the source’s own tool), with a note saying how, or takes that back. It is your statement: nothing in RunningLake does the item or checks it, and the item is then not planned. The note is required, at most 500 characters of text with no control character; it is kept with the item, read by everyone who may read the item and erased with the migration, and is never in the audit log (which records only that a statement was made or taken back, and the kind of item). The note may not hold a control character or an invisible formatting character (a bidirectional override, a zero-width character). The item must be mapped, waiting for a person or refused, or planned by a change set that came to nothing (the statement lets go of it); one a change set still in play plans, or that is copying or beyond, is refused. -clear takes the statement back and the item goes back to the state it was in; clearing an item that is not marked changes nothing. The call is safe to repeat (the same statement again, and the same take-back again, change nothing and write nothing), so a failure is just asked again. A table marked handled outside is not unloaded by rl migrate unload-sql, even when it is named. A credential scoped to one project is refused (the call belongs to the organisation). Exits: 0 done, 1 the control plane said no (its sentence is printed), 2 a usage mistake (a flag after the plan is one) or a failure that is not an answer. |
RL_SERVER, RL_TOKEN, migration.manage |
rl migrate unload-sql -bucket NAME -account ID -prefix FOLDER/ [-integration NAME] [-role-name NAME] [-max-file-bytes N] [-server url] [-o json] <plan-id> [<item-id>...] |
Prints the SQL you run in Snowflake to take a migration’s tables out of Snowflake into a bucket you own: the storage integration’s SQL and then one COPY INTO for each table, and nothing else on stdout, so it can be piped to a Snowflake client. The bucket is its name (not an ARN, not an s3:// address), the account is the 12-digit ID of the AWS account that owns it and the prefix is a folder ending in /; each table’s files go under <prefix><resource id>/. With no item ids the tables are the plan’s that the copy has not started for (mapped, planned or applied) and that have a place to land; the ones that are refused or wait for a person are counted on stderr as left out, and the ones already copying, copied or verified, and the ones marked handled outside, are not in the kit (a second COPY INTO writes a second set of files). Name one or more item ids to take exactly those, in that order and in any state (a plan of more than 500 tables asks for a selection). It runs nothing and keeps nothing: the control plane renders the text, the bucket, the account and the prefix are recorded nowhere, and no Snowflake credential is held. The kit also holds three policies that are not SQL (the integration role’s, its trust and the statement you add to your own bucket policy so that the plane’s migration reader may read the unload): -o json prints the whole kit, runninglake.v1.GetMigrationUnloadKitResponse. The kit names the plane’s migration reader, and AWS refuses a bucket policy that names a role that does not exist, so it is rendered only for a plane whose operator recorded that role from its apply: an environment bound to no plane, a plane that is not your own (the pooled trial plane), a revoked plane and a plane with no recorded reader are each refused in a sentence that says what to do. A read, safe to repeat. A credential scoped to one project is refused (the call belongs to the organisation). Exits: 0 printed, 1 the control plane said no (its sentence is printed), 2 a usage mistake (a flag after the plan is one) or a failure that is not an answer. |
RL_SERVER, RL_TOKEN, migration.manage |
rl asset certify -state certified|deprecated [-replacement collection/id] [-note text] [-project p -env e] [-key k] [-server url] [-o json] <collection/id> |
Marks one table or view certified, or deprecated with the table or view to use instead. Nothing is pre-selected: -state has no default, and leaving it out is a usage mistake. -replacement is required for deprecated and refused for certified, before anything is sent. -note (at most 500 characters, refused and never cut) is kept by the control plane with the mark and printed back from its answer; if the answer does not return it, rl says the note was sent and not returned, and never that it was recorded. It prints the mark in the console’s words (Certified by ana@acme.test, 2026-10-07, Deprecated: use tables/orders_v2, Certification due), the date a certified mark falls due when the control plane sends one, and the idempotency key it used. A mark falls due after a year; the control plane computes that and rl works out nothing. The control plane decides who may certify and says so in its own sentence when it refuses (exit 1): a person with the role that holds certification.set at the asset’s project, or the asset’s owner or steward by assignment when signed in as that person; an API token certifies by its own role, which can be narrower than its person’s. Agents may not. A table or view you may not see reads as one that is not there. -key is random unless given; a repeat with the same key is the same change. |
RL_SERVER, RL_TOKEN, catalog.read, then certification.set or an assignment as owner or steward |
rl asset decertify [-project p -env e] [-key k] [-server url] [-o json] <collection/id> |
Takes the certification mark off a table or view and prints certification removed from <name>; an asset with no mark answers the same success and says nothing was changed, so a retry is safe. Only an owner or an admin removes a mark (stewardship.assign): a steward by assignment may set one and may not take it off. It asks nothing first. Same exit codes as rl asset certify. The control plane keeps no idempotency key for this change: -key is accepted and sent, and nothing prints it. A retry is safe because the mark is simply absent the second time. |
RL_SERVER, RL_TOKEN, stewardship.assign |
rl asset describe (-text description | -file path|-) [-column name] [-wait duration] [-project p -env e] [-key k] [-server url] [-o json] <collection/id> |
Writes a table’s description, or one top-level column’s with -column, onto the catalog object in your own account: the call the console’s Edit… makes, with the same refusals in the control plane’s own sentences (a description git declares, a table your plane has not reported yet, a text that is empty, over 4096 bytes or not text, a save of the same field still on its way). The text comes from -text or from -file (- is standard input; one trailing new line is dropped), one of the two and not both, and is never printed back. Clearing a description is not offered: -text "" is sent and refused in the control plane’s sentence. The write is asynchronous. rl asks again with the same request and key (1 s, then longer, up to 6 s apart) until your plane answers or -wait (default 60 s; 0 asks once) is over, and prints where the write stands in the console’s words (Held for your plane, which has not answered yet, Sent to your plane, Your plane is writing it, Saved by your plane, Not saved with why). Only Saved by your plane exits 0. A write your plane did not save exits 1 and says why; one still on its way when the wait is over exits 2, says nothing here shows it was saved, and names the key to ask again with (the same text and -key). A save is a person’s, and is labelled unreviewed until someone else reads it. The control plane decides who may: a role that holds metadata.edit at the project, or the asset’s owner or steward by assignment when signed in as that person; an API token edits by its own role. Agents may not. A table you may not see reads as one that is not there. -key is random unless given and is printed (on stderr under -o json). Other exit codes as rl asset certify: a connection lost after the request was sent is a 2 and the sentence names the key. |
RL_SERVER, RL_TOKEN, catalog.read, then metadata.edit or an assignment as owner or steward |
rl steward domain list [-archived] [-server url] [-o json] |
The organisation’s data domains, one line each: its name, its owner, its steward, how many tables and views it reaches that you may see, how many of those are certified, and whether it is archived. A cover is by project, kind and name, so it applies in every environment of its project; under the table, a domain with covers that name an asset existing nowhere in its project (a table renamed in git keeps its cover on the old name) is listed with how many, as a count and never a name, and nothing is printed when none is. The control plane gives the count only to a reader from whom nothing in the project is hidden; for anyone else it is absent, and nothing is printed, which does not mean there are none. Under the count it also prints the NAMES of the covers that match nothing, for the projects where you hold stewardship.assign (project, collection and name), with the command that removes one; the control plane gives the names only to a reader from whom nothing is hidden who also holds stewardship.assign at the project, and the count to every such reader, so a domain with a count and no names has its covers where you may not remove them; a list is cut at 200 for each domain in each project and says so. A domain is a business area of the organisation’s data and reaches across every project, so this verb takes no -project and no -env; a script that passes one gets “flag provided but not defined” and exit 2, and nothing is sent. A dash is a count the control plane did not send (its scan stopped at its bound), and the listing says so: it is not a zero. The counts cover only what you may see, and nothing says how much is out of sight. Archived domains are left out unless -archived is given. -o json prints runninglake.v1.ListDataDomainsResponse. |
RL_SERVER, RL_TOKEN, catalog.read |
rl steward domain create -name display [-description text] [-add-asset address]... [-project p -env e] [-key k] [-server url] [-o json] <slug> |
Makes a data domain. The slug (lowercase letters, digits, - and _, beginning with a letter, at most 63 characters) never changes and is never reused; -name is the display name a person reads (1 to 100 characters, required), -description is optional (at most 1000). -add-asset names an asset the domain covers from the start, and may be repeated: a layer, schema, table or view as collection/id (-project and -env, or the profile, only complete that address) or its whole projects/…/environments/…/tables/… name; a volume is refused before anything is sent, and one that is not an asset, or that you may not see, refuses the whole call as not found. Making a domain does not make you its owner: name one with rl steward assignment set. Prints the domain as stored and the idempotency key; a retry with the same key makes one domain. Owners and admins only (stewardship.assign). |
RL_SERVER, RL_TOKEN, stewardship.assign |
rl steward domain update [-name display] [-description text] [-add-asset address]... [-remove-asset address]... [-project p -env e] [-key k] [-server url] [-o json] <slug> |
Changes a domain’s display name, its description, and which assets it covers. At least one flag is required, and a flag that is given and empty is a usage mistake (there is no way to empty a description from rl). Coverage changes by adding and removing, never by replacing the list: you see only part of what a domain covers, and a replacement would drop the rest. The name and the description are replaced as a whole by the control plane, so rl first reads the domain and sends back what you did not change: a change somebody else makes between the two calls is replaced, and the last write wins. A slug the list does not hold is refused, with nothing sent (exit 1). The slug cannot be changed. The control plane keeps no idempotency key for this change: -key is accepted and sent, and nothing prints it, because it protects nothing. Repeating the command sets the same state again and changes nothing if it was already so; it does not guard against a change somebody else made in between. -remove-asset also takes a cover on a name that no environment declares any more (a table renamed or deleted in git keeps its cover on the old name): write it as an address in any environment of its project, e.g. -remove-asset tables/old-orders -project p -env e. It is removed only for a reader who holds stewardship.assign at the project and from whom nothing is hidden in any project they read, and only while the name still matches nothing; for anyone else it is “not found”, as any name that is not an asset, and a repeat of the removal is “not found” too once it is gone (read the list before repeating one whose answer was lost). Owners and admins only. |
RL_SERVER, RL_TOKEN, stewardship.assign |
rl steward domain archive [-key k] [-server url] [-o json] <slug>, rl steward domain restore [-key k] [-server url] [-o json] <slug> |
Hides a domain from new assignments, or brings it back. A domain is archived and never deleted; archiving deletes nothing: an archived domain keeps the assignments it had and takes no new assignment and no new asset. Same read-first and last-write-wins as rl steward domain update, and the same note on -key. Owners and admins only. |
RL_SERVER, RL_TOKEN, stewardship.assign |
rl steward assignment list [-person email|user-id] [-kind domain|layer|schema|table] [-scope domains/<slug>|collection/id] [-unmatched] [-project p -env e] [-server url] [-o json] |
Who owns and who stewards what: person, role, kind, scope, where (the project and environment of an asset: an assignment is by project, kind and name, so it holds in every environment of the project and a listing sums the environments you may read, one line for each environment that declares the asset, each saying which) and since when. The control plane requires the list to be narrowed, and says so in its own sentence (exit 1) if it is not: -person takes an email or a user id, -kind one of the scope kinds, -scope one domain (domains/<slug>) or one asset. Only the assignments on assets you may see, and on domains, are listed; every page is followed and -o json merges them into one runninglake.v1.ListAssignmentsResponse with no page token. An email that is no member is refused before anything is sent. A filter that is given and empty is a usage mistake. -unmatched, alone, lists the assignments on a layer, schema or table that no environment of its project declares any more (person, role, kind, project, name, since) and says how many there are in every project you read; the control plane gives the list and the count only to a reader from whom nothing is hidden, so an answer with neither prints that it was not given and never “none”. The list holds the projects where you hold stewardship.assign. A steward is an assignment, not a role, and being one grants no access to data. |
RL_SERVER, RL_TOKEN, catalog.read |
rl steward assignment set -role owner|steward (-person email|user-id | -none) [-project p -env e] [-key k] [-server url] [-o json] <domains/slug | collection/id> |
Names the one person who owns or stewards a domain, layer, schema, table or view. One person per scope and role: this replaces whoever held it, and -none clears it (there is no separate verb for removing). The scope is domains/<slug> or an asset as collection/id (-project and -env, or the profile) or its whole name; a volume is not a scope. -person is an email or a user id: an email is looked up among the organisation’s members first, and one that is no member is refused before any write; an id is sent as it stands and the control plane refuses a non-member. It prints what the control plane answered, ana@acme.test is now steward of table projects/…, or that it already was and nothing was written. The control plane keeps no idempotency key for an assignment: -key is accepted and sent, and nothing prints it, because it protects nothing; a repeat sets the same state and does not guard against somebody else changing it in between. Being a steward grants no data access. People only: there are no teams. A member who leaves the organisation loses their assignments. Owners and admins only (stewardship.assign); an agent may not. -none also clears an assignment on a name that no environment declares any more, written as an address in any environment of its project, for a reader who holds stewardship.assign at the project and from whom nothing is hidden, only while the name still matches nothing (every holder of the role is ended); naming a person for such a name is “not found”, and so is a repeat of the clear once it is gone. |
RL_SERVER, RL_TOKEN, org.member.read (to find an email), stewardship.assign |
rl steward queue [-server url] [-o json] |
What waits on you across the organisation: references and counts, never text, in the text form. Each kind of work is a heading with its count, and under it one line per item: the project and environment, the asset, and since when. The queue lists the kinds of work the control plane sends, grouped by kind; a kind it sends none of is not listed, which is not a zero. A count the control plane could not complete is not printed, and the heading says so. It sends certified marks that have fallen due on an asset you own or steward by assignment (and, if your role may certify in a project, every due mark of that project you may see, whoever is assigned), oldest due first (a mark that is due because its table was removed and declared again, or moved, is dated by the last change to the table, so a later edit to the table moves its date), at most fifty, and the terms waiting for an approval you may give: a term is a line term <slug>, the version and the date it began to wait, with no name and no text, and rl glossary get <slug> opens it. -o json prints more than the text form does: the term’s and the proposer’s names (see the JSON table). The control plane judges what your credential may decide, and an API token is judged by its own role. It covers only what you may see, and it decides nothing: each item opens where it is decided. -o json prints runninglake.v1.ListStewardQueueResponse. |
RL_SERVER, RL_TOKEN, catalog.read |
rl steward certification list [-state certified|due|deprecated] [-server url] [-o json] |
Every certification mark on an asset you may see, in every environment you may read, one line each: the asset, where it is (project and environment), the mark in the console’s words (Certified by ana@acme.test, 2026-10-07, Deprecated: use tables/orders_v2, Certification due) and when it falls or fell due (due 2027-09-12; fell due 2026-10-02 for a mark the control plane calls due; no due date for a deprecated one, whatever the answer carries; no date sent where it carries none). -state is the call’s own filter: the control plane filters, rl does not, and a state it does not know is refused in its own sentence (exit 1). The listing is in the control plane’s order, and nothing is counted: a mark on an asset you may not see is not in the answer and no figure says how many there are, so an empty list prints no certification mark is visible to you. (or no <state> mark is visible to you.) and never says there are none. It decides nothing: open an asset with rl asset get, set a mark with rl asset certify, remove one with rl asset decertify. Marks belong to the organisation’s environments, so this verb takes no -project and no -env; a script that passes one gets “flag provided but not defined” and exit 2, and nothing is sent. Every page is followed (200 at a time) and -o json merges them into one runninglake.v1.ListCertificationsResponse with no page token; a listing that fails on any page, repeats a page token or is still going after 100 pages prints nothing. |
RL_SERVER, RL_TOKEN, catalog.read |
rl lineage [-project p -env e] [-direction up|down|both] [-depth n] [-column name] [-server url] [-o json] <collection/id | projects/p/environments/e/collection/id> |
-column name (E13 I5d-2) reads a column of the asset instead of the whole asset: a Column edges block replaces the table-level one, one line per column edge (<from column> -> <to column>, the derivation word, parsed from <file>), the marks block gains a Columns: line in the same words, and one sentence, that a column a statement only filters, joins, groups or sorts by is not followed, prints once, at the end. Without -column, every edge printed is at table level, exactly as before this increment, and text and -o json never disagree about which. -column is always sent, and what comes back is the control plane’s: the column edges the column pass has read among the assets you may see, each column reached from the one you named, upstream and downstream, to the depth asked. Columns: says Recorded once the pass has read SQL, the words of a kind of edge that is not recorded before it has, and Could not run when the column parser could not be used, and then the one sentence Column edges may be missing: the column parser could not be used, so SQL that is new or has changed has not had its columns traced. prints under the marks block. A column an asset’s SQL has not been traced for has no edge, and the asset’s line says why in the words of its state — whether a pass has run is the control plane’s question, never this binary’s to guess. Where a table’s or a view’s data comes from and where it goes, at table level: one line per edge, with the two assets it joins, its mark (declared, parsed or observed, the word itself), its file (the column is FILE: the file that declares a declared edge, and the file that holds the statement a parsed edge was read from) and how it is known. A view’s edge says its author listed the read and that it is not checked against the view’s SQL; a pipeline’s edge says the pipeline runs one asset after another, and is never called a read. A parsed edge says whose SQL read the table (parsed: the SQL of pipeline <name> reads <from> to write <to>, or parsed: the SQL of the view reads <from>) and then (parsed from <file>), and never a commit or a fragment of SQL, because neither is sent. A marks block follows the edges and states, from the control plane’s answer, whether each kind of edge is recorded, in words (Recorded, Could not run when the parser could not be used for SQL that is new or has changed, and the words that say a kind of edge is not recorded); an answer that carries none prints not reported, never none. When parsed says Could not run, one sentence under the block says Parsed edges may be missing: the SQL parser could not be used, so SQL that is new or has changed has not been read. After it, a statements block lists each asset of the answer whose SQL the control plane has not read, with the words of its state (for SQL the parser refused, Could not be parsed; for SQL a pipeline keeps in a file, Kept in a file, not read; and the words of SQL that is new or changed and waits), under one sentence that counts them among the assets the answer holds; it is never a count of the statements of an environment, and it is not printed when every asset the answer holds was read. A view whose SQL was read and whose declared reads and SQL reads differ is listed under a reads label with two counts, declared, not in its SQL and in its SQL, not declared, under the sentence A view’s SQL reads differently from what the view declares it reads, for the views listed. This is a finding; nothing is enforced.: the counts are of reads that resolve to an asset you may see, so a read of an asset you may not see is in neither, and these counts add nothing to the line Some lineage is not visible to you., which says a cut only for the directions the walk read, and they are counts of reads, never of statements; a view that agrees, a view whose SQL was not read and every asset that is not a view are not listed, and -o json carries each view’s two counts as the control plane sent them, absent for an asset that has none. The header prints the direction and the depth the control plane applied, so a -direction or -depth you left out is the control plane’s own and is said. The answer covers only what this credential may see: an asset you may not see is absent with every edge that touched it, and the line Some lineage is not visible to you. says only that something is cut, with the address of the visible asset it is next to, never what it is or how many. The control plane cuts an answer at its limit and says so, and what it cut is not counted. An asset with no edge in the direction asked prints nothing is declared there (nothing is declared or parsed when the answer records parsed edges), which is not a statement that nothing is connected, and, when the answer names the parsed mark in any of the three states the marks block words, the line under it says SQL kept in a file is not read, so what it reads is not shown., and under -o json on stderr; an answer that names no parsed mark says nothing of files, as it says nothing of parsing. An environment whose lineage is not worked out is refused with exit 1 and the control plane’s own sentence, which is different from nothing is declared: it is a refusal, not an empty graph. -direction is up, down or both; -depth is 1 or more, and the control plane refuses one over its limit by name. Every role holds lineage.read, a viewer included. -o json prints the answer itself. |
RL_SERVER, RL_TOKEN, lineage.read |
rl lineage impact [-project p -env e] [-column name] [-server url] [-o json] <collection/id | projects/p/environments/e/collection/id> |
-column name (E13 I5d-2) asks for the impact of one column of the asset instead of the whole asset, by the same rule and the same unbuilt state rl lineage -column has (above): every column downstream, at any depth (the depth limit is table-level’s and does not apply), each with the asset it reaches and, for that asset, its own column. Every asset downstream of a table or a view, at any depth: its address, kind, distance in hops, owner, steward and certification. An owner or steward that the control plane did not report prints not reported, and one it reported as nobody prints none. It covers only what this credential may see; when something downstream is not visible the list says it is not all of what depends on the asset, without a count. An empty list says nothing is declared downstream of <address>. (declared or parsed when the answer records parsed edges), and, when the answer names the parsed mark, the line under it says SQL kept in a file is not read, so what it reads is not shown. as rl lineage does. It reads and decides nothing, and it takes no -depth: the control plane gives every asset downstream up to its limit and says when it cut the list. An asset with nothing downstream prints nothing is declared downstream (nothing is declared or parsed downstream when the answer records parsed edges). The same marks block as rl lineage follows the list; a parsed edge brings an asset into the list as a declared one does, and an entry does not say which kind of edge brought it. |
RL_SERVER, RL_TOKEN, lineage.read |
rl secret list [-project p -env e] [-server url] [-o json] |
The secret references the environment declares, one line each: its name, its store, what uses it, the version the plane last saw (its first eight characters), when it was last checked and its state in words. A dash is a figure no plane has reported. It is never a zero. It prints a reference’s name and nothing that says where its secret lives: no name in your store, no key and no pinned version (those are the declaration, which rl get -project p -env e SecretReference <name> prints), and never a value. -o json prints the answer with those three fields left out, every page merged into one. A project on the shared trial plane is refused as a whole: exit 2, the closed sentence that says secrets are not offered there, and no document, because an empty list under exit 0 would read as “this environment declares none”. An environment that declares none prints a note on stderr, in both forms, and exits 0 with an empty list on stdout: standard output holds the list and nothing else. -project and -env are both needed, from the flags or the profile. |
RL_SERVER, RL_TOKEN, config.read |
rl secret get [-project p -env e] [-server url] [-o json] <name> |
One reference as the control plane holds it: its environment, store and state in words, when it was last checked, the full id of the version the plane last saw, and when it was last rotated and last resolved, each not reported until a plane says (a time no plane has reported is not “never”). Where copies of the value live is printed only when a plane reports it: an answer that says nothing prints no line, and is never read as “nowhere”. What uses the reference follows, by kind and name (none when the control plane counted and nothing does), and the last line is the rl get command that prints its declaration. It prints no name in your store, no key, no pinned version and no value, and -o json leaves out the first three. On the shared trial plane the reference is shown as not offered, with the sentence that says why. Exit 1 when the environment declares no reference of that name. |
RL_SERVER, RL_TOKEN, config.read |
rl secret check [-project p -env e] [-server url] [-o json] <name> |
Asks the plane the environment is bound to to ask your own store for the version of the secret a reference names, and prints what the control plane answered: the outcome as a word, the time the plane answered (on every outcome a plane gave, a not found included) and, when a plane found a version, its id. A check asks your store for the secret’s version and never reads the value. An answer the control plane makes without asking a plane (the plane cannot check yet, the plane is not connected, or a name the plane keeps for itself) carries no version and no time, and prints the word alone: it never says that anything was read. A reference is checked at most once a minute, and a second check inside the minute is refused in the control plane’s own sentence, which says when to ask again. The exit code is the verdict, under either form (the table under Exit codes): 0 found, 1 the reference will not be found as declared, 3 no verdict, 2 nothing was asked of the plane (a credential that may not check is a 2, and so is a typo). -o json prints the answer, with the outcome as its token. |
RL_SERVER, RL_TOKEN, source.connect |
rl secret reads [-project p -env e] [-since t] [-until t] [-run id] [-server url] [-o json] <name> |
Who or what checked a reference, newest first, one line each: when, the audit action it was recorded under, by whom (a person’s email as the control plane returned it, - for a person it did not name, and an API token or an agent as its kind, verbatim), the run, task and attempt when a task made the read, the version (its first eight characters), the outcome in words and the plane’s id. Ids and words only: never a name in your store, a key or a value. The first line says where the window starts, from the start the control plane applied (your -since, or its own default, which it states); rl states no retention, and says nothing about how long reads are kept. -since and -until are RFC 3339 or a UTC minute such as 2026-10-01T09:30; the window does not include -until, and the control plane refuses one that is not after the start; -run keeps one run’s reads. A dash is a field the record does not carry. A window that holds no reads prints its heading and puts the note that none were found on stderr, in both forms. -o json prints the answer with every page merged into one. |
RL_SERVER, RL_TOKEN, secret.reads.list |
rl secret set |
Does nothing and makes no call. A value that a reference names never goes through RunningLake: put it in your own store, then declare a SecretReference for it (rl schema secret-reference lists its fields) and propose it with rl plan. Whatever follows the verb is not read, echoed or sent, and it needs neither a server nor a credential. No rl secret verb has a flag that could carry a value. Exit 2. |
— |
rl key list [-project p] [-server url] [-o json] |
The encryption keys of the projects this credential may see, or of one project, one line each: the key’s id, its project, its plane (the name your organisation gave it, else its id), where that plane runs and who made the key (both as the control plane sent them), its state in words and when AWS KMS last answered (ANSWERED: how fresh what is shown is; the time RunningLake last tried is not shown in the list, because it moves on a silence). A key reads Not checked only while nothing has tried to ask about it (a key RunningLake made is never asked): an unknown RunningLake tried and got no answer to reads No verdict, which is neither a revocation nor a pass, and Could not check is RunningLake being unable to ask, which is not a revocation (AWS KMS has not said the key is disabled, scheduled for deletion or withdrawn). rl key list and rl key get read what the control plane recorded and ask AWS nothing, so a state is the word RunningLake recorded. A dash is a figure no plane has reported, not a zero. The key’s ARN names your cloud account and is never printed, and -o json leaves it out. Keys belong to a project, so there is no -env; without -project (and with no profile naming one) a credential that is not confined to a project reads every key it may see. A credential confined to one project must pass -project: one that does not is refused with a bare permission denied, and rl adds the sentence that says to pass it (exit 1, the control plane’s refusal). A project with no key prints a note on stderr, in both forms, and exits 0. The shared trial plane has no key of its own and is refused as a whole: exit 2, the closed sentence, and no document. |
RL_SERVER, RL_TOKEN, project.read |
rl key get [-project p] [-server url] [-o json] <key-id> |
One key by the id rl key list prints, never an ARN or an alias: either is refused by name, without being echoed, before anything is sent. Its project, plane, where it runs, who made it and its state in words, and the times and figures RunningLake records or a plane reports (when the state began, when AWS KMS last answered, when RunningLake last tried, who registered it and when, the rotation period, whether it holds data), each not reported until there is one: an unreported holds data is not no. A key pending deletion also prints the deletion date AWS gave, with a note when that date has passed; nothing is printed for a key that is not pending deletion or when AWS gave no date. Exit 1 when there is no such key for this credential, which is also how a trial organisation’s key answers; a credential confined to one project that names none gets the same bare permission denied and sentence as the list. The ARN is not printed, and -o json leaves it out. |
RL_SERVER, RL_TOKEN, project.read |
rl key check -project p [-server url] [-o json] <key-id> |
Asks whether the control plane, as the plane’s provisioner in your account, can make a data key under a key you supplied and decrypt it now. It needs -project (or a project in your profile): the control plane refuses a check that names none, and the command says so before sending anything. It needs only project.read, not a fresh sign-in, so a token may use it. The key is named by its id, never an ARN or an alias, and the answer carries no ARN. It prints the state’s word, the closed word beside it (four states read Revoked), when it was checked, and the deletion date for pending_deletion when AWS gave one. access_withdrawn and an unknown answer (printed as No verdict) carry a note, because their word could mislead. A check’s answer is not recorded: rl key get still shows the state the control plane last recorded. It makes up to three calls that show in your CloudTrail and writes one entry to your audit log, so it is asked once and never retried. A key RunningLake made is refused, and one control plane process allows two checks of one key in twenty seconds. Exit codes below. |
|
rl key register |
Does nothing and makes no call. A key of your own is registered, and taken back, only in the console: Keys, then Register a key, and Stop using this key on the key’s page. It takes an owner or admin with a confirmation from the last five minutes, which a command-line token cannot give. Whatever follows the verb is not read, echoed or sent, so an ARN typed after it is not repeated, and it needs neither a server nor a credential. A control plane answers it only once its operator has switched customer keys on. Exit 2. | |
rl key stop |
Does nothing and makes no call, for the reason rl key register gives. |
|
rl principal list [-project p] [-state active|disabled|inert] [-server url] [-o json] |
The service principals of one project, or of every project this credential may see, one line each: id, project, name, state in words, live secrets, when the next one expires and when the principal was last used. A dash is a time the control plane has not recorded (a principal never used has no last use): it is not a time. An empty list prints a note on stderr, in both forms, and exits 0. | RL_SERVER, RL_TOKEN, principal.read |
rl principal get -project p [-server url] [-o json] <principal-id> |
One principal: grants, the environments and pipelines it may reach (all, the named ones or none), its state (and, if disabled, since when and why), who made it and when, its count of live secrets, next expiry, last use and trust policies. No secret is shown: a principal carries none. The id must be the UUID rl principal list prints; anything else is refused without being repeated and nothing is sent. |
RL_SERVER, RL_TOKEN, principal.read |
rl principal disable -project p -reason text [-server url] [-o json] <principal-id> |
Closes every door of a principal at once, for an incident: it needs no fresh sign-in and a token may do it. The reason is required and is kept in the audit log. A repeat is a no-op that answers the principal as it is. | RL_SERVER, RL_TOKEN, principal.revoke |
rl principal credential list -project p [-server url] [-o json] <principal-id> |
A principal’s secrets, live ones first, then expired and revoked: id, label, state, when it expires, was last used and was revoked. The secret itself is never shown and -o json has no field for it. |
RL_SERVER, RL_TOKEN, principal.read |
rl principal credential revoke -project p [-server url] [-o json] <principal-id> <credential-id> |
Kills one secret immediately and for good; no fresh sign-in is needed. A repeat is a no-op that answers the secret as it is. | RL_SERVER, RL_TOKEN, principal.revoke |
rl principal create, update, enable, delete and rl principal credential create, rotate |
Make no call and exit 2. They give access, so they are made in the console after you sign in again: the sentence says to open /access on your control plane’s address. update exits 2 whether it would widen or narrow, because only the server can judge the diff. Nothing after the verb is parsed, read or echoed. |
|
rl trust list -project p -principal id [-server url] [-o json] |
The CI systems a service principal trusts, one line each: the policy’s id, when it was made and what it trusts, in the control plane’s words. A principal that trusts none prints a note on stderr, in both forms, and exits 0. | RL_SERVER, RL_TOKEN, principal.read |
rl trust delete -project p -principal id [-server url] [-o json] <policy-id> |
Stops trusting one CI system and revokes every token exchanged under that policy; it says how many. No fresh sign-in is needed and a token may do it. | RL_SERVER, RL_TOKEN, principal.revoke |
rl trust evaluate -project p -principal id -policy policy-id -claims-file file|- [-server url] [-o json] |
Tests a stored policy against the decoded claims of a job, as one JSON object read from a file or, with -, from standard input: never from an argument, and never a token (a pasted live token could be exchanged by anyone who sees it, so a file that is not a JSON object is refused and not repeated). Prints each claim as presented and as required, and whether it matches. Exit 0 if the policy matches, 1 if it does not (the first claim that differs is named), 2 if nothing was evaluated. |
RL_SERVER, RL_TOKEN, principal.read |
rl trust create |
Makes no call and exits 2. Adding trust gives access, so it is made in the console after you sign in again. | |
rl webhook list -project p [-env e] [-server url] [-o json] <pipeline> |
The webhook endpoints of one pipeline, newest first, disabled and expired ones included: id, environment, trigger, the principal they start runs as, state, when each expires, when a delivery last verified (a dash is none yet) and the address to configure the sender with. An address is not a secret, and no signing secret is ever shown: it was shown once, to the person who made the endpoint, and -o json has no field for one. Without -env every environment of the pipeline is listed. When the control plane says its webhook receiver is switched off, a note on stderr says that its public address answers every delivery with the same 404 and starts nothing, so no sender can reach a listed endpoint yet; a control plane that does not say prints no note. An empty list prints a note on stderr, in both forms, and exits 0. |
RL_SERVER, RL_TOKEN, principal.read |
rl webhook deliveries -project p [-env e] [-outcome words] [-since 24h] [-server url] [-o json] <pipeline> <trigger> |
The deliveries to the endpoints bound to one trigger whose signature verified, newest first, from every endpoint of the trigger (or those of -env): when it arrived, which endpoint, the sender’s own id for it, the attempt (a sender’s retry raises it and adds no row), the outcome in words, the run it started or returned, the first parameter that was refused, the names of the parameters the body supplied (never their values) and how long the control plane took. A call that failed verification is counted and never listed, and no body or header is kept, so there is none to show. -outcome takes a comma-separated list of started, replayed, refused_mapping, refused_ceiling, refused_not_ready, refused_spend_cap, refused_not_authorized and test; -since is a length of time looking back, such as 24h; deliveries are kept 30 days. A trigger with more than 25 endpoints is refused until -env narrows it. Nothing to show prints a note on stderr and exits 0. |
RL_SERVER, RL_TOKEN, principal.read |
rl webhook preview -project p -env e -payload-file file|- [-server url] [-o json] <pipeline> <trigger> |
Runs a trigger’s applied mapping and the pipeline’s parameter checks over a test body, read from a file or, with -, from standard input (at most 256 KiB; never an argument), and says what each parameter would be: its type, where it comes from (pointer, constant, default or unmapped), the pointer and the value as a run would record it. It writes nothing, starts nothing and keeps the body no longer than the call. A parameter the body would be refused for is listed by name after the table, with the control plane’s reason, which never repeats the value. The values are printed from your own file; a mapping that is only proposed in a change set is not applied and is not previewed here (the console previews a draft). Exit 0 when the body maps cleanly, 1 when parameters would be refused (the table is printed first), 2 when nothing was previewed. |
RL_SERVER, RL_TOKEN, principal.read |
rl webhook disable -project p [-server url] [-o json] <endpoint-id> |
Stops an endpoint at once and for good, for an incident: it needs no fresh sign-in and a token may do it. A repeat is a no-op that answers the endpoint as it is. | RL_SERVER, RL_TOKEN, principal.revoke |
rl webhook create, rl webhook rotate |
Make no call and exit 2. An endpoint gives a machine access and its signing secret is shown once, so both are made in the console after you sign in again: the sentence says to open /access on your control plane’s address. Nothing after the verb is parsed, read or echoed. |
|
rl auth whoami [-server url] [-o json] |
Who the control plane thinks is calling, from the credential you hold (CallerService.GetCaller, which answers about that credential alone and needs no permission). A sentence first (service principal ci-deployer via GitHub Actions acme/etl; a session, an API token, an agent and a service principal’s secret each have theirs), then the organisation, the project the credential is confined to (or every project of the organisation), when it expires, and for a service principal its principal, the actions it was given and the environments and pipelines it reaches, with the trust policy that admitted an exchanged token. A service principal’s answer has no email. Every name is printed as text (an escape sequence or a bidirectional override in a name cannot repaint the terminal), and the credential is never printed. Exit 0 when the control plane answered; exit 2 when nothing was decided (no server or credential, a credential the server did not accept, a call it does not serve). |
RL_SERVER, RL_TOKEN |
rl audit export [-from-seq n] [-out file] [-max-pages n] [-o json] |
Takes a copy of your organisation’s audit chain as one file, from the first entry (or from -from-seq) to the chain’s head, and checks as it reads that every entry names the one before it. The file is NDJSON: a manifest line first (its kind, its version, the range it covers, and whether it reaches the head), then one line per entry carrying every field the hash covers, the organisation and project ids included, with before_json and after_json as the server stores them (canonically equivalent to what was hashed, not byte-for-byte it). An entry written by a machine credential also carries via_json, the door that credential came through; the key is left out of an entry with no door, and the manifest’s version is 1 for a file in which no entry has one (written exactly as before) and 2 for a file in which one does. Every page rl asks for says it can verify version 2; a control plane whose chain holds an entry written through a door refuses a client that does not say so (a failed precondition: upgrade rl) and records nothing, rather than hand it an entry whose hash it would recompute wrongly. It goes to -out through a temporary file that is made before the first page is requested (so a destination that cannot be written fails before the audit log is touched) and renamed only when the whole chain linked, or to standard output. The file is private to you (mode 0600 under your umask): it is the unfiltered chain, actors, client addresses and configuration included, and rl does not widen it; change its mode yourself if you mean to share it. When the chain does not link, no file is written and rl exits 1 naming the sequence. A control plane that does not return the organisation id with each entry yet cannot be exported from, because every hash covers it: rl exits 3 and writes nothing. Every page of 1000 entries appends one entry to the audit log recording that the log was read, and rl says so; -max-pages stops a walk early (complete is then false in the manifest, and rl prints the -from-seq to continue from). -o json prints the summary as one JSON document on stderr, since standard output may be carrying the file. A credential confined to one project is refused: a filtered chain cannot be verified. |
RL_SERVER, RL_TOKEN, audit.export |
rl audit verify [-resume-hash h] [-against-server] [-o json] FILE|DIR... |
Checks a copy you hold, without trusting the server to say it is fine: for every entry it recomputes the hash from the entry’s own fields with the same encoder the server uses, then checks that the entry names the one before it, that sequences run on without a gap (and without a repeat inside a file), and that the file’s own count and head are what the manifest says. A clean result says consistent, not intact: the hash is not keyed, so anyone holding the file can edit an entry and recompute its hash and every hash after it, and the result is still consistent. It shows the copy agrees with itself and not who wrote it; what shows that nothing before a point was rewritten is a later copy checked against an earlier copy’s head hash (-resume-hash). Several files, or a directory of them, are ordered by their manifests: first sequence ascending, and of two files that start together the longer first. A directory is searched all the way down: at its top every file is read, below it only files ending in .ndjson (so a copy nested as audit/v1/<organisation>/<yyyy>/<mm>/<dd>/<first sequence>.ndjson is checked by naming the directory), and a checkpoints folder is skipped. An entry that two files both hold is checked once and accepted when the sequence, organisation, previous hash and hash agree, because a delivery that is at-least-once can write the same entries into two files; the same sequence with a different hash is a hash_mismatch, never forgiven, and a repeat inside one file, or from further back than the last 65,536 entries rl remembers, is out_of_order. It stops at the first break and says its kind (hash_mismatch, link_mismatch, sequence_gap, out_of_order, wrong_org, malformed), the sequence, and what to do; a change to the value of any hashed field is a hash_mismatch at that entry. A copy that starts after entry 1 needs -resume-hash, the hash of the entry before its first, to be anchored; without it rl says nothing before the first entry was checked. Without -against-server, rl says in so many words that it did not compare with the server: a chain that is consistent with itself has not been shown to be the server’s. -against-server reads the server’s entry at your last sequence and compares the hashes, which shows the server still holds what you hold; it is the same server that wrote the chain answering about itself, so it does not show the chain was never rewritten before you took the copy. It appends one entry to the audit log. A file with no version, or a version this rl does not know (it reads 1 and 2), is refused and not reported as a break: the chain is not broken, the reader is old. Every line is read strictly: a line that repeats a key, carries a key that differs from the format’s only by case (ACTION beside action), or carries any key outside the exact set of the manifest’s version (via_json belongs to version 2 only) is refused with exit 3 naming the line and the key, because a decoder that matches keys case-insensitively and keeps the last duplicate would read a different record than a case-sensitive one (jq, a SIEM); so is text that is not UTF-8, or nesting beyond 500 levels. at must be exactly YYYY-MM-DDTHH:MM:SS.ffffffZ, a sequence plain decimal, and a hash 64 lower-case hex digits, or the record is malformed. What the hash does not cover, so editing it passes both verifiers: the project slug (the hash covers project_id, not the slug beside it), the spacing, key order and number spelling of before_json and after_json (texts that are canonically equivalent hash the same), and the manifest’s complete, page_size and max_pages; the manifest’s records, through_seq, head_seq and head_hash are checked against the records. -o json prints the result as one JSON document on stdout. |
-against-server: RL_SERVER, RL_TOKEN, audit.export |
rl audit delivery status [-o json] |
How delivery of your audit log into your own storage is doing: its state (not set up, off, delivering, behind, paused, failing or unavailable), the plane it goes to, how far it has got (entry N of H, the newest entry delivered and the newest the log holds), the lag, the failures in the last 24 hours and 7 days with the class of the newest, and who last changed the state and when. A figure the control plane has not reported prints not reported yet, never 0: a lag of zero means nothing is pending, and a lag nobody measured is a different statement. Exits 0 whenever the read happened, whatever the state, so a script that wants to branch on the state reads -o json. It never prints a storage name, a key or a policy, in text or in -o json (which drops the server’s audit_root and session_policy, and a checkpoint’s key and object key, before printing): the destination is named by its plane, and a failure by its class word and one fixed sentence. Delivery is read-only here: it is turned on, paused, resumed and turned off in the console. Turning it on needs a confirmation from the last five minutes (the console’s Confirm it is you step; a sign-in alone is not one), so only a person in the console can; pausing, resuming and turning off are open to the API. Owner and admin only, because what delivery writes is the unfiltered chain, as an export is. |
RL_SERVER, RL_TOKEN, audit.delivery.read |
rl audit delivery test [-plane id] [-o json] |
Runs the checks that can be run on a plane, and writes nothing: not an object, not a test event, not an entry in the audit log. One line per check, in the order the control plane ran them: the lock mode, the retention and the key. A check that failed prints its class and the fixed sentence for it; a conditional write is not among the checks yet, and the line says not checked yet instead of showing it passed. Without -plane it tests the plane delivery is set to, else the organisation’s only ready plane; with several to choose from it exits 2 and lists them. The verdict says the N checks that ran passed only when at least one check ran and every check that ran passed, and never that delivery would work: the conditional write is not checked. In -o json a failed check carries the fixed sentence for its class. Exits 0 then, 1 when a check failed (naming each class once) or the control plane refused the plane, and 3 when it ran no check. Owner and admin only. |
RL_SERVER, RL_TOKEN, audit.delivery.manage |
rl audit delivery attempts [-outcome delivered|failed] [-limit n] [-older token] [-o json] |
The last 30 days of what the delivery job tried, newest first: when, the outcome, the range of entries, the bytes, how long it took and, for a failure, its class with the fixed sentence for it. Only a delivered or a failed run is recorded; a run with nothing new to send is not, so an empty list is not a fault. -outcome narrows to one outcome, -limit is the size of a page (1 to 200, 50 when unset), and a page that has older attempts prints the -older token to continue from (-page-token is the same flag). It lists one page, not the whole 30 days, and -o json carries nextPageToken. Owner and admin only. |
RL_SERVER, RL_TOKEN, audit.delivery.read |
rl login [-server url] |
Checks an API token against the control plane and keeps it in the OS keychain — macOS Keychain, the Secret Service on Linux, the Windows Credential Manager — under the profile in effect (or, with no profile, the server). The token is read from the terminal without echo, or from standard input (rl login < file), never from an argument. Nothing is stored unless the server accepted it. Where there is no keychain (a CI runner) it says so and points at RL_TOKEN. |
a token from the API tokens screen |
rl logout [-server url] |
Removes that keychain entry. Says so when there was none. | — |
rl profile |
The profile in effect and where each value comes from — the profile, RL_SERVER, or nothing — and whether RL_TOKEN is set (never its value). See Profiles. |
— |
rl version (also --version) |
Which build this is: the commit, when that commit was made, the Go version, and where the answer came from — the build’s own stamp when there is one, otherwise the commit record the Go toolchain embeds in any binary built from a checkout (which -ldflags "-s -w" does not strip). A binary that carries neither says unknown; it never invents a version. +modified means it was built with uncommitted changes. |
— |
rl run -project p -env e [-key k] [-p name=value]... [-pfile name=path]... [-pstdin name] [-params-file path] [-server url] [-o json] <pipeline> |
Starts one run of the whole pipeline now and prints run <id>: <status>. Parameters carry values for the pipeline’s declared parameters; rl does not know their types: the control plane types each value in the spelling it is sent in and refuses by name, with one line for each parameter on stderr (parameter limit must be an integer) and exit 1 (the words never carry the value). -p name=value (repeatable) is literal text, always: a value that starts with @ is not a file, so -p title="$PR_TITLE" can never turn untrusted text into a file read. -pfile name=path takes the value from a file and -pstdin name from standard input (read once, refused on a terminal); -params-file (a path, or - for standard input) is a JSON object of scalars. A value typed after -p is in the process list and in the shell history of the machine, and -p "token=$TOKEN" puts the expanded secret in the command line, where set -x and a CI trace print it: use -pfile or -pstdin to keep it out (a -pfile secret sits on disk until it is removed). Quote every expansion: an unquoted $PR_TITLE that word-splits can add a flag of its own (x -pfile stolen=/path sent a file as the parameter stolen). One trailing line ending is removed from a file or stdin value; a read of nothing (or of a line ending alone) is refused by name (-p name= sends an empty value on purpose). A number in the params file is sent as its own digits (12345678901234567890, 0.10 and 1e3 arrive as written), true and false as those words, a string as it is; a duplicate key, null, a list, an object, text that is not valid UTF-8 (a lone surrogate escape included), a byte order mark or a file that is not an object of scalars exits 2 naming the parameter, or saying where the file stops (a byte offset, never a character of it). A value that is not valid UTF-8 from any flag exits 2 by name. A file or stdin value over 1 MiB is refused while it is read. -params-file "" (an unset variable) exits 2, and the same name given twice by any flags exits 2 by name. No value, and no path, is echoed in any error about a parameter (-o <bad> echoing the bad form and -h printing $RL_SERVER as a default are older and unchanged); a name that has anything but printable characters in it is quoted, so it cannot forge a log line; a sentence the control plane sends back has any sent value of four BYTES or more replaced (not another case of it, nor a JSON-, URL- or Go-quoted form, nor a truncated, whitespace-collapsed, base64 or hex echo of it; two values that overlap leave a residue, aaaa in aaaaaaa leaves aaa; and an unrelated word that contains a value is rewritten, test turns latest into la[a value you sent]), in the control plane’s words and never in a parameter’s name. -params-file given twice exits 2. A redirect from the control plane is not followed (it would replay the values). A start that carries values is refused unless -server is https, or http for loopback (exit 2, nothing sent, no key made, and no value or address in the sentence): the values and the token would cross the network in the clear, so use an https control plane URL. The address is judged the way the client reads it, and one that does not parse, has no host or has another scheme is refused. Loopback means localhost, 127.0.0.1 and [::1] spelled so: LOCALHOST, localhost., 127.1, 0.0.0.0 and this machine’s own network address are refused though they are this machine. The plane commands apply the same rule to every start, with or without values; rl run applies it only when values are sent, so a start with no value is as it was (and -params-file of {} counts as no value, -p name= as one). The bearer token alone is not protected by this: a start with no value over http:// to another host still sends it in the clear. What rl protects, with -pfile or -pstdin, is the shell history, the process list and logs, and only by not printing a value: it is in this process’s memory while the call waits (a Go string cannot be wiped), and with -p it is in the command line, so the process list and the history hold it. Whatever is sent is in the control plane’s copy of the run, which every role that holds run.read can read, all five (viewer included) and the repair agent: seven days after the run ends (the default restart window), or at once when an owner or an admin erases them; a run that never ends keeps them, and the stored copy also holds the raw text that was sent. The audit log carries a name, a type, a source and a keyed digest, never a value. A retry with the same -key and the same parameters is the same start (a retry of a -pstdin start must send byte-identical text); the same key with different parameters is refused (a key already used for a different start) and exits 1. It does not wait for the run: there is no watch loop, no -no-wait and no -timeout yet. Nothing can run a task yet: the control plane starts a run only when the environment’s data plane reports that it can run a task, and no data plane reports that yet, so today every start is refused, rl prints the control plane’s own sentence and exits 2: nothing was started and nothing was recorded (no run, no audit entry). A pipeline that is not ready or has no compiled version yet, an environment with no data plane, a data plane that is not connected, or is cordoned, draining, suspended, resuming, degraded or revoked (it may report the capability and still be refused), and a project whose runs in flight are at their limit (60 by default), where a run is in flight while it is queued, held, admitted, running or cancelling, are refused the same way, in the control plane’s words, and exit 2 too, because nothing was started and a later retry may start one. A control plane that has no run service at all (rl says so in its own words) exits 2. A call that is refused (denied, not found, an invalid key, a key already used for a different start, or a start that collided with another change to the organisation’s configuration and was rolled back, which says to start it again) exits 1. A call whose answer did not arrive (the control plane was unavailable or timed out, or the connection broke) exits 1 and says the answer did not arrive: it is not known whether the run started; pass -key <k> to retry safely: the control plane may have recorded the run. A run that was started is queued and nothing moves it yet, so rl run prints run <id>: queued and exits 3 (not terminal), never 0: 0 means the run succeeded, as it will once a wait exists. A script must not read a 3 as a pass. -key makes a retry the same start: the same key answers with the run it started, unless the pipeline or the caller’s access has gone (the answer is then not found or denied, and the run is not shown). The key is always printed first, on stderr (key <k> (pass -key to retry this exact start)), so a script that lost the answer can retry the same start; the default is a random key. A replay is said on stderr and never on stdout: rl run: this key had already started run <id>; nothing new was started by this call; stdout stays run <id>: <status> and the exit stays the run’s own. A key is permanent for its caller and pipeline: a CI job that fixes its key (-key nightly) is answered with its first run for ever and starts no other, and the stderr notice is the only sign; leave the key unset, or build it from something that changes (a date, a build number), for a job that should start a run every time. |
RL_SERVER, RL_TOKEN, pipeline.run |
rl run cancel -project p [-reason text] [-server url] [-o json] <run-id> |
Asks one run to stop. A queued or held run ends cancelled at once; an admitted or running one moves to cancelling, its live tasks are stopped, and the run ends cancelled when they have stopped; a run that had already ended is returned as it is. rl prints one line, in words that hold for the first ask and the second because the control plane answers with the run as it is after the call: run <id> is cancelled, run <id> is cancelling: its live tasks are being stopped, and the run ends cancelled when they have stopped, or run <id> is already succeeded; nothing was cancelled (also failed and timed_out). It exits 0 when the run is cancelling, cancelled or had already ended, not when a cancelling run is over; a script that needs it over reads the run until its status is terminal. -reason is optional, at most 500 characters of text (a longer one, or one with a NUL, is refused before anything is sent), is kept in the audit log only, and is never printed back. A cancel is idempotent, so there is no -key: a call whose answer did not arrive exits 1 and says it is not known whether the run was cancelled; run the same command again. A run that is absent, belongs to another project or is outside what the credential may reach is one sentence (no run <id> in <project>, or this credential cannot see it, exit 1); a caller without run.cancel is told so (exit 1). A control plane whose runs orchestrator is not switched on, or an older one, answers in its own sentence, rl adds nothing was cancelled, and it exits 2. A usage mistake (no project, an id that is not a run’s id, a reason that is too long, a flag after the id) exits 2 and sends nothing. A pipeline named cancel is started with its flags first (rl run -project p -env e cancel): the verb is read only when cancel is the first word after run. |
RL_SERVER, RL_TOKEN, run.cancel |
rl hold place -project p [-env e] [-pipeline x] | -org -reason text -for 2h [-cancel-active] [-key k] [-server url] [-o json] |
Stops new runs of a project, an environment, one pipeline or (with -org, an owner or an admin) the whole organisation until the hold ends by itself, at most 72 hours on (-for, required, and -reason, 3 to 500 characters, required). A run that starts under a hold is held, never refused, and a run already running continues; -cancel-active also asks the runs already in flight (admitted or running) in the scope to cancel, after the hold is in force and each by the cancel door with the class hold_emergency; it needs run.cancel as well as run.hold, prints runs in flight: <n> asked to cancel, <m> could not be, <k> skipped (not yours to cancel), and exits 1 when it could not reach every run (the hold stands; the same -key tries the ones that failed or were left over by the bound of 1,000 runs a call; a run in a project where you may not cancel is skipped, and nothing retries it, because an organisation-wide hold covers projects where its placer is, say, only a viewer). A hold that ends while the walk runs (released, or past its expiry) ends the walk too, and cancels no run its end let go. Queued and held runs are not cancelled: the hold holds them. Prints hold <id>: <what it covers> until <time>. No step-up: an emergency stop must be instant. -org is never implied by an empty -project, and is refused together with an explicit -project (the profile’s project must not turn a hold on one project into a hold on every one), and -env is never taken from the profile. The key is printed first, on stderr, and -key makes a retry the same placement (it answers with the hold it made; the key of a hold that has ended is refused: use a new one). Exit 0 if the hold was placed (or this key had placed it), 1 if the control plane refused in its own words (denied, not found, an invalid request, the key reused for a different hold, a project being deleted) or the answer did not arrive (it is not known whether the hold was placed; pass -key <k> to retry safely), 2 if nothing was done: a usage mistake, no server or credential, or a control plane on which emergency holds are not switched on (RL_EMERGENCY_HOLDS). |
RL_SERVER, RL_TOKEN, run.hold |
rl hold list -project p [-env e] [-active] [-server url] [-o json] |
The holds that bear on a project, newest first: its own, its environments’ and pipelines’, and the organisation-wide ones, one line each (id, what it covers, until when, whether it is in force or released or expired, who placed it, why; the reason is printed escaped). -active lists only those in force. Exit 1 if refused or the answer did not arrive, 2 if emergency holds are not switched on or a usage mistake. |
RL_SERVER, RL_TOKEN, run.read |
rl hold release |
Is not a verb. Releasing a hold resumes the runs it held, which needs step-up, and no token and no rl command can give step-up: release it with RunService.ReleaseHold from a signed-in session (an owner, an admin, or the person who placed it; a hold a token placed is an owner’s or an admin’s to release). The console’s release control is not built yet. It exits 2 without calling anything. |
|
rl pipeline files -project p -env e [-server url] [-o json] <pipeline> |
The files of one pipeline’s folder as the control plane last read them from git, in the words of the console’s Files tab (the same read). A table on stdout, one row to a file: its path, its kind (Design, Flow, SQL, Python, Scala or Other), its size, its fingerprint (the first eight characters of its SHA-256) and the assets that use it; then one line with the number of files and bytes, the folder’s fingerprint (the first eight hex characters of its digest) and its generation. The files are in the order the Files tab shows them: the design document, the flows, SQL, Python, Scala, then the rest, each by path. Notes on stderr, one line each, in the Files tab’s sentences: a SQL file that was generated from a flow (note: <path>: Generated from <flow>. …), a Python or Scala file that is recorded and not run, a file in trouble (a flow that is not in the format the platform writes or that no longer matches its SQL file, a file too big, not text or with a name the platform does not write, a SQL file or a flow that no asset uses, a design document the declaration does not name) and a listing cut short at 100 files. A fingerprint or a size the control plane did not report is a dash and never a zero, and a kind or a problem this rl has no sentence for is printed as the control plane sent it. A folder the control plane has not read from git yet says so on stderr (<pipeline>: its folder has not been read from git yet) and exits 0: that is “not yet”, and it is never an empty folder (a folder with no files prints 0 files; folder fingerprint …). It only reads. A file is added, changed or removed by a change set (rl plan, rl deploy, the console), and the files the platform writes, flows/<name>.yaml and DESIGN.md, are not added or changed by anything here. The list is what the control plane last read, which may be a commit behind. Exit: 0 the read happened; 1 the control plane answered and the answer was no, in one sentence for a pipeline that is not declared in that environment or that this credential cannot see, or in the control plane’s own sentence for a credential that may not read pipelines; 2 nothing was read: a usage mistake (no project or environment, none in the profile, a flag after the pipeline’s name), no server or credential, a credential the server did not accept, a control plane that does not serve the call (pipeline files: this control plane does not serve GetPipeline yet; nothing was attempted) or that could not be reached. -o json prints the control plane’s response and nothing else (see Output); no note is printed under it. |
RL_SERVER, RL_TOKEN, run.read |
rl pipeline params -project p -env e [-server url] [-o json] <pipeline> |
The parameters a pipeline’s declaration names, as the control plane last applied them, in the words of the console’s Parameters card (the same read). A table on stdout, one row to a parameter, in the order they are declared: its name, its type (string, integer, decimal, boolean, date, timestamp or enum), required or optional, its default (a dash when there is none, "" when the default is the empty string), the rules its author wrote (one of …, up to N characters, matches …, from … to …, at least …, at most …, up to N decimal places; a dash when there are none) and what it is for. One note on stderr: a change still waiting for review is not in the list. It shows what a run may be started with and never a value, and there is no column for a secret reference because a parameter cannot be one. A declaration that could not be read (or a control plane older than the field) says so on stderr, prints nothing on stdout, and is never printed as a pipeline that declares none: The pipeline’s declared parameters could not be read, so none are listed. This is not the same as declaring none. A pipeline that declares none says This pipeline declares no parameters; a start with any parameter is refused. on stdout. Flags go before the name. Exit 0 when the read happened, 1 if the control plane answered no (no such pipeline for this credential, or it may not read pipelines), 2 if nothing was read. |
RL_SERVER, RL_TOKEN, run.read |
Update 2026-10-19: via_json is not only a machine credential’s. The rl audit export row above says an entry carries via_json when a machine credential wrote it. Since this date an entry for a change to a pipeline draft that a person made with an API token carries it too (credential_kind token and credential_id, the token’s id; the person is still the actor), and so a window of the log that holds one is written as file version 2 and is refused to a client that does not say it reads version 2, as rl does. The sentence above is left as it was written.
Environment variables
RL_SERVER— the control plane URL.-serveroverrides it.RL_TOKEN— the API token. There is deliberately no flag: a token on the command line ends up in shell history. When it is unset, the tokenrl loginkept in the OS keychain for the profile in effect is used;RL_TOKENalways wins over it.RL_PRINCIPAL— in CI, with no stored secret: the id of a service principal (asrl principal listprints it).rlgets the job’s identity token, trades it at the control plane for a token of that principal that lives 15 minutes, and keeps it in memory only: it is not written to disk, not put in the environment and not printed, and no command shows it. SettingRL_TOKENas well is refused (exit 2), so a job that holds a stored token never switches identity silently. NeedsRL_ORGand a CI identity token: on GitHub Actions the job’spermissions: id-token: write, which letsrlask the runner for one; on GitLab anid_tokensentry namedRL_OIDC_TOKEN, with the audiencerunninglake:org:<your organisation id>. A refused exchange exits 2: the principal’s CI trust tab in the console lists the refusals the control plane could verify. A GitHub identity token can be exchanged once, so the outcome is kept for the life of the process. Renewing before the token expires is not built, and no verb waits yet.RL_ORG— your organisation’s id, whichRL_PRINCIPALneeds: the identity token is made for that organisation. (The audience cannot be looked up first, because looking it up takes a credential.)RL_AUDIENCE— the audience to ask the runner for, when it is notrunninglake:org:<RL_ORG>.RL_OIDC_TOKEN— on GitLab, the job’s identity token, read byRL_PRINCIPAL.RL_CAPABILITIES_DIR— where the constraint bundle is. See the lookup order below.RL_PROFILE— which profile to read server, project and environment from. See Profiles.RL_CONFIG— the profiles file, when it is not~/.runninglake/config.
Profiles
A profile is a control plane, a project and an environment under a name, so a command does
not repeat -server, -project and -env on every line. They live in
~/.runninglake/config (or the file RL_CONFIG names):
[profile default]
server = https://swim.runninglake.com
project = analytics
env = dev
[profile prod]
server = https://swim.runninglake.com
project = analytics
env = prod
RL_PROFILE=prod rl status reads prod; with RL_PROFILE unset, [profile default] is read
when there is one. A flag always wins, then RL_SERVER, then the profile, so a command that
names everything means what it always meant. rl query list -env is the one exception: an empty
-env there means every environment, and a profile does not narrow it.
A profile holds no credential, and a token line is refused (without echoing it). A token
at rest lives in the OS keychain, put there by rl login under the profile’s name, and nowhere
else; RL_TOKEN wins over it, which is how CI passes one. A line the file cannot be read
as — an unknown key, a section that is not [profile <name>], a profile named twice — is an
error that names the file and the line, and RL_PROFILE naming a profile the file does not
hold is an error too: a deploy aimed at prod must never quietly go wherever the flags
defaulted. Only the verbs that talk to a control plane read the file, so a broken one never
stops rl schema or rl version, or the offline rl diff (the preview form, rl diff -project, does talk to one).
The variable is not a flag because rl plane preflight -profile already names an AWS
profile.
The constraint bundle
rl diff and rl plan run the authoring gate against a constraint bundle, the same
versioned rules the control plane pins per project. The bundle is a runtime input, not
an embedded asset, and it is found in this order:
-capabilities dir$RL_CAPABILITIES_DIRcapabilities/under the project directory (-C)capabilities/in the current directorycapabilities/beside the binary’s directory — the release layout shipsbin/rlandcapabilities/side by side
An explicit location (1 or 2) that holds no bundle is an error, never a fall-through to a
different bundle than the one asked for. When none is found, the offline rl diff cannot run (exit 2),
the preview form notes it and goes on, and rl plan sends the plan with a note that the control plane runs the gate against the
project’s pinned bundle.
What the gate cannot know offline
The project’s environment list. An overlay for an environment the project does not have
(overlays/pord/) passes rl diff and is refused by rl plan and by hydration; the
rl diff summary names every environment it projected so the typo is on the screen.
The files the control plane holds. The directory is a working copy, and a file added in the console is not in your
checkout, so a sqlFile that names a well-formed path absent from the directory passes rl diff and is refused by
rl plan when neither the branch nor the request holds the file (rule pipeline-file-missing; see Sending a pipeline’s
files below). What the path alone proves, or a file that is here proves, is refused before anything is sent, once the
tree declares a layer. The gate that rl plan and rl deploy run is the same gate and knows no more than rl diff
does; the word that a file is missing is the control plane’s, in the diff of rl plan, rl deploy and rl diff -project.
rl pipeline files lists what the control plane holds for a person to read. rl plan, rl deploy and rl diff -project read the same list to leave out of the request the files it already holds (see Sending a pipeline’s files), and neither gate reads it, so a path absent from the directory is still not judged missing.
Sending a pipeline’s files
A pipeline can keep files beside its pipeline.yaml, in its own folder: SQL under models/, Python under python/,
Scala under scala/ and any text under files/. rl plan, rl deploy and rl diff -project send them with the
declaration, in the same change set, so the SQL an asset names reaches the control plane together with the asset.
- What is sent. The working directory as it is on disk, not only the files git tracks: each file of a pipeline’s folder that
the control plane would take and does not already hold, exactly as written, in path order (the next point says how
rlknows). Before the requestrllists them on stderr,files to send (N), M unchanged and not sent:and then one line<path> (<bytes> bytes)for each, up to 20, thenand K more. TheM unchanged and not sentpart is there whenrlread the control plane’s list of at least one pipeline’s folder, and counts the files of those pipelines that it left out; when it read none, the line isfiles to send (N):. With nothing to send and files left out, the line stands alone and ends without a colon:files to send (0), 4 unchanged and not sent. It lists nothing for-no-filesor when there is nothing to send, and never a file’s text. A path is printed with every control character written out (\x0a,\x1b), so a file name cannot forge a line or move the terminal. - Only the files that differ are sent. For each pipeline the environment already holds,
rlfirst reads the list thatrl pipeline filesprints (it needs the permission to read pipelines), and leaves out of the request each file the list names with the SHA-256 of the very bytesrlread. A folder of 40 files with 2 changes sends 2. Every doubt is a send, and sending a file the control plane already holds changes nothing: a file the list does not name, one it names with a different, missing or unusable fingerprint, every file of a pipeline whose list was not read, and every file of a pipeline the environment does not hold yet. When a list was not readrlsays so, once for the pipeline, and sends all of its files:note: <pipeline>: the files the control plane holds for it were not read (<reason>), so every file of it is sent, where the reason isits folder has not been read from git yet,this control plane does not serve the callorthe call failed. The control plane’s own error text is never printed, and a list that cannot be read never fails the command. The list is what the control plane last read of the branch, which may be a commit behind: a file equal to it is not sent, so a change made on the branch since that read is not put back, and a file that differs from it is sent. There is no option to send every file; changing a byte sends it. The list names the first 100 files of a folder by path, and a larger folder is cut there:rlsends the files after the last one listed whether or not they changed, and says so,note: <pipeline>: the control plane lists only the first 100 files of its folder, by path, so the K file(s) after <path> are sent whether or not they changed.rlcompares the bytes it read when it started, so a file edited whilerlruns is neither compared nor sent in its new form.rl plan,rl deployandrl diff -projectall do this; the offlinerl diffsends nothing and reads no list. - A file git does not track is marked. When git answers, a file in the list
rlprints before it sends that is not in git’s index (one that was never added, or one an ignore rule covers) carries the mark after its size,<path> (35 bytes, not tracked by git), and the line that counts the rest says how many it holds,and 4 more, 1 not tracked by git. It is a mark and not a question:rlstill sends the working directory, nothing waits for an answer, and a job in CI is never stopped by one. When git does not answer (it is not installed, the directory is not a repository, git refuses the directory or takes more than a few seconds) nothing is marked and nothing is said. A file inside a submodule is never marked: the index lists the submodule as one entry andrldoes not ask git inside it.rlasks git for its index and nothing else, with the repository’s own commands switched off. rl planandrl deploysend a file that differs from the control plane’s list as a replacement and can replace a console edit with an older copy. They send no fingerprint of the text a file began from, as the console’s Edit does, so a file whose text differs from the one the control plane holds is replaced, whoever changed it last. When anyone edits files in the console, bring your working copy up to date from the repository before you runrl deploy. Leaving out the files the control plane already holds makes a request smaller and never makes a replacement safer. Norlverb reads a file’s text either.-no-fileswithholds every file, the ones you changed as well, and sends the declarations only. It says so in onenote:line, and only when the tree has a file it would have sent. It is a usage error (exit 2) for the offlinerl diff, which sends nothing. It is no way out for a new pipeline that names asqlFile: nothing is sent, so once the tree declares a layer the control plane holds no file for a pipeline that does not exist yet and refuses it (pipeline-file-missing). A new pipeline that names asqlFileneeds its file, or inline SQL. Whereverrloffers-no-filesas the way out (the bounds below, a credential scan that could not run, an agent’s token) it says so beside the offer.rldoes not remove a pipeline’s files. A file missing from the working directory is left where it is on the control plane. The console’s Files tab removes a file.- What is not sent. A file the folder recognises and the control plane would refuse (a name the platform would not
write, text over 256 KiB, not UTF-8 text, or empty) is named in a
warning:line on stderr, with the reason and what to do about it, and the build goes on. The files the platform writes are the one exception: they are named in anote:line, never a warning (the next point). An empty file is the one that bites:python/__init__.pyis empty, is not sent, and the control plane will not hold it unless it has a comment line. Files the folder does not look at (a README, notes, any other extension outsidefiles/, a name with a backslash in it) are counted in onenote:line that names the first five paths and counts the rest. Hidden files and symbolic links are skipped without a word. A folder that holds files and nopipeline.yamlgets one warning, and none of its files is sent. - A SQL file that no asset names gets a
warning:and is never refused. For a.sqlfile in a pipeline’s folder thatrlwould send and that no asset names as itssqlFileor itspythonFile,rl diffprintswarning: <path>: no asset in any environment uses this file. It is kept with the pipeline.It is what the console’s Files tab callsnot used, and it stops nothing: the exit code is what it was. The offline form prints it on stdout with the other findings, and its verdict readsok, N warning(s); the preview (-projectand-env) prints it on stderr and leaves the control plane’s diff alone on stdout. Which declaration it judges depends on the form. The offlinerl diffknows no environment, so it reads the pipeline’s base declaration and every environment’s overlay merged over it, and warns only when none of them names the file; a file that one environment’s asset list uses is used.rl diff -project p -env ejudges that environment alone and saysno asset in <env> uses this file. It says nothing where it cannot tell (an overlay that does not merge, a declaration it cannot read), for a file that already has a warning of its own (empty, too big, not text, a name the platform does not write), or for a Python or Scala file, a flow orDESIGN.md. The warning has norule:line. - Files the platform writes are not sent.
flows/<name>.yamlandDESIGN.mdin a pipeline’s folder are written by the platform, and a request cannot add or change either: the control plane refuses it, in a sentence that says the platform writes the file. Removing one is allowed.rlleaves them where they are, sends neither, and names them in onenote:line on stderr,note: N file(s) in pipeline folders are written by the platform and are not sent, followed by the first five paths and a count of the rest. It is a note and never a warning, the plan’s exit code is what it was without them, and they are not counted against the bounds below. A name that only looks like one of them (design.md,flows/X.yaml,flows/a.b.yaml,flows/x.yml) is not one, and is counted with the files outside the convention. A SQL file the platform generated from a flow is a model like any other, andrlsends it. - An asset whose
sqlFilenames a file in the folder here thatrldoes not send stops the plan, before anything leaves the machine and before the gate runs, with exit 1:asset <name> names <file>, which rl cannot send (<reason>); fix the file or give the asset inline SQL, rulefile-not-sent. ThesqlFilejudged is the one the environment being planned gets, so an overlay of that environment that points an asset at an unsent file is refused too; the offlinerl diffknows no environment and judges every overlay. One that names a symbolic link gets a warning, becauserldoes not follow links. This isrl plan,rl deployandrl diff -project. The offlinerl diffruns the gate first and says each defect once: where the gate has its own sentence for the same asset’ssqlFile(the next point) that sentence stands and this one is dropped, so the count in its verdict is a count of defects, and where the gate has none this line stands: an empty named file, a filerlkept no bytes of (below), and every tree that declares no layer. - A
sqlFilethe control plane refuses, and which verb says what. All of it is judged once the tree declares a layer, as every reference rule is: in a tree that declares none,rlsays nothing about asqlFilebut what the previous point says. The rule is the control plane’s one function, run on the working copy, so it says only what a working copy can prove.- A well-formed path absent here passes
rl diff(the control plane may hold the file) and is refused byrl planwhen neither the branch nor the request holds the file: rulepipeline-file-missing, in the diff, with the pipeline, the asset and the file in its sentence.rl planexits 0 on a refused plan;rl deployandrl diff -projectexit 1. - What the path alone proves is refused before anything is sent, in the control plane’s own sentence, whether or not
the file is here: a path that is not a
.sqlfile outsidefiles/, a path with a name that begins with a dot (a path under a hidden directory is refused, not passed), a name the platform does not write (an upper-case letter), a backslash (its own sentence:which has a backslash in it; use / as the separator) and a path that leaves the folder. The offlinerl diffsays it and exits 1.rl plan,rl deployandrl diff -projectsay it from the gate they run before sending, when a bundle is at hand and the file is not onerlitself refuses to send, and exit 1; with no bundle at hand the gate does not run,rlnotes it, and the control plane says the same sentence in a refused plan..//models/x.sql,./models/x.sqlandmodels//x.sqlnamemodels/x.sqland are not refused. - A file that is here and unusable. The offline
rl diffsays the control plane’s sentence for one that is not UTF-8 text (pipeline-file-unusable,which is not UTF-8 text).rl plan,rl deployandrl diff -projectstop onfile-not-sentfirst, inrl’s sentence and its own rule id, because that line returns before the gate. - The size limit, exactly. A file may be 262,144 bytes (256 KiB).
rlkeeps the bytes of a pipeline-folder file up to 262,145 bytes and, for a larger one, only its path and its size. The offlinerl diffsayspipeline-file-unusable(which is over 256 KiB) for an asset that names a file over the limit, whatever its size, and adds its ownwarning:that the file is not sent.rl plan,rl deployandrl diff -projectsayfile-not-sentfirst, because that line returns before the gate. All exit 1. -no-filessends no file, so the gate judges no folder,rlsays nothing about anysqlFile, and the control plane judges everysqlFileagainst what it holds. The offlinerl diffrefuses-no-files(exit 2).
- A well-formed path absent here passes
- Bounds, checked before anything is sent. One pipeline carries at most 100 files in one change, and one change set
carries 100 files and 1 MiB of file text and paths together, so a pipeline new to the environment carries at most 100 in its
first change, and a pipeline that exists is refused at 101 as a change set is. The count is of every file the request would
carry, after the files the control plane already holds have been left out, and does not include the files the platform writes. Over a bound
rlsends nothing, says the counts and offers-no-files, which withholds ALL files, the changed ones included: exit 1 forrl planandrl deploy, exit 2 for the preview, which stores nothing. The refusal’s offer of-no-filesends(a new pipeline that names a sqlFile needs its file, or inline SQL), because a tree sent without files is judged by the control plane against the files it holds. A folder of more than 100 files or 4 MiB is refused by the control plane, whichrldoes not check. - What
rlprints, and where.rladds no file text of its own. The control plane’s diff does show the text of every file that changes,rlprints that diff as it is, and so the text of a changed file lands in your terminal and your CI log. Each stream:- stdout: for
rl planandrl deploy, the control plane’s diff,rules fired, thechange setline and the sqlFile refusal, as before. Forrl diff -project, the control plane’s diff and nothing else. - stderr, for all three verbs and only when the tree has files: the
warning:lines, the outside-the-convention note, the CRLF note, the-no-filesnote, thefiles to sendlist, the linefiles in this change set: 2 added, 1 replaced, 4 unchanged(from what the control plane accepted; it says nothing has been applied), and what follows a refusal. A tree with no pipeline-folder files prints the same bytes on both streams as before. - When the plan is refused,
rlprints on stderr each reason that carries a file and line, because the diff names no file for a credential and the control plane gives the file and line to the change set’s author alone; a reason with no path is already in the diff and is not printed again, and neither is a warning. A refusal for asqlFilenames the pipeline, the asset and the file in its own sentence, so it reads the same in the diff, in a log and in the console. That sentence is said once on a terminal. The diff carries it under itsBLOCKED <rule>line, so for a refused planrlprints on stderr only where it is,<path>:<line>: where the BLOCKED <rule> finding in the diff is; under-o jsonof the preview the diff is a field of a document and has not been read, so the finding is then said in full.
- stdout: for
- A string that looks like a credential in any file sent, changed or not, refuses the whole plan. The way out is an entry
in the config repository’s
.runninglake/secret-allowlist.yaml.rldoes not read that directory, so its plan key does not move when the entry does; the control plane stores the refusal under the key and, now plans the same tree again when the key comes back (it reads the allowlist from the branch), answering with a new change set when the entry now excuses the findings and with the refusal that stands when it excuses some. A refusal whose findings are the same files and lines is still the stored one. A refused change set cannot be withdrawn.rlsays:to retry after fixing an allowlist entry, run it again once the entry is on the branch: the control plane reads the allowlist there and plans the same tree again; an older control plane answers with the refusal it stored, and then a file rl sends (a pipeline.yaml or a file in a pipeline folder) must be changed first. If the control plane could not scan at all, it says so and names-no-filesinstead, with the caveat above (a new pipeline that names asqlFileneeds its file, or inline SQL), and the same older-control-plane clause. - A stored refusal is replayed for the same tree, whatever its rule, except the ones the control plane judges again (a credential finding, a plan-dependent
refusal, a refusal made while something could not be read). When the
control plane answers a refused plan with one it stored earlier,
rlsays:this is the refusal stored earlier for this exact tree; changing a file rl sends or a declaration plans it again. - A draft that already exists with the same effect is returned instead of a new one (planned earlier without files, by a
colleague, or from the console). It holds no record of these files, and
rlsaysan existing change set with the same effect was returned (<id>); it carries no record of these filesand goes on:rl deploysubmits it as it would any draft. - A control plane that does not take files stores the declarations and reports none of the files back; one that reports
only some is treated the same.
rl planandrl deployboth exit 1:rlsays that the change set above carries declarations only and was not submitted, and printsrl withdraw <id>for it. The change set is stored as a draft, which a teammate who can approve could still submit, sorlprints the command that takes it back and does not run it;rl deploydoes not submit it. The preview stores nothing and exits 2. Exit 2 means nothing was started and exit 1 means a draft exists, so a script that retries on 2 and stops on 1 does the right thing. - A token minted for an agent is refused when the tree has files:
rlprints the control plane’s sentence and says to use-no-files, and to give a new pipeline inline SQL and not asqlFile, because an agent cannot send the file and a new pipeline that names one is refused (pipeline-file-missing) once the tree declares a layer. - Files with CRLF line endings are sent as they are, with one note.
- The offline
rl diffjudges the same tree asrl planandrl deploy: the file warnings, and thesqlFilerefusal for what the working directory proves, which makes it exit 1. It judges what the working tree can prove and not what the control plane holds, so a path absent here is the control plane’s to refuse. A greenrl diffof a tree whose pipelines name files is therefore not the control plane’s word that it holds them:rl planagainst the environment is, which is whyrl lake seedends by saying to run it before pushing.
Previewing on the control plane
rl diff -project p -env e asks the control plane what the working tree would change in one
environment, and prints the answer. It reads the tree and builds the change exactly as rl plan
does, with the same files and the same lists and notes, but nothing is stored: no change set, no approval waiting for anyone, no entry in the audit log.
A second preview of the same tree is a second render, not a replay of the first.
- stdout is the control plane’s diff, character for character, and nothing else. It is the
diff the control plane renders for you as the reader: what a restricted layer hides from your
credential is hidden from the preview too, and the violations that refuse the tree
are in it, in the control plane’s own words. Everything
rladds — the verdict line, the notes about the constraint bundle and the engines it judged against, the rules that fired, whether the environment follows approvals — goes to stderr, so a pipe or a redirect carries the diff only. - Only an explicit
-projectmakes it a preview. A profile that names a project,RL_SERVERbeing set, or a project named anywhere else never turns a barerl diffinto a call to a control plane: the offline gate stays offline.-env,-serveror-owithout-projectis a usage mistake, and-envis required with-project. The server and the credential come from-server, thenRL_SERVER, then the profile, as for every verb that talks to a control plane. - A missing constraint bundle is a note, not a failure. The offline form needs the bundle and exits 2 without one. The preview runs the local gate when a bundle is at hand and says when none was, and the control plane runs the gate against the project’s own bundle either way. An overlay for another environment is not part of what the preview sends, so the control plane does not judge it; the local gate, which runs when a bundle is at hand, does check every environment’s overlays and refuses the tree before anything is sent if one of them is refused.
- Exit 0 when the control plane would take the tree, 1 when it would refuse it (the reasons
are in the diff, and a tree the local gate refuses is refused before anything is sent), 2 when
nothing was previewed: a usage mistake, a directory with no resources in it (
rl planwould plan every deletion; a preview refuses what is most likely the wrong-C), a tree that needs more than 100 changes at once (preview a part of it), a tree whose files are over what one request carries (see Sending a pipeline’s files), a control plane that does not take files (or names only some of them), no server or credential, a credential that was not accepted, a control plane that cannot be reached or is rate limiting you, or a control plane that does not serve the call yet. -o jsonprints the control plane’s response as one document on stdout and nothing else (see Output); the exit code is the same under either form.
Posting the preflight report
rl plane preflight -plane <id> prints its report as always and then posts it to the control plane, which keeps the newest one per plane. AWS only. Without -plane nothing is posted and no configuration file is read.
- The plane id is the uuid
rl-controlplane register-planeprinted asid, and the one the console’s Planes page shows. A blank value (an unset shell variable) is refused, not skipped. - The control plane is
-server, elseRL_SERVER, else the active profile’sserver: the address you sign in to the console at. It must behttps(httpis accepted for a loopback address only), because the token and the report go to it. - The credential is the control plane’s, not the cloud’s: the token
rl loginstored, orRL_TOKEN(see Environment variables and Profiles). It must be organisation-wide: a token confined to one project is refused, because a plane belongs to the organisation, and only an owner or an admin can mint one. - What is sent: the account and region the checks ran against, and each check’s id, status and text with ARNs, access keys and external ids taken out on your machine. The tfvars the command prints, the external id among them, are never sent. A check’s id is its name as a word (a version in the name is not part of it), so the same check has the same id from run to run.
- What happens to it: the control plane calls a report stale after 24 hours, and records whether the account the checks ran against is the one the plane records (
verifiedis the command’s own statement compared, not proof). Today the plane’s Connect page shows that a report exists and how old it is; the check-by-check table is still being built. - A post that fails does not change what the checks found. The report is printed first. A blocked report is still exit 1 and says it was not posted (or may not have been, when the control plane did not answer); a clean one whose post failed is exit 2. The plane id, the address and the credential are checked before any check runs.
Exit codes
rl diff and rl plane preflight document three:
| Code | Meaning | What CI should do |
|---|---|---|
| 0 | clean | proceed |
| 1 | the tree was refused, or a check failed | fix the tree |
| 2 | the check could not run: a missing bundle, an unreadable directory, a usage mistake | fix the invocation; never read this as a pass, and not as a refusal either |
rl query documents four of its own, so a CI job that runs a smoke query can tell
“the query failed” from “no query ran” from “the query is not over”:
| Code | Meaning | What CI should do |
|---|---|---|
| 0 | the query succeeded — including a succeeded record whose report was lost (class report_lost, no figures) |
proceed |
| 1 | the query ran and did not succeed (failed, cancelled), or a call was refused or lost (denied, not found, unreachable), or the statement was refused because the environment’s work cap is reached (the sentence is the control plane’s, printed as it stands: what is counted, who can raise the cap, and when the count starts again), or because the encryption key you supplied for the project is disabled, scheduled for deletion or deleted in your AWS account (also the control plane’s sentence, as it stands: the key’s id; for a key that is not yet deleted the deletion date or that it has passed, what to do in AWS KMS, and that RunningLake checks the key every five minutes and lifts this when it works again; for a deleted key that its data can never be read again and that it cannot be restored, with no remedy and nothing that lifts) |
fix the statement, or the grant; for the work cap, retrying the same statement is refused again until an owner or admin raises the cap or the month turns; for the key, fix it in your AWS account (a deleted key cannot be restored); the same statement is refused for as long as RunningLake’s last answer from AWS KMS (under 30 minutes old) says the key is disabled, scheduled for deletion or deleted |
| 2 | nothing ran: a usage mistake, no server or credential, or a control plane that cannot dispatch a query — Unimplemented (query dispatch is not wired on this control plane) or FailedPrecondition other than the key refusal in the row above (the environment is bound to no plane, or the plane lacks the capability the engine needs); also when the record could not be read back mid-watch, in which case the id is printed and rl query get reads it later |
fix the invocation or the control plane; never read this as a pass, and not as a refusal either |
| 3 | the record was still not terminal when -timeout ran out (still queued after 30m; the record is at rl query get ...); the last record read is printed above the error |
read it back later with rl query get, and look at the plane the environment is bound to — a query stays queued while nobody has heard from its plane. Not a pass, not a failure: the control plane has not decided, and rl does not decide for it |
| 4 | the query succeeded and -download could not fetch its result: the link had expired, the endpoint refused it, or fewer bytes arrived than the record says. Nothing is written and no temporary file is left |
retry the download, not the query — rl query get -project p -download <path> <query-id> reads the same record back. The statement already ran and re-running it costs the query again |
rl audit export and rl audit verify use four, so a CI job that archives the audit log can
tell “the chain is wrong” from “the check did not happen”. rl audit delivery status, test
and attempts use the same four; what each means for them is in the paragraph after the table:
| Code | Meaning | What CI should do |
|---|---|---|
| 0 | export: the file was written. verify: consistent — every hash recomputed and every link held, which shows the copy agrees with itself and not who wrote it — and, with -against-server, the server returns the same hash at your last sequence |
proceed |
| 1 | the chain does not link, or a hash does not recompute (the break kind and sequence are printed), or -against-server found the server holding another hash. export writes no file in this case |
treat the copy as altered: export again, and tell RunningLake if the fresh copy fails the same way |
| 2 | nothing ran: a usage mistake, a missing file, no server or credential where one is needed, or a credential the server did not accept | fix the invocation; never read this as a pass |
| 3 | the check did not happen: the server could not be reached, refused the credential’s role, or answered nothing it should have, or a file has no version, a version this rl does not know, a line with a repeated key, a key that differs only by case or a key outside the format, text that is not UTF-8, nesting deeper than 500 levels, or no org_id on its records (an export from before they were carried) |
retry, or export the file again with a current rl. Not a pass, and not a finding about the chain |
rl audit delivery status and rl audit delivery attempts exit 0 when the read happened, whatever
the state of delivery or the outcomes listed, 2 on a usage mistake or a credential the server did not accept,
and 3 when the read did not happen (the control plane could not be reached, refused the credential’s role, or
does not serve audit delivery). rl audit delivery test exits 0 when every check that ran passed, 1 when a
check failed (the class is printed) or the control plane refused the plane, 2 on a usage mistake or when
-plane must be chosen, and 3 when no check ran or the answer did not agree with itself. A 3 is never a pass.
rl query get and rl query list exit 0 when the read happened, whatever the status
of the queries they show, 2 on the usage and not-wired cases above, 4 when -download was given and the fetch
failed, and 1 otherwise.
rl query logs -list exits 0 when the list was read, 1 when the job has no readable log or the caller may not read it, and 2 on a usage mistake, a missing server or credential, Not built yet and Not on this control plane; it never exits 3 or 4, because it fetches no segment. Reading a stream’s lines or downloading it adds 4: the fetch from the plane failed even with a fresh link, or a segment was not one rl can read. It never exits 3.
rl query cancel exits 0 when the control plane recorded the request or the query had already ended (read it
back with rl query get to know it is over), 2 on the usage and not-wired cases above or when the control
plane answered with no record at all, and 1 otherwise: the cancel was refused, the query was not found, the
request could not be handed to the data plane and nothing was recorded, or the control plane answered a query
that had not ended with no request on it.
rl gates documents three, by whether anything was decided:
| Code | Meaning | What CI should do |
|---|---|---|
| 0 | every gate ran and passed | proceed |
| 1 | a gate failed, or could not run because something broke | fix the change set |
| 2 | nothing was decided: a gate was not run (not built yet, not offered on this plane, or still pending), the control plane did not report all four gates, or it does not serve the call yet | read the sentence it printed; never read this as a pass, and not as a refusal either |
rl ai policy documents three, by whether the policy was read:
| Code | Meaning | What CI should do |
|---|---|---|
| 0 | the policy was printed | proceed |
| 1 | the control plane refused the read: the credential’s role may not read configuration (the action is named) | ask for the role, or use another credential |
| 2 | nothing was read: the call is not built yet or this control plane does not serve it, the credential was not accepted, no server or credential, a usage mistake (a stray argument, -env with no -project, a project name the control plane refuses), or the control plane could not be reached |
read the sentence it printed; never read this as “no policy” and not as a refusal |
rl routing rules and rl routing rule exit 0 when the read happened, whatever state the rules are in. They exit 1 when the control plane answered no in its own sentence (it has no rule of that id, or the credential may not read routing), and 2 when nothing was read: a usage mistake (a stray argument, a missing or extra id), no server or credential, a credential that was not accepted, or a control plane that does not serve the call.
rl plan and rl deploy exit 2, as rl diff does, for a constraint bundle the loader refuses: the bundle is the invocation’s fault and not the tree’s, and nothing was sent. A tree the bundle refuses is still 1.
rl plan and rl deploy exit 1 against a control plane that does not take files, or names only some of the files they sent: the control plane stored a draft of the declarations, so there is something to take back. The change set is named and the command that withdraws it is printed, not run. rl diff -project exits 2 in the same case, because a preview stores nothing.
rl storage-rule list, rl storage-rule get and rl storage-rule events exit 0 when the read happened, whatever state the rules they show are in. They exit 2 when nothing was decided: a usage mistake (an unknown -state, a -place that is not a place, a rule name with a slash in it), no server or credential, a credential the server did not accept, a control plane that does not serve storage rules or answers Unimplemented or FailedPrecondition, and a call this build of the control plane has not built yet, whose own sentence is printed. They exit 1 when the control plane answered and the answer was no: a rule that is not there (or that this credential cannot see, which the control plane words the same), or a credential that may not read.
rl addon list exits 0 when the read happened, whatever state the add-ons it shows are in. rl addon get exits 0 only when the add-on is ready, and 1 for every other state, after printing the answer. Both exit 1 when the control plane answered and the answer was no: an environment that is not there (or that this credential cannot see, which is worded the same), an environment bound to no data plane, a kind with no row, or a credential that may not read the plane’s add-ons. They exit 2 when nothing was decided: a usage mistake (no project or environment, none in the profile, a flag after the kind), no server or credential, a credential the server did not accept, and a control plane that does not serve the call.
rl source list, rl source get, rl source kinds and rl source prereqs exit 0 when the read happened, whatever state the sources they show are in. They exit 1 when the control plane answered and the answer was no, in its own sentence: a source that is not there (or that this credential cannot see, which it words the same), a credential that may not read configuration (config.read), a -type or -state it refuses by name, and, for prereqs, a draft it cannot write steps for (no database user, no tables, a name it cannot write safely) or an environment on the shared trial plane, where no source can be prepared for; and when the answer was read and could not be written to standard output (a closed pipe), which is said. They exit 2 when nothing was decided: a usage mistake (a stray argument, an empty -type, -state or -f, both a name and -f or neither, -sql with -o json, a -flavour that is not one of the four, a name that is not a source’s, -env with no -project), a -f file that cannot be read, is over 1 MiB or is not a Source the gate accepts (nothing is sent), no server or credential, a credential the server did not accept, a control plane that cannot be reached, answers a fault, does not serve the call or has not built it (its own sentence is printed), and one that redirects the request (a redirect is never followed). rl source drop-slot makes no call and exits 2 every time, with a sentence on standard error; a name that is not a source’s, or a flag, is refused before it.
rl code-step list exits 0 when the read happened, an environment with no code step included (it says so, on stdout under text and on stderr under -o json). It exits 1 when the control plane answered and the answer was no (a refusal, an environment that is not there) and 2 when nothing was decided: a usage mistake (a stray argument, a missing or malformed -project or -env, an unknown -o), no server or credential, a call this control plane does not serve, a failed call, or a listing that did not end within 200 pages.
rl pipeline files exits 0 when the read happened, whatever the folder holds: a folder with problems, a listing cut short at 100 files, a folder with no files, and a folder the control plane has not read from git yet, which it says on stderr and does not print as empty. It exits 1 when the control plane answered and the answer was no: a pipeline that is not declared in that environment (or that this credential cannot see, which is one sentence for both) or a credential that may not read pipelines, in the control plane’s own sentence. It exits 2 when nothing was decided: a usage mistake (no project or environment and none in the profile, a name that is not a pipeline’s, a flag after the name), no server or credential, a credential the server did not accept, a control plane that cannot be reached or does not serve the call (it says so, and that nothing was attempted). Nothing is printed to stdout when a read fails, and the credential is taken out of what is printed.
rl pipeline params exits as rl pipeline files does. It exits 0 when the read happened, whatever the declaration holds: a table, none, and a declaration the control plane could not read, which it says on stderr and does not print as none. It exits 1 when the control plane answered and the answer was no: a pipeline that is not declared in that environment (or that this credential cannot see, which is one sentence for both) or a credential that may not read pipelines, in the control plane’s own sentence. It exits 2 when nothing was decided: a usage mistake (no project or environment and none in the profile, a name that is not a pipeline’s, a flag after the name), no server or credential, a credential the server did not accept, a control plane that cannot be reached or does not serve the call. Nothing is printed to stdout when a read fails, and the credential is taken out of what is printed.
rl storage-rule test exits by the verdict and by nothing else, so a script can branch on it:
| Code | Meaning | What CI should do |
|---|---|---|
| 0 | at least one action would fire for the sample | proceed |
| 1 | none would: the sample does not match, or it matches and something stops every action (the answer says which, and why) | read the answer; this is the rule’s verdict, not a fault |
| 2 | nothing was evaluated: a usage mistake (no sample or two, -size or -event with -event-file, a bad size, a document over 256 KiB), no server or credential, a credential that was not accepted or may not read, a rule that is not there, a document that is not an S3 event, or a call this control plane has not built |
fix the invocation; never read this as a 1, and a mistyped rule name is a 2 |
rl asset list, rl asset get and rl asset coverage exit 0 when the read happened. They exit 1 when the control plane answered and the answer was no, in its own sentence: an asset that is not there (or that this credential cannot see, which the control plane words the same), a credential that may not read (catalog.read), or a -kind, -tier or -q it refuses by name. They exit 2 when nothing was decided: a usage mistake (a stray argument, an empty -kind, -layer, -tier or -q, a name that is not an asset or has a slash it should not, -env with no -project), no server or credential, a credential the server did not accept, a control plane that cannot be reached, answers a fault, does not serve the call or has not built it (its own sentence is printed), and a control plane that keeps handing back the same page token. Nothing is printed to stdout when a read fails, and the credential is taken out of what is printed.
rl lineage and rl lineage impact exit 0 when the read happened, an answer the control plane cut at its limit included. They exit 1 when the control plane answered and the answer was no, in its own sentence: an asset that is not there (or that this credential cannot see, which it words the same), a credential that may not read lineage (lineage.read), an environment whose lineage the control plane has not worked out (a change held for review, or briefly an environment that existed before lineage was recorded), which it refuses in its own sentence and never answers with an empty graph, or a -depth over its limit or a -direction it refuses, by name. They exit 2 when nothing was decided: a usage mistake (not exactly one asset, a -direction that is not up, down or both, a -depth below 1 or not a whole number, a flag given and empty, a name that is not an asset or has a slash it should not, -env with no -project, a -depth given to impact), no server or credential, a credential the server did not accept, a control plane that cannot be reached, answers a fault, or does not serve the call or has not built it (its own sentence is printed), and an answer that lists an edge whose end it does not list, which is refused whole. Nothing is printed to stdout when a read fails, and the credential is taken out of what is printed.
rl steward domain list, rl steward assignment list, rl steward queue, rl steward certification list, rl glossary list, rl glossary get and rl glossary export exit 0 when the read happened, 1 when the control plane answered and the answer was no in its own sentence (a credential that may not read, a list it refuses to give without a narrowing, an asset it words as not there), and 2 when nothing was decided, as rl asset list does; a listing that fails on any page prints nothing.
rl steward domain create, update, archive and restore, rl steward assignment set, rl asset certify, rl asset decertify and the five rl glossary writes (propose, update, approve, reject and deprecate) use the same three. domain create, rl asset certify and the five rl glossary writes have a key the control plane keeps; domain update, archive, restore, assignment set and rl asset decertify do not:
| Code | Meaning | What CI should do |
|---|---|---|
| 0 | the change was made, or was already so (an assignment already held, a mark already absent) | proceed |
| 1 | the control plane answered and the answer was no, in its own sentence, unchanged: not permitted (the action is named when the control plane names it), a domain that already exists, a domain or asset that is not there or that you may not see (worded the same), a value over its limit. For a glossary write also: a term or version that is not there, a version that is not waiting or a newer one was made since, a name already taken, and the proposer’s own approval. Also an email that is no member of the organisation, and a slug rl steward domain update cannot find: those are refused before any write, in rl’s own words |
read the sentence; the fix is in what was asked for |
| 2 | nothing was decided: a usage mistake (no slug, two slugs, a flag that is given and empty, -role that is not owner or steward, -state that is not certified or deprecated, -replacement with the wrong state, a name that is not an asset or a volume where one is not taken; for rl glossary, a missing -version, or one outside 1 to 2147483647, a missing -reason, -replacement, -name, -domain or definition, both -definition and -definition-file, a definition file over 16 KiB, -name or -domain given to update, -out with -o json), no server or credential, a credential that was not accepted, a call the control plane does not serve or has not built (its own sentence is printed), or a control plane that could not be reached. For a write, a connection lost after the request was sent is also a 2: the change may have been made. The sentence says so, and names the key where the control plane keeps one |
fix the invocation or wait. After a lost connection, for domain create, certify and the five glossary writes repeat the same command with the same -key: that makes the retry the same change, never a second one. For domain update, archive, restore, assignment set and decertify there is no key to repeat: repeating the command sets the same state and changes nothing if it was already so, but it does not guard against a change somebody else made in between, so read the record first if that matters |
rl withdraw, rl approve, rl reject, rl env rename and the preview form of rl diff use three, by whether the control plane
answered:
| Code | Meaning | What CI should do |
|---|---|---|
| 0 | done: the change set is withdrawn (also when it already was), approved or rejected, the environment is renamed, or the preview says the tree would be taken | proceed |
| 1 | the control plane answered and the answer was no: it printed its own sentence, unchanged — not the author, the change set is past the point where it can be taken back, an environment that holds something, a name already used, not found, not permitted. For the preview, a tree the control plane would refuse. For rl approve, also what it refuses on its own, each with the sentence that says what to do: a gate that blocks the change set, changes that moved since the ones -state names were shown, and, on an environment that follows approvals, the first command, which approves nothing and prints the command that approves what was read (in full, or only the part the approver may see); and a change set whose approval needs a person’s fresh sign-in, which no token gives |
read the sentence; the fix is in what was asked for |
| 2 | nothing was attempted or decided: a usage mistake (a flag typed after the arguments is one, since it is not read as a flag), no server or credential, a credential that was not accepted, a control plane that cannot be reached or is rate limiting you, or a call this control plane does not serve or has not built yet. For rl approve and rl reject, also an answer that is not an approval or a rejection. For a verb that changes something, a 2 that came after the request was sent (a timeout, an outage, a fault) may be a change that was made: rl approve, rl reject, rl withdraw and rl env rename say so and how to repeat the call safely |
fix the invocation or wait; never read this as a pass, and not as a refusal either |
rl migrate analyse, rl migrate analyses, rl migrate analysis (also with -csv) and rl migrate delete use three, by whether the control plane answered and what it said. This is the contract these verbs start with, and it is a recommendation that can still change.
| Code | Meaning | What CI should do |
|---|---|---|
| 0 | done: the export was analysed and its analysis printed, or the listing or the analysis was read | proceed |
| 1 | the control plane answered, and the answer was no, in its own sentence: it read the export and refused it (printed as This file was refused., with Nothing from this file was kept.), the organisation already keeps as many analyses as it may (This analysis was not kept.), the credential’s role may not analyse, read or, with -csv, take the file (the permission is named), or the analysis asked for is not there or is not an analysis. Also: the analysis was made and could not be read back, in which case its id and the command that reads it are printed; and, for rl migrate analysis -csv, the control plane made and recorded the file and rl could not write it to standard output |
read the sentence; a refused file is fixed by running the export query again and sending its file |
| 2 | nothing was decided, or the control plane did not say what became of the file: a usage mistake (a flag typed after the file is one, since it is not read as a flag; a missing -source; -csv together with -o json), a file that is empty, over 16 MiB or cannot be read, a -rate that is not a price (each of these says Nothing was sent.), no server or credential, a credential that was not accepted, a control plane that cannot be reached, answers a fault, times out, is rate limiting you, does not serve the call or has not built it, or, for rl migrate analyse, answers something that is not the analyser’s answer, such as a gateway’s refusal |
fix the invocation, or look with rl migrate analyses before sending again: a call that did not answer may still have made an analysis, so the message says neither that the file was kept nor that it was not. For rl migrate analysis -csv, a call that did not answer may still have made a file and recorded it, the message says neither, and rl does not ask again: look in the audit log before asking again. Never read a 2 as a pass, and never as a refused file |
rl secret list, rl secret get, rl secret reads, rl key list and rl key get exit 0 when the read happened, whatever state the references or keys they show are in. They exit 1 when the control plane answered and the answer was no, in its own sentence: a reference or a key that is not there, a credential that may not read (the permission is named), or a window that ends before it starts. They exit 2 when nothing was decided: a usage mistake (a stray argument, a missing -project or -env where both are needed, a reference’s name, a project or an environment outside the control plane’s grammar (1 to 63 lowercase letters, digits, - and _), an ARN, an alias or anything else that is not a key’s id where a key’s id goes, a -since or -until that is not a time, a flag given and empty; a refusal of this kind does not repeat what was typed, and nothing is sent), no server or credential, a credential the server did not accept, a control plane that cannot be reached, answers a fault, or does not serve the call or has not built it (its own sentence is printed), a list that keeps handing back the same page token, and a list the control plane refuses as a whole. The shared trial plane offers no secrets and has no key of its own, so its list prints nothing and exits 2: an empty list under exit 0 would say that none is declared. Nothing is printed to stdout when a read fails, and the credential is taken out of what is printed. rl secret set exits 2 on every input and makes no call.
rl principal list, get, disable, credential list and credential revoke exit 0 when the call was made and done. They exit 1 when the control plane answered and the answer was no, in its own sentence (no such principal or secret for this credential, a credential that may not, with the permission named). They exit 2 when nothing was decided: a usage mistake (a stray argument, a missing -project or -reason, a project outside the control plane’s grammar, an id that is not a UUID, a -state that is not active, disabled or inert; a refusal of this kind does not repeat what was typed, and nothing is sent), no server or credential, a credential the server did not accept, a control plane that cannot be reached, or one that does not serve or has not built the call. rl principal create, update, enable, delete and rl principal credential create, rotate exit 2 on every input and make no call. Nothing is printed to stdout when a call fails, and the credential is taken out of what is printed.
rl trust list and rl trust delete exit as the principal verbs do. rl trust evaluate exits by what it found, and by nothing else: 0 when the policy matches the claims, 1 when it does not (the claim-by-claim result is printed first), 2 when nothing was evaluated (a usage mistake, a claims file that is not one JSON object, a refusal of the call). rl trust create exits 2 on every input and makes no call.
rl webhook list, deliveries and disable exit as the principal verbs do: 0 when the call was made and done, 1 when the control plane answered and the answer was no (in its own sentence, with the permission named), 2 when nothing was decided (a usage mistake: a stray argument, a missing -project, a pipeline, trigger or environment that is not one name, an id that is not a UUID, an -outcome it does not know, a negative -since; a refusal of this kind does not repeat what was typed, and nothing is sent; no server or credential; a credential the server did not accept; a control plane that cannot be reached or does not serve the call; a list that keeps handing back the same page token). rl webhook preview exits by what it found, and by nothing else: 0 when the body maps cleanly, 1 when parameters would be refused, 2 when nothing was previewed (a usage mistake, a body that is empty or larger than 256 KiB, a refusal of the call). rl webhook create and rotate exit 2 on every input and make no call.
rl secret check exits by what the check found, and by nothing else, so a script can branch on it. A check has two answers about the reference and one that says nothing about it:
| Code | Meaning | What CI should do |
|---|---|---|
| 0 | the plane found the secret: the one outcome Found, printed with the version id and the time |
proceed |
| 1 | the answer means the reference will not be found as declared: Not found (the secret has no current version of it), Deleted in your store, Access denied (your store refused the plane’s reader role; it gives the same answer for a name that does not exist, so a check cannot tell the two apart) or Name refused (a name the plane keeps for itself). The five words only a run can say are a 1 too (key_missing, key_denied, secret_version_gone, malformed and too_large). And one answer of the control plane’s own, in its sentence: it holds no such reference there (or no such project or environment), so there is nothing declared to be found |
read the word. The fix is in the reference’s name or, in your store, in the secret’s tags or in giving it a current version |
| 2 | nothing was asked of the plane: a usage mistake (a missing name, -project or -env, a name, project or environment outside the control plane’s grammar), no server or credential, a credential the server did not accept, a credential that may not check (the permission, source.connect, is named), a request the control plane refuses by its own rules before any plane is asked, a control plane that does not serve the call or has not built it, or a precondition it refuses (the shared trial plane offers no secrets, or the environment is bound to no plane) |
fix the invocation, the credential or the environment; never read this as a pass, and not as a finding about the reference: rl secret check x || deploy never reads a permission or a typo as “will not resolve” |
| 3 | no verdict: the check could not be made, which says nothing about the secret. Plane not connected (the plane was not asked, or did not answer in time), Plane cannot check yet (nothing was checked), Could not reach your store and Store is rate-limiting; the once-a-minute limit; a control plane that could not be reached, timed out or answered a fault; an answer that could not be written to standard output (the check was made and nobody was given its verdict); and any outcome this rl has no word for. A plane answers a check only once its Terraform module has created the reader role and its release carries that role’s ARN; a plane that has neither ends a check Plane cannot check yet or Plane not connected. A Vault reference can be declared and cannot be checked yet, so it never gets a verdict today |
ask again later. Not a pass, and not a finding about the reference |
The only word that is a 0 is Found. A script that retries on 3 is safe, and one that is told “it will not be found” by a word nobody here has read is not, so an outcome this rl does not know is a 3 and never a 0 or a 1. A check on a plane that cannot check yet prints that word and exits 3: it never prints a version or says that the secret was read.
rl key check exits by what the check found, and by nothing else, so a script can tell a key that is not usable from a check that could not be made:
| Code | Meaning | What CI should do |
|---|---|---|
| 0 | In use: the control plane, as the plane’s provisioner, made a data key under the key and decrypted it. It does not show what the plane’s engines can do: they run as other roles |
proceed |
| 1 | the control plane could not use the key: disabled_by_owner, pending_deletion (with the date AWS gave), deleted (AWS KMS says the key does not exist) or access_withdrawn, which is AWS KMS refusing the control plane’s use of the key after it had worked; it does not say who did it, and it does not show whether your data can be read |
stop and look |
| 2 | nothing was decided and a retry changes nothing: a usage mistake (a missing id, a missing -project, an ARN or an alias, a project outside the grammar), no server or credential, a credential that may not read or was not accepted, a control plane that does not serve the call, a key that is not there (never a 1, so a mistyped id does not read as a revocation), and a key RunningLake made |
fix the command |
| 3 | no verdict: unknown, printed as No verdict (the check could not tell: AWS KMS gave no answer it can read as a state of the key, or the key is in a state the check does not word; it shows neither a revocation nor that the key is usable), the plane is not ready or the control plane could not assume its role in your account (run the plane check), the rate limit (one control plane process allows two checks of one key in twenty seconds; the wait is printed), or the control plane could not be reached |
retry later |
rl run exits by what became of the run, and by nothing else, so a script can branch on it:
| Code | Meaning | What CI should do |
|---|---|---|
| 0 | the run succeeded. No run can finish yet, so no start exits 0 today | proceed |
| 1 | the control plane answered and the answer was no, in its own sentence (denied, a pipeline or project that is not there, an invalid key, a key already used for a different start, a start that collided with another configuration change and was rolled back), or the answer was lost (unavailable, timed out, a broken connection: it is not known whether a run started), or the run it answered with ended failed, cancelled or timed_out |
read the sentence; after a lost answer retry with the same -key (it is printed on stderr before the call is sent), never with a new one |
| 2 | nothing was started: a usage mistake, no server or credential, a control plane that serves no run service, or a start the control plane refused before it recorded anything (no data plane is bound, the pipeline is not ready or has no compiled version, no data plane reports that it can run a task, the plane that does is not live (not connected, cordoned, draining, suspended, resuming, degraded or revoked), or the project’s runs in flight are at their limit) | fix the invocation or wait; a retry with the same -key is safe and starts at most one run. Never read this as a pass, and not as a failed pipeline |
| 3 | a run was started and has not finished (queued, held, admitted, running or cancelling). rl run does not wait yet, so every run it starts exits 3 |
the run exists and can be read; never read a 3 as a pass |
The line a script reads, run <id>: <status>, is on stdout whatever the exit; the sentence that explains a 1, 2 or 3, the key line and the notice of a replay are on stderr.
Every other verb exits 1 on any error.
A command rl does not know exits 2, not 1 — rl dif is a usage mistake, and a CI script
must not read it as “the tree was refused”. It returned 1 until 2026-09-21. rl run exits 2 for the same
reason when nothing was started. rl with no arguments prints the usage
text to stderr and exits 2; rl help prints it to stdout and exits 0.
What rl query prints, and what it does not
The record of the run, as the control plane holds it: status, the engine together with
the reason the record stores for it (duckdb — pinned: no other engine serves the SQL window),
a rule row beneath it, and the figures the plane measured in human units (1.5 GiB, 0.41 s).
The rule row says only what the code decided. rule none consulted means the record names
how the engine was chosen and no rule from the constraint bundle was consulted (the table format
is not known when the engine is chosen for a window query, so today this is every new record).
rule not recorded is a record written before the control plane kept the kind of reason. A rule
id, when a record carries one, is printed as it is. A kind this rl does not know (a newer
control plane’s) gets no rule row, because the row would claim something rl cannot read, and
a record with no engine yet gets none either: rl query get prints engine not chosen yet — The record names no engine yet. and rl query list prints not chosen yet in the ENGINE column.
A statement the control plane had to keep waiting, because the plane’s SQL engine was
already running as many statements as it takes at once, carries one more row: waiting for a free slot on the plane's engine, since <time> while it is in line, waited <duration> for a free slot on the plane's engine once it was sent, and waited for a free slot on the plane's engine, and ended before one came free when it was cancelled or gave up first. The status stays queued while it
waits, and a statement that never waited has no such row. Streamed engine <engine> — <reason>
lines are unchanged. On a failure, the
error CLASS — a token such as plane_not_connected — with the one sentence that says
what it means (the plane did not take the query in time); a token rl does not know
is printed with “the message stays in the plane”, because an engine’s error text
routinely embeds row values and never transits the control plane. No rows, for the
same reason. This is exactly what the console’s SQL workspace shows, from the same
three calls, with the same sentence for the same class.
What it says about cost, and what it never computes. A finished query’s record
carries charged, the vCPU-seconds the query used: a quantity against your compute
allowance, never a price, and not settled until the plane’s final report has been
counted, never a zero. Beneath it, once the control plane has priced the query, is the
price row: a figure at the node’s list rate, with the formula, the rate and
what it leaves out in the same line (a cold start and the idle tail are not attributed
yet), or not priced: and the reason (no rate card, a node the plane did not report, a
spot node with no list rate). A query on a shared engine has no list price, so it reads
not priced and a metered line gives its usage at the engine’s own cluster rate, an
estimate the bill supersedes. When a reconciliation has read your cloud bill for the
node-hours the query ran on, a reconciled row gives the bill’s own figure (its usage and
its share of the nodes’ idle, in the bill’s currency, and whether every hour was in) and a
difference row sets it against the list price, usage only, and says why there is none
when the two are in different currencies or there is no list price. A figure whose bill
line named no currency is withheld, not printed in a guess. Every one of these is read
from the record: rl never computes a price from CPU seconds, and a row the control
plane did not send is absent, never 0.
Two figures are withheld rather than printed as zero. A succeeded record with the
class report_lost is a query the plane finished whose report never reached the
control plane; it prints report report_lost — succeeded on the plane; the numbers were not received and no figures, and exits 0, because nothing failed. And bytes scanned prints not reported when the plane sent zero, because the runner cannot
measure it yet and a zero in that row would read as a measurement.
Output
Human text is not a stable interface, with one exception. What a verb prints for a
person — its columns, its wording, which lines appear — changes whenever a person would
be better served by the change, without notice and without a version. Do not parse it.
The exception is a violation, as rl diff, rl plan and every verb that runs the gate
print it (the last paragraph of this section): the file:line: it opens with — after
warning: when it does not block, and file: alone when there is no line to point
at — and the rule: line under it are kept stable for CI to match on. Those verbs have
no -o json yet, apart from the preview form of rl diff, and a CI gate needs something to match.
-o json is the contract. It prints exactly one JSON document on stdout: the RPC’s
response message as protojson, which is the JSON the Connect protocol itself carries on
the wire, so its rules are protobuf’s own JSON mapping:
field names in lowerCamelCase, enum values by name, 64-bit integers as strings,
timestamps in RFC 3339, and a field at its zero value absent unless it tracks presence
and was set (an optional scalar, a oneof member, a message), so a script reads a
missing field as its zero value. The shape is therefore the API’s protobuf message, and a change that removes, renames
or retypes one of its fields is refused before it is released. Whitespace is not part of the contract. Anything a verb says
to a person meanwhile — progress, a warning, an empty listing’s hint — goes to stderr,
so rl project list -o json | jq reads one value.
The document is the response as your rl understands it. rl decodes the answer
and prints it again, so a field the control plane gained after your rl was built is
absent from the document, and reads as its zero value, until you upgrade rl. Upgrade
before relying on a new field.
-o takes text (the default) or json. Any other value is a usage mistake: exit 2,
and nothing is sent. The exit codes are the same under either form.
| Verb | The document |
|---|---|
rl project list -o json |
runninglake.v1.ListWorkspacesResponse |
rl get -o json |
runninglake.v1.GetResourceResponse: the resource, with its status as the control plane holds it (phase, conditions, convergence, and the fields the text form does not print) and its spec as the JSON text the control plane holds (specJson, a string, not a nested object). A field at its zero value is absent, so a script reads a missing field as its zero value. The text form’s lines are not a contract. When no resource was read there is no document and the exit is the text form’s: 1 for a resource the control plane does not have, a credential that may not read it, or a name it refuses; 2 when nothing was decided (a usage mistake or -h, no control plane or credential, a credential it does not accept, a call it does not serve, an answer with no resource in it, or a control plane that cannot be reached or answers with a fault) |
rl audit verify -o json |
a summary, not an RPC message: consistent (the copy agrees with itself; server_agrees is the separate answer to the comparison with the server), records, from_seq, through_seq, anchored, hashes_recomputed, reaches_head, compared_to_server, server_agrees, server_note, and a break with its kind, seq, expected, actual and detail when there is one. A check that did not happen (exit 2 or 3) prints no document |
rl audit export -o json |
a summary on stderr, because stdout may be carrying the export: records, from_seq, through_seq, head_seq, head_hash, complete, pages, appended_entries, out |
rl audit delivery status -o json |
runninglake.v1.GetAuditDeliveryResponse, as protojson, with audit_root and session_policy cleared: a figure the control plane has not reported is absent, never 0 or null |
rl audit delivery test -o json |
runninglake.v1.TestAuditDeliveryResponse, printed before the exit code (0, 1 or 3) is decided |
rl audit delivery attempts -o json |
runninglake.v1.ListAuditDeliveryAttemptsResponse: one page, with nextPageToken when there are older attempts |
rl routing rules -o json |
runninglake.v1.ListRoutingRulesResponse |
rl routing rule -o json |
runninglake.v1.GetRoutingRuleResponse |
rl addon list -o json, rl addon get -o json |
runninglake.v1.ListPlaneAddonsResponse, narrowed to the one environment (and, for get, to the one add-on); a replica count is set only where the plane reported one, and absent is not zero |
rl source list -o json |
runninglake.v1.ListSourcesResponse, every page merged into one, so it never carries a page token. A figure the plane has not reported (lagSeconds, rowsLastDay, rowsLastWeek, retainedWalBytes, tablesCopied) is absent, and absent is not zero; planeState is set when the control plane gave a reason that no source can be declared in this environment (the shared trial plane). An empty list prints {} and says so on stderr |
rl source get -o json |
runninglake.v1.GetSourceResponse. credentialReference is a SecretReference’s name; the document holds no host, no table or column name and no secret |
rl source kinds -o json |
runninglake.v1.ListSourceKindsResponse: each kind’s type, available, and for one that is not available the unavailableReason token and the control plane’s unavailableSentence |
rl source prereqs -o json |
runninglake.v1.GetSourcePrerequisitesResponse: the steps, each a check (the id of the check that verifies it), its sql and needsReboot. The SQL names the tables and the role the source declares and no password. Not combined with -sql |
rl code-step list -o json |
runninglake.v1.ListResourcesResponse for the environment’s Pipelines, every page merged and narrowed to the pipelines that hold a code step, so it never carries a page token. A script reads a step’s declarations from each resource’s specJson (assets[].pythonFile and assets[].code); an environment with no code step prints {} and says so on stderr |
rl hold place -o json |
runninglake.v1.PlaceHoldResponse: the hold as the control plane recorded it (scope, the names it covers, who placed it, when it expires). Nothing else is printed to stdout; the key and a replay are on stderr |
rl hold list -o json |
runninglake.v1.ListHoldsResponse: every hold that bears on the project, all pages, newest first |
rl pipeline files -o json |
runninglake.v1.GetPipelineResponse, whole: the pipeline’s summary, its applied specification as JSON text (specJson), the generation the controller last observed and its source, which holds the files, the folder’s digest and generation, and the listing’s warnings. A source that is absent means the control plane has not read the folder from git yet, and absent is not an empty folder. Nothing else is printed to stdout, and no note is printed. |
rl pipeline params -o json |
runninglake.v1.GetPipelineResponse, whole, as rl pipeline files -o json prints it. Its parameters is what the command reads: absent means the declaration could not be read, and {} means the pipeline declares none, which is how a script tells the two apart. Each parameter has name, type, description, required and, when the declaration wrote them, defaultValue (absent when there is no default), maxLength, pattern, min, max, scale and values; all are text in the type’s own spelling, and none is ever a run’s value. Nothing else is printed to stdout, and no note is printed. |
rl run cancel -o json |
runninglake.v1.CancelRunResponse: the run as it is after the call (run.id, run.status, and run.cancelClass and run.finishedAt when the control plane set them). Nothing else is printed to stdout, and the exit is the same as for text. |
rl storage-rule list -o json |
runninglake.v1.ListStorageRulesResponse, every page merged into one, so it never carries a page token. A rule’s 24-hour window (files matched and ignored) is set once a plane has counted the rule; its recorded events (recorded_reported), batch figures (batches_reported) and repeat deliveries (duplicates_reported) are set only for a plane that records, its action figures (actions_reported) are absent everywhere until an action runs, and its 7- and 30-day windows, breaker and dead-letter total are absent until a plane reports them, and absent is not zero |
rl storage-rule get -o json |
runninglake.v1.GetStorageRuleResponse |
rl storage-rule test -o json |
runninglake.v1.TestStorageRuleResponse; it carries no key, and the exit code is the verdict under either form |
rl storage-rule events -o json |
runninglake.v1.ListStorageEventsResponse, every page merged into one, so it never carries a page token; the hours are the first page’s. Each event is an id, a kind, a size (read only where size_reported is true), an outcome, a time and a batch id, and no field of it can hold a name. retention_days is 30 where the purge runs (0 from a control plane with no store, which reads as “no limit is stated”) |
rl asset list -o json |
runninglake.v1.ListAssetsResponse, every page merged into one, so it never carries a page token. A table’s tier is absent until its plane reports it, and absent is not “unclassified” |
rl asset list -q text -o json |
runninglake.v1.SearchAssetsResponse, every page merged into one, so it never carries a page token: hits, each an asset and matchedIn (name, display_name, description, the console’s match fields). A search that matches nothing prints {} and says so on stderr. A search that fails on any page prints nothing |
rl asset get -o json |
runninglake.v1.GetAssetResponse. facts is absent for a table its plane has not reported, and absent is not zero rows; columns holds only the columns git declares |
rl asset coverage -o json |
runninglake.v1.GetCoverageResponse. A facet the control plane did not count is absent from facets, and absent is not 0; proto3 JSON leaves a count of zero out of a facet that is present, and its total is then what says it was counted. tables_described and columns_described carry reported (the tables the planes reported) and splits (the count by description label); their count is over the reported tables only |
rl asset certify -o json |
runninglake.v1.CertifyResponse: the mark as the control plane stored it, with its note when it returns one. The idempotency key is on stderr |
rl asset decertify -o json |
runninglake.v1.DecertifyResponse: removed is absent when the asset had no mark, and absent is not an error. No key is printed: the control plane keeps none |
rl asset describe -o json |
runninglake.v1.UpdateMetadataResponse: the last answer rl got, where the write stands (state, and failure with not_saved), never the text. The idempotency key is on stderr |
rl steward domain list -o json |
runninglake.v1.ListDataDomainsResponse. A domain’s assetCount and certifiedCount are absent when the control plane did not count them, and absent is not 0 |
rl steward domain create -o json |
runninglake.v1.CreateDataDomainResponse. The idempotency key is on stderr |
rl steward domain update -o json, archive, restore |
runninglake.v1.UpdateDataDomainResponse: the domain as stored. No key is printed for update, archive and restore: the control plane keeps none |
rl steward assignment list -o json |
runninglake.v1.ListAssignmentsResponse, every page merged into one, so it never carries a page token |
rl steward assignment set -o json |
runninglake.v1.SetAssignmentResponse: assignment is absent after a clear, and changed is absent when nothing was written. No key is printed: the control plane keeps none |
rl steward queue -o json |
runninglake.v1.ListStewardQueueResponse. A kind that is not in sections was not counted, which is not a section with nothing in it; a section’s count is absent when the scan stopped, and absent is not 0; a term_approval item holds a term id, a version, the term’s display name, its domain and the proposer’s display name (proposedBy), and no asset. The text form prints only the term id, version and date, so -o json carries the customer-typed term and proposer names that the text form leaves out; treat it as holding names when you pipe it into a ticket or a log |
rl glossary list -o json |
runninglake.v1.ListTermsResponse, every page merged into one, so it never carries a page token. A term’s pendingVersion is absent when no version waits, and its steward is absent when nobody is named |
rl glossary get -o json |
runninglake.v1.GetTermResponse: the term and every version, newest first; with -version n, only that version |
rl glossary propose -o json, update, approve, reject, deprecate |
The writes’ responses (ProposeTermResponse, UpdateTermResponse, ApproveTermResponse, RejectTermResponse, DeprecateTermResponse): the term as stored. selfApproved is present only when the proposer decided their own version in a one-member organisation. The idempotency key is on stderr |
rl glossary export -o json |
runninglake.v1.ExportGlossaryResponse: the YAML is the string field yaml, with the counts and the time it was made |
rl lineage -o json |
runninglake.v1.GetLineageResponse: nodes, edges (each between two nodes of the answer, named by their name), marks, and the direction and depth the control plane applied. truncated is absent when the answer was whole, and absent is not false. A node’s lineageIncomplete is a bit: it carries no count, name or kind of what is not visible, and its statementState is one word about the SQL that writes the asset (parsed, pending, unparsed, not_read or none), absent when the control plane did not say, which is not none. An edge’s mark may be parsed, and no edge carries a commit or a statement |
rl lineage impact -o json |
runninglake.v1.GetImpactResponse: the asset and downstream, every entry with its asset, its distance and the same bit. someNotVisible and truncated are absent when false, and absent is not a count |
rl reject -o json |
runninglake.v1.RejectChangeSetResponse: the change set as it stands once rejected, with its status, the commitSha of the files an approval of part had already committed when there were any, and the rest of the change set as the control plane sends it. It carries no reason, so a -reason is not in it. The text form’s sentences (rejected …; nothing was committed) are not a contract. When the call failed, or the answer is not a rejection, there is no document |
rl withdraw -o json |
runninglake.v1.WithdrawChangeSetResponse: the change set as it stands, with its author and when it was closed, and, for one that had a pull request, what the control plane last read of it (pull_request: its state and when, or why it could not be read). It carries no reason, so a -reason is not in it; reason sent is on stderr |
rl env rename -o json |
runninglake.v1.RenameEnvironmentResponse: the environment under its new name. The reminder about profiles and Terraform is on stderr |
rl spendcap held -o json |
runninglake.v1.ListHeldSpendCapsResponse: the held changes, every page merged into one response with no page token. Amounts are micro-cents (a whole unit is 100,000,000) as strings, as protojson writes an int64; inForce and proposed are absent when the platform could not read them, and an absent cap is not a zero. rl spendcap confirm has no -o json: it never confirms |
rl spendcap status -o json |
runninglake.v1.GetWorkCapResponse: capState (in_force, none or unreadable; treat a word you do not know as unreadable), cap, month, shadow. Amounts are micro-cents (a whole unit is 100,000,000) as strings, as protojson writes an int64. An absent part was not read and is not a zero: no month means the month could not be read, no unpricedTrinoStatements means that count could not, no shadow means there is no cap in force, the evaluation is off, or its record could not be read; meteredMicroCents is absent for a month priced in several currencies. Compare month.currency with cap.currency before setting one against the other |
rl diff -project p -env e -o json |
runninglake.v1.PreviewChangeResponse: the change set the control plane rendered, with no id and no creation time because nothing was stored; its renderedDiff is the diff the text form prints. The exit code is the verdict under either form |
rl query -o json |
runninglake.v1.GetQueryResponse: the last record read, printed whatever its status, so a failed query and a watch that ran out (-timeout, exit 3) print one too; with -no-wait, runninglake.v1.ExecuteQueryResponse, the record as the submit left it. Every line of progress (query <id>: queued, → running, the engine, downloaded …) is on stderr, and the text form’s closing summary is not printed: the document is the record. A statement the control plane refused prints no document (exit 1 or 2) |
rl query get -o json |
runninglake.v1.GetQueryResponse; -download still writes the file, and says so on stderr |
rl query list -o json |
runninglake.v1.ListQueriesResponse, every page merged into one, so it never carries a page token. A list with nothing in it prints {} and says so on stderr |
rl query cancel -o json |
runninglake.v1.CancelQueryResponse: the record the control plane then holds, with cancelRequestedBy and cancelRequestedAt when a request is recorded. The sentence the text form prints, and the command that reads the record back, are on stderr; the exit code is the text form’s: 0 when the request is recorded or the query had already ended, and not “the query is over” |
rl query logs -list -o json |
runninglake.v1.ListRunLogStreamsResponse, every page merged into one, so it never carries a page token. It is printed whatever the answer: a job with no readable log prints the response with its unavailableReason and exits 1, the sentence on stderr. A list with no stream and no reason prints {} and says no log streams for <id> on stderr |
rl run -o json |
runninglake.v1.StartRunResponse: the run as the control plane recorded it, and replayed when the key had already started it. Each parameter’s value is always left out of the printed run (its name, type and source stay), on a terminal too, because a CI log is read by everyone with access to the repository and a job may have a pseudo-terminal; a person who needs a value reads the run in the console. The document is printed whatever the run’s status, and the exit is the run’s (3 for a run that is not terminal). When nothing was started, stdout is empty: the exit code and stderr say why |
rl migrate analyse -o json and rl migrate analysis -o json |
runninglake.v1.GetMigrationPlanResponse: the analysis as read back, its plan and its projection. For analyse this is the answer of the second call, since the answer of the first (runninglake.v1.AnalyseUsageResponse) is only the analysis id; the id is also printed on stderr. A figure the control plane did not send is absent, and absent is not zero. A refused file prints no document |
rl migrate delete -o json |
runninglake.v1.DeleteMigrationPlanResponse: itemsErased, the number of items erased with the plan (zero for an analysis, which is left out of the document: {} means none) |
rl migrate principal -o json |
runninglake.v1.SetMigrationItemPrincipalResponse: item, the role as it now reads (state, reason, and principal when one is chosen) |
rl migrate handled-outside -o json |
runninglake.v1.SetMigrationItemHandledOutsideResponse: item, the item as it now reads (state, and handledNote while it is handled outside) |
rl migrate unload-sql -o json |
runninglake.v1.GetMigrationUnloadKitResponse: the integration role’s rolePolicy and roleTrust, the integrationSql, the bucketPolicy that names the plane’s readerRoleArn, one unloads entry for each table (itemId, sourceRef, path, sql and rows when the inventory stated them), the notes, the whole sql and leftOut, the number of the plan’s tables the kit left out |
rl migrate analyses -o json |
runninglake.v1.ListMigrationPlansResponse, every page merged into one, so it never carries a page token. A plan’s coveredTodayMicroCents is absent when the control plane left it unset, and absent is not zero |
rl secret list -o json |
runninglake.v1.ListSecretReferencesResponse, every page merged into one, so it never carries a page token. A reference’s path, key and versionPin are absent: they are the declaration, which rl get SecretReference prints. A figure no plane has reported (currentVersion, lastRotated, lastResolved, lastChecked, copiesLive) is absent, and absent is not zero. A list the control plane refuses as a whole prints no document and exits 2 |
rl secret get -o json |
runninglake.v1.GetSecretReferenceResponse: the reference without path, key and versionPin, and with a figure no plane has reported absent |
rl secret check -o json |
runninglake.v1.TestSecretReferenceResponse: outcome is the closed token (resolved, not_found, and so on), not the word the text form prints; checkedAt is present on every answer a plane gave and absent on one the control plane made without asking a plane; versionId is absent unless a plane found a version. The exit code is the verdict under either form |
rl secret reads -o json |
runninglake.v1.ListSecretReadsResponse, every page merged into one, so it never carries a page token: the reads, newest first, and effectiveSince, the start of the window the control plane applied. retentionDays is absent unless the control plane states one, and absent is not a number of days |
rl key list -o json |
runninglake.v1.ListProjectKeysResponse, every page merged into one, so it never carries a page token. A key’s arn is absent: it names your cloud account. A time or a figure no plane has reported is absent, and absent is not zero (an absent holdsData is not false). A list the control plane refuses as a whole prints no document and exits 2 |
rl key get -o json |
runninglake.v1.GetProjectKeyResponse: the key without its arn, and with the same absences |
rl key check -o json |
runninglake.v1.CheckProjectKeyResponse: the state, when it was checked and, for a key pending deletion, the date AWS gave. It has no ARN and no prose; the exit code still follows the state |
rl principal list -o json |
runninglake.v1.ListServicePrincipalsResponse, every page merged into one, so it never carries a page token. A time the control plane has not recorded is absent |
rl principal get -o json |
runninglake.v1.GetServicePrincipalResponse. It has no secret field |
rl principal disable -o json |
runninglake.v1.DisableServicePrincipalResponse |
rl principal credential list -o json |
runninglake.v1.ListCredentialsResponse, every page merged into one. A credential has no secret field |
rl principal credential revoke -o json |
runninglake.v1.RevokeCredentialResponse |
rl trust list -o json |
runninglake.v1.ListTrustPoliciesResponse, every page merged into one |
rl trust delete -o json |
runninglake.v1.DeleteTrustPolicyResponse |
rl trust evaluate -o json |
runninglake.v1.EvaluateTrustPolicyResponse; the exit code still says whether it matched |
rl webhook list -o json |
runninglake.v1.ListWebhookEndpointsResponse, every page merged into one, so it never carries a page token. An endpoint has its address and a description of each signing secret version (number, state, times), and no field that holds a secret. Read receiver, not receiverEnabled: an absent receiver is a control plane that does not say, which is not “off” |
rl webhook deliveries -o json |
runninglake.v1.ListWebhookDeliveriesResponse, the deliveries of every endpoint of the trigger merged into one, newest first; each carries its endpointId. It never carries a body, a header or a parameter value |
rl webhook preview -o json |
runninglake.v1.PreviewWebhookMappingResponse; the exit code still says whether any parameter would be refused |
rl webhook disable -o json |
runninglake.v1.DisableWebhookEndpointResponse |
rl auth whoami -o json |
runninglake.v1.GetCallerResponse, as the control plane sent it |
That is every verb so far. A new verb is born with -o json; the others gain it later,
and until then they do not accept -o.
A violation is printed as file:line: message, then the rule id a CI script can match
on (rule: iceberg-dml-engines), then what to do instead (try: ...). A rejection
without an alternative is a support ticket, so every rule that fires carries one.