Skip to content

Latest commit

 

History

History
397 lines (346 loc) · 39.9 KB

live-trial.md

File metadata and controls

397 lines (346 loc) · 39.9 KB

Preparing and starting a live trial

The sessions in this document are named by the level table: the child is the project session, and a child session (a code id, not a layer) is no layer. The start record keeps the relay's role ids for its two participants, parent for the child and child for the child session, as Names used only in code lists them.

A live trial drives the installed runtime through a real completion, a real delivery to a child, a real verdict and a real correction round trip, on the shared store that ordinary work uses. The automatic legs of one have closed with no operator inside the window. What went wrong on the way there was the preparation, three times, and each failure produced a result that looked like a runtime problem until it was read: a supervisor that stopped polling, a child the relay refused to deliver to, and a child session that emitted against a relationship that had already been archived.

This document fixes what is confirmed before a trial starts, in what order the parts are built, and how the interventions during preparation are kept apart from the ones inside the window that produces the result. The ledger (crw-dev trial-ledger) grades the window after it closes; see Running it. Until todo 44 a preflight (scripts/trial_startup.py preflight) performed, before the dispatch, the checks it could perform on a Python install; it retired with that install and has no Go counterpart, so the checks below are made by hand.

This page is developer-only. The ledger runs from a checkout, in the repository's development binary; it is not part of the crw runtime a host installs.

One other thing was named similarly and is not this. runtime_install.py --trial was a diagnosis mode of the Python fence installer that registered, emitted and delivered once to fill one field of its own record (its trial mode); it left with that installer in todo 44. That name is not reused here.

The three failures this exists for

What happened What it looked like What catches it now
A supervisor launched with nohup did not survive the tool shell that started it, so emissions sat staged with nothing polling a delivery that never arrived the processPersistence reading, which requires the supervisor to be outside the caller's session and to have advanced its own witness between two observations
Participants created without a turn could not be looked up at all, and the relay withheld delivery with lifecycle_unknown rather than sending to a recipient whose lifecycle it could not read a recipient that never woke the parentLifecycle reading, and the standby turn that the order now puts before registration
A child session read its assignment file before the new relationship id had been written into it, emitted against the archived relationship, and was refused with relationship_not_active a child session that failed for a reason it had not caused the order gate, which compares the file, the store and the message about to be sent, before the message is sent

None of the three was a defect in the runtime. All three were preparation, and the relay refused each of them correctly. That is the reason this document is about preparation and says nothing about the runtime's own guarantees.

Four readings are taken, and two are graded

Six things are confirmed before the first dispatch. Four of them the checker reads itself. The other two it cannot: no read-only relay command performs a lifecycle read, and none returns what a creation receipt echoed, so those two arrive as captures the operator recorded elsewhere. The lifecycle capture is per participant rather than per child: a child session created without a standby turn cannot be looked up either, and a correction is delivered to it. A capture is the caller's claim about a read this process did not make, it carries the time it was taken, and it is stale past the record's own bound, which may not exceed fifteen minutes. Every capture is aged again at the end of the run, because one fresh when it was read can expire while the readings after it are being taken and readiness is published after all of them.

Reading Answered by Met when Never established by
processPersistence os.kill(pid, 0) and os.getsid, the supervisor's witness file, and service status where the supervisor is the relay's own service the process is alive at two observations, sits outside the caller's session, has been running at least the declared minimum by its own start time where the host reports one, and by the record's declaration where it does not, the cell saying which it read, since a restarted supervisor keeps the old declaration, and its witness names that pid and advances its counter between them; where a service is declared, ownership is ours, the lock is held, staleRecord is false, and the pid and store id agree that a launch command returned, or that a pid appears in a record. A recorded pid with a free lock is what staleRecord is for
lifecycle a captured host response per participant the capture names that task, carries no error, and resolves a thread status. That is all it establishes: whether the recipient is archived, paused, usage-limited or able to accept input is what the relay reads for itself at delivery, and this reading does not stand in for it. The host answers that status as a structured object rather than a word, so either shape resolves it: a word, or that object carrying its state at type, which is where the relay's own adapter reads it. No particular state is required, only that one is there a creation receipt. thread not found, missing source rollout and no rollout found are the three answers that fail it by name, read from the fields a host puts a refusal in rather than from anywhere in the payload, so a goal or a title describing one of them is not one. A null goal is not one of them: a healthy task without a goal has one. A status no state can be read from is unreadable rather than resolved, and the start is refused. JSON false, 0, a null and a list are not statuses at all, and str() turns the first two into nonempty words; an object with keys but no state at type, such as {"unexpected": true}, is not one either. Reading any of them as an answer resolves a participant the host never resolved, which is the missing-rollout condition this reading exists to catch
capability the captured creation receipt, settings-show for the same task, and settings-show again for every participant immediately before the gate each payload names the task it is about, which the creation receipt does at threadId, because that is where the bridge writes the thread it created and it writes no taskId anywhere, while the relay's own word for that same participant is taskId and either spelling names it, the receipt's settings.requested carries every requestable setting the record declares with the value it declares, its settings.verified is the list that request builds, so a receipt verifying something it never asked for is not one the bridge wrote, the receipt's settings.actual equals every setting the record declares, its findings are empty, every requestable setting the record declares is named among the ones the receipt says it verified -- a setting the creation never asked for cannot produce a finding, so empty findings would say nothing about it while settings.actual reported whatever the thread inherited, and the approval policy is not one of those because the contract decides it first and alone on every receipt -- and the store's own settings record is usable, complete and carries the same values, including the workspace the record states for that participant, which the store holds per task and delivery reads. The receipt and the store are also compared to each other on the access a delivery actually runs with — cwd, runtimeWorkspaceRoots and environments, the rest of the relay's settings contract — because a record can be usable, complete and agree on all four declared settings while those say the trial will run somewhere else. A permission profile is answered from both sides: the store's row is what a resume is compared against, so an expectation there that the creation response never named is unreadable rather than inapplicable, and only a creation and a row that both name none leaves nothing for a resume to be checked against. The receipt carries the environment selection on the created thread rather than under settings.actual, which is where the bridge's observable list leaves it. And every row is read again immediately before the gate: delivery reloads the current row when it sends, so a row replaced while the readings ran would be the one used that a provider served that model. A matching echo says the host recorded the request. usable alone is completeness, not agreement
storeIdentity doctor with --expect-store, --expect-inode, --expect-log and --expect-nonce, and each peer's captured doctor doctor reaches the App Server socket, the relay's own sameStore reports proven here, and every declared participant has a captured doctor of its own reporting proven about this trial's challenge, with its own store identity AND its own log location agreeing with the record. A verdict speaks only for the nonce it was given, so a peer asked about an older challenge is not this trial's proof. A participant with no capture is unknown, never absent from the count an equal path string, or an agreeing store id and inode. The relay grades those as unproven on their own: proof takes a nonce another participant wrote, found beside an agreeing device and inode and an agreeing log location - the directory entry the write-ahead log is written under, which is the part a name count cannot see because a file bind mount adds a pathname without changing it
boundaries the record's declaration, each boundary's captured registration receipt, git rev-parse --show-toplevel in each declared directory, and git rev-parse --git-common-dir in each declared root there are at least two boundaries whose issue key, scope reference, resolved repository root and participants are all pairwise distinct, because two boundaries sharing a participant are not two child (project) sessions, no two of them are in one repository, which --git-common-dir settles because it resolves to a single directory for every linked worktree of one repository, each receipt names that boundary's issue, an active status, its scope reference, both endpoints' tasks and workspaces, and both endpoints among the recipients it authorises, and for the assignment being dispatched its relationship and generation as well, and each directory is the repository it was declared to be a display name, a title or a working directory, none of which identifies anything (OPS-7.2). Distinct roots are not distinct repositories, and neither are distinct --show-toplevel answers: linked worktrees of one repository have both. Run the two boundaries as worktrees of one checkout and a reading of the roots calls one repository two, which is the ordinary arrangement on a host that keeps its work in linked worktrees rather than an edge of it
assignmentState assignment-find --issue and criteria-show, each run once before the readings and once again immediately before the gate the responsible relationship is the one the record names, its entry carries the same parentTaskId, childTaskId and execution generation with status active, its state is requested and what it waits for is child_emits -- the state whose generation carries no head revision, which is what a first dispatch goes into, because every later state means a head or a verdict is already there and dispatching reuses one or opens a competitor, and the criteria set matches by digest, source reference and count. The child is compared because a relationship under another child delivers to that child, whatever the trial intended a relationship id written in a file. A non-empty criteria set is not the intended one. Neither read is established by the other: the first is separated from the gate by the witness delay and every probe between them, and a relationship archived or a criteria set replaced in those seconds would otherwise be graded from an answer already stale

Every fact a reading later compares a payload against is required in the record itself, before any command runs. Two absent values compare equal to each other, so a record that stated nothing would have agreed with a payload that carried nothing, and the run would have reported a precondition nobody established.

Every boundary declares exactly one child and exactly one child session, the one being dispatched and the others alike. The assignment being dispatched belongs to one of the declared boundaries, and its child and child session are that boundary's own; otherwise the readings would cover one set of tasks while the dispatch went to another. A captured registration is compared as a whole registration, because one agreeing on a scope and two task names while carrying another issue, relationship, generation or an archived status is a stale registration whose authorised roots belong to something else.

A reading that could not be taken answers unknown and refuses the start. It never answers false, and it never takes the value of the reading beside it. The distinction is the whole point: a missing answer and a wrong answer call for different actions, and a start that proceeded on an unknown is how two of the three failures above were discovered after the fact rather than before.

The store readings have an ordering rule of their own. Every relay command opens the store on construction, creating the directory and an empty database when they are absent, so a mistyped path produces a silent empty store rather than an error (OPS-3.4). doctor constructs nothing, so it runs first, and a store whose createdAt is not earlier than the checker's own start is a refusal rather than a pass.

The order

create ──▶ standby turn ──▶ register ──▶ write the start record ──▶ preflight ──▶ dispatch

Each arrow carries the failure that happens when it is reversed. Since todo 44 the preflight is the checks this page lists, made by hand with the relay's own commands (crw relay doctor, store-identity); the Python install's preflight script that made them left with that install.

Create, then a standby turn, before anything is registered. A task with no turn has no rollout, so the host cannot resolve its goal or its active turn, and the relay withholds delivery from a recipient whose lifecycle it cannot read. Materialising every participant with a completed turn is what makes the parentLifecycle reading answerable at all.

Register before dispatching, and record settings and criteria while registering. Registration is the first mutating step, it mints the relationship id, and it either replays an existing relationship or refuses the whole registration without writing anything else. Everything after it reads what it returned. An issue that already holds an active or paused assignment cannot acquire a second registered child session, so two children on one store need two distinct issues; a trial that binds its assignment onto a real backlog issue puts a trial row in the path of real coordination, and the genuine project identity travels in the scope reference instead.

Write the start record from what register returned, not from what the previous window left. The assignment file in the child session's workspace is the file the third failure read too early. It is written after registration, from the returned relationship id, and the dispatch message carries the same identity in its own text, so the child session's first turn does not depend on reading that file at all.

Preflight last, immediately before the dispatch. The record declares the window this dispatch opens, and it has to be ahead: a record whose window has already opened is a trial already running, and starting from it again dispatches a second time into one measured interval. The window is also the one this dispatch opens rather than any window ahead: a record whose window opens hours later is refused, because the completion and any intervention would happen before the declared interval and a later ledger would report a window it never measured. It is read again at the end of the run, because the readings take real time and a window a few seconds ahead can open while they are being taken, and that reading takes the clock once: both bounds and the moment it reports are the same instant, because two readings a moment apart called a window that opened between them both still ahead and inside the allowance. The ledger still grades that same record afterwards, which is what it is for, and it refuses a line dated after the moment it is being graded: the ledger is appended as things happen. All six readings are taken by one run, as close together as separate processes allow, because the window they are true in is the thing worth narrowing. They are not simultaneous and this does not claim they are: the gate's own reads are taken again after the last of them and the span that pass covered is reported, and what remains is named in the stand-ins. Two more readings are taken again there for the same reason. The store's identity is asked of the relay a second time, because the first doctor runs before anything else constructs a store and every probe after it opens whatever the state directory names at that moment: a database replaced in between carries the same store id, the same challenge nonce and the same rows, and a device and inode of its own, so the peers proved access to a store that is no longer the one being read. That answer is graded on the reachability and the write access it reports as well as on the identity, because the same payload answers all three and a socket that stopped answering or a database that stopped being writable leaves those first cells verified while the dispatch this clears cannot run at all. The supervisor is read again last of all, because a poller that exits while the lifecycle, boundary, capability, criteria and assignment probes run leaves every cell those probes filled verified and publishes readiness for a trial with nothing polling, which is the first observed failure arriving as a pass. Its counter is held there to the advance the record declares for it once that long has passed, and to not going backwards when it has not, because a poller that has not ticked yet is not a stopped one. Liveness there is the kernel's own state letter rather than a signal alone: a process that has exited and has not been reaped answers a signal and still reports a detached session, and it polls nothing. So does one stopped by a signal, one stopped by a tracer, one being killed and one parked: the decision covers every state letter the kernel defines rather than the ones a review happened to report, a letter it does not cover is unreadable rather than running, and it is made in one place that every liveness reading here goes through. All three observed failures were checks made too early, or not made.

Then dispatch. The window opens here.

What the order gate compares, and what it cannot promise

the assignment file      the store's own answer           the message about to be sent
relationshipId    ═══    responsibleRelationship    ═══    contains that id
childTaskId       ═══    childTaskId
executionGeneration ═    executionGeneration
                         relationshipStatus is active
artifacts         ═══    the artifacts the record declares
artifacts         ─────  inside the registration receipt's artifact roots
                                                          contains each artifact path

A file written for a previous relationship carries that relationship's id, and that is the comparison that catches it. The artifacts compared are the file's own: the file is what the child session reads, so a missing or empty list there is a mismatch rather than an occasion to fall back on what the record intended. Every artifact path is absolute and normalised, and its containment in an authorised root is decided on the path rather than on the place it resolves to, because that is the contract the relay's own manifest enforces: it compares normalised paths without following links and then opens every component refusing to follow one, so an absolute symlink outside a root whose target lands inside it is outside the root, and an artifact under a symlinked directory is refused at emit however its string reads, which the gate checks by looking at the components that exist. A NUL byte is refused for the same reason the relay refuses it. Private trial records are the opposite case and keep the resolved comparison, where following the link is exactly the escape worth catching: a relative one is resolved against whichever directory a reader happens to be in, and an unnormalised, trailing-slashed or tilde-bearing one is refused at emit, so a gate that accepted it would report a dispatch as ready that the relay then refuses. The message is prose, and the identities inside it are named as words of their own: each appears delimited by whitespace, nothing else. A POSIX filename may hold any byte but a separator and a NUL, so no rule could tell a trailing bracket or full stop that belongs to the sentence from one that belongs to the path, and two attempts proved it: a substring test read /repo/artifact.py.bak as naming /repo/artifact.py, and stripping punctuation afterwards read /repo/file! as naming /repo/file while refusing a real artifact ending in a bracket. The requirement therefore sits on the message, where an operator can meet it exactly, and an artifact path holding whitespace is refused: the relay would accept one, but no message can name it as a word. The artifact roots come from the captured registration receipt, because no read-only command returns them; what actually enforces them is the manifest emit builds, which is where it belongs.

A read-only checker makes this race refusable, not impossible. It reads at one moment and the child session reads at a later one. What makes it impossible is the order: register first, write the file from what register returned, carry the same identity in the message, and leave the relay's own relationship_not_active refusal as the guard it already is. The gate's job is to stop a dispatch that would fail, and to say which field disagreed.

Interventions, counted in two places

An operator who fixes something mid-run has not produced an uninterrupted result, and a record that reports one is worse than a record that reports none. The trial keeps a ledger, appended as it goes at /ledger.jsonl, and the checker grades it.

{"at": "<ISO-8601 UTC>", "kind": "dispatch|segment_start|segment_end|window_open|window_close|intervention",
 "segment": "<name>", "actor": "operator|user", "target": "<task, store or process>",
 "action": "<what was done>", "claimed": "preparation|window"}

The window is one named interval, so its open and its close must name the same segment, and no preparation segment may overlap it: preparation still running inside the measured window is preparation inside it, whether or not anyone wrote an intervention down. The window must have a duration: an open and a close at the same instant hold none of the completion, delivery, verdict and correction a trial exists to measure, and are clean of interventions by construction. It must also have closed: a window whose close is still ahead has not yet had the interventions it would need to be clean of. A segment is bounded by its own start and end, which are times rather than positions in the file: segments are built as intervals and two that intersect are a refusal, because a pairing taken from line order accepted two overlapping segments and counted one intervention inside both. segment_end carries failed or succeeded. The window opens at the dispatch. The ledger carries one dispatch line and its time is the window's own open, because a record naming a time some minutes ahead and a dispatch that went at once left everything between them outside the measured interval: an intervention the trial actually needed was counted as preparation and the window still read clean. What this cannot see is whether the dispatch happened when the line says; that time is the operator's record, and window.corroboration is how it is raised above a bare declaration.

The window is bounded by window_open and window_close, which must be the same window the record declares; a ledger naming a different one is a refusal, because a moved boundary moves interventions out of the measured window. Every segment must close, and close saying failed or succeeded: a segment left open is an attempt whose outcome was never recorded, and it is refused rather than reported. Classification is by timestamp and by nothing else: an intervention inside the window's bounds is a window intervention whatever it calls itself, and claimed exists so that a label disagreeing with its own timestamp is a refusal rather than a correction.

The report gives the preparation segments with their own counts, the failed ones among them named, and the window's count beside windowIsClean. That boolean is never printed without the provenance of the bounds it was computed from: corroborated when the record supplies a store time to compare against and compared with them, declared when the bounds are the operator's word; a corroborating time that disagrees is a refusal. A window with any intervention in it fails its judgment and the command exits non-zero, so a corrected run cannot be reported as a clean one by a gate that only read the exit status.

What this accounting cannot see is an intervention nobody wrote down.

Private records, public harness

Everything the trial writes for itself lives under one trial root outside every git worktree: the start record, the captures, the ledger and the result. The shared state directory the relay probes reach is held to the same rule, because operational state never lives inside a repository and the first command to open one there would construct it. The assignment file is the one in the owning child session's own workspace, because the file under test is the file that child session reads, and a matching copy anywhere else is a different file. It and the dispatch message sit where that workspace is, outside this repository. Paths are compared after symbolic links are followed, so a link out of the trial root is the escape it looks like rather than a spelling that passes, and no private record may sit in a repository nested under the root either: the rule is that operational state is in no repository, not that the root itself is in none.

The reason is not tidiness. Operational state never lives inside a repository (OPS-3.2), a committed host identifier is a private receipt in a public repository, and a synthetic harness that acquired one would stop being synthetic. This document carries placeholders for the same reason.

Running it

crw-dev trial-ledger --start <trial-root>/start.json

It is run after the window has closed, which is what it needs in order to grade anything. It is the development binary's ledger (make crw-dev builds dist/crw-dev), and it prints the document the Python ledger (scripts/trial_startup.py ledger, deleted in todo 44) printed, byte for byte, so a grade taken with either compares with a grade taken with the other, when that Python was CPython 3.14. The exceptions are a record nested tens of thousands of containers deep, near where the interpreter's stack runs out, which no operator writes, and, under another CPython, a character its Unicode database assigns differently from 3.14's (known differences). The Python install's preflight (scripts/trial_startup.py preflight), run once immediately before the dispatch, reached the relay through the Python runtime's host record and console script and retired with that install. Both printed one JSON object on standard output and created no file of their own. That is a claim about the checker rather than about the commands it runs: every relay command opens the store on construction and doctor writes a temporary file to measure whether the state directory is writable, so a mistyped state directory is where a probe leaves a new empty store behind instead of failing. The store readings are what catch that. Exit 0 means no judgment in the document said false, 1 means one did, and 2 means it refused before it could assemble a result and printed the refusal instead.

The start record is one JSON object under the trial root, with absolute paths throughout. The whole store block except the challenge nonce is copied from one participant's store-identity, the three log* fields among them: they say where that participant's write-ahead log is written, and the preflight sends them back as doctor --expect-log (the Python preflight did so; by hand, the operator does).

{
  "source": "live-trial-start",
  "recordVersion": 2,
  "trialRoot": "<trial root, outside every git worktree>",
  "relay": {"launcher": "<destination>/current/bin/codex-session-relay",
            "launcherSha256": "<digest of that file>",
            "stateDirectory": "<the shared state directory>",
            "socket": "<the App Server control socket>"},
  "store": {"storeId": "<from store-identity>", "device": 64512, "inode": 4242,
            "logDevice": 64512, "logInode": 4200, "logName": "relay.sqlite3",
            "challengeNonce": "<written by a participant during preparation>"},
  "supervisor": {"pid": 12345, "witness": "<trial root>/supervisor.jsonl",
                 "launchedAt": "<ISO-8601 UTC>", "minimumAliveSeconds": 60,
                 "witnessAdvanceSeconds": 5, "service": false},
  "assignment": {"relationshipId": "<from register>", "parentTaskId": "<task>",
                 "childTaskId": "<task>", "issueKey": "<issue>",
                 "executionGeneration": 1, "artifacts": ["<absolute path>"],
                 "assignmentFile": "<child workspace>/assignment.json",
                 "dispatchMessageFile": "<trial root>/dispatch.txt",
                 "criteria": {"setDigest": "<from criteria-show>", "sourceRef": "<reference>",
                              "count": 4}},
  "boundaries": [{"name": "A", "issueKey": "<issue>", "scopeRef": "<scope reference>",
                  "repositoryRoot": "<repository>",
                  "participants": [{"role": "parent", "taskId": "<task>", "cwd": "<directory>",
                                    "expect": {"model": "<model>", "reasoningEffort": "<effort>",
                                               "sandbox": {"type": "<mode>"}, "approvalPolicy": "<policy>"}},
                                   {"role": "child", "taskId": "<task>", "cwd": "<directory>",
                                    "expect": {"model": "<model>", "reasoningEffort": "<effort>",
                                               "sandbox": {"type": "<mode>"}, "approvalPolicy": "<policy>"}}]},
                 {"name": "B", "issueKey": "<another issue>",
                  "scopeRef": "<another scope reference>",
                  "repositoryRoot": "<another repository>",
                  "participants": [{"role": "parent", "taskId": "<task>", "cwd": "<directory>",
                                    "expect": {"model": "<model>", "reasoningEffort": "<effort>",
                                               "sandbox": {"type": "<mode>"}, "approvalPolicy": "<policy>"}},
                                   {"role": "child", "taskId": "<task>", "cwd": "<directory>",
                                    "expect": {"model": "<model>", "reasoningEffort": "<effort>",
                                               "sandbox": {"type": "<mode>"}, "approvalPolicy": "<policy>"}}]}],
  "captures": {"parentLifecycle": {
                   "<parent A>": {"path": "<trial root>/lifecycle-<parent A>.json",
                                  "capturedAt": "<ISO-8601 UTC>"},
                   "<child A>": {"path": "<trial root>/lifecycle-<child A>.json",
                                 "capturedAt": "<ISO-8601 UTC>"},
                   "<parent B>": {"path": "<trial root>/lifecycle-<parent B>.json",
                                  "capturedAt": "<ISO-8601 UTC>"},
                   "<child B>": {"path": "<trial root>/lifecycle-<child B>.json",
                                 "capturedAt": "<ISO-8601 UTC>"}},
               "creationReceipt": {
                   "<parent A>": {"path": "<trial root>/receipt-<parent A>.json",
                                  "capturedAt": "<ISO-8601 UTC>"},
                   "<child A>": {"path": "<trial root>/receipt-<child A>.json",
                                 "capturedAt": "<ISO-8601 UTC>"},
                   "<parent B>": {"path": "<trial root>/receipt-<parent B>.json",
                                  "capturedAt": "<ISO-8601 UTC>"},
                   "<child B>": {"path": "<trial root>/receipt-<child B>.json",
                                 "capturedAt": "<ISO-8601 UTC>"}},
               "registration": {"A": {"path": "<trial root>/register-A.json",
                                      "capturedAt": "<ISO-8601 UTC>"},
                                "B": {"path": "<trial root>/register-B.json",
                                      "capturedAt": "<ISO-8601 UTC>"}},
               "peerDoctor": {
                   "<parent A>": {"path": "<trial root>/doctor-<parent A>.json",
                                  "capturedAt": "<ISO-8601 UTC>"},
                   "<child A>": {"path": "<trial root>/doctor-<child A>.json",
                                 "capturedAt": "<ISO-8601 UTC>"},
                   "<parent B>": {"path": "<trial root>/doctor-<parent B>.json",
                                  "capturedAt": "<ISO-8601 UTC>"},
                   "<child B>": {"path": "<trial root>/doctor-<child B>.json",
                                 "capturedAt": "<ISO-8601 UTC>"}}},
  "captureMaxAgeSeconds": 600,
  "window": {"opensAt": "<ISO-8601 UTC>", "closesAt": "<ISO-8601 UTC>"}
}

Two of those values are refused rather than graded when they cannot be right. minimumAliveSeconds has to be greater than zero: a bound of zero is met by a process that started this instant, so the reading would report a persistence it never observed, and the observation this exists for is a supervisor that outlived the shell which launched it. sandbox has to be the policy object the relay records, carrying its mode at type, because a record declaring a bare mode could never agree with the row the store holds. A record may also declare only settings a creation can ask for, plus the approval policy the contract decides on every receipt: a key outside those is one nothing in the path requests, verifies or preserves, so a capture and a store row that both carry it agree with each other about a field delivery would drop.

The record names parameters, never command lines. The checker composes every command it runs, and every relay probe carries both the state flag and the state environment variable, because the flag moves the store and the variable moves the adapter ledger beside it, and the only programs it can start are git and the relay entry point that the runtime host record under the state home names through its owned pointer. The record cannot nominate another program: the host record's location comes from the environment, the declared launcher has to be that pointer as the host record spells it rather than something that resolves to it, and the pointer is the path that runs. Its bytes are read again after the last probe, because the pointer is an atomically movable symlink and moving it is how an update is meant to work: reading it at both ends reports a move rather than preventing one. A link accepted because it resolved to the pointer could be replaced afterwards, and the probes would have run whatever replaced it.

The ledger command reads the trial root and the window and nothing else, so it does not ask for the installation at all: a trial that finished stays gradable after the relay it ran against is upgraded or removed.

The nonce is not written by the checker. During preparation one participant writes a challenge into the store and the rest read it back, which is what turns an agreeing store id, device and inode, and log location into proof. The log location is the newest of those and the one a name count cannot stand in for: it says where each participant's write-ahead log is written, so one database reached at two pathnames - which a file bind mount produces without changing the name count - is refused rather than proved.

What this does not answer

The checker reads. It does not create a task, register a relationship, emit, deliver, record a verdict, or start or stop anything, so nothing in its document is evidence that a delivery happened. That is not the same as saying nothing is written: this process creates no file of its own, but every relay command it runs opens the store on construction, and doctor measures whether the state directory is writable by writing a temporary file in it. The honest claim is that it composes only doctor, service status, assignment-find, criteria-show and settings-show, that none of those registers, emits, delivers, records a verdict or changes service state, and that doctor runs before anything that could construct a store.

Stand-in What it replaces What a reading through it cannot prove
the captured lifecycle response a lifecycle read this process made that the host would answer the same way at the moment of dispatch. It carries its own time, and it goes stale
the captured creation receipt the host's own echo, read live that a provider served the model. Only that the host recorded the request
the supervisor's witness a witness at the process boundary that the pid inside it is the process that wrote it. That witness is CRW-102's, and this reading is that something naming the pid advanced the file
a peer's captured doctor a reading attributed to the participant that took it which participant took it. doctor does not name the process that ran it, so two peers legitimately produce identical payloads and the report says when they did. Filing a capture under a participant is the operator's attribution
the runtime host record a trusted inventory of what is installed the provenance of the launcher's bytes. It is a private file the same operator writes, so the launcher agrees with the installed-runtime record rather than being proven to be the relay
the gate's reads, taken in sequence one transaction across the store that nothing moved between them. They are separate processes, not one transaction: each read is a moment, the last of them is the assignment the gate compares against, and what changes between that read and the dispatch is unread, as is anything that changes inside the confirmation pass after its own read. No finite number of passes closes that and the relay exposes no revision to bind to. The order narrows the window, the report says how wide it measured, and the relay's own refusal at delivery is what closes it
the declared window bounds times taken from the store that the window is where the operator says it is, unless a corroborating store time was supplied. The report says which of the two it had

A preflight that passed says the trial may start. It says nothing about whether the trial will succeed, and a trial that succeeds afterwards does not retroactively make an unread precondition read.