Read GLOSSARY.md first: it defines the words used here.
Protocol v2 is the standard these tools write to.
OCX has three entry points to one set of tools:
- CGI: the OCX page,
/ext/ocx2-cgiunder the repository's address, served beside the Fossil repository by theocx2-cgiprogram, for people in a browser, the list of its tools, its header linkingconsole, a page of its own of what waits on you and what runs, or JSON for a client such as Stone, the iOS app of the stone repository. Fossil authenticates the viewer. - CLI: one
ocx2command, for people at a terminal, scripts and the node's own services. It acts as the user its clone remembers, or on the served host as the user it names, and prints text, or with--jsonthe same JSON the CGI and MCP return. Its own options,--json,--repositoryand--user, may go anywhere on the line; where a tool has an argument of the same name, its own wins after the tool's name;--ends the options. - MCP: the tools offered to a program, such as a run's in its sandbox,
acting as its user, within its Fossil capabilities. The program serves MCP
as
ocx2-mcp, or asocx2 --mcp, so a program's own configuration can start it, on standard input and output; over HTTP and through a provider's tunnel, for a remote agent, as docs/remote-agents.md says.
Each tool is defined once; entry points adapt its inputs and answer.
They authenticate the caller and encode the request and result, but they
never decide what the tool does. A tool's input, output and context are
types whose schemas are derived, and every answer has one shape: the log of
operations the tool performed, or in a dry run would perform, its output
when it ran to the end, and why it stopped. The CGI, the CLI and MCP reach each tool's one definition (the maintainer,
a001dc306e: "Don't try to tell me that functionality should only be made available to 1 of them--we should resolve that with login/capabilities restrictions and sandboxing"); an entry point withholds a tool only where the tool says why, such as secret and approve from MCP, since a person sets a secret and approves a login, and stage from the page, which would run a checkout's own code as the web server's account; the table below shows each. Who may use a tool is decided once,
by the caller's user, its Fossil capabilities, and by the sandbox it runs
in; a call that is not allowed is refused the same way from every entry
point.
ARCHITECTURE.md explains the parts these tools act on.
Tools
Checked means built; unchecked names are proposals. PLAN.md tracks gaps.
| Tool | Built | CGI | CLI | MCP |
|---|---|---|---|---|
| List the tools, give help, or show who is calling | ☑ | OCX page (the list), each tool with its form | ocx2, ocx2 list, ocx2 help, ocx2 status |
tools/list, ocx_list, ocx_help, ocx_status |
| See what waits on you and what runs, or the questions as data | ☑ | OCX page: console, /ext/ocx2-cgi/console, a page of its own its header links, each option a button writing your decision with post; questions |
ocx2 console [--for USER], ocx2 questions [--for USER], on the served host or in a clone's checkout |
ocx_console, ocx_questions |
| Answer wiki cards across repositories | ☑ | OCX page: cards, card | ocx2 cards REPOSITORY... [--for LOGIN] [--now TIME]; ocx2 card PROJECT NAME REPOSITORY... [--for LOGIN] [--now TIME] |
ocx_cards, ocx_card |
| Echo the input | ☑ | OCX page: echo | ocx2 echo WORD [--option OPTION] [--flag] |
ocx_echo |
| Say what a tool would do | ☑ | the dry_run checkbox on every form |
--dry-run, on every tool |
dry_run, an argument of every tool |
| Write a run | ☑ | OCX page: create | ocx2 create OBJECTIVE [--pattern URL] [--title TITLE \| --to POST \| --ticket TICKET] [--login LOGIN] [--process-user LOGIN] [--fields JSON] [--of CHECK-IN] [...] |
ocx_create, on the served repository or in a clone's checkout |
| Start a manual run by hand | ☑ | not offered: it starts on the agent machine | ocx2 run RUN --repository CLONE |
not offered: it starts on the agent machine |
| Stop a run | ☑ | OCX page: stop | ocx2 stop RUN |
ocx_stop, on the served repository |
| Join a run's terminal | ☑ | not offered: a page holds no terminal | ocx2 attach RUN, at the node's host, as the account the node runs as |
not offered: a run's sandbox holds no node's directory |
| Write a plain post, a question or a decision, or read a thread or ticket | ☑ | thread not offered: read in Fossil's own forum and ticket pages; OCX page: post | ocx2 post TO TEXT [--for USER [--references 'HASH-OR-LINK ...'] [--options CHOICES] [--stakes TEXT] [--run RUN]] [--decision], ocx2 thread OF [--after POST] (tools) |
ocx_post, ocx_thread |
| Record a verdict, or list the check-ins none has judged | ☑ | OCX page: audit, unaudited | ocx2 audit CHECK-IN pass\|fail NOTE AGAINST, ocx2 unaudited [--branch BRANCH] |
ocx_audit, ocx_unaudited |
| Say who sent an artifact | ☑ | OCX page: receipt | ocx2 receipt [--artifacts 'HASH ...'] [--capabilities-of USER] |
ocx_receipt |
| Stage, accept or roll back a release | ☑ | OCX page: import, accept, rollback, release (stage and follow are not offered) | ocx2 stage CHECKOUT, ocx2 import RUN, ocx2 accept, ocx2 rollback, ocx2 release, ocx2 follow SERVER --root ROOT |
ocx_stage, ocx_import, ocx_accept, ocx_rollback, ocx_release (follow is not offered) |
| Run a node | ☑ | not offered: it runs on its own machine | ocx2 daemon |
not offered: it runs on its own machine |
| Watch one run on a node | ☑ | not offered: it runs on the agent machine | ocx2 watch RUN SERVER CREDENTIALS --repository CLONE [--by-hand] |
not offered: it runs on the agent machine |
| Ask for a login, approve it, collect its password | ☑ | OCX page: approve, from the link the asker prints; collect (register is not offered) | ocx2 register SERVER-URL PASSWORDS-FILE [--login LOGIN] [--capabilities LETTERS], on the asker's machine; ocx2 approve LOGIN KEY CAPABILITIES, on the server's host |
not offered: approving is a person's decision, and only the asker registers and collects |
| Let a user send runs, or no longer | ☑ | OCX page: send_runs, from a link naming the user (?user=USER), one press |
ocx2 send_runs USER [--off], on the server's host |
ocx_send_runs, on the served repository |
| Install or remove the hook | ☑ | OCX page: hook, unhook; status shows where it stands | ocx2 hook, ocx2 unhook |
not offered: a sandbox has no served repository |
| Tag the messages left untagged | ☑ | OCX page: tag_messages | ocx2 tag_messages, on the served repository or in a clone's checkout |
ocx_tag_messages |
| Keep the git export current | ☑ | OCX page: export; the hook, when a check-in arrives | ocx2 export |
not offered: a sandbox has no served repository |
| Onboard a project | ☑ | OCX page: project, detached; status shows it running, then its outcome | ocx2 project NAME [--daemon-root DAEMON_ROOT] [--from UPSTREAM] [--mirrors DIR] [--kinds "session"] |
not offered: a sandbox has no served repository |
| See a run live | ☐ | Run page: live stream | name open | name open |
| Read, find and change a run's checkout | ☑ | not offered: the web page is not a checkout | ocx2 context, ocx2 find TEXT [--path PATH], ocx2 read PATH [--from LINE], ocx2 edit PATH OLD NEW, in a clone's checkout |
ocx_context, ocx_find, ocx_read, ocx_edit |
| Test and save a run's checkout | ☑ | not offered: the web page is not a checkout | ocx2 test [--name NAME], ocx2 save TITLE INTENT EXPRESSION DEMONSTRATES |
ocx_test, ocx_save |
| Share wiki pages and unversioned files | ☑ | not offered: the web page is not a checkout | ocx2 wiki_read NAME, ocx2 wiki_edit NAME OLD NEW, ocx2 unversioned_list, ocx2 unversioned_get NAME, ocx2 unversioned_put NAME CONTENT [--replaces HASH] |
ocx_wiki_read, ocx_wiki_edit, ocx_unversioned_list, ocx_unversioned_get, ocx_unversioned_put |
| Work a run's objective with a local model | ☑ | not offered: it is a run's command | ocx2 --mcp-ollama URL --model NAME --steps N --system TEXT, as a run's command in its checkout, the run keeping its standard output always and, a session, taking deliveries: start (interfaces, "Serving") |
not offered: it is MCP's caller, offered the checkout's group alone |
| Propose a change | ☐ | Branch page | ocx2 propose BRANCH |
ocx_propose |
Cards
Put a JSON definition in a card/NAME wiki page of any repository. A card is named by that repository's project name and NAME. cards answers every such page in the named repositories, ordered by project then name, with project, name, title, count and say; card PROJECT NAME also answers as_of and items. Give one or more repository files to read together. On the web page, only the repository that page serves can be read: leave repositories blank, and ask each other project at its own page, so Fossil checks the viewer on each. Every item carries its repository's project name. --for supplies the login used for $viewer; without it the caller's login is used. --now is an RFC 3339 UTC time for time spans; without it the clock is used. as_of is the newest event time any named repository holds.
A definition names title, find, fields, order (newest or oldest), limit, say and say_none. find is one of posts_tagged with value and unanswered_by, checkins_tagged, tickets with type and open: true, or threads_active_within in seconds. Tags are named exactly as Fossil stores them. Fields come from indexed columns, including ticket fields; a missing table or requested ticket column gives no items. say fills {count}, {titles} and a field of the first item; say_none is used for no items. The shared examples give complete definitions and answers.
Write a run
create OBJECTIVE writes a run as your user on
the served repository, from the web page or --repository on its host, for a
user with admin or setup, or R (below). Its objective says what the run is for,
and what counts as passing. It is written as the first post of a new thread,
titled --title or, without one, by the objective; or as a reply in the
thread of a post (--to), or a change to a ticket (--ticket), a post or
ticket named by a hash or a prefix of one. Its fields come in three layers,
each over the one before: an object given over an object merges into it,
member by member, and any other value replaces what was there, whether OCX
knows the field or not:
- a pattern:
--pattern, a wiki page of the served repository by its name, such asrun/claude, or an http or https URL whose text's first fenced JSON block holds a run's fields, fetched without signing in; - the fields named: the objective,
--login,--cloneand--process-user, the run'sprocess.user; - the rest:
--fields, a JSON object, such as'{"process": {"session": true, "hears": ["*"]}}', a session hearing every user where it is; astopends only the run it names.
The run must name its node, in the pattern, --login LOGIN or --fields.
On the served repository, when no clone is given and no matching local daemon
address supplies it, create reads the one unversioned file under
ocx-clones/LOGIN/SERVER-CODE.json. No file or several files is refused,
naming the directory or files; give --clone SERVER-CODE then. The run
written still requires clone. Each daemon refreshes only its clone's file at
start and when a clone joins, as the clone's user, only if the server grants
that user y, as for shares. It holds only {"login": LOGIN, "clones": [SERVER-CODE]};
unversioned files sync to every clone, so it must hold no secret.
Without a process user, create makes one for admin or setup when its workspace
signs in: run- and eight random hex digits, holding gio24R. Its random
password is kept on the served repository, approved for the run's node login
to collect at its password step; the answer names the run, where it was
written and any user made (made). For a named process.user, admin or setup's
create makes it if absent, with the same capabilities and approval, or
approves its kept password for that node too, when one is kept. Approval and
reset refuse Fossil's reserved users, admin or setup, and any clone user
known from local daemon entries, published clones or an administrator's run's to.
Only create --reset-password, by admin or setup on the served repository,
sets a new random password on an existing process user and keeps it approved,
preserving its earlier approvals; clones using its old password stop syncing.
For a node on another host, create refuses an existing user it keeps no
password for unless asked to reset. A local node may still use its own file.
From a clone, create makes no user and approves no password.
A pattern holds a way of working once; give only what differs. By convention
it is run/NAME, in Markdown (Fossil wiki markup turns brackets into links):
what it is for, then a fenced JSON block of fields, such as
protocol v2's examples' session that answers each post, without
its ocx, kind and objective; its sandbox's workspace_at shows every run its workspace at /workspace, so Claude, which keeps its trust per folder, asks once. An agent's pattern also holds, in its prompt, how its agent writes on the record, its links built from $$server_url$$.
ocx2 create "Answer what is said here." --pattern run/claude --login lab-1 then writes
that run, every field of the pattern in it, in a new thread, for lab-1, as a user made for it. Of a wiki page of the served repository
itself, create reads only the newest version that a user holding admin or
setup wrote, in the repository: a pattern names the program a node runs and
the host files it binds, and Fossil lets a user with check-in alone push any
wiki page (the battery, round 6). Any other URL is fetched as it
is, and the caller vouches for it. The run is checked as a node checks it, and
refused, writing nothing, if a node would refuse it as written. The daemon and watcher answer the request later; create reports no
immediate action. Programs read the run and session deliveries as JSON lines
on standard input (protocol v2);
with process.terminal, lines are typed with Enter and kept STDOUT is what
the terminal showed. Arguments and environment, including TERM, are run data.
Bring in a remote agent
Setting a run's secret, secret, and syncing the work of an agent that keeps no login key, sync,
commit_and_sync_guide and commit_and_sync, are in docs/remote-agents.md.
Start a manual run by hand
run RUN --repository CLONE starts a manual run on the agent machine.
CLONE needs its matching project password file and a completed pull. The tool
signs in with that entry, checks the node user, clone and manual flag, decides
by hand and hands watch --by-hand, answering the full run hash and runs
handed. The watcher locks and decides again, posting as the node user. The
tool refuses nonmanual runs, another node or clone, and runs already started, ended or in care.
A manual run waiting only for a person can start; the tool refuses one its node leaves waiting because its user has another run in care, or it is from a user holding R alone while another such run is in care. Refusal decisions go to the watcher to post.
It needs setup on the clone: the node user is its default user and holds setup there as the user who made the clone. CGI and MCP withhold it; a dry run starts no watcher.
Stop a run
stop RUN writes a stop for a run as your
user, where the run is: a reply to its post in its thread, or a change to its
ticket. The command needs admin or setup on the served repository and
answers the stop and run hashes. It starts nothing: after a complete pull,
the watcher acts on a stop from admin or setup, or from a user holding R
who sent the run. A running program ends; a run not yet started ends stopped.
Join a run's terminal
attach RUN, at the node's host, as the account the node runs as, whose
alone the socket in the node's directory is, joins your terminal to that of a
run whose process.terminal is true while it runs: its program is told your
window's size at once, as by any terminal, so a full-screen one redraws to
fit; you see what it shows and type into it, one at a time, until Ctrl-]
leaves it running, at 50 by 200 again, or it ends. OCX's own code joins,
reading none of what the run shows, so the run cannot make it run anything (HISTORY.md decisions 83 and 85).
Write a plain post
post TO TEXT writes TEXT as your user, from your clone's checkout or on the served repository: a reply in
the thread of the post TO names, or a change to the ticket it names, by a
hash or a prefix of one, a ticket's or any change's. The reply is composed as Fossil's forum artifact, put
into the clone and pushed, and the served repository takes it as a post by the
user that pushed it. Pushing needs Fossil's Check-In (i); a failed push is
reported, naming the post, for the clone's next push to send; a user without
Check-In posts in Fossil's own forum page, as everyone does from the web. On the served repository, as from the OCX page (/ext/ocx2-cgi), the post is made there as the caller, who needs Check-In too. post refuses text that holds an OCX
message, so a program's posts never carry one: create writes a run, and stop a stop. A run's program answers in its run's thread or on its ticket
this way, and may read what was said there first with thread RUN. With --for USER, TEXT is a question for that user (questions), an OCX message, with --options, none or two to four, one per line, the one you recommend first, and --stakes; TEXT, each option and --stakes name things in words, never by a hash or a link, which post refuses, nor by a number or a shorthand, which it cannot detect, and what they refer to goes in --references: hashes, prefixes, ids or links of the server, each written as a link from its root to the one thing it names, the server must have it, and linked by its title; it waits on that user until they decide, whoever else has, and stays open until you confirm it answered, whatever is posted after it, and a run's program asks where its run is, so that its sessions hearing the user are given each decision, naming its run with --run RUN, its hash as the first line of its standard input gives it, so that whoever sent the run may confirm it too; post refuses a run whose user you are not, or that takes no password step. With --decision, post QUESTION TEXT --decision writes your decision where the question is, whoever you are: TEXT, or an option's number, which the decision holds as the option's words, never the number; from its sender, TEXT says in words why it is answered, never a number, which confirms it and closes it, as when you are satisfied or it was answered some other way; and so from whoever sent the run it names as asking it, when that run counts, so a question a run asked is closed after the run has ended, but for that sender's first decision to a question for them, which is their decision. A question may reference a wiki page, /wiki?name=PAGE, for a user who may read and edit it with no moderator to wait for, to answer many questions on it: once they decide, read the page itself with wiki_read PAGE, or Fossil's history, then confirm it or ask again.
questions [--for USER] writes nothing: on the served repository, from the OCX page, /ext/ocx2-cgi/questions, at its host or as ocx_questions, or in a clone's checkout, it answers what waits on USER, you unless named, as data, the same at every entry point, laid out as any tool's answer is (HISTORY.md decision 100). Of every question and decision, oldest first: waiting, each question for USER that USER has not decided, whoever else has; to_confirm, each USER sent, or a run USER sent asked, that has a decision; open, every other open question, USER's own that no one has decided among them. Each question gives its fields once: asked, the post or ticket change asking it, by its hash; where, its thread or ticket; title, link, from the repository's root by a hash's first ten digits, and url, in full where a public URL is set; from and at; for, question, options, stakes and run, as the question holds them; replies_to and references, each described by its title, kind, sender, time, excerpt (an OCX message's block left out) and link, as far as you may read it; decisions, each by from, at and its note, each hash in it shown by its artifact's title; and confirmers, who may confirm it answered. It holds no post's text. From a clone's checkout it asks the server's receipt tool who sent each question and decision, as post --decision does. A client such as Stone reads it as JSON, /ext/ocx2-cgi/questions?json, signed in to Fossil.
console [--for USER] [--login] writes nothing: it shows a person what waits on USER, you unless named, and what runs, reading questions' answer and the record's runs for every node user an administrator's run names, as status reads them, laid out for each entry point (HISTORY.md decisions 100 and 101). On the OCX page, /ext/ocx2-cgi/console, which the list's header links, it is a page of its own, with no list of tools and no answer around it: what waits on you, each question a card with who asked it and how long ago, its time in UTC on hover, where, what it changes, what to read first, its references, each a link with its excerpt folded (where it is says what it replies to), a button per option, the recommended marked, and a field for your own words, where a number alone chooses that option; what you may confirm answered, with its decisions and a field saying why; every other open question on a line; and a table of the runs that have not ended, and of those that ended the five the server received last: where, what for, who sent it, its user, where it stands (waiting, starting, running, stopping, lost or ended, and why) and since when. A section holding nothing is left out, and one line names each left out, such as "Nothing for you to confirm; no other question is open."; where no node user is named by an administrator's run in the record, the runs say "Runs: none known here". A button writes your decision with post, and the page comes back to console saying it is written, by a redirect, so a reload presses nothing again. At its host's command line, in a clone's checkout or as ocx_console, it prints the same sections as text with no answer around it: each question a short card, the call that answers it said once, answer with: the post command after the program and --repository and --user as given, or over MCP ocx_post and its input as JSON, the field to fill in as TEXT, then its options numbered beneath, and or your own words as TEXT; links as their title and full URL, each excerpt's lines quoted, the runs in aligned columns. A refusal, such as of input it does not take on the page or over MCP, or of a viewer lacking 2r, is one line on its page and in its text, with no answer around it; in its text, then next: where the tool says what to do instead. A dry run, which shows nothing as console changes nothing, is likewise one line, then next: in its text, its line saying it was a dry run and, where console would stop without it, why, so it never reads as a refusal. With --json, it gives the answer, its result the view's data, each press as its tool, its input and whether it is recommended, which a client such as Stone posts to /ext/ocx2-cgi/post, with the console page as its Referer, or MCP's ocx_post takes, as it is. A press or a place names an artifact by its hash's first ten digits; no other hash is shown, and each record's words are on one line. A press is offered only in your own view, holding what post needs, Check-In (i). From a clone's checkout it shows no runs unless --login names its entry; then it reads local care through the last completed pull.
See a run live
Planned: an agent's output turn by turn, status as JSON and server-sent events from a separate port, since Fossil buffers a page until complete. The tool name is open (backlog).
Tools planned elsewhere
☐ rows without sections are in slices-parity.md, "Tools planned past parity"; PLAN.md holds the bound surface.
Record a verdict
Tags a check-in audited with pass or fail, a note on one line, and the
objective it was judged against, as the reviewer's own user: a raw tag whose
value is pass: NOTE (against AGAINST)
(protocol v2's verdicts). AGAINST is a run's
full hash, or the path of a file that states the objective, such as
PLAN.md, read as it stands in the check-in judged. Nobody audits their own check-in. A local model's
review is evidence a reviewer may cite, not a verdict.
audit CHECK-IN pass|fail NOTE AGAINST needs Fossil's Check-In (i). The check-in is a hash, a prefix of it or a
name such as a branch. From a clone's checkout the tag is made there and pushed, as a post is; on the served
repository, from the web page or --repository on its host, it is made as the viewer. Either way the verdict is read
back as the served repository holds it: there, from its tags; from a clone, by a second push that must send nothing. A
push the server refuses, as it refuses a tag from a user without Check-In, is reported as a verdict not given.
unaudited [--branch BRANCH] lists the check-ins changing files that no verdict that counts has judged.
Say who sent an artifact
receipt --artifacts 'HASH ...' says of each artifact, by its hash in full,
who the served repository received it from, what they may do there, its
receipt's number and its row number, which order what the server received, or
that it has not received it; --capabilities-of USER,
what USER may do there. It reads the served repository, from the web page or
--repository on its host; in a clone's checkout, whose receipts name only its pulls
(Fossil, senders and nodes), it asks the server's, refusing an answer not of what it asked. It never says
where an artifact came from, and tells a caller without Read (o), or x
for a private artifact, that the server has not received it.
Stage, accept or roll back a release
crystal run ci.cr -- programs prints each program's name, source, kinds (tool, also cgi for ocx2), and architectures (x86_64) as JSON, with no checks or writes. The list comes from shard.yml's targets under tools/; scripts/ is never released.
crystal run ci.cr -- build OUT ARCH runs plain CI, builds with shards, and smokes each program with no arguments. Before CI, it checks that ARCH is supported and matches uname -m, and OUT is absent or empty.
On success OUT holds the programs, release.txt with each file's SHA-256, and gate.txt with the gate's note, in stage's formats. A fresh directory beside OUT is renamed into place only after success; failed checks leave OUT as it was.
Neither command publishes. Plain CI and its host, merge-gate and live modes keep their existing checks.
stage CHECKOUT runs the checkout's checks, logged to ROOT/staged/gate.log as they run, and builds with shards. It copies targets under tools/ and their inventory into ROOT/staged/NAME (the check-in's first twelve hex digits),
smokes the copy and offers it as next, the pre-release candidate, run by Fossil's ocx2-cgi-next page.
Accepting serves it as the active release candidate (ROOT/current, the page ocx2-cgi), keeping the old one as previous; rollback serves previous again. Each needs Fossil's setup capability (the maintainer, dd06d0a5e5).
Each takes --root ROOT, defaulting to the program's release root; --dry-run says what it would do (HISTORY.md decision 31).
Every entry point checks the capability. The web page withholds stage, which runs the checkout's own code, and shows the server's release root, taking none from the request. A person with setup accepts and rolls
back there; the CGI takes a change only from a POST whose Referer is OCX's own page, /ext/ocx2-cgi or
/ext/ocx2-cgi-next under the repository, never from another page of the site, such as a file a user committed, which Fossil serves under /doc (HISTORY.md decision 111).
Once a release is served, only the active release accepts: the pre-release candidate may accept only a node's first release, trusted on first use, and either may roll back. From a terminal, a user with setup stages, and
accepts or rolls back with ocx2 --repository REPO --user SETUP-USER accept --root ROOT.
For a session to build a pushed check-in, use the stage run pattern:
ocx2 create "stage CHECKIN" --pattern URL --login LOGIN --clone CODE --to POST --of CHECKIN. --of must be the check-in's full hash. Import refuses a run whose of is not that full hash.
It clones anonymously, opens that check-in and runs the CI release build for x86_64,
writing only in its workspace. OCX sends ocx2, release.txt and gate.txt as
unversioned files under releases/$$run$$/; failed checks or builds leave those
outputs missing. It binds no release root. A person imports the files on the
server with import RUN --root ROOT, where RUN is the full stage run hash.
The root owner writes ROOT/settings/builders.json as {"stage_pattern":"SHA3 of the pattern text","builders":[{"login":"x86-1","clone":"FULL SERVER-CODE","architecture":"x86_64"}]}.
A missing or malformed file refuses import. The pattern text must be in the served repository's files or unversioned files under that SHA3.
Import compares the run field by field with that pattern and counts its sender and first started and ended by server receipts.
It requires exit 0, the three named outputs, matching SHA-256 hashes and a matching inventory.
It copies into staged/NAME, smokes ocx2 for its usage, then offers next.
If import stops after copying, staged/NAME is left and a second import of that check-in is refused as already existing. Remove staged/NAME by hand and import again.
It removes the three shared files. For an unwanted release, remove its shared files by hand.
Import needs setup and acts on the served repository. It is a POST under the page's rules above; GET and --dry-run change nothing.
ocx2 release [--root ROOT] reads current and previous, each null when absent, otherwise its name and files. name is the abbreviation recorded as the release directory's name. files maps every name listed in release.txt to its SHA-256 there.
On the served repository's HTTPS page, GET /ext/ocx2-cgi/release?json=on gives the same answer, using the server's root and refusing a root supplied in the request. Add &release=NAME&file=FILE (CLI: --release NAME --file FILE) for a listed file of current or previous only; give both fields or neither. result.content carries the file's exact bytes as base64 in the ordinary tool answer; decode it to recover the file, then check the advertised SHA-256.
A file not listed, another release, or a name holding .. or / is refused. Both reads require a signed-in login holding setup or listed in the root's settings/builders.json, the owner-written list that import reads. These reads write nothing, including on refused requests and dry runs. Dry runs perform the read checks and omit the result, as other tools do.
ocx2 follow SERVER --root ROOT runs only on the command line, inside the client's root, as its owner. SERVER is the served repository's URL. It signs in as the daemon's clone user for that repository, using the same files in ROOT/settings/credentials/; it never prints, logs or copies the password.
It asks the server's release tool for current. If that name is already the client's current, it changes nothing and says so. If it is the client's previous, it moves current to it without fetching files, keeping the release it leaves as previous. Otherwise it fetches listed files it lacks by name, checks every SHA-256, and only then installs the release with accept's inventory and file modes and moves current, keeping previous. A failed fetch or mismatched hash names the file and leaves ROOT unchanged.
It holds the release root's lock. When current moves, the daemon ends; the answer tells the person to start it again with confine daemon, as Start describes. Nothing follows on a timer.
Dry runs perform checks and list changes. Stage, import, accept, rollback and follow hold one root lock, refusing while another holds it.
Install or remove the hook
hook installs Fossil's %F git export --if-mirrored -R %R as the served
repository's after-receive hook, replacing old ocx2-hook commands and
repeated export hooks; installed once, it is unchanged. unhook removes both commands,
leaving other hooks. Both need setup on the served web page or its host with --repository REPO.
project installs the hook on a new repository; hook does so on the served one.
The hook schedules no work. project also sets chat-timeline-user; an admin may set it
with fossil setting chat-timeline-user NAME -R REPO. Fossil posts timeline events as NAME,
whose chat uses Fossil wiki markup, not Markdown, so choose a NAME that is no real user
(chat.c 133–143).
status [--login] shows hooks, backoffice, timeline chat user and ROOT/state/daemon.log.
On the served repository it lists readable open runs (2 for forum, r for tickets), from
the record alone, for every node user an administrator's run names. With
--repository CLONE --login, it signs in as that entry's node user and shows the last
pull, node, server-code, limits, care and decision. Setup also sees /proc processes and the projects running.
Neither status nor the daemon finds runs by message tags; tag_messages back-fills the tags.
Ask for a login
register asks for a login from your own machine, like Fossil's
self-registration, but the user is made only when an admin approves:
- On your machine:
ocx2 register https://SERVER/REPO FILE --login LOGIN [--capabilities LETTERS]keeps a random code in FILE, readable by its owner alone, with the login and the capabilities asked (goC4wunless given), and prints a link toapprovenaming them and the code's SHA-256, the same at every call. - On the server, an admin may open the link, only one the asker printed
itself, and press once:
approvemakes LOGIN holding exactly those capabilities, nevera,s,uorv, with a password no page or command shows, and never changes a user it did not make. Approved again for another code, it sets a new password; the earlier one stops working. - Call
registeragain without--login: once approved, it collects the password, sending the code in a POST's body, and keeps it in FILE.
Run a node
On each agent machine install ROOT with current/ocx2 and staged/, Fossil
2.27 (the daemon facts pin it), bubblewrap and GNU
timeout. Register with ocx2 register https://SERVER/REPO FILE --login lab-1;
an admin approves its link, then register collects the password. Keep FILE
outside paths a run binds. The node user needs goC4w, no admin or setup;
add i for verdicts and y for shares. Ticket changes must not await moderation.
Set chat-timeline-user as Install or remove the hook says; without it, after its
first pull at start the daemon pulls only when chat polls time out.
On each agent machine, register each project with its password file at
ROOT/settings/credentials/NAME.json. The daemon finds these files in name order,
and uses ROOT/state/clones/NAME.fossil for each. The file's server URL names
the served repository. For an older file without server, the daemon reads the
clone's ocx-server remote. A file without either is reported and skipped.
ocx2 daemon collects approved credentials and makes missing clones, listing
unapproved entries while serving the rest. It checks the password directory
each minute, making clones for new and newly approved projects without a restart.
--dry-run checks existing clones without creating, locking or serving.
The maintainer's serve starts the daemon as confine daemon inside its fixed sandbox,
also starts confine receipt-reader, and links confine into Fossil's extroot as ocx2-cgi;
see scripts/README.md.
Each start pulls first; later rounds follow chat replies, even empty ones.
A failed pull or receipt read ends the daemon.
The daemon or run starts watch RUN SERVER CREDENTIALS --repository CLONE [--by-hand],
which posts through Fossil's pages (How a post reaches an agent).
On the server's host, project readies a new repository's node
and users for new runs. A run naming a clone uses its server-code, shown by
status --repository CLONE --login; clone over HTTP, never copy a node's repository.
Shared node users require runs naming each clone.
For a run that signs in, the password step collects its user's password,
signed in as the node login it was approved for, keeping none in FILE.
An entry in FILE's runs is used only when the server has no approval;
other refusals and failed requests fail the step. A process user is
bound to no node: several runs may use it at once on every clone it is approved
for. There is no lock per process user. A run with no password step needs no password. Send runs to the server.
Let an agent send runs
A user holding R may send runs, and stop the runs it sent, without admin or setup.
Each user create makes for a run holds it, so a session an admin sent may send runs from its clone. An admin gives it to any other user with send_runs (NEW TERM, needs approval), every other letter as it was: on the OCX page a link /ext/ocx2-cgi/send_runs?user=USER fills its form and one press adds it; at the server's command line ocx2 send_runs USER; over MCP ocx_send_runs; --off (off) takes it away. It answers what the user may do then, as receipt reads it. Fossil's own Users page may drop R when that user is saved there: give it again with send_runs.
The pattern sets the run's sandbox and limits; the clone takes such runs with no additional file (HISTORY.md decision 132).
A run from R alone must have a sandbox with ends_with_parent true, have no path holding .., use directory steps whose paths end in $$run$$, and share files only under names holding the whole value $$run$$.
A rule it breaks is refused, naming it (protocol v2, row 1). A node takes one such run at a time, the others waiting until it ends (HISTORY.md decision 108).
From its run's clone an agent sends one with create, such as ocx2 create OBJECTIVE --pattern run/build --to RUN --of CHECKIN (ocx_create): pulled first, written as a reply or on a ticket, never a new thread, and pushed, so the server receives it from the agent's user; --of sets the check-in each files step takes.
A wiki pattern counts only in a version the server says an admin or setup sent, and no user is made, so the run names its own. A session whose process.ends is true is given the ended of each run in its thread or ticket, such as its build's.
Fetch the git export
The program served from Fossil's extension root as git (git-next from the
release offered as next) serves each repository's git export read-only over
git's smart HTTP, for mirrors: git clone <ext>/git/NAME.git fetches the export
of NAME, the repository the URL serves, never another's, from where
fossil git export last wrote it (its last-git-export-repo setting). A push,
a viewer who may not read code, and a request over 50 MB, raw or expanded, are
refused. The hook keeps the export current after each receipt; export (setup)
exports now, one run at a time, refusing with what Fossil said when it fails,
and its dry run says what awaits, the autopush and the last failure.
Onboard a project
ocx2 project NAME [--daemon-root DAEMON_ROOT] [--from UPSTREAM] [--mirrors DIR] [--kinds "session"] makes
NAME beside the served repository, for a user with setup. The OCX page offers
the same tool and runs it detached. --daemon-root is the ocx2 daemon's
release root, given only when that daemon runs on this machine; its state/
directory must already exist. With
--from, UPSTREAM is a git URL or a
local git directory; its branches and commits enter Fossil through the bounded,
sandboxed git import, and its export keeps those commits. Without --from,
Fossil makes an empty repository with project-name NAME. DIR selects the git
exports directory; by default it is beside the served repository's export.
The project shares the served repository's login group and patterns, gets the
git export hook and its public email-url, and has milestone and due ticket
fields. It takes the served repositories' timeline chat user. With
--daemon-root, it gets one NAME-node user holding goC4w and one NAME-KIND
user per kind holding the run-user letters; their random passwords are filed
privately under DAEMON_ROOT/settings/credentials, and that password file lets
the daemon make DAEMON_ROOT/state/clones/NAME.fossil within a minute.
Without it, as for the client on its own machine (ocx-rootfs/README.md),
project makes no node or run user and files no password: on the node's
machine, ocx2 register URL DAEMON_ROOT/settings/credentials/NAME.json --login
NODE asks for the node's login, an admin approves the link it prints, and
the same register again collects the password, from which that daemon makes
its clone. On the served repository, create approves a run user's kept
password for its node to collect (the password step).
On the web page, project returns its process and output paths at once; status
shows projects running and their last outcome. A failed project is discarded.