All checks were successful
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 7s
standards workspaces build + test / proof-rs (proof/conformance/v1/joulespec.toml, proof-conformance, proof/proof-rs, proof-rs, linux, true) (pull_request) Successful in 1m1s
standards workspaces build + test / joule-ui-rs (joule-ui/joule-ui-rs, joule-ui-rs, linux) (pull_request) Successful in 1m25s
standards workspaces build + test / sandbox-rs (sandbox-conformance, sandbox/sandbox-rs, -p sandbox-tee --features amd-kds,intel-pcs, sandbox-rs, linux) (pull_request) Successful in 1m37s
standards workspaces build + test / jcp-rs (jcp-conformance, jcp/jcp-rs, -p jcp-energy --features nvml, jcp-rs, linux) (pull_request) Successful in 1m46s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 3m21s
standards workspaces build + test / jouledesk-rs (jouledesk/jouledesk-rs, jouledesk-rs, macos) (pull_request) Successful in 7m1s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 12m16s
standards workspaces build + test / joulecontract-rs (joulecontract/joulecontract-rs, joulecontract-rs, linux-arm64, true) (pull_request) Successful in 1m15s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 7m28s
§13 prices the wait a run spends parked. It says nothing about the wait a run
spends *running*, which is usually the larger number. An interval that dispatches
an outward call and then sits until the result arrives holds its sandbox, and its
receipt charges the sitting as computation. A twenty-minute build, a review that
comes back the next morning, an approval that never comes — each is, in a
blocking runtime, an interval whose receipt is mostly waiting. Two runs doing
identical work bill hours apart, and the record reported both the same way.
So an executed segment may now carry a `CallAccount`: the calls it dispatched
and the results it resolved, each by call id.
A run is **non-blocking** when every executed interval accounts, and every
interval resolves only calls that were outstanding before it began. An interval
that resolves a call it dispatched itself did not hand off and return — it
waited, and the wait is inside its receipt. `RunRecord::wait_proof()` walks the
segments in order holding the outstanding set and returns one of:
- `NonBlocking` — no receipt in the run contains a wait for an outward call, so
the run's whole cost of waiting is the idle energy §13 already records, and
which §13.1 makes small for a transcript park. This is the condition under
which "waiting costs no computation" is a statement about the record rather
than about the runtime that produced it.
- `Blocked { segment }` — that interval consumed its own dispatch.
- `Malformed { segment }` — the account does not add up: a result answering a
call never dispatched, a call resolved twice, or one id dispatched twice. A
record that cannot be read is a different problem from a run that waited, and
is reported separately.
- `Unproved` — some interval did not account, or the run has no accounted
interval at all.
Two deliberate pieces of honesty
**An absent account is not an empty one.** Not accounting and accounting for no
calls are different statements, and they do not fold to the same bytes — a test
and a conformance check both hold that, so the marker is not merely a comment.
**A property of no intervals is not a property.** A run with nothing accounted
returns `Unproved`, not `NonBlocking`. Without that, every empty record would
certify the strongest claim in the section.
Backwards compatibility, checked rather than asserted
`calls` is `Option`, `serde(default, skip_serializing_if)`, and folded into the
chain only when present, so an interval recorded by `record_execution` hashes
exactly as it did before — pinned by a test against bytes written out by hand.
The committed vector confirms it: `durable.json` diffs as one added line and no
changed line, so `expect_runrecord_head` and `expect_transcript_head` are
unchanged, and the accounted trace gets its own address at
`b3:FINebhSRpv_1ncmpE3PaRG85MR8Ri1fDy4lmR1yI2DA`. That trace is the transcript
one with the account added and nothing else changed, so an implementation that
dropped the account would produce the transcript head where this one is
expected. Each id set is length-prefixed, so no id boundary is ambiguous.
Conformance class C15, and the pack's deliberate class count moves 14 → 15.
Evidence, on the build host: clippy `-D warnings` clean over the workspace, the
workspace's tests pass, and the full pack reports all 15 classes passed with no
skips. `cargo fmt` was not run: these workspaces are not rustfmt-clean.
Co-Authored-By: Claude Opus 5 <[email protected]>
181 lines
9.2 KiB
Markdown
181 lines
9.2 KiB
Markdown
# Durable run records
|
|
|
|
Agent work is no longer minutes long. The valuable runs span hours or days,
|
|
outlive the machine that started them, pause for review, and resume elsewhere.
|
|
Most platforms make the *process* the unit of survival and keep it alive in a
|
|
cloud sandbox. This standard takes the stronger position: **the record is the
|
|
run**. An execution is a chain of sealed segments; the sandbox that hosted any
|
|
one segment is disposable, and what survives — what can be suspended for a
|
|
week, restored on different hardware, and handed to an auditor afterwards — is
|
|
the run record.
|
|
|
|
## The record
|
|
|
|
A **run record** is an append-only, hash-chained sequence of **segments**
|
|
belonging to one unit under one grant set. The genesis head binds both:
|
|
|
|
```
|
|
head₀ = BLAKE3("sandbox.runrecord/1" ‖ unit_id ‖ grants_id)
|
|
headₙ = BLAKE3(headₙ₋₁ ‖ segmentₙ)
|
|
```
|
|
|
|
Two records can never be spliced — not across units, and not across grant
|
|
sets. A continuation that wants different grants is a **new run**.
|
|
|
|
Three segment kinds:
|
|
|
|
- **Executed** — one active interval, recorded by the content address of its
|
|
attestation (§7), which already binds the interval's energy receipt (§6),
|
|
replay-journal root (§8) and outcome. Nothing is re-stated; the record
|
|
points at sealed objects.
|
|
- **Suspended** — the run parked. Records a **snapshot reference**: what was
|
|
retained, in one of the two forms below, and the environment manifest the
|
|
run was created from.
|
|
- **Resumed** — the run un-parked. Records the snapshot reference restored
|
|
and the **idle energy** the retained state drew while parked.
|
|
|
|
## What a park retains
|
|
|
|
There are two forms, and a snapshot reference carries exactly one.
|
|
|
|
**An image park** retains host state: a filesystem root, and a memory image
|
|
where the executor suspends resident state. This is the form a shell, a build
|
|
or a long-lived server needs, because its resumable state is spread across a
|
|
filesystem and a process.
|
|
|
|
**A transcript park** retains nothing on the host. Some units — an agent
|
|
driven by a model is the clear case — hold their resumable state entirely in
|
|
the record of what they have already done. For those, that record *is* the
|
|
continuation: restore it and the unit is where it left off, with no process to
|
|
revive. The park names its content address and nothing else.
|
|
|
|
That is not a smaller version of the first form; it is a different claim about
|
|
where the run lives, and it buys two things.
|
|
|
|
- **It resumes anywhere.** The content-identity precondition is satisfied by
|
|
any host that can fetch the transcript, so a transcript park is portable
|
|
across hardware by construction. An image park also needs a host able to
|
|
restore its image. A record of the first kind says a run can resume; a
|
|
record of the second says where.
|
|
- **Its park is small, and priced.** What it holds is bytes, so the park's
|
|
draw is the draw of storing them — orders below a retained volume, and
|
|
bounded by a number the host already knows.
|
|
|
|
A reference naming neither form, or both, or a transcript beside a resident
|
|
memory image, is **refused rather than hashed**. The last of those is the
|
|
interesting one: it is a park claiming to retain nothing on the host while
|
|
holding an image on it. Admitting any of the three would put an ambiguity
|
|
inside a chain whose purpose is to remove ambiguity, so the refusal happens
|
|
before the bytes are folded, and a reference edited into a record afterwards
|
|
fails verification rather than hashing to something.
|
|
|
|
The image form's canonical bytes are unchanged from before the second form
|
|
existed, so every record and every conformance address written under the
|
|
single-form chain still recomputes.
|
|
|
|
## Resumption preconditions
|
|
|
|
A host **MUST** refuse to resume unless:
|
|
|
|
1. the offered snapshot is content-identical (by address) to the one recorded
|
|
at suspension — a forked or tampered snapshot breaks the chain rather than
|
|
silently continuing it;
|
|
2. the resuming environment satisfies the recorded manifest address — "same
|
|
toolchain" is checked by hash, not by trust;
|
|
3. the record verifies from genesis: head recomputation, strict
|
|
execute/suspend/resume phase alternation, and snapshot identity at every
|
|
resume.
|
|
|
|
A record may be **closed** at any point, including while parked — abandonment
|
|
is an honest outcome and leaves an auditable stub.
|
|
|
|
## Energy honesty across suspends
|
|
|
|
A parked run is not free. Retained volumes, snapshot storage and warm-pool
|
|
residency draw power continuously. Each *Resumed* segment carries `idle_uj` —
|
|
the microjoules attributed to the retained state for the parked interval,
|
|
under the same provenance discipline as live metering (§6.2): measured where
|
|
the host can measure, estimated and labelled where it cannot. The total cost
|
|
of a run is the sum over its receipts **plus** its idle energy; a host that
|
|
reports only active-interval joules for a week-long run does not conform.
|
|
|
|
A transcript park is where that rule earns its keep. Nothing is running and
|
|
nothing is resident, and it is tempting to call the wait free — a claim that
|
|
is nearly true and therefore worth stating precisely. What the park holds is
|
|
a transcript in storage, and storage draws power for every hour of the wait.
|
|
So a host **MUST NOT** record a transcript park's `idle_uj` as zero on the
|
|
grounds that no computation happened: the figure it owes is the draw of the
|
|
bytes it kept, under the same provenance discipline as any other. The claim
|
|
this form supports is that **waiting costs no computation**. It does not
|
|
support the claim that waiting costs nothing, and a record that says zero for
|
|
a week of storage is not a cheaper run, only a less honest one.
|
|
|
|
## The wait inside a receipt
|
|
|
|
Everything above prices the wait a run spends parked. The wait it spends
|
|
*running* is the larger number, and until an interval accounts for its outward
|
|
calls, nothing in the record can see it.
|
|
|
|
An interval that dispatches a call and then sits until the result arrives holds
|
|
its sandbox for the duration, and its receipt charges the sitting as
|
|
computation. A build that takes twenty minutes, a review that comes back the
|
|
next morning, an approval that never comes — each of those is, in a blocking
|
|
runtime, an interval whose receipt is mostly waiting. Two runs doing identical
|
|
work bill differently by hours, and the record as described so far reports both
|
|
the same way.
|
|
|
|
So an executed segment may carry an **account** of the calls it dispatched and
|
|
the results it resolved, each by call id. The shape that makes a run
|
|
non-blocking is: dispatch, **end the interval**, park, and resolve in a later
|
|
one. An interval that resolves a call it dispatched itself did not hand off and
|
|
return — it waited.
|
|
|
|
Three things make this worth the two fields.
|
|
|
|
- **It is checkable, not promised.** Walk the segments in order holding the
|
|
calls outstanding at each one's start; a segment may resolve only those. No
|
|
trust in the implementation is required, and no instrumentation beyond the
|
|
ids the host already has to match results to calls.
|
|
- **It is what licenses the energy claim.** Where every interval accounts and
|
|
every one resolves only what was outstanding, no receipt in the run contains
|
|
a wait for an outward call. The run's whole cost of waiting is then its idle
|
|
energy — which §13 already records, and which §13.1 makes small for a
|
|
transcript park. That is the condition under which "waiting costs no
|
|
computation" is a statement about this record rather than about the runtime
|
|
that produced it.
|
|
- **An absent account proves nothing.** Not accounting is not the same as
|
|
accounting for no calls, and the two do not hash alike. A run with an
|
|
unaccounted interval, or with no accounted interval at all, yields no
|
|
verdict rather than a favourable one. A property of no intervals is not a
|
|
property.
|
|
|
|
An account that does not add up is reported separately from blocking: a result
|
|
answering a call never dispatched, a call resolved twice, or one id dispatched
|
|
twice is a record that cannot be read, which is a different problem from a run
|
|
that waited.
|
|
|
|
## Relation to the audit chain
|
|
|
|
The S7 audit chain (§7.7) orders *runs* within a steward's domain; the run
|
|
record orders *segments* within one run. They compose: each executed segment's
|
|
attestation may itself carry `audit_prev`, and a continuation attestation
|
|
SHOULD cite the run record's current head, mirroring that convention.
|
|
|
|
## Conformance
|
|
|
|
Class **C6** (§10): chain advance and verification across
|
|
execute → suspend → resume → execute; refusal of forked snapshots
|
|
(`SnapshotMismatch`); refusal of phase violations (execution while parked);
|
|
idle-energy accumulation; tamper-evidence through the wire form (a modified
|
|
recorded snapshot must fail verification); refusal of appends after close.
|
|
|
|
Class **C14**: the two forms a park may retain. A transcript park chains,
|
|
verifies and accumulates idle energy like any other; a reference naming
|
|
neither form, both, or a transcript beside a memory image is refused with
|
|
`MalformedSnapshot` and leaves no segment behind; an image and a transcript
|
|
reference over the same inner address are different parks, so offering one to
|
|
resume the other is a `SnapshotMismatch`; and the image form's canonical bytes
|
|
are pinned against a hand-written layout, so the chain written before the
|
|
second form existed still recomputes.
|
|
|
|
The reference implementation is `sandbox_attest::runrecord`.
|