- 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]]↗
A Regular User Cannot Kick from a Chat Room
Claim
In the eOS-Harness MVA running against DGD 1.7.9 plus the eOSContinuum/eOS-kernellib feature/cohesive-lift branch carrying the kernel-layer cohesive lift, an admin verb in an application-tier daemon that calls a private capability-check helper at the public-LFUN entry rejects the call when the actor holds no admin-token covering the target room and action. The dispatch shape is: a boot-time test driver clones three users and one room, then has a no-token user call admin->kick(actor, target, room). The capability check walks the actor's empty chat-user.admin-tokens list, finds no match, and calls error(...). The error propagates up through kick's frame and lands in the test driver's catch{}. The verb body never runs past the check; the target's chat-room.member-list membership is unchanged. The test driver asserts the unchanged membership and writes a CAP-REJECT OK sentinel to its result file. The boot log records the error with a [caught] annotation -- DGD logs every error regardless of catch{} so the platform retains a trace, with the caught/uncaught distinction in the suffix. The same kick LFUN that accepts the token-holding actor in the paired Observation rejects the no-token actor; the gate is parameterized on the actor's token holdings, not on the caller's program or the LFUN's name.
Grounds
This is an Empirical Observation. The grounds are a captured cold-boot smoke run 2026-05-27 against the live MVA demonstration instance with examples/chat-app/ deployed at the isolated runtime staging tree's src/usr/Chat/. Phase 1 of the boot-time test driver exercises the rejection path; phase 2 (the paired positive Observation) exercises the acceptance path. Both phases share the same cold-boot DGD process.
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-kernellibonfeature/cohesive-liftat HEAD456716c(the chat-app commit; built atop kernel-layer baselineecb4e58). - Application layer:
examples/chat-app/(this commit's deliverable), deployed into the isolated runtime staging tree atsrc/usr/Chat/. - Runtime config: the instance config, telnet
127.0.0.1:8023, binary/HTTP127.0.0.1:8080, the isolated runtime staging tree layout.
Load-bearing source (the rejection branch in examples/chat-app/sys/admin.c):
private
void _check_admin_token(object actor, object room, string action)
{
mixed *tokens;
int i, sz;
if (!actor) {
error("admin: nil actor for " + action);
}
tokens = actor->query_admin_tokens();
sz = sizeof(tokens);
for (i = 0; i < sz; i++) {
/* ... match logic; falls through if no token matches ... */
}
error("admin: actor " + (actor->query_chat_name() ? actor->query_chat_name() : "(unnamed)") +
" not authorized for " + action + " in room " +
(room ? (room->query_id() ? room->query_id() : "(unnamed)") : "(realm)"));
}
Boot sequence: a clean cold boot against the isolated runtime staging tree, with examples/chat-app/ materialized at src/usr/Chat/ and the instance config loaded, reaching ** Initialization complete. in under one second.
** DGD 1.7.9
** Initializing...
** Initialization complete.
Phase 1 smoke transcript (the load-bearing portion of the sentinel result log):
ChatApp:test: starting
ChatApp:test: CAP-REJECT OK
ChatApp:test: CAP-ACCEPT OK
Phase 1 boot-log trace of the rejection:
** admin: actor bob not authorized for kick in room ChatApp:Room:LobbyA [caught]
/usr/Chat/sys/test
58 setup_and_run /usr/Chat/sys/test
96 * run_tests /usr/Chat/sys/test
/usr/Chat/sys/admin
52 kick /usr/Chat/sys/admin
148 _check_admin_token /usr/Chat/sys/admin
The frame walk reads bottom-up: _check_admin_token raised the error at line 148; the error propagated up through kick at line 52; the test driver's run_tests at line 96 caught it via its catch{} block. The [caught] suffix on the error line confirms the error did not propagate to the runtime -- DGD's convention is to log every error regardless of catch{} so the platform retains a trace, with the caught/uncaught distinction in the suffix.
CAP-REJECT OK writes to the sentinel file only when:
admin->kick(bob, alice, room_a)threw (the capability check rejected the call); the test driver'srejectedflag was set insidecatch{}.member(alice, room_a->query_members())returned true (alice's membership in room_a was unchanged by the rejected call -- the verb body never ran).
Both assertions ran inside catch{} blocks. The driver continued to phase 2 only after phase 1's PASS.
Process identity check: the same DGD process served phase 1 and phase 2. The boot log records no ** System halted. entry between phases. No statedump was invoked. No admin_console session was opened.
What the evidence establishes:
- A capability check at the public-LFUN entry rejects the call when the actor's token list contains no matching token. The rejection fires before any verb-body code runs.
- The rejection is observable two ways: as a thrown error the caller must
catch{}, and as a[caught]annotation in the boot log naming the actor and the requested action. - The verb body's state-mutating code (
chat::leave_room) does NOT run on the rejection path. The room's member list is unchanged after the rejected call. - The same LFUN that accepts the token-holding actor in the paired Observation rejects the no-token actor; the gate is parameterized on the actor's holdings, not on the caller's identity or the verb's name.
- The rejection signal carries enough information for diagnostics (actor name, action, room id) without leaking implementation detail (no token-list dump, no internal LFUN frame names).
What the evidence does not establish:
- Whether a sufficiently determined caller could bypass the capability check via direct property mutation. A caller with a handle to the room object could write to
chat-room.member-listviaroom->set_property(...). The capability check sits at the LFUN entry; the property-write path is not gated at MVA scope. See the positive Observation's "What the evidence does not establish" for the same boundary. - Whether the rejection holds against a malformed token. A token with
query_actions() == nilwould short-circuit the innermembercheck; the smoke does not exercise this. A future hardening probe would construct deliberately-malformed tokens and assert rejection. - Whether the rejection holds when the actor object is destructed mid-call. The smoke uses live, non-destructed actor objects. Concurrent destruction of the actor object during the capability check is not exercised.
- Whether the rejection composes with statedump. A future persistence-demonstration phase will exercise the persistence-of-capability-tokens path; the current evidence is cold-boot only.
What Would Revise It
- A demonstration that the rejection holds against direct cross-domain dispatch. A future probe that, from a different domain, calls
ADMIN_DAEMON->kick(no_token_actor, target, room)viacall_otheragainst the same admin daemon, asserting that the capability check still rejects the call, would extend the claim from "rejection holds against in-domain test-driver call" to "rejection holds against cross-domain caller". - A property-write bypass probe. A probe that has the no-token actor (or a hostile object) write directly to
room->set_property("chat-room.member-list", new_list), observing whether the membership mutates, would document the MVA-scope LFUN-only-gate boundary explicitly. If the property write succeeds, the boundary is exactly where MVA scope says it is. If the property write fails (because of some other discipline in/lib/util/propertiesor auto-chain that the MVA hasn't surveyed), the boundary is tighter than the MVA assumed. - A malformed-token probe. A probe that issues a token with
set_actions(nil)orset_roompointing at a destructed object, then callskick(actor, target, room), asserting that the check rejects the call rather than accepting it on a degenerate match path, would harden the empirical claim against degenerate-token attacks. - A token-tampering probe. LWOs in DGD are not language-level immutable. A probe that mutates an attached token's
_actionsfield via direct LPC access between issuance and verb-call, asserting that either (a) the runtime prevents the tampering or (b) the check still operates against the tampered list and accepts the now-broader scope, would document the LWO mutation surface explicitly. The current evidence assumes tokens are convention-immutable. - Concurrent dispatch under rejection. The empirical sequence uses sequential boot-time calls. A concurrent-dispatch probe with multiple simultaneous no-token actors attempting
kickwould extend the claim from "sequential rejection holds" to "concurrent rejection holds without race". - A DGD or eOS-kernellib upgrade that changes LFUN-entry semantics or error-propagation surface. The current evidence rests on DGD 1.7.9 plus
feature/cohesive-liftHEAD456716c. A future runtime release that changes the cost or semantics oferror()inside aprivateLFUN, or changes howcatch{}interacts with cross-object dispatch, would invalidate the snapshot claim.
Sources
examples/chat-app/sys/admin.c(ineOS-kernellib feature/cohesive-lift) -- the application-daemon source whosekickLFUN is the load-bearing verb. The_check_admin_tokenhelper'serror(...)call at the end of its body is the rejection point.examples/chat-app/data/admin_token.c-- the capability LWO. A clone of bob has no entry in itschat-user.admin-tokenslist because nogrant_admincall was made to attach one.examples/chat-app/obj/room.c-- the Room clonable.query_membersexposes the member list; the unchanged list after the rejected call is what phase 1's assertion checks.examples/chat-app/obj/user.c-- the User clonable.query_admin_tokensreturns an empty array for bob at phase 1.examples/chat-app/sys/test.c-- the boot-time test driver. Phase 1 callskick(bob, alice, room_a)insidecatch{}and assertsrejected == 1ANDmember(alice, room_a->query_members())returns true (unchanged).examples/chat-app/README.md-- the deployment + verify recipe.docs/chat-applications.md(ineOS-kernellib feature/cohesive-lift) -- the walkthrough doc; the "Phase 1 -- CAP-REJECT" section quotes the boot-log trace shape.- The isolated runtime staging tree's
src/usr/Chat/data/test-result.log-- the sentinel file. CarriesCAP-REJECT OKafter a clean cold-boot. - The isolated runtime staging tree's boot log -- carries the
[caught]annotation on the rejection error. - The instance config (telnet 8023, binary/HTTP 8080, the isolated runtime staging tree's source directory).
src/usr/Merry/sys/merry.c(ineOS-kernellib) -- the_check_registrarcapability gate whose rejection path_check_admin_tokenmirrors; same private-helper-throws-with-context pattern.
Relations
-
conforms_to::[[Observation Form Contract]]
- Carries
has_epistemic_status::[[Empirical Observation]], names the measurement (one cold-boot smoke run with two assertions insidecatch{}), states the limits of the measurement (LFUN-entry gate only; property-write bypass not closed; malformed-token cases unexamined; sequential dispatch), and lists concrete revision conditions.
- Carries
-
informs_downstream::[[Capability Boundaries Are Runtime-Enforced, Not Policy-Checked]]
- The capability-separation Conviction's "What It Asks" anticipates this evidence: a capability check that throws rather than returning a permission code, with the verb body never running on the rejection path. This Observation is the negative half; the paired positive Observation shows the same gate's acceptance path.
-
grounded_in::[[Conviction Form Contract]]
- The rejection mechanism inherits the same "runtime, not policy" stance the Conviction names. The application's
_check_admin_tokenis a runtime check (it cannot be opted out of by the verb body), not a policy hint (the calling object cannot ignore the rejection -- the LFUN raises an error that propagates until caught).
- The rejection mechanism inherits the same "runtime, not policy" stance the Conviction names. The application's
-
contrasts_with::[[An Admin-Capability-Holding User Can Kick from a Chat Room]]
- The positive-case peer Observation. The two together cover the with-token and without-token cases of the same kick LFUN. Reading one without the other gives an incomplete picture: this case alone shows rejection but not what acceptance looks like; the positive case alone shows acceptance but not what the rejection signal carries. The paired sentinels (
CAP-REJECT OKandCAP-ACCEPT OK) land in the same result file across the same cold-boot.
- The positive-case peer Observation. The two together cover the with-token and without-token cases of the same kick LFUN. Reading one without the other gives an incomplete picture: this case alone shows rejection but not what acceptance looks like; the positive case alone shows acceptance but not what the rejection signal carries. The paired sentinels (