- 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 LPC Function Without the atomic Modifier Does Not Roll Back on Caught Error
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 WITHOUT the atomic modifier that mutates dataspace and then calls error() does NOT roll back its mutations when the error is caught anywhere up the call stack. The empirical evidence covers three dispatch shapes:
- Smoke B (from the paired positive Observation): HTTP route handler catches a cross-object
->call to a modifier-less function. Result: mutation persists. - Probe C: Same-object catch wrapper calls a modifier-less function locally (no
->operator). Result: mutation persists. - Probe D: HTTP route handler invokes a cross-object
->call to a modifier-less function with NO catch at the immediate handler; the error propagates to/usr/WWW/obj/server::dispatch's outer catch at line 143. Result: mutation persists.
In all three cases the boot-log error trace carries the [caught] annotation but does NOT carry the [atomic] annotation that DGD emits when an atomic envelope is engaged. The runtime treats the modifier-less function body as ordinary (non-transactional) execution; statements commit immediately, and the error fires after the commits without a rollback path.
Grounds
This is an Empirical Observation. The grounds are the paired positive Observation's smoke B transcript plus two additional probes (C and D) authored in the same phase. The probes are HTTP-reachable and were exercised against the live runtime 2026-05-17.
Setup (identical to the paired positive Observation):
- DGD 1.7.9; eOS-kernellib at upstream HEAD
c5b1dee; the eOS-Harness application layer through the prior probe round; the instance config at the repo root; the isolated runtime staging tree layout. - New probe functions in
src/usr/Test/atomic_demo.c:probe_increment_no_atomic(mutatesprobe_counter, then errors; NO modifier),probe_c_same_object_caught(same-object wrapper that catchesprobe_increment_no_atomic), andquery_probe(readsprobe_counter). - New probe routes in
src/usr/Test/sys/atomic_handler.c:GET /probe-counter,GET /probe-c,POST /probe-d. The probe-d handler intentionally does NOT wrap the cross-object call in catch, so the error propagates to the outer dispatcher.
Probe C transcript (same-object dispatch; caught):
$ curl -s http://127.0.0.1:8080/probe-counter
probe_counter=0
$ curl -s http://127.0.0.1:8080/probe-c
probe_c_caught=1
$ curl -s http://127.0.0.1:8080/probe-counter
probe_counter=1
The same-object wrapper probe_c_same_object_caught calls probe_increment_no_atomic() locally (no -> operator) wrapped in catch. The catch fires (probe_c_caught=1); the error was observed. probe_counter advances from 0 to 1, so the increment is NOT rolled back.
Probe D transcript (cross-object dispatch; NO immediate catch; outer dispatcher catch):
$ curl -sS -X POST http://127.0.0.1:8080/probe-d
500 Internal Server Error
probe: deliberate failure without atomic modifier
$ curl -s http://127.0.0.1:8080/probe-counter
probe_counter=2
The handler dispatches to /usr/Test/atomic_demo->probe_increment_no_atomic() WITHOUT wrapping the call in catch. The error propagates through the handler's handle() (no catch in that path) up to /usr/WWW/obj/server::dispatch at line 143 where err = catch(result = handler->handle(...)) catches it; server.c then emits a 500 response. probe_counter advances from 1 to 2 (the prior probe C run left it at 1), so the increment is NOT rolled back even when the immediate caller does not catch.
Boot-log error traces (key evidence):
** probe: deliberate failure without atomic modifier [caught]
/usr/WWW/obj/server#NNN
429 receiveBytes /usr/HTTP/lib/Connection1
99 receiveHeaders /usr/HTTP/api/lib/Server1
183 receiveRequest /usr/WWW/obj/server
143 dispatch /usr/WWW/obj/server
/usr/Test/sys/atomic_handler
140 handle /usr/Test/sys/atomic_handler
99 handleProbeC /usr/Test/sys/atomic_handler
/usr/Test/atomic_demo
71 * probe_c_same_object_caught /usr/Test/atomic_demo
56 probe_increment_no_atomic /usr/Test/atomic_demo
** probe: deliberate failure without atomic modifier [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
143 handle /usr/Test/sys/atomic_handler
116 handleProbeD /usr/Test/sys/atomic_handler
/usr/Test/atomic_demo
56 probe_increment_no_atomic /usr/Test/atomic_demo
The * marks the catch frame. Neither trace carries the [atomic] annotation that DGD emits for atomic envelopes (visible in the paired positive Observation's smoke A trace ** deliberate failure for atomicity demonstration [atomic]). The runtime does not engage an atomic envelope at any frame in either probe; the absence of the annotation is the runtime's own evidence that no rollback is in flight when the error propagates.
Mechanism discrimination outcomes:
The paired positive Observation named three candidate mechanisms to explain smoke B's modifier-less persistence:
- (a) The modifier is always required. Without
atomic, the runtime does not mark the function body as transactional, so no rollback fires regardless of catch position. Predicted: probe C persists, probe D persists. Observed: both persist. Consistent with (a). - (b1) The catch in the IMMEDIATE caller absorbs a default rollback. Predicted: probe D rolls back (the immediate handler doesn't catch). Observed: probe D persists. Rules out (b1).
- (c) Cross-object dispatch specifically requires the modifier. Predicted: probe C rolls back (same-object dispatch is the default case). Observed: probe C persists. Rules out (c).
After the probes, the candidate (b2) -- any catch up the stack absorbs a putative default rollback -- remains formally undistinguished from (a). Constructing a no-catch-anywhere probe is impractical in this MVA: the outer /usr/WWW/obj/server::dispatch always catches errors propagating from application handlers (otherwise a deliberate-error route would crash the per-connection server clone); the operator console's code verb similarly wraps evaluation in a catch. Both (a) and (b2) lead to the same operational conclusion: in any realistic application path with a catch somewhere up the stack, the atomic modifier is required for function-body rollback.
What the evidence establishes:
- The eOS-kernellib
docs/lpc-essentials.mdclaim that "Every function call is its own atomic context by default. If the function body errors, every dataspace mutation it performed is rolled back as if the call had never happened" is contradicted across three dispatch shapes (cross-object/caught/immediate; same-object/caught; cross-object/uncaught-with-outer-catch). - The
atomicmodifier is the operative trigger for function-body rollback. The boot-log[atomic]annotation is the runtime's own signal that an atomic envelope is engaged; the annotation is absent in all modifier-less probes. - Mechanism candidates (b1) and (c) are ruled out empirically. (a) and (b2) survive as observationally equivalent in any normal application context.
What the evidence does not establish:
- The pure-(a)-vs-(b2) distinction. Constructing a probe with no catch anywhere in the call chain would require modifying the eOS-kernellib runtime's outer dispatch path or running the probe from a fully uncaught DGD top-level context. Neither is part of the current MVA scope.
- Whether the docs' claim applies to a NARROWER reading that some other empirical setup would satisfy. For example, atomicity for kfun execution (where the kfun completes atomically or not at all) is a separate property that the modifier-less probes do not test. The Observation's contradiction is specifically against the function-body interpretation of the claim.
- The behavior under concurrent dispatch, statedump-restore cycles, or cloned-instance variants. Same limits as the paired positive Observation.
What Would Revise It
-
A targeted no-catch-anywhere probe. A probe that exercises the modifier-less function in a path where no catch sits anywhere up the stack (constructible only with runtime modification or a custom DGD top-level invocation) would distinguish (a) from (b2). If such a probe shows a rollback, (b2) is favored over (a). If it still shows persistence, (a) is favored. Either outcome leaves the practical conclusion unchanged.
-
An upstream citation from DGD's maintainer specifying
atomicsemantics. A message from DGD's author, an LPC specification section, or DGD documentation that pins down the modifier's contract precisely would adjudicate (a) vs (b2) by reference rather than by probe. The current evidence does not adjudicate between "the docs are wrong about default function-body atomicity" and "the docs are imprecise about a condition the probe falls under" by empirical means alone. -
A future DGD or eOS-kernellib release that changes atomic-modifier semantics. The evidence rests on DGD 1.7.9 + eOS-kernellib
c5b1dee. A future runtime release that makes function-body atomicity automatic, or that changes the way catch interacts with atomic envelopes, would shift the empirical foundation. -
A demonstration that the same probes show different behavior under a different dispatch mechanism. The current probes route through eOS-kernellib's HTTP/1 dispatcher. If a future eOS-Harness build adds a non-HTTP transport (operator console, gRPC, etc.) and the probes show different behavior under that transport, the Observation's claim is narrower than currently stated and must be qualified accordingly.
Sources
src/usr/Test/atomic_demo.c-- carries the modifier-lessprobe_increment_no_atomic, the same-object wrapperprobe_c_same_object_caught, and thequery_probereader. The shape mirrors the paired positive Observation'sincrement_with_failurebut uses a separateprobe_counterso that demo's empirical state is not contaminated.src/usr/Test/sys/atomic_handler.c-- carries the/probe-counter,/probe-c, and/probe-droute handlers.handleProbeDintentionally does NOT wrap the cross-object call in catch./usr/WWW/obj/server.c:143(the eOS-Harness application HTTP server) -- the outer catch that fires for probe D. The catch is the platform's safety net against uncaught application errors; without it a deliberate-error route would crash the per-connection server clone.docs/lpc-essentials.mdin eOS-kernellib -- carries the "Every function call is its own atomic context by default" claim contradicted by this Observation. The claim's specific wording is preserved verbatim in this Observation's Claim section so a future reader can adjudicate without re-fetching the docs source.- [[An atomic LPC Function Body Rolls Back on Deliberate Error in an HTTP-Dispatched Call]] -- the peer Observation this Observation discriminates the mechanism for. That Observation establishes smoke A and smoke B; this Observation extends the empirical surface with probes C and D.
Relations
-
conforms_to::[[Observation Form Contract]]
- Carries
has_epistemic_status::[[Empirical Observation]], names the measurement (probes C and D against the live MVA), states the limits of the measurement (no-catch-anywhere case not constructible in this MVA), and lists concrete revision conditions.
- Carries
-
informed_by::[[An atomic LPC Function Body Rolls Back on Deliberate Error in an HTTP-Dispatched Call]]
- The peer Observation's smoke B framed the negative-case claim as a finding with three candidate mechanism explanations and a
contrasts_with::link reserved for this Observation. Probes C and D narrow the candidate set to (a) and (b2) -- observationally equivalent in any realistic application context. The two Observations together form the empirical evidence base: the peer Observation carries the positive claim (atomic-modified function body rolls back) and the smoke-B negative-case framing; this Observation carries the negative claim refined to (a)/(b2) by additional probes.
- The peer Observation's smoke B framed the negative-case claim as a finding with three candidate mechanism explanations and a
-
informs_downstream::[[Agent Operations Commit Wholly or Roll Back Wholly]]
- The atomicity 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 condition that the agent operation's function is declared
atomic. The Conviction's prose currently does not surface this condition; this Observation strengthens the empirical case for amending the Conviction to make the modifier-by-convention requirement explicit, so that "agent operation" is unambiguously "atomic-modified function body" in the Conviction's load-bearing claim.
- The atomicity 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 condition that the agent operation's function 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 refines the framing: the foundation provides the modifier-engaged rollback mechanism; the application author must declare the function
atomicto get the rollback. The Reference does not name the modifier requirement; this Observation surfaces it as part of this graph's empirical foundation.
- The Cloud Server Reference's eight-primitives table marks atomicity as cloud-server "Provided; the atomicity demo flows directly". This Observation refines the framing: the foundation provides the modifier-engaged rollback mechanism; the application author must declare the function
-
contrasts_with::[[An atomic LPC Function Body Rolls Back on Deliberate Error in an HTTP-Dispatched Call]]
- The peer Observation is the positive-case peer. The two Observations 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.