- conforms_to::[[Observation Form Contract]]
- has_epistemic_status::[[Empirical Observation]]
- in_practice_domain::[[eOS Continuum]]
- authored_by::[[Christopher Allen]]
- has_lifecycle::[[Seed Stage]]↗
- has_curation::[[Working Draft]]↗
An atomic LPC Function Body Rolls Back on Deliberate Error in an HTTP-Dispatched Call
Claim
In the eOS-Harness MVA running against DGD 1.7.9 plus the eOS-kernellib HTTP/1 platform at upstream c5b1dee, an LPC function declared atomic that mutates a counter and then calls error() rolls back the mutation as observed from outside the runtime over HTTP. The dispatch shape is: HTTP POST /increment-with-failure -> route handler catch(/usr/Test/atomic_demo->increment_with_failure()) -> the atomic function body mutates counter += 1 and then fires the deliberate error. The error reaches the handler's catch, the handler returns 200 OK with the captured error in the body, and the next GET /counter returns the pre-mutation value. The pattern repeats: each subsequent POST /increment-with-failure followed by GET /counter continues to show the pre-mutation value, confirming the rollback is not a one-shot effect of cold-boot state but an ongoing property of the atomic envelope. The same probe with the atomic modifier removed from increment_with_failure shows the increment persisting past the error: the counter advances from 0 to 1 across one POST and would continue to advance on subsequent POSTs.
Grounds
This is an Empirical Observation. The grounds are two captured smoke sequences run 2026-05-17 against the live MVA demonstration instance -- one with the atomic modifier present, one with it removed.
Setup (verifiable from repo state):
- Driver: DGD 1.7.9 built from
dworkin/dgd; binary symlinked at the isolated runtime staging tree'sbin/dgd. - Kernel layer:
eOSContinuum/eOS-kernellibat HEADc5b1dee(the PR #5 OSS-community-files merge; runtime-layer fixes from PR #2 already landed at0ca7bd5). - Application layer:
src/usr/Test/atomic_demo.c(the atomic-counter LPC deliverable, commit8dd64ea),src/usr/Test/initd.c(its wiring, commitfe54e7f),src/usr/Test/sys/atomic_handler.c(the HTTP route handler, commit8dd64ea), plus the HTTP/1 platform layer that these routes inherit (src/usr/WWW/initd.c,src/usr/WWW/sys/httpd.c,src/usr/WWW/obj/server.c). - Smoke script:
tests/atomic-rollback-demo.sh(commit20027c9). - Runtime config: the instance config, telnet
127.0.0.1:8023, binary/HTTP127.0.0.1:8080, the isolated runtime staging tree's isolated-runtime hygiene.
Atomic-modified source (the load-bearing function in src/usr/Test/atomic_demo.c):
atomic void increment_with_failure()
{
counter += 1;
error("deliberate failure for atomicity demonstration");
}
Boot sequence:
$ rm -f state/snapshot state/snapshot.old state/swap state/boot.log (within the isolated runtime staging tree)
$ <materialize the isolated runtime staging tree>
$ cd <isolated runtime staging tree> && ./bin/dgd <instance config> > state/boot.log 2>&1 &
...
** DGD 1.7.9
** Initializing...
** Initialization complete.
** Initialization complete. reached in under one second. The Test domain's initd compiled atomic_demo plus sys/atomic_handler; the deferred call_out in sys/atomic_handler::create registered the URL roots counter and increment-with-failure with /usr/WWW/sys/httpd once the WWW domain's initd had finished.
Smoke A -- WITH the atomic modifier present:
$ ./tests/atomic-rollback-demo.sh
=== atomic-rollback smoke against http://127.0.0.1:8080 ===
Step 1: GET /counter
counter=0
Step 2: POST /increment-with-failure
deliberate-failure-fired: deliberate failure for atomicity demonstration
Step 3: GET /counter (after deliberate failure)
counter=0
=== PASS: counter unchanged across deliberate-failure increment ===
initial=0 final=0 (rollback verified)
$ echo $?
0
Re-running the smoke immediately:
$ ./tests/atomic-rollback-demo.sh
...
=== PASS: counter unchanged across deliberate-failure increment ===
initial=0 final=0 (rollback verified)
The counter remains at 0 across repeated runs; each POST's deliberate failure rolls back the increment.
Boot-log trace of the deliberate error during smoke A (note the [atomic] annotation):
** deliberate failure for atomicity demonstration [atomic]
/usr/Test/atomic_demo
31 increment_with_failure /usr/Test/atomic_demo
** deliberate failure for atomicity demonstration [caught]
/usr/WWW/obj/server#NNN
429 receiveBytes /usr/HTTP/lib/Connection1
99 receiveHeaders /usr/HTTP/api/lib/Server1
193 receiveRequest /usr/WWW/obj/server
143 dispatch /usr/WWW/obj/server
/usr/Test/sys/atomic_handler
85 handle /usr/Test/sys/atomic_handler
65 * handleIncrementWithFailure /usr/Test/sys/atomic_handler
The [atomic] line precedes the rollback frame for the atomic function body; the [caught] line records the catch in the handler. DGD records both because the error propagates from inside the atomic envelope to outside it.
Smoke B -- WITHOUT the atomic modifier (the modifier-less version of the same source, run earlier in the same session before the modifier was added):
$ ./tests/atomic-rollback-demo.sh # plain `void increment_with_failure()`
...
Step 1: GET /counter
counter=0
Step 2: POST /increment-with-failure
deliberate-failure-fired: deliberate failure for atomicity demonstration
Step 3: GET /counter (after deliberate failure)
counter=1
=== FAIL: counter changed across deliberate-failure increment ===
initial=0 final=1 (rollback did NOT fire)
The same probe shape -- same handler, same dispatch path, same error -- produced counter=1 rather than counter=0. The boot log for smoke B carried [caught] on the error trace but did NOT carry [atomic]; DGD did not treat the function body as an atomic envelope.
Process identity check: a single DGD process served all requests in each smoke run. The boot log records no ** System halted. entry between requests. No statedump was invoked. No admin_console session was opened. The recompile from non-atomic to atomic between smoke B and smoke A was achieved by editing the source on disk and re-running the isolated-runtime materialization step plus cold-boot (the overlay replaces the isolated runtime staging tree's src/usr/Test/atomic_demo.c); the runtime instances of smoke A and smoke B are different DGD processes.
What the evidence establishes:
- An LPC function declared
atomicthat mutates state and then errors rolls back the mutation when the call is dispatched via HTTP and the error is caught by the calling handler. - The runtime's atomic envelope is observable from outside the runtime (the rollback is detectable by a subsequent HTTP GET).
- The repeating smoke (run twice immediately, both PASS) demonstrates the rollback is not a one-shot effect of cold-boot state.
- The platform's atomic guarantee composes with the platform's HTTP dispatch (
HTTP1_SERVERin eOS-kernellib + the eOS-Harness application-tier server.c + the httpd registry + the application handler insys/atomic_handler.c) without operator-tier admin_console mediation.
What the evidence does not establish:
- The exact mechanism by which the
atomicmodifier is required in smoke B's modifier-less case. Three candidate explanations remain in play after this probe and would each be consistent with the smoke B finding:- (a) Default function-body rollback does not exist in DGD. The eOS-kernellib
docs/lpc-essentials.mdclaim of "Every function call is its own atomic context by default" is simply wrong, and theatomicmodifier is required to declare the function-body envelope. - (b) The catch in the calling object's handler absorbs the default rollback. Without the modifier, the rollback fires until something catches the error; the caller's catch then "consumes" the rollback signal, leaving the mutation visible. The modifier extends the rollback past the catch boundary.
- (c) Cross-object dispatch specifically requires the modifier. The function-body rollback may fire when the error is uncaught and the dispatch is within the same object, but not when the error crosses an object boundary into a different object's catch.
- (a) Default function-body rollback does not exist in DGD. The eOS-kernellib
- The behavior under concurrent requests. The empirical sequence used sequential curl, not concurrent. Whether a concurrent request that began during the increment's atomic envelope would observe an inconsistent intermediate state, or see the rollback as atomic-from-the-outside, is not adjudicated by this evidence.
- The behavior of statedump and snapshot-restore relative to atomic envelopes that span the snapshot boundary. The current evidence ran cold-boot to live; a statedump-restore cycle was not exercised.
- The behavior of clones (cloned instances of
atomic_demo). The current evidence exercises the master object; cloned instances were not probed. The persistence-of-rollback-across-clones question is a separate empirical claim.
What Would Revise It
-
A targeted probe that pins the mechanism. A second probe shape -- the same
increment_with_failure(without modifier) called from a handler that does NOT catch the error, with the error propagating to the dispatcher's outer catch inobj/server::dispatch-- would discriminate among the three candidate explanations. If that probe rolls back, explanation (b) is favored (catch in the immediate caller is what absorbs the default rollback). If the same probe still shows the increment persisting, explanation (a) or (c) is favored. A third probe shape that callsincrement_with_failurefrom a wrapper IN THE SAME OBJECT asatomic_demowould discriminate (a) from (c). The current Observation defers these probes; a future workstream that runs them would tighten this Observation's mechanism claim. -
An upstream DGD-list or Felix Croes citation on atomic-modifier semantics. If a published source from the DGD maintainer specifies the modifier's requirement contract precisely, the docs claim in eOS-kernellib should be reconciled to that source rather than to this probe. The current evidence does not adjudicate between "the docs are wrong" and "the docs are imprecise about a condition the probe falls under"; an upstream citation would.
-
A demonstration that the atomic envelope holds under concurrent dispatch. A concurrent-request probe (multiple simultaneous POST /increment-with-failure plus interleaved GET /counter) showing that no GET observes a counter value other than the pre-mutation value would extend the empirical claim from "atomic rollback observable across sequential dispatch" to "atomic rollback is the only state any concurrent observer sees". Absence of that demonstration leaves the concurrent behavior as the explicit limit of the current Observation.
-
A demonstration that the atomic envelope's rollback covers more than the immediate dataspace mutation. The current evidence rolls back a single
counter += 1. A future probe that mutates multiple dataspace values in sequence (across multiple objects, with explicit->calls) and verifies all of them roll back would extend the claim from "single-counter rollback" to "transactional rollback over a call tree". The Conviction's prose names the latter ("the rollback covers object references, accumulated state mutations across all objects touched in the call tree, queued callouts and events, and per-task resource accounting"); this Observation does not yet measure the multi-mutation case. -
A DGD or eOS-kernellib upgrade that changes atomic-modifier semantics. The current evidence rests on DGD 1.7.9 plus eOS-kernellib
c5b1dee. A future DGD release that changes the runtime's handling ofatomicor a kernel-layer change that reshapes the dispatch path would invalidate the snapshot claim; the pinning is part of the grounds. -
A finding that the
atomicmodifier carries the file-operation prohibition documented in LPC.md §3.4.3 differently in practice than the spec states. The eOS-kernellibdocs/lpc-essentials.mdnotes that the modifier forbids file operations inside the function body and carries a double-tick cost. The current evidence exercises neither file operations nor tick accounting; if a future probe shows that the spec's restrictions differ in practice (e.g., file ops are tolerated, or the tick cost is not doubled), the modifier's contract is wider than this Observation assumes and the Conviction's framing may need adjustment.
Sources
src/usr/Test/atomic_demo.c-- the LPC counter object whoseatomic void increment_with_failure()is the load-bearing function. The atomic modifier is on the function declaration at the top of the body.src/usr/Test/sys/atomic_handler.c-- the HTTP route handler.handleIncrementWithFailure()callscatch(/usr/Test/atomic_demo->increment_with_failure()); on caught error returns 200 OK with the captured error string.src/usr/Test/initd.c-- the Test domain initd compiling bothatomic_demoandsys/atomic_handlerat cold-boot.tests/atomic-rollback-demo.sh-- the smoke script. Three-step curl probe withfinal == initialassertion and PASS/FAIL banner; idempotent across repeated runs.- The instance config -- runtime config (telnet 8023, binary/HTTP 8080, the isolated runtime staging tree's
srcsource directory). - The isolated-runtime materialization step -- the rsync-into-isolated-runtime layout used to materialize the runtime staging tree's
src/from the eOS-kernellib kernel source plus the eOS-Harness application source overlay. eOSContinuum/eOS-kernellibdocs/lpc-essentials.md-- carries the "Every function call is its own atomic context by default" claim that smoke B contradicts. The doc also carries the canonical counter-with-deliberate-failure worked example (lines 184-204 at the doc's current state) thatatomic_demo.cmirrors structurally.dworkin/dgd-- driver source, including the runtime's atomic-envelope handling that the boot log's[atomic]annotation surfaces.
Relations
-
conforms_to::[[Observation Form Contract]]
- Carries
has_epistemic_status::[[Empirical Observation]], names the measurement (the two smoke runs against the live MVA with and without the modifier), states the limits of the measurement (mechanism between the three candidates not adjudicated; concurrent dispatch unmeasured; multi-object call tree unmeasured), and lists concrete revision conditions.
- Carries
-
informs_downstream::[[Agent Operations Commit Wholly or Roll Back Wholly]]
- The atomicity Conviction's "What It Asks" section calls explicitly for the MVA's atomicity evaluation phase to produce empirical evidence. This Observation is that evidence. The Conviction's stance ("every agent operation either commits all of its state changes or reverts the runtime's in-memory state to the pre-operation snapshot") is supported under the dispatch shape probed AND under the condition that the function carrying the operation is declared
atomic. The Conviction's prose does not currently surface the modifier requirement; a future amendment to the Conviction may want to be explicit that agent operations are atomic-modified by convention, not by default.
- The atomicity Conviction's "What It Asks" section calls explicitly for the MVA's atomicity evaluation phase to produce empirical evidence. This Observation is that evidence. The Conviction's stance ("every agent operation either commits all of its state changes or reverts the runtime's in-memory state to the pre-operation snapshot") is supported under the dispatch shape probed AND under the condition that the function carrying the operation is declared
-
grounded_in::[[Cloud Server (Croes, 2012, rev. 2026)]]
- The Cloud Server Reference's eight-primitives table marks atomicity as cloud-server "Provided; the atomicity demo flows directly". This Observation is the demonstration that the Reference's mapping anticipated; the foundation layer's
atomic-modifier handling plus the eOS-kernellib HTTP/1 platform plus this Observation's application dispatcher compose into the empirical sequence cited above.
- The Cloud Server Reference's eight-primitives table marks atomicity as cloud-server "Provided; the atomicity demo flows directly". This Observation is the demonstration that the Reference's mapping anticipated; the foundation layer's
-
grounded_in::[[Hot Reload Is a Runtime Operation, Not a Deployment Event]]
- The hot-reload Observation established that recompiling
/usr/Test/hello.cvia POST /compile updates the next HTTP response without restart. This Observation's dispatch path inherits the same HTTP platform layer (WWW/initd, WWW/sys/httpd, WWW/obj/server). The hot-reload Observation grounds the dispatch-path side of this Observation's setup; without that platform layer being verified, this smoke's results would be confounded by uncertainty about the dispatch path itself.
- The hot-reload Observation established that recompiling
-
contrasts_with::[[An LPC Function Without the atomic Modifier Does Not Roll Back on Caught Error]]
- The negative-case peer Observation. The two together cover the with-modifier and without-modifier cases of the same runtime behavior; reading one without the other gives an incomplete picture of the platform's actual atomicity surface. The peer was authored 2026-05-17 alongside two additional mechanism probes (probe C same-object, probe D no-immediate-catch) that ruled out the cross-object-specific and immediate-caller-catch hypotheses this Observation had named as candidates; the surviving candidates (a) modifier always required and (b2) any-catch-anywhere absorbs are observationally equivalent in any realistic application path.