Login
Protocol version 2, draft: the process and a run’s life
Login

Written against the hook and poll design, which the daemon replaced (How a post reaches an agent); what it says of today's code is not current.

The second part of the draft of the proposal, awaiting the maintainer; nothing of it is built.

2.8 sandbox

Every key is required, so the run says all of it. On Linux, OCX passes the keys to bubblewrap (bwrap), applied in this table's order, so a later one is seen over an earlier one at the same place.

Key Type Meaning
uid, gid whole numbers the Linux account and group IDs the process runs as inside
linux_capabilities NEW TERM list of names the Linux capabilities it keeps; empty keeps none
network true or false whether it shares the host's network; it never sees the host's processes, accounts, host name or message queues
isolate_terminal NEW TERM true or false whether it is kept from every terminal outside the sandbox, in a Linux session of its own (bwrap --new-session)
ends_with_parent NEW TERM true or false whether it ends when its watcher ends (bwrap --die-with-parent)
proc, dev, tmp paths where it sees its own /proc, its own /dev, and an empty directory for temporary files
read_only list of paths host paths it sees read-only, each where it is on the host
read_write list of paths host paths it sees read-write; a path in both lists is read-write
homes list of paths host paths it sees read-write inside the workspace, by base name: /root/.claude as $$workspace$$/.claude
created_files NEW TERM list of {"at", "text"} files made for it alone: at a path, text what the file holds

Nothing else of the host is visible to it. A path in read_only, read_write or homes is refused (refusal 7) when, once links are followed, it is, holds or lies within one of OCX's own files: the run's clone, ROOT/care/, ROOT/settings/, the release root (which holds OCX's programs and every workspace), or the passwords file. But $$workspace$$ may be named in any of the three, and $$release$$ in read_only alone.

2.9 limits

Key Type Counted from Enforced on Linux by Past it
wall a whole number and s, m, h or d the launch; with deliveries start, the first OCX ending the process ended, budget
cpu whole seconds the launch, for each process the kernel, SIGXCPU ended, budget
processes a whole number any moment the kernel, for the sandbox's own account no new process starts
file_size NEW TERM a size each file a process writes the kernel, SIGXFSZ ended, budget
memory a size any moment not yet provided, so refused (refusal 6) ended, budget

A size is a whole number of bytes, or a whole number and K, M or G, for 1024, 1024² or 1024³ bytes.

2.10 process

Key Type Default Meaning
user a login required the process's user (2.5)
command list of text required the program, then its arguments; a program without / is found on env's PATH
env object of text empty the process's whole environment; OCX adds nothing
cwd text $$workspace$$, or / with no workspace where it starts
manual true or false false true, a manual run: it waits until a user holding admin or setup starts it with OCX's start
session true or false false false: it runs to completion; true: it is given what it subscribes to (2.11)
hears list of logins, or ["*"] for every user empty whose posts and ticket changes a session is given
checkins list of branch names empty the branches whose check-ins a session is given
deliveries feed or start feed feed: each delivery goes to the running process; start: each starts a process of its own, the same command, one at a time
terminal true or false false it runs on a terminal OCX holds, which a person may join with OCX's attach
keep {"stderr", "stdout", "stdout_when"} 64K, 64K, failure, each on its own how much of the end of STDERR and STDOUT OCX keeps; STDERR always, STDOUT on failure (an exit but 0, or a signal) or always
verdict true or false false when its process has run and was not stopped, OCX writes a ci verdict (2.16) on $$checkin$$, as the clone's user, against the run: pass for exit 0, else fail and how it ended. Refused with no step setting checkin (refusal 3), and in a session (refusal 2)
unversioned_files NEW TERM list of {"at", "as"} none when its process has run and was not stopped, OCX sends each file at, within $$workspace$$, as the unversioned file as, as the clone's user: only a regular file, never a link nor one leading out of the workspace; on a clone's host, only if the clone's user holds y on the server. Then a reply where the run is names each, with its hash, or why not. Never for a session whose deliveries is start

Streams:

Stream Without a terminal With a terminal
STDIN the line {"run", "objective"}; then, for a session that feeds, each delivery, a line each; otherwise closed the terminal: the first line and each delivery typed in, and what a person joined types
STDOUT a pipe OCX reads the terminal, which a person joined sees
STDERR a pipe OCX reads a pipe of its own, which OCX copies to the terminal

With deliveries start, each process reads the first line, then its delivery's line, then its input closes; with a terminal, each has a terminal of its own.

Kept logs and where things go:

When OCX writes As
a process exits, and the run is over ended, first (2.3) a reply in the run's thread, or a change to its ticket
a process exits, and a stream it keeps printed a byte $$workspace$$/stderr.log, $$workspace$$/stdout.log, when the run has a workspace files, rewritten for each process
the same a reply where the run is: each stream's end in a fenced block headed stderr or stdout, and the exit a plain post or ticket change, holding no OCX message, as the clone's user
its process has run, was not stopped, and the run names unversioned_files the unversioned files, then a reply naming each as the clone's user
its process has run, was not stopped, and the run asks a verdict a ci tag on $$checkin$$ a raw tag, as the clone's user

Where a run is, a thread or a ticket, decides only where its reports and kept logs go.

2.11 Subscriptions

What a session is given. An artifact that arrives is given only if it passes every filter, at most once, in the server's order.

# Filter Passes when
1 session the run's process.session is true, and its started counts (2.4)
2 after the server received it after the run's started
3 known the server has said who sent it
4 no message it holds no OCX message
5 not its own its sender is neither the process's user nor any clone's user
6 where a post in the run's thread, a change to its ticket, or a check-in on a branch in checkins
7 heard a check-in; or a post or change whose sender is in hears, or hears is ["*"]
Arrives Line
a post {"post": HASH, "from": LOGIN, "text": TEXT}
a ticket change {"change": HASH, "ticket": TICKET-ID, "from": LOGIN, "text": TEXT}
a check-in {"checkin": HASH, "branch": BRANCH, "from": LOGIN, "comment": TEXT}, the branch as Fossil's timeline shows it

OCX gives an artifact only as it acts on its arrival: on the server's repository when the hook is called, on a clone at the poll whose pull brought it. One missed, as when the hook failed, is never given later; the session may read its thread itself. On a clone's host, delivery stops at an artifact whose sender the server has not said, so none after it is given first. What waits for a process that does not read is held up to a bound; past it, nothing more is given until it reads, and OCX's log names what it could not give. An edit of a post is a post, and is given.

2.12 stop, and the outcomes of ended

A stop names no clone: OCX on the run's clone acts on it, from the senders of 2.3 alone; others are ignored.

outcome When exit note
refused a refusal of 2.14 holds absent required: which, in plain text
stopped a stop ended it, or came first absent absent
exited its process exited by itself its code; 128 plus N for signal N absent
budget past a limit absent required: which limit
lost OCX on the run's clone found its counted started but not the run in its care absent required: why

A reader that does not know an outcome takes the run as ended.

2.13 A run's life

A run's state is read from the repository (its started and ended that count) and from whether OCX on the run's clone has it in its care.

State What arrives Next OCX writes or does
none a run a refusal holds for ended ended, refused, and the note
none a run, manual false started takes it into its care, makes its workspace, writes started, launches the command
none a manual run waiting nothing
waiting a user holding admin or setup starts it with start started as for a run that is not manual; a refusal then ends it refused
none or waiting a stop, the run not refused by refusal 1 or 2 ended ended, stopped, even where refusals 3 to 8 would hold
started a delivery, deliveries feed started the delivery to its STDIN
started a delivery, deliveries start started a process for it, once the one before exited
started a stop started ends the process
started its process exits, deliveries not start ended ended: exited, budget or stopped; then kept logs and publications
started a process exits, deliveries start started kept logs
started a stop, or past wall, deliveries start ended ends its process, ended: stopped or budget; then kept logs
started OCX on the run's clone finds it not in its care ended ended, lost
ended a publish, from a sender 2.3 allows, the run's workspace kept ended publishes again from the workspace what the run names, without launching anything
ended anything else ended nothing

OCX writes started before it launches, so it launches at most once; on a clone's host, only once the server has received the started. Every run it acts on gets one ended. When OCX starts on a repository, and whenever a person with the host's command line asks (hook --login LOGIN on the server's host, poll --all on a clone's), it reads every open run naming its clone, with its stops and reports, and decides each by this table: one no longer in its care is answered lost, its process ended first if it still runs; one with no started is decided as if it had just arrived. Nothing reads them in the background.

2.14 Refusals

Checked in order when a run arrives, and again when a person starts a manual run; the first that holds ends the run refused, written as the clone's user, so the refusal counts.

# Holds when Note names
1 its sender holds none of admin, setup and R on the server; or the run's to sent it; or a user holding R sent it, and ROOT/settings/capability-R.json allows no such run, or the run asks more than it allows: no sandbox, the network or a Linux capability not allowed, a path to read outside its own $$workspace$$ and the paths listed, a path to write outside its own $$workspace$$, a home, a secret, a password step, a path with .., a directory whose path does not end in $$run$$, or a limit the file caps left out or set above the cap which
2 a required key is missing, a key has the wrong type or form, or a key version 2 does not define, such as node the key
3 a step kind 2.7 does not have, a step whose needs are not met before it, or a value not set by then which
4 process.user is the run's to, or any clone's user (one named as the to of a run that a user holding admin or setup sent), or holds admin or setup on the server which
5 no sandbox, and the host has no ROOT/settings/unconfined that
6 a sandbox key or limit OCX cannot provide which
7 a path outside where 2.7 and 2.8 allow the path
8 a workspace step fails, collect refusing included the step, and why

A run written straight into a clone reaches the server as received from that clone's user, so refusal 1 holds: send runs to the server.

2.15 What OCX keeps on a host

The repository holds every message. OCX keeps on the host what its runs need, and nothing the record depends on: a run's state while it is in OCX's care, and each run's workspace, kept after ended so what it made can be read and published again (2.3).

Kept Where For
the runs in its care ROOT/care/RUN.lock, held by the run's watcher so it acts on each run once, and finds what its going down left
what each session has been given, and the deliveries waiting for it ROOT/care/RUN.inbox each artifact at most once
the run's workspace path and watcher log ROOT/care/RUN.workspace, ROOT/care/RUN.log for the watcher and status
one call of the hook or poll deciding at a time ROOT/care/round.lock so two calls never decide one run
the passwords file, on a clone's host a path the clone's owner gives poll, readable by OCX's host account alone the clone's user's password and the code it registered with; never a process's user's
each run's workspace the path its directory step names kept after ended, so what the run made can be read and published again

2.16 Verdicts

A verdict is a raw tag on one check-in, never propagating: audited for a person's or an agent's judgement, ci for a program's. Its value is pass: or fail:, the reason on one line, then (against X), X a run's full hash or the path of a file stating the objective, read as it stands in the check-in judged. Anyone may write one, with OCX's audit or fossil tag add; it needs no run. Readers also read sym-audited and sym-ci.

A verdict counts only if its sender neither sent the check-in nor is the user named in it, and:

For each sender, a reader counts its latest verdict for each tag and each objective, by the server's order, taken from Fossil's event table (type g); a reader on a clone asks the server with receipt. How to combine them is the reader's choice. Cancelling a tag writes no verdict.

2.17 Stability

Change Within version 2
a new key, in any kind, whose absence means what version 2 says without it allowed
a new message kind, step kind, value, outcome or refusal row allowed
renaming a key, changing its meaning, type or default, or removing it never: that is version 3
OCX meeting a key it does not know in a run refuses the run, naming the key (refusal 2)
a reader meeting a key it does not know in a stop, started or ended ignores the key
a reader meeting a kind it does not read passes it by: it is for another reader (2.0)

A post may carry a block for each version; a reader takes the first block of the newest version it knows.

2.18 Version 1 parts dropped

2.19 Two uses, one envelope

Words in capitals stand for values: a login, a server-code (40 lower-case hex digits), a hash (40 or 64), a path or a program. Everything else is as it would be sent.

A build, from a pattern holding its content; create adds to and clone:

{
  "ocx": 2,
  "to": "LAB-USER",
  "run": {
    "clone": "SERVER-CODE-OF-THE-LAB-CLONE",
    "objective": "build trunk for the lab board and send the image",
    "workspace": [
      {"step": "directory", "at": "$$root$$/$$process_user$$/projects/$$repository_name$$/$$run$$"},
      {"step": "files", "of": "trunk", "into": "$$workspace$$/tree"}
    ],
    "sandbox": {
      "uid": 1000, "gid": 1000, "linux_capabilities": [], "network": false,
      "isolate_terminal": true, "ends_with_parent": true,
      "proc": "/proc", "dev": "/dev", "tmp": "/tmp",
      "read_only": ["/usr", "/etc/alternatives"], "read_write": ["$$workspace$$"],
      "homes": [], "created_files": []
    },
    "limits": {"wall": "1h", "cpu": 3000, "processes": 256, "file_size": "2G"},
    "process": {
      "user": "BUILD-USER",
      "command": ["make", "-C", "$$tree$$", "image"],
      "env": {"PATH": "/usr/bin:/bin", "HOME": "$$workspace$$"},
      "verdict": true,
      "unversioned_files": [{"at": "$$workspace$$/tree/out/board.img", "as": "builds/board.img"}]
    }
  }
}

A session of an agent that answers each post, from another pattern: the envelope is the same; only the content differs, and layer 2 reads none of it.

{
  "ocx": 2,
  "to": "SERVER-HOOK-USER",
  "run": {
    "clone": "SERVER-CODE-OF-THE-SERVER-REPOSITORY",
    "objective": "answer each post in this thread about the parser",
    "workspace": [
      {"step": "directory", "at": "$$root$$/$$process_user$$/projects/$$repository_name$$/$$run$$"},
      {"step": "password"},
      {"step": "clone", "into": "$$workspace$$/clone", "password": "$$password$$"},
      {"step": "checkout", "of": "trunk", "into": "$$workspace$$/tree"}
    ],
    "sandbox": {
      "uid": 1000, "gid": 1000, "linux_capabilities": [], "network": true,
      "isolate_terminal": true, "ends_with_parent": true,
      "proc": "/proc", "dev": "/dev", "tmp": "/tmp",
      "read_only": ["/usr", "/bin", "/lib", "/etc/resolv.conf", "/etc/ssl/certs", "$$release$$"],
      "read_write": ["$$workspace$$"],
      "homes": ["AGENT-SIGN-IN-DIRECTORY"], "created_files": []
    },
    "limits": {"wall": "8h"},
    "process": {
      "user": "AGENT-USER",
      "command": ["AGENT-PROGRAM", "AGENT-ARGUMENTS"],
      "cwd": "$$tree$$",
      "session": true, "hears": ["PERSON-USER"], "deliveries": "start"
    }
  }
}

Their answers, each a reply where the run is:

{"ocx": 2, "re": "RUN-HASH", "started": {"values": {"workspace": "...", "password": "$$password$$"}, "limits": {"wall": "8h"}}}
{"ocx": 2, "re": "RUN-HASH", "stop": {}}
{"ocx": 2, "re": "RUN-HASH", "ended": {"outcome": "stopped"}}

OCX on the repository whose server-code the run's clone gives, acting as the run's to, takes each; every other repository does nothing with it. Layer 2 carries, addresses and answers both without reading run; layer 3 enforces each one's sandbox, limits and steps.

A message of a kind OCX does not read rides the same envelope. Here, for illustration only, a person asks a reviewing agent for a verdict, and it answers; no command, workspace or sandbox is involved, and OCX's readers pass both by:

{"ocx": 2, "to": "REVIEWER-USER", "review": {"checkin": "CHECK-IN-HASH", "objective": "PATH-OF-THE-OBJECTIVE"}}
{"ocx": 2, "re": "REVIEW-REQUEST-HASH", "reviewed": {"verdict": "pass", "note": "ONE LINE"}}

Layer 2 finds each, says who sent it, whom the request asks and what the answer answers, and hands each content to a reader of its kind, here the reviewer's own session; it reads neither content.