> ## Documentation Index
> Fetch the complete documentation index at: https://docs.canary.effectiveai.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Submission log

> The commands, stages, quote options, status output, contract, refusal codes, stores, entries and script lines of a submission log.

A **submission log** records one submission against one program: a guideline, or a rater alone.
`rslc submission` writes and reads the log. `rslc script` plays a whole submission from a script
file. [Run a submission](https://docs.canary.effectiveai.app/rsl/how-to/run-a-submission.md) shows the commands at work.

## The example on this page

The examples read the staffing example of the synthetic carrier Example Mutual. Its `bind.script`
plays one submission to a bound quote, and `--keep` writes the log:

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc examples get staffing
cd staffing
rslc script bind.script --keep submission.jsonl
rslc submission history submission.jsonl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
WROTE staffing/application.json
WROTE staffing/bind.script
WROTE staffing/guideline.rsl
WROTE staffing/rater.rsl
WROTE staffing/stale.script
PASS bind.script: 26 steps, 0 failed, 13 log entries, stage Bound
  1  assistant:intake  opened guideline.rsl (program 6c92e7971a32)
  2  assistant:intake  answered operation = "staffing" from application.json
  3  assistant:intake  answered policy.limit = 1000000 from application.json
  4  assistant:intake  answered policy.payroll = 2500000 from application.json
  5  assistant:intake  answered years_in_operation = 2 from application.json
  6  uw:alice  answered policy.experience = "no_claims" because "Loss runs, five years"
  7  manager:morgan  signed off new_venture = Approved on years_in_operation = 2 because "Experienced owners."
  8  uw:alice  quoted q1: 1 outcomes
  9  uw:alice  answered policy.payroll = 3000000
 10  uw:alice  answered years_in_operation = 1
 11  manager:morgan  signed off new_venture = Approved on years_in_operation = 1 because "Still acceptable."
 12  uw:alice  quoted q2: 1 outcomes
 13  uw:alice  bound q2
```

## Commands

Each command takes the log as its first argument. `contract` takes a log or a program file.

| Command | What it does | Who may run it |
| - | - | - |
| `open LOG --program FILE` | Starts the log and pins it to the program | Each role that the program declares |
| `answer LOG QUESTION=VALUE` | Records an answer to an input, or a sign-off of a referral | The question's role, or a role above it |
| `retract LOG QUESTION` | Takes back an answer or a sign-off, so the question is open again | The roles that may answer the question |
| `status LOG` | Prints the stage, quotes, open questions, declines, referrals and outcomes | Everyone; it reads only |
| `quote LOG NAME` | Records the present outcomes as a quote under a new name | The role of the `quote` act, or a role above it |
| `bind LOG NAME` | Binds one quote that stands, and makes the submission final | The role of the `bind` act, or a role above it |
| `close LOG REASON` | Ends the submission as `withdrawn`, `expired` or `lost`; it is then final | Each role that the program declares |
| `close LOG declined` | Declines the submission while a rule or a sign-off declines; it is then final | The role of the `quote` act, or a role above it |
| `history LOG` | Lists every entry; it reads the log only, and leaves the program unread | Everyone; it reads only |
| `copy LOG NEW` | Writes every entry of a log into a new log, in either [store](#log-stores) | Everyone; it reads the first log only |
| `contract LOG` | Prints [the contract](#the-contract) of the log's program | Everyone; it reads only |
| `contract --program FILE` | Prints the contract of a program file, before a log exists | Everyone; it reads only |
| `rslc script FILE` | Plays a script file on a log in memory and checks what it expects | Everyone; each step names its writer with `as` |

Each row but the last follows `rslc submission`. Six commands write entries: `open`, `answer`,
`retract`, `quote`, `bind` and `close`. These rules decide who may write:

* A role acts for itself and for each role below it in `Roles`.
* An input that lacks `by` takes an answer from each declared role.
* A write replaces only an answer or a sign-off from the writer's own role or a role below it.
* An act that `Acts` leaves out is open to each declared role.
* A program that lacks `Roles`, such as a rater alone, accepts each role name.
* One `answer` command with several answers records all of them or refuses all of them.

`open` compiles the program, and then holds it to four more rules:

| Rule | Message |
| - | - |
| Each `by` and each `refer` belongs to the program's root record | `E-ROLE`, `E-REFER` |
| An input marked `per quote` stands outside each path that a role answers | `E-ROLE` |
| A union or a list marked `per quote` holds only inputs with the mark | `E-ROLE` |
| Two arms of one union that share a field name mark that field alike | `E-ROLE` |

`rslc check` passes a program that breaks one of these rules. `open`, `contract --program` and
`rslc script` then stop with the message and exit code 1. To give a role to an input of another
record, write its path on the root record, as in `policy.experience by @Role.uw`.

### Options

| Option | Commands | Meaning |
| - | - | - |
| `--as ROLE:NAME` | The six commands that write | The writer: a role, and a name for the person or the tool |
| `--program FILE` | `open`, `contract` | The program's file; `open` stores its path from the log's own folder |
| `--file FILE` | `answer` | A JSON object; each top-level key is one question |
| `--because TEXT` | `answer`, `close` | The writer's reason, stored in the entry |
| `--from SOURCE` | `answer` | Where the answer comes from; with `--file`, the default is the file's name |
| `--in OPTION` | `answer`, `retract`, `quote`, `status` | The [quote option](#quote-options) that the command works in |
| `--based-on SEQ` | The six commands that write | The last entry that the writer read; see [STALE](#stale) |
| `--at SEQ` | `status` | The status after entry `SEQ`; a number past the end gives the present status |
| `--keep LOG` | `rslc script` | Writes the script's log to a new log, in either [store](#log-stores) |
| `--json` | Each command | Prints one JSON value on standard output |

`rslc submission help` prints the usage text, which [Commands](https://docs.canary.effectiveai.app/rsl/reference/commands.md) shows.

### Exit codes

| Exit code | `rslc submission` | `rslc script` |
| - | - | - |
| `0` | The log applied the command, or `status`, `history`, `copy` or `contract` printed | Every step passed |
| `2` | The log refused the command; the output starts with `refused` and a code | Unused |
| `1` | An error: a wrong command line, a missing log, or a program that fails its check; the output starts with `error:` | A step failed, or an error |

These commands read their own arguments. For the other `rslc` commands, `2` marks a wrong command line.

## Log stores

The end of a log's name selects its store:

| Name ends in | The log is | Refused commands go to |
| - | - | - |
| `.jsonl` | A JSON Lines file with one entry on each line | A second file beside it, `NAME.refused.jsonl` |
| `.sqlite` | A SQLite database with one row for each entry | The table `refusals` of the same database |

A name with another ending is an error with exit code 1. Both stores keep the same rules:

* An entry is the same JSON text in both stores, so a log gives one status and one history in each.
* Commands only add entries. Entry `n` has `seq` `n`, and each read checks it.
* A write adds all of its entries, or it adds zero.
* A write lands only while the log still ends at the entry that the command read.
  When another writer came first, the command reads the log again and decides again.
* A `.sqlite` log refuses a changed entry, a removed entry and an entry out of place, from each program that writes to the database.

`copy` writes every entry of one log into a new log:

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc submission copy submission.jsonl submission.sqlite
rslc submission status submission.sqlite
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
copied 13 entries to submission.sqlite
stage: Bound
quotes:
  q1  lapsed (policy.payroll, years_in_operation, new_venture changed)
  q2  bound
referrals:
  new_venture  Approved  @manager
outcomes:
  premium = 6000
```

* The new log starts empty. A target that holds entries is an error.
* `copy` keeps each entry as it is, so the copy stores the same path to the program.
  Put the new log in a folder where that path names the same program file.
* `copy` writes the entries only. The refused commands stay with the first log.

[Entries](#entries) lists what each store holds.

## Questions and values

A question is one of two things:

* **An input's path** from the program's root record: field names joined by `.`, such as `policy.payroll`.
* **A referral's name**, such as `new_venture`.

A field name starts with a lowercase letter or `_`. [List items](#list-items) gives the path of one item of a list.

A value is JSON. Text that JSON leaves unread, such as a bare word, is a string.

| Written | Value |
| - | - |
| `2500000`, `0.25`, `25e5` | A number, stored in the log exactly as written |
| `true`, `false` | A `Bool` |
| `no_claims`, `"no_claims"` | A string, such as an enum value |
| `2026-03-01` | A string that a `Date` input reads as year, month, day |
| `{"payroll": 2500000}` | A record |
| `[{"factor": 1.1}]` | A list |
| `Approved`, `Declined` | The two answers to a referral |

* The log checks each answer against the input's type and its rules before it writes.
* Two spellings of one number, such as `2500000` and `25e5`, are the same answer.
* A record that holds a question of another role is stored field by field, one entry for each field.
* To retract an answer, name the path that its entry holds.

### List items

The log names an item of a list by its key: the field that the program gives after `keyed by`.

| Path | Names |
| - | - |
| `modifiers` | The whole list |
| `modifiers[number=1]` | The item whose `number` is 1 |
| `modifiers[number=1].factor` | One input of that item |
| `placements[class="clerical"].share` | An input of an item whose key is text, a JSON string |

* A key's value is a JSON string or a JSON number.
* An answer under a key that the list lacks adds the item, with that key. The writer also needs the role of the key field.
* An answer to a whole item is a record. The record leaves the key out, or repeats the key of the path.
* A key names its item for the life of the log. An answer to the key field itself is refused as [BAD\_PATH](#bad_path).
* A place in a list, such as `[0]`, is refused as [BAD\_PATH](#bad_path). A place moves when an item before it comes or goes.
* A list that lacks a key has zero item paths. Answer it whole, as one JSON list.
* A keyed list is a map from key to item. The same items in another order are the same answer, and a quote of them stands.
* Open questions, declines, the answers that a referral reads, lapsed quotes and `STALE` messages name items by these paths.
* In a shell, put the pair in single quotes, as the commands below do.

These commands open a log on the `fn-folds` example. It is a rater alone and lacks `Roles`, so
each role name writes. Its `modifiers` list is keyed by `number`, and its `endorsements` list lacks a key:

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc examples get fn-folds --out ../fn-folds
rslc submission open lists.jsonl --program ../fn-folds/rater.rsl --as uw:alice
rslc submission answer lists.jsonl 'modifiers[number=1].factor=0.9' 'endorsements=[{"factor": 1.1}]' --as uw:alice
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
WROTE ../fn-folds/rater.rsl
  1  uw:alice  opened ../fn-folds/rater.rsl (program e878372d9cf7)
stage: Open
open (3):
  locations     input    any role
  modifiers     input    any role
  endorsements  input    any role
  2  uw:alice  answered modifiers[number=1].factor = 0.9
  3  uw:alice  answered endorsements = [{"factor":1.1}]
stage: Open
open (1):
  locations  input    any role
```

The same answer by place is refused:

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc submission answer lists.jsonl 'modifiers[0].factor=0.9' --as uw:alice
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
refused BAD_PATH: `modifiers[0].factor`: `[0]` names a list place, and a place moves when an item before it is added or taken; name the item by its key, `[<key>=<value>]`, or answer the list whole
  candidates: locations
stage: Open
open (1):
  locations  input    any role
```

## Stages

The log stores entries only. Each status computes the stage from the entries and the program. The
first row of this table that holds gives the stage:

| Order | Stage | Holds when |
| - | - | - |
| 1 | `Bound` | The log holds a `bound` entry |
| 2 | `Declined` | The log holds a `closed` entry with the reason `declined` |
| 3 | `Closed` | The log holds a `closed` entry with another reason |
| 4 | `Ineligible` | The answers break a rule that has a code, or a referral is signed off as `Declined` |
| 5 | `Open` | An outcome still needs an input, or a referral whose condition holds waits for a sign-off |
| 6 | `Quotable` | Every outcome is known, and each referral whose condition holds is `Approved` |

| Command | The stage |
| - | - |
| `open` | Starts at `Open` |
| `answer`, `retract` | Moves among `Open`, `Ineligible` and `Quotable`, by the rows above |
| `quote` | Stays as it is; a quote is an entry, and the stage ignores it |
| `bind` | Moves to `Bound` |
| `close` with `withdrawn`, `expired` or `lost` | Moves from `Open`, `Ineligible` or `Quotable` to `Closed` |
| `close declined` | Moves from `Ineligible` to `Declined` |

* An answer is recorded or refused, and the submission stays open either way.
* `Ineligible` takes corrections: a new answer or a retracted sign-off moves the stage again.
* `Declined` comes only from `close declined`, by the role of the `quote` act, while a rule or a sign-off declines.
* `Bound`, `Declined` and `Closed` are final: the log refuses each later write as [CLOSED](#closed).
* With `--in`, the stage is the stage of that [quote option](#quote-options).

## Referral and quote states

A status gives each referral one state:

| State | Meaning |
| - | - |
| `Unknown` | The condition still needs an answer |
| `NotReferred` | The condition is false |
| `Open` | The condition is true, and the referral waits for a sign-off |
| `Approved` | The condition is true, and a sign-off of `Approved` stands |
| `Declined` | The condition is true, and a sign-off of `Declined` stands; the stage is `Ineligible` |

A sign-off stands for the answers that the referral's condition reads. The status shows those
answers, and the [`signed_off` entry](#signed_off-entry) records them as `read`.

A sign-off **lapses** when one of those answers changes after it. A lapsed sign-off stops counting:
the state follows the condition alone, and `lapsed` is set. The sign-off stands again when the
answers return to the signed values.

A status gives each quote one state:

| State | Meaning |
| - | - |
| `stands` | The program, the answers and the sign-offs are the ones that the quote was given on |
| `lapsed` | One of them changed; the status names each changed answer and sign-off |
| `bound` | `bind` took this quote |

## Quote options

One submission may hold several quotes that stand at once, each on its own terms.

| Word | Meaning |
| - | - |
| Term | An input that the program marks `per quote` |
| Fact | Every other input; all quotes of the submission share one answer to it |
| Shared answers | The answers and sign-offs that commands give when `--in` is absent |
| Quote option | A named set of answers to terms, and of sign-offs |
| View | The shared answers with one option's answers over them; a quote of the option prices it |

`--in OPTION` puts a command in an option:

| Command | With `--in OPTION` |
| - | - |
| `answer` | Records an answer to a term, or a sign-off, in the option |
| `retract` | Takes back the option's own answer or sign-off; the view reads the shared one again |
| `quote` | Quotes the option's view; the quote keeps the option |
| `status` | Prints the status of the option's view |

The setup below opens a log, makes it `Quotable` and gives a first quote on the shared answers:

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc submission open options.jsonl --program guideline.rsl --as assistant:intake
rslc submission answer options.jsonl --file application.json policy.experience=no_claims --as uw:alice
rslc submission answer options.jsonl new_venture=Approved --as manager:morgan
rslc submission quote options.jsonl q1 --as uw:alice
```

The option `high` then answers the limit, which the rater marks `per quote`, and gets its own quote:

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc submission answer options.jsonl policy.limit=2000000 --in high --as uw:alice
rslc submission quote options.jsonl q2 --in high --as uw:alice
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
  9  uw:alice  answered policy.limit = 2000000 in high
stage: Quotable
quotes:
  q1  stands
referrals:
  new_venture  Approved  @manager
outcomes:
  premium = 5500
 10  uw:alice  quoted q2 in high: 1 outcomes
stage: Quotable
quotes:
  q1  stands
  q2  stands
referrals:
  new_venture  Approved  @manager
outcomes:
  premium = 5500
```

* An option's name starts with a lowercase letter and holds lowercase letters, digits and `_`.
* An option begins with its first answer or sign-off. `quote` in an option that holds zero of them is [NOT\_QUOTABLE](#not_quotable).
* An answer to a fact in an option is refused as [NOT\_A\_TERM](#not_a_term), so every quote prices one risk.
* A record answer in an option is stored field by field, and each field is a term.
* A record that the shared answers lack enters an option only when the record itself is a term.
* A write in an option changes that view alone, so it lapses only the quotes of that option.
* A write to the shared answers changes each view. Each quote whose view changes lapses.
* A shared sign-off counts in each view that holds the answers it read. A sign-off in an option counts in that option alone.
* A command with `--in` checks roles, values and [STALE](#stale) in the option's view, and prints the status of that view.
* A status lists the quotes of every option. Its other blocks read one view: the shared answers, or the option that `--in` names.
* `status --in` with a name that the log lacks prints the shared answers.
* `bind` names a quote, and the quote keeps its option. The bound policy is that option's view.
* `--in` with `open`, `bind` or `close` is an error with exit code 1. So is a malformed option name.
* A role's answer is always a fact: [`E-ROLE`](https://docs.canary.effectiveai.app/rsl/reference/check-messages/guidelines#e-role).

## Status output

Each command that writes prints the entries it wrote, and then the status. `status` prints the
status alone. This is the example after entry 10:

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc submission status submission.jsonl --at 10
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
stage: Open
quotes:
  q1  lapsed (policy.payroll, years_in_operation changed)
open (1):
  new_venture  signoff  @manager  because: The firm is in operation for less than 3 years.
                 reads years_in_operation = 1
referrals:
  new_venture  Open  @manager  (sign-off lapsed)
outcomes known so far:
  premium = 6000
```

The status has up to seven blocks, in this order. A block with an empty list is left out.

| Block | Each line |
| - | - |
| `stage:` | The stage |
| `quotes:` | A quote's name and its state; a lapsed quote lists what changed |
| `open (N):` | A question, its kind (`input` or `signoff`), and the lowest role that may answer |
| `declines:` | A rule's code and message, then `at` and the path of the record or input |
| `invalid:` | An answer that the program's types refuse; a log that these commands wrote has an empty list |
| `referrals:` | A referral that is `Open`, `Approved`, `Declined` or lapsed: its state, its role, and `(sign-off lapsed)` |
| `outcomes known so far:` | An outcome and its value; the title is `outcomes:` outside the `Open` stage |

* An open question of every role shows `any role`. A role shows as `@uw`.
* An open `signoff` line ends with `because:` and the referral's reason.
* Under an open `signoff` line, each `reads` line gives one answer that the referral's condition reads. A sign-off stands for exactly these answers.
* An open `input` line ends with `one of:` and the answers it takes, when a table lookup limits them.
* A record such as `policy` stands for its inputs. An input of another role inside it gets its own line.
* An open list shows as the list. An open input of a keyed item shows by its [path](#list-items).
* An outcome is listed as soon as the answers decide it. Later answers to open questions leave it as it is.

A submission that breaks rules shows them under `declines:`. Here one rule is the rater's and one
is the guideline's:

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc submission open declined.jsonl --program guideline.rsl --as assistant:intake
rslc submission answer declined.jsonl operation=peo years_in_operation=2 policy.payroll=500 --as assistant:intake
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
...
stage: Ineligible
declines:
  PAYROLL_BELOW_MINIMUM  The payroll must be at least $1,000.  at policy
  PEO_NOT_ESTABLISHED  A PEO must be in operation for at least 3 years and be financially sound.
  the account stays open: correct an answer, or decline it with `close declined`
```

* A rule of the root record has an empty path, so its line ends with the message.
* A sign-off of `Declined` shows the referral's name, then `declined by` and the signer.
* In the `Ineligible` stage, the block ends with a line that names the two ways on.

`close declined` then records the decision, and names the codes it rests on:

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc submission close declined.jsonl declined --because "A PEO in its second year" --as uw:alice
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
  5  uw:alice  closed (declined) on PAYROLL_BELOW_MINIMUM, PEO_NOT_ESTABLISHED because "A PEO in its second year"
stage: Declined
declines:
  PAYROLL_BELOW_MINIMUM  The payroll must be at least $1,000.  at policy
  PEO_NOT_ESTABLISHED  A PEO must be in operation for at least 3 years and be financially sound.
```

### JSON output

With `--json`, a command prints one JSON value on one line:

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc submission status submission.jsonl --at 10 --json
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{"ok":true,"applied":[],"refusal":null,"seq":10,"program":"6c92e7971a32e57b198aa194a27c329a7f5e1705411b2037abc6ca99eaf4248e","status":{"stage":"Open","outcomes":{"premium":6000},"declines":[],"open":[{"question":"new_venture","kind":"signoff","role":"manager","choices":["Approved","Declined"],"because":"The firm is in operation for less than 3 years.","reads":[{"path":"years_in_operation","value":1}]}],"referrals":[{"name":"new_venture","state":"Open","role":"manager","because":"The firm is in operation for less than 3 years.","lapsed":true,"reads":[{"path":"years_in_operation","value":1}]},{"name":"large_account","state":"NotReferred","role":"manager","because":"The premium is $10,000 or more.","lapsed":false,"reads":[{"path":"policy.experience","value":"no_claims"},{"path":"policy.limit","value":1000000},{"path":"policy.payroll","value":3000000}]}],"invalid":[],"quotes":[{"name":"q1","outcomes":{"premium":5000},"stands":false,"bound":false,"lapsed_by":["policy.payroll","years_in_operation"]}]}}
```

The same value, laid out for reading:

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "ok": true,
  "applied": [],
  "refusal": null,
  "seq": 10,
  "program": "6c92e7971a32e57b198aa194a27c329a7f5e1705411b2037abc6ca99eaf4248e",
  "status": {
    "stage": "Open",
    "outcomes": {
      "premium": 6000
    },
    "declines": [],
    "open": [
      {
        "question": "new_venture",
        "kind": "signoff",
        "role": "manager",
        "choices": ["Approved", "Declined"],
        "because": "The firm is in operation for less than 3 years.",
        "reads": [{ "path": "years_in_operation", "value": 1 }]
      }
    ],
    "referrals": [
      {
        "name": "new_venture",
        "state": "Open",
        "role": "manager",
        "because": "The firm is in operation for less than 3 years.",
        "lapsed": true,
        "reads": [{ "path": "years_in_operation", "value": 1 }]
      },
      {
        "name": "large_account",
        "state": "NotReferred",
        "role": "manager",
        "because": "The premium is $10,000 or more.",
        "lapsed": false,
        "reads": [
          { "path": "policy.experience", "value": "no_claims" },
          { "path": "policy.limit", "value": 1000000 },
          { "path": "policy.payroll", "value": 3000000 }
        ]
      }
    ],
    "invalid": [],
    "quotes": [
      {
        "name": "q1",
        "outcomes": {
          "premium": 5000
        },
        "stands": false,
        "bound": false,
        "lapsed_by": ["policy.payroll", "years_in_operation"]
      }
    ]
  }
}
```

| Field | Value |
| - | - |
| `ok` | `true` when the log applied the command or read the status; `false` for a refusal |
| `applied` | The entries that this command wrote, in the form of [Entries](#entries); empty for `status` |
| `refusal` | `null`, or the refusal: `code`, `message` and, when the log has some, `candidates` |
| `seq` | The number of the last entry that the status covers |
| `program` | The program's hash, 64 hexadecimal characters |
| `status` | The status, with the seven fields below |

| Field of `status` | Value |
| - | - |
| `stage` | `Open`, `Ineligible`, `Quotable`, `Bound`, `Declined` or `Closed` |
| `outcomes` | Each known outcome by name; a referral's condition is left out |
| `declines` | Each broken rule that has a code, and each sign-off of `Declined` |
| `open` | Each open question |
| `referrals` | Every referral of the program, in each state |
| `invalid` | Each answer that the program's types refuse, in the form of a decline |
| `quotes` | Every quote of the log, by name |

| Object | Fields |
| - | - |
| An open question | `question`; `kind`, `input` or `signoff`; `role`, or `null` for every role; `choices` and `because` when known; `reads` for a `signoff` |
| A decline | `code`; `message`; `path`; `cite` when the rule has a citation |
| A referral | `name`; `state`; `role`; `because`; `lapsed`, `true` for a sign-off that lapsed; `reads` |
| A quote | `name`; `outcomes`; `stands`; `bound`; `lapsed_by`, the changed answers and sign-offs, when lapsed |
| A read | `path`, the input's path, and `value`, its answer |

* `reads` lists each answer that the referral's condition reads now. An unanswered input is left out.
* With `--in`, `stage`, `outcomes`, `declines`, `open`, `referrals` and `invalid` describe that option's view.

The declines of `declined.jsonl` are these:

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
[
  {
    "code": "PAYROLL_BELOW_MINIMUM",
    "message": "The payroll must be at least $1,000.",
    "path": "policy",
    "cite": "ts__rules:p1"
  },
  {
    "code": "PEO_NOT_ESTABLISHED",
    "message": "A PEO must be in operation for at least 3 years and be financially sound.",
    "path": "",
    "cite": "guideline:p1"
  }
]
```

* A rule of the root record has an empty `path`.
* A decline by a sign-off has the referral's name as `code`, the signer in `message`, and an empty `path`.
* A citation of the rater carries the rater's short name, as in `ts__rules:p1`.
* A number in the JSON keeps the digits that the answer or the program gave it.

Five other values have their own shape:

| Case | JSON value | Exit code |
| - | - | - |
| An error | `{"ok": false, "error": "..."}` | `1` |
| [PROGRAM\_CHANGED](#program_changed) | `{"ok": false, "program": "...", "refusal": {...}}`, with the hash of the program on disk | `2` |
| `history` | `{"ok": true, "entries": [...]}`, each entry in the form of [Entries](#entries) | `0` |
| `copy` | `{"ok": true, "entries": 13}`, the count of copied entries | `0` |
| `contract` | `{"ok": true, "contract": {...}}`, with [the contract](#the-contract) | `0` |

## History output

`history` prints one line for each entry: its number, its writer as `role:name`, and what it
records. [The example](#the-example-on-this-page) shows the lines of a whole submission.

| Entry | Line |
| - | - |
| `opened` | `opened`, the program's file, and the first 12 characters of its hash |
| `answered` | `answered`, the path, `=` and the value; then `in` and the option, `from` and `because`, when given |
| `signed_off` | `signed off`, the referral, `=` and the decision; then `in` and the option, `on` and the answers read, and `because` |
| `retracted` | `retracted` and the path or the referral; then `in` and the option |
| `quoted` | `quoted`, the name, `in` and the option, and the count of outcomes |
| `bound` | `bound` and the quote's name |
| `closed` | `closed` and the reason in parentheses; then `on` and the codes of a decline; then `because` |

* Each part after the first appears only when the entry holds it.
* After `on`, a sign-off line lists each answer that the signer saw, as `path = value`.
* `history` reads the log alone. It prints a list among those answers as one value.

## The contract

`contract` prints what a program asks, who may write each answer and take each act, and what blocks
each act. The value comes from the compiled program, the same one that decides each write.

| Form | Reads |
| - | - |
| `rslc submission contract LOG` | The program that the log is pinned to |
| `rslc submission contract --program FILE` | A program file, before a log exists |

Both forms print the same value for the same program. This excerpt keeps one part of each field:

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc submission contract --program guideline.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "acts": {
    ...
    "decline": {
      "blocked_by": [
        "no rule or sign-off declines the answers"
      ],
      "role": "uw",
      "spelled": "close declined"
    },
    ...
  },
  "program": {
    "id": "6c92e7971a32e57b198aa194a27c329a7f5e1705411b2037abc6ca99eaf4248e",
    "root": "Submission"
  },
  "questions": {
    ...
      "policy": {
        "additionalProperties": false,
        "properties": {
          "experience": {
            "$ref": "#/$defs/ts.Experience",
            "x-rsl-role": "uw"
          },
    ...
  },
  "referrals": [
    {
      "answers": [
        "Approved",
        "Declined"
      ],
      "because": "The firm is in operation for less than 3 years.",
      "name": "new_venture",
      "reads": [
        "years_in_operation"
      ],
      "role": "manager",
      "when": "self.years_in_operation < established_years"
    },
    ...
  ],
  "roles": [
    ...
    {
      "acts_for": [
        "assistant"
      ],
      "name": "uw"
    },
    ...
  ],
  "rules": [
    ...
    {
      "cite": "ts__rules:p1",
      "code": "PAYROLL_BELOW_MINIMUM",
      "constraint": "ts.Policy where self.payroll >= 1000",
      "effect": "decline",
      "message": "The payroll must be at least $1,000.",
      "path": "policy"
    }
  ],
  "tools": []
}
```

| Field | Value |
| - | - |
| `program` | `id`, the program's hash, and `root`, the name of its root record |
| `roles` | Each role: `name`, and `acts_for`, the roles below it |
| `acts` | `quote`, `bind`, `close` and `decline`, each with its `role` |
| `questions` | The JSON Schema of the root record's inputs, with the marks below |
| `referrals` | Each referral: `name`, `role`, `when`, `because`, `answers` and `reads` |
| `rules` | Each rule: `path`, `code`, `message`, `constraint`, `cite` and `effect` |
| `tools` | Each answer that a host tool fills: `path` and `filled_by` |

| Part | Meaning |
| - | - |
| `role` of an act | The lowest role that may take the act; `null` when each declared role may |
| `blocked_by` | What stops `quote`, `bind` or `decline`, in words |
| `reasons` of `close` | `withdrawn`, `expired` and `lost` |
| `spelled` | The command that declines: `close declined`. Its role is the role of `quote`. |
| `x-rsl-role` | On a property of `questions`: the lowest role that may answer it. Each role answers a property that lacks the mark. |
| `x-rsl-when` | On a follow-up input: the condition under which the program asks for it |
| `reads` of a referral | The paths of the inputs that its condition reads, with list keys left out |
| `effect` of a rule | `decline`, `refuse` or `call_declined`; see the next table |

| `effect` | The rule | A value that breaks it |
| - | - | - |
| `decline` | Has a code | Is recorded, and the stage is `Ineligible` |
| `refuse` | Lacks a code | Is refused as [INVALID\_VALUE](#invalid_value) |
| `call_declined` | Has a code, in a rater that the guideline reads with `match` | Is recorded; the call gives `Declined`, and the guideline decides |

* `rules` lists each rule of a record, and each rule of one input that has a code. A plain range or type of an input sits in `questions`.
* A rule of the root record has an empty `path`.
* With `--json`, the value is `{"ok": true, "contract": {...}}` on one line.
* After the program's files change, `contract LOG` is refused as [PROGRAM\_CHANGED](#program_changed).

## Refusal codes

A refused command leaves the log as it was. The output starts with `refused`, the code and a
message, then a `candidates:` line when the log knows what it accepts, then the status.

| Code | The log refuses |
| - | - |
| [ALREADY\_OPEN](#already_open) | `open` on a log that is open |
| [NOT\_OPEN](#not_open) | A script step before `open` |
| [PROGRAM\_CHANGED](#program_changed) | Each command, `status` included, after the program's files changed |
| [CLOSED](#closed) | A write to a submission that is `Bound`, `Declined` or `Closed` |
| [UNKNOWN\_ROLE](#unknown_role) | A writer whose role is outside the program's `Roles` |
| [NOT\_AUTHORIZED](#not_authorized) | A writer whose role is below the role that the write needs |
| [BAD\_PATH](#bad_path) | A question that the program lacks, or a list item named another way than by key |
| [INVALID\_VALUE](#invalid_value) | A value that breaks the question's type, or a rule that lacks a code |
| [NOT\_REFERRED](#not_referred) | A sign-off of a referral whose condition is false or unknown |
| [NOT\_ANSWERED](#not_answered) | `retract` of a question whose answer or sign-off is missing |
| [NOT\_A\_TERM](#not_a_term) | An answer in a quote option to an input that lacks the `per quote` mark |
| [NOT\_DECLINABLE](#not_declinable) | `close declined` while zero rules and zero sign-offs decline |
| [NOT\_QUOTABLE](#not_quotable) | `quote` outside the `Quotable` stage, or under a name that is taken or malformed |
| [NOT\_QUOTED](#not_quoted) | `bind` of a quote that is missing or lapsed |
| [STALE](#stale) | A write whose facts changed after the entry that `--based-on` names |

The examples below run on a second log that holds the four answers of the application:

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc submission open second.jsonl --program guideline.rsl --as assistant:intake
rslc submission answer second.jsonl --file application.json --as assistant:intake
```

### ALREADY\_OPEN

`open` ran on a log that is open. Go on with the log, or open a new log.

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc submission open second.jsonl --program guideline.rsl --as assistant:intake
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
refused ALREADY_OPEN: this log is already open
...
```

### NOT\_OPEN

A script step ran before the script's `open` step. Put `open` first. The script under
[Script files](#script-files) has such a step.

On the command line, a command on a log that is missing or empty is an error. It prints
`error:` and a hint to run `open`, and it exits with code 1.

### PROGRAM\_CHANGED

The files of the program differ from the files that the log was opened with. The log holds a hash
of each source file and each table of the program. Restore the files, or open a new log. `history`
still reads the log. This is the refusal after one edit to `guideline.rsl`:

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
refused PROGRAM_CHANGED: this log is pinned to program 6c92e7971a32, and the program on disk is d760adc3c683; restore the program to read the status
```

### CLOSED

The submission is `Bound`, `Declined` or `Closed`, and the three stages are final. Open a new log for a new submission.

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc submission answer submission.jsonl years_in_operation=3 --as uw:alice
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
refused CLOSED: the submission is Bound; it takes no more commands
...
```

### UNKNOWN\_ROLE

The role in `--as` is outside the program's `Roles`. The candidates are the declared roles.

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc submission answer second.jsonl operation=staffing --as broker:bob
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
refused UNKNOWN_ROLE: `broker` is not a role of this program
  candidates: assistant, uw, manager
...
```

### NOT\_AUTHORIZED

The writer's role is below the role that the question or the act needs. The candidates are the
roles that may write. Ask a person in one of them.

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc submission answer second.jsonl policy.experience=no_claims --as assistant:intake
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
refused NOT_AUTHORIZED: answering `policy.experience` needs role `uw` or a role above; `intake` is `assistant`
  candidates: uw, manager
...
```

The log gives the same code in three more cases:

* A write replaces an answer or a sign-off of a higher role, or of a role beside the writer's role.
* `close declined` comes from a role below the role of the `quote` act.
* An answer adds a list item, and the item's key field belongs to a higher role.

### BAD\_PATH

The question is other than an input's path or a referral's name. The candidates are the open questions.

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc submission answer second.jsonl revenue=5000000 --as assistant:intake
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
refused BAD_PATH: `revenue` is not a question of this program
  candidates: policy.experience, new_venture
...
```

The log gives the same code to each path that names a list item another way than [by its key](#list-items):

| Path | The message says |
| - | - |
| `modifiers[0].factor` | `[0]` names a list place; name the item by its key |
| `modifiers[id=1].factor` | The list is keyed by `number` |
| `modifiers.factor` | `modifiers` is a list; name one item by its key |
| `modifiers[number=1].number` | The key names the item; answer the item under its key |
| `endorsements[factor=1.1].factor` | The list lacks a key; answer it whole |
| `policy..payroll` | The text is other than a path |

A whole item whose record holds another key than its path gets the same code.

### INVALID\_VALUE

The value breaks the input's type, its precision, its range or another rule that lacks a code. The
message names the input and quotes the rule, and it ends with `nothing was written`. A sign-off
value other than `Approved` or `Declined` gets the same code.

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc submission answer second.jsonl operation=franchise --as assistant:intake
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
refused INVALID_VALUE: operation must be one of: staffing, peo at `operation` (INPUT-ENUM: operation: Enum(Operation)); nothing was written
...
```

A value that breaks a rule with a code is a different case. The log records the answer, and the
stage is `Ineligible`.

### NOT\_REFERRED

The sign-off names a referral whose state is `NotReferred` or `Unknown`. Sign off a referral that
the status lists as open. Here the premium is still unknown:

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc submission answer second.jsonl large_account=Approved --as manager:morgan
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
refused NOT_REFERRED: `large_account` is not referred now (Unknown); a sign-off answers a referral whose condition holds
  candidates: policy.experience, new_venture
...
```

### NOT\_ANSWERED

`retract` names a question whose answer or sign-off is missing. The candidates are the paths that
hold one. The payroll's answer sits at `policy.payroll`, so a retract of `policy` finds it missing:

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc submission retract second.jsonl policy --as assistant:intake
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
refused NOT_ANSWERED: `policy` has no answer to retract; retract an answer by the path it was given at
  candidates: operation, policy.limit, policy.payroll, years_in_operation
...
```

With `--in`, `retract` looks at the option's own answers and sign-offs, and the candidates are those.

### NOT\_A\_TERM

An answer in a [quote option](#quote-options) names a fact: an input that lacks the `per quote`
mark. Every quote shares a fact. Give the answer again with `--in` left out, and it reaches each option.

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc submission answer options.jsonl policy.payroll=3000000 --in high --as uw:alice
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
refused NOT_A_TERM: `policy.payroll` is a fact every quote shares, so it is answered without `in high`; an option answers only an input marked `per quote`
...
```

The log gives the same code to a record that an option would make present, when the record itself is a fact.

### NOT\_DECLINABLE

`close declined` ran while zero rules and zero sign-offs decline: the stage is `Open` or `Quotable`.
A decline rests on a rule or a sign-off. The candidates are the other reasons to close.

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc submission close second.jsonl declined --as uw:alice
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
refused NOT_DECLINABLE: the submission is Open; no rule and no sign-off declines it, and a decline rests on one. Close it as withdrawn, expired or lost, or refer it
  candidates: withdrawn, expired, lost
...
```

### NOT\_QUOTABLE

`quote` ran outside the `Quotable` stage. The candidates are the open questions. Answer them first.

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc submission quote second.jsonl q1 --as uw:alice
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
refused NOT_QUOTABLE: the submission is Open; a quote is given once every question is answered and every referral is approved
  candidates: policy.experience, new_venture
...
```

The log gives the same code in three more cases:

* The stage is `Ineligible`. The candidates are then the codes that decline.
* The log holds the quote name already, or the name is malformed. A name starts with a lowercase letter and holds lowercase letters, digits and `_`.
* `--in` names an option that holds zero answers and zero sign-offs. The candidates are then the options of the log.

### NOT\_QUOTED

`bind` names a quote that the log lacks, or a quote that lapsed. The candidates are the quotes that
stand. Bind one of them, or quote again under a new name.

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc submission bind second.jsonl q1 --as uw:alice
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
refused NOT_QUOTED: the log holds no quote `q1`; bind names a quote that stands
...
```

### STALE

The command names the last entry that its writer read, with `--based-on`, and what the command
reads changed after that entry. The message names each changed answer. Read the status again, then
decide on what it holds.

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc submission answer second.jsonl years_in_operation=1 --based-on 4 --as assistant:intake
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
refused STALE: answering `years_in_operation` is based on entry 4, and years_in_operation changed after it; read the submission again and decide on what it holds now
...
```

| Command | Compared at the named entry and now |
| - | - |
| A sign-off | The answers that the referral's condition reads, and the sign-off it replaces |
| `answer` | The value at the path, and at each path under it and over it |
| `retract` | The value at the path, or the sign-off |
| `quote` | The program, every answer and every sign-off |
| `close declined` | The rules and the sign-offs that decline, each at its path |
| `bind`, `close` | Only that the log holds the named entry |

* A change elsewhere in the log passes.
* An entry number past the end of the log is `STALE` for each command.
* With `--in`, the log compares the option's view at the named entry and now.

## Entries

Two more commands give the second log a `retracted` entry and a `closed` entry:

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc submission retract second.jsonl years_in_operation --as assistant:intake
rslc submission close second.jsonl withdrawn --because "The applicant withdrew" --as assistant:intake
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
  6  assistant:intake  retracted years_in_operation
stage: Open
open (2):
  policy.experience   input    @uw
  years_in_operation  input    any role
  7  assistant:intake  closed (withdrawn) because "The applicant withdrew"
stage: Closed
open (2):
  policy.experience   input    @uw
  years_in_operation  input    any role
```

An entry is one JSON object. A `.jsonl` log holds it as one line. A `.sqlite` log holds it as the
text of the `entry` column, in the table `entries`, beside its `seq`.

| Field | Value |
| - | - |
| `seq` | The entry's number, from 1; entry `n` has `seq` `n` |
| `at` | The time of the write in UTC, to the second; `script` in a log that `rslc script` keeps |
| `by` | The writer: `role` and `id`, the two parts of `--as` |
| `event` | What happened: `type`, and the fields of that type |

A read of a log stops with an error when an entry is out of place, or when a path names a list
place. The seven types follow, each with a line from a log of this page.

### `opened` entry

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{"seq":1,"at":"2026-11-10T20:28:01+00:00","by":{"role":"assistant","id":"intake"},"event":{"type":"opened","program":"6c92e7971a32e57b198aa194a27c329a7f5e1705411b2037abc6ca99eaf4248e","source":"guideline.rsl"}}
```

| Field | Value |
| - | - |
| `program` | A SHA-256 hash of the program: its source files, tables and CSV enum files |
| `source` | The program's file, as a path from the log's folder |

### `answered` entry

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{"seq":4,"at":"script","by":{"role":"assistant","id":"intake"},"event":{"type":"answered","path":"policy.payroll","value":2500000,"from":"application.json"}}
{"seq":6,"at":"script","by":{"role":"uw","id":"alice"},"event":{"type":"answered","path":"policy.experience","value":"no_claims","because":"Loss runs, five years"}}
{"seq":9,"at":"2026-11-10T20:28:01+00:00","by":{"role":"uw","id":"alice"},"event":{"type":"answered","path":"policy.limit","value":2000000,"option":"high"}}
```

| Field | Value |
| - | - |
| `path` | The input's path, with each list item [by its key](#list-items) |
| `value` | The answer as JSON, each number as written |
| `from` | The source, when `--from` or `--file` gives one |
| `because` | The reason, when `--because` gives one |
| `option` | The quote option, when `--in` names one; absent for a shared answer |
| `arms` | In an option, under a union: the `path` and the `tag` of each union above the answer |
| `made` | In an option: each record or list item above the path that this answer made present |

An answer in an option under a union counts while each union in `arms` keeps that arm.

### `signed_off` entry

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{"seq":7,"at":"script","by":{"role":"manager","id":"morgan"},"event":{"type":"signed_off","referral":"new_venture","decision":"Approved","key":"22c4a4f9ca8540d093859f3c0e9d48bf361dec04f26f5349e6a6fc8a668c62a1","read":{"years_in_operation":2},"because":"Experienced owners."}}
```

| Field | Value |
| - | - |
| `referral` | The referral's name |
| `decision` | `Approved` or `Declined` |
| `key` | A hash of the referral and of `read` |
| `read` | The answers that the referral's condition read, in the shape of the answers |
| `because` | The reason, when `--because` gives one |
| `option` | The quote option, when `--in` names one; absent for a shared sign-off |

The sign-off stands while the referral's present key equals `key`. So it stands for the answers
in `read`, which are the answers that the status showed to the signer.

### `retracted` entry

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{"seq":6,"at":"2026-11-10T20:28:01+00:00","by":{"role":"assistant","id":"intake"},"event":{"type":"retracted","path":"years_in_operation"}}
```

| Field | Value |
| - | - |
| `path` | The input's path, or the referral's name |
| `option` | The quote option, when `--in` names one |

### `quoted` entry

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{"seq":8,"at":"script","by":{"role":"uw","id":"alice"},"event":{"type":"quoted","name":"q1","key":"6ea5d0af602638b382ea51e29a5cf94715f420325599af0819b26928736fd573","outcomes":{"premium":5000}}}
{"seq":10,"at":"2026-11-10T20:28:01+00:00","by":{"role":"uw","id":"alice"},"event":{"type":"quoted","name":"q2","key":"807a4f6a2ea485e4691f1f010279b72aa6cb6831bde17ae425e73addd2cc9e98","outcomes":{"premium":5500},"option":"high"}}
```

| Field | Value |
| - | - |
| `name` | The quote's name |
| `key` | A hash of the program, and of every answer and sign-off of the quote's view |
| `outcomes` | The outcomes that the quote gives |
| `option` | The quote option that the quote prices; absent for the shared answers |

The quote stands while the present key of its view equals `key`.

### `bound` entry

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{"seq":13,"at":"script","by":{"role":"uw","id":"alice"},"event":{"type":"bound","quote":"q2"}}
```

| Field | Value |
| - | - |
| `quote` | The bound quote's name |

### `closed` entry

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{"seq":7,"at":"2026-11-10T20:28:01+00:00","by":{"role":"assistant","id":"intake"},"event":{"type":"closed","reason":"withdrawn","because":"The applicant withdrew"}}
{"seq":5,"at":"2026-11-10T20:28:01+00:00","by":{"role":"uw","id":"alice"},"event":{"type":"closed","reason":"declined","on":["PAYROLL_BELOW_MINIMUM","PEO_NOT_ESTABLISHED"],"because":"A PEO in its second year"}}
```

| Field | Value |
| - | - |
| `reason` | `withdrawn`, `expired`, `lost` or `declined` |
| `on` | For `declined`: the code of each rule and the name of each referral that declines |
| `because` | The reason, when `--because` gives one |

## Refused commands

The log keeps each refused command beside its entries. A `.jsonl` log writes one JSON line for
each to a second file. The file's name is the log's name with `.refused.jsonl` in place of
`.jsonl`. A `.sqlite` log writes the same JSON to the `record` column of its table `refusals`.

These are two lines of `second.refused.jsonl`:

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{"arguments":["policy.experience=no_claims"],"at":"2026-11-10T20:28:01+00:00","by":{"id":"intake","role":"assistant"},"command":"answer","refusal":{"candidates":["uw","manager"],"code":"NOT_AUTHORIZED","message":"answering `policy.experience` needs role `uw` or a role above; `intake` is `assistant`"}}
{"arguments":["years_in_operation=1"],"at":"2026-11-10T20:28:01+00:00","based_on":4,"by":{"id":"intake","role":"assistant"},"command":"answer","refusal":{"code":"STALE","message":"answering `years_in_operation` is based on entry 4, and years_in_operation changed after it; read the submission again and decide on what it holds now"}}
```

| Field | Value |
| - | - |
| `arguments` | The words after the log's name, with the options left out |
| `at` | The time of the command in UTC, to the second |
| `based_on` | The entry number of `--based-on`, when the command names one |
| `by` | The writer: `id` and `role` |
| `command` | `open`, `answer`, `retract`, `quote`, `bind` or `close` |
| `refusal` | `code`, `message` and, when the log has some, `candidates` |

`rslc script` keeps its log in memory and leaves this record unwritten.

## Script files

A script file plays one submission and checks what it expects. It is a plain text file with one
line for each step. Save this script beside the `staffing/` folder as `lines.script`. It uses
every form of line:

```text lines.script theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
# Every line form of a script. This line is a comment.
program "staffing/guideline.rsl"
as assistant:intake
refused NOT_OPEN: answer operation = staffing
open
answer file "staffing/application.json"
expect stage Open
expect open policy.experience, new_venture
expect referral new_venture Open
answer operation = peo because "Stated on the application"
expect decline PEO_NOT_ESTABLISHED
expect stage Ineligible
retract operation
answer operation = "staffing"
as uw:alice
answer policy = {"experience": "no_claims"}
expect outcome premium = 5000.00
refused NOT_QUOTABLE: quote q1
answer years_in_operation = 1
as manager:morgan
refused STALE: based on 9: answer new_venture = Approved
based on 10: answer new_venture = Approved because "Experienced owners."
as uw:alice
quote q1
expect quote q1 stands
answer policy.limit = 2000000 because "A second option for the broker" in high
expect outcome premium = 5500 in high
quote q2 in high
refused NOT_A_TERM: answer policy.payroll = 3000000 in high
retract policy.limit in high
expect quote q2 lapsed
answer years_in_operation = 2
expect quote q1 lapsed
expect referral new_venture Lapsed
answer years_in_operation = 1
bind q1
expect quote q1 bound
expect stage Bound
refused CLOSED: close withdrawn because "The applicant withdrew"
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
cd ..
rslc script lines.script
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
PASS lines.script: 33 steps, 0 failed, 18 log entries, stage Bound
```

| Line | Meaning |
| - | - |
| `# text` | A comment, on a line of its own; a blank line is skipped too |
| `program "FILE"` | The program, in quotes, by its path from the script's folder; it comes before each step |
| `as ROLE:NAME` | The writer of the steps that follow |
| `open` | Opens the log |
| `answer QUESTION = VALUE` | An answer or a sign-off; add `because "TEXT"` for a reason |
| `answer file "FILE"` | One answer for each top-level key of a JSON file, by its path from the script's folder |
| `retract QUESTION` | Takes back an answer or a sign-off |
| `quote NAME` | Gives a quote |
| `bind NAME` | Binds a quote |
| `close REASON` | Closes as `withdrawn`, `expired`, `lost` or `declined`; add `because "TEXT"` for a reason |
| `STEP in OPTION` | Runs an `answer`, `retract` or `quote` step in a quote option; it ends the step, after `because "TEXT"` |
| `expect stage STAGE` | Passes when the stage is `STAGE` |
| `expect open Q1, Q2` | Passes when exactly these questions are open, in each order; alone, it expects an empty list |
| `expect outcome NAME = VALUE` | Passes when the outcome is known and equal; `5000` equals `5000.00` |
| `expect decline CODE` | Passes when `CODE` is among the declines |
| `expect referral NAME STATE` | Passes on the referral's state; `Lapsed` passes on a lapsed sign-off |
| `expect quote NAME STATE` | Passes when the quote `stands`, is `lapsed` or is `bound` |
| `expect CHECK in OPTION` | Runs the check on the status of a quote option |
| `refused CODE: STEP` | Passes when the log refuses the step with that code |
| `based on N: STEP` | Runs the step with entry `N` as the last entry that its writer read |

* A step that the log refuses fails the script, unless its line starts with `refused` and the code.
* A line with both prefixes puts `refused CODE:` first.
* A step is written as the command is. Spaces around `=` are optional.
* A script's log lives in memory, and each of its entries has `at` set to `script`.
* `--keep LOG` writes that log to a new log. A log that holds entries is an error.
* A line outside these forms stops the script with `error:` and exit code 1.

`rslc script` prints one `FAIL` line and a note for each step that fails, then a summary line.
The summary counts the steps and `expect` lines, the failures and the log entries, and names the last stage.

With `--json`, the value has four fields:

| Field | Value |
| - | - |
| `ok` | `true` when every step passed |
| `steps` | Each step and `expect` line: its `line` number, its text as `step`, `ok` and a `note` |
| `entries` | The count of log entries |
| `status` | The last status of the shared answers, as in [JSON output](#json-output) |

## Related

* [Run a submission](https://docs.canary.effectiveai.app/rsl/how-to/run-a-submission.md) works through the commands on one submission, with two quote options.
* [Write your first guideline](https://docs.canary.effectiveai.app/rsl/tutorial/first-guideline.md) builds the staffing guideline that these examples read.
* [Raters and guidelines](https://docs.canary.effectiveai.app/rsl/explanation/raters-and-guidelines.md) explains why sign-offs and quotes lapse, and why a decline is a decision.
* [Language reference](https://docs.canary.effectiveai.app/rsl/reference/language.md#terms-of-a-quote) states the `per quote` mark.
* [Refusals](https://docs.canary.effectiveai.app/rsl/reference/refusals.md) lists the `INPUT` codes that an `INVALID_VALUE` message quotes.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.