kernel: Implement capability transactions

7f768ce834731c8e71cc2366e6628904fac995a997b234afc4f0c75317bcb9a5
Assisted-by: Codex:gpt-6
Alexis Sellier committed ago 1 parent b13595ab
kernel/kernel.rad +1 -0
9 9
export mod range;
10 10
export mod sync;
11 11
export mod platform;
12 12
export mod trap;
13 13
export mod capability;
14 +
export mod transactions;
14 15
export mod frames;
15 16
export mod backing;
16 17
export mod pages;
17 18
export mod boot;
18 19
@test export mod tests;
kernel/kernel/pages.rad +53 -0
4 4
use super::limits;
5 5
use super::slots;
6 6
use super::frames;
7 7
use super::backing;
8 8
use super::capability;
9 +
use super::transactions;
9 10
use super::platform;
10 11
11 12
/// Page capability rights created by physical allocation.
12 13
export constant DEFAULT_RIGHTS: u16 = abi::READ | abi::WRITE | abi::GRANT | abi::TRANSFER;
13 14
146 147
        return;
147 148
    }
148 149
    try! slots::release(&mut store.slots[..], object);
149 150
    try! backing::releasePage(&mut store.backings, page.backing);
150 151
}
152 +
153 +
/// Acquire page lifetime and publish an attenuated capability into a resolved table.
154 +
unsafe fn receive(store: &mut Store, target: &mut capability::Table, owner: abi::Ref,
155 +
    source: abi::Handle, entry: capability::Entry, rights: u64) -> abi::Handle throws (abi::Error)
156 +
{
157 +
    let receiver = target.owner;
158 +
    let pending = try transactions::reserve(target, owner, source, entry, rights, false);
159 +
    try retain(store, entry.object, receiver) catch err {
160 +
        transactions::cancel(target, pending); throw err;
161 +
    };
162 +
    return transactions::grant(target, pending);
163 +
}
164 +
165 +
/// Grant page authority to a separately resolved domain table.
166 +
/// The caller serializes both tables and page metadata through publication.
167 +
export unsafe fn grant(store: &mut Store, source: &capability::Table, target: &mut capability::Table,
168 +
    handle: abi::Handle, rights: u64) -> abi::Handle throws (abi::Error)
169 +
{
170 +
    let entry = try capability::lookup(source, handle, abi::Kind::Page, abi::Rights(0));
171 +
    return try receive(store, target, source.owner, handle, entry, rights);
172 +
}
173 +
174 +
/// Create a narrower page capability in the calling domain's table.
175 +
export unsafe fn selfGrant(store: &mut Store, table: &mut capability::Table, handle: abi::Handle, rights: u64)
176 +
    -> abi::Handle throws (abi::Error)
177 +
{
178 +
    let entry = try capability::lookup(table, handle, abi::Kind::Page, abi::Rights(0));
179 +
    let owner = table.owner;
180 +
    return try receive(store, table, owner, handle, entry, rights);
181 +
}
182 +
183 +
/// Move page authority without changing its handle count or ending source exposure.
184 +
/// The caller serializes the resolved domain tables and page metadata.
185 +
export unsafe fn transfer(store: &mut Store, source: &mut capability::Table, target: &mut capability::Table,
186 +
    handle: abi::Handle, rights: u64) -> abi::Handle throws (abi::Error)
187 +
{
188 +
    let entry = try capability::lookup(source, handle, abi::Kind::Page, abi::Rights(0));
189 +
    let page = try get(store, entry.object);
190 +
    let receiver = target.owner;
191 +
    let pending = try transactions::reserve(target, source.owner, handle, entry, rights, true);
192 +
    try backing::expose(&mut store.backings, page.backing, receiver) catch err {
193 +
        transactions::cancel(target, pending); throw err;
194 +
    };
195 +
    return transactions::transfer(source, target, pending);
196 +
}
197 +
198 +
/// Drop a page capability and release its object reference under serialization.
199 +
export fn drop(store: &mut Store, table: &mut capability::Table, handle: abi::Handle) throws (abi::Error) {
200 +
    let entry = try capability::lookup(table, handle, abi::Kind::Page, abi::Rights(0));
201 +
    try release(store, entry.object);
202 +
    let removed = try! capability::invalidate(table, handle);
203 +
}
kernel/kernel/tests.rad +1 -0
8 8
export mod trap;
9 9
export mod capability;
10 10
export mod frames;
11 11
export mod backing;
12 12
export mod pages;
13 +
export mod transactions;
kernel/kernel/tests/pages.rad +88 -2
10 10
11 11
/// Page and allocation metadata workspace.
12 12
unsafe static STORE: pages::Store = undefined;
13 13
/// Test domain's capability table.
14 14
unsafe static TABLE: capability::Table = undefined;
15 +
/// Receiver table for page capability transactions.
16 +
unsafe static TARGET: capability::Table = undefined;
15 17
/// Mapped bytes with room for page alignment and eight full pages.
16 18
static RAM: [u8; 36864] = [0; 36864];
17 19
/// Byte offset to the first page-aligned test frame.
18 20
static OFFSET: u32 = 0;
19 21
41 43
    });
42 44
}
43 45
44 46
/// Drop the test's handle and its object reference in the same serialized operation.
45 47
unsafe fn drop(handle: abi::Handle) {
46 -
    let entry = try! capability::invalidate(&mut TABLE, handle);
47 -
    try! pages::release(&mut STORE, entry.object);
48 +
    try! pages::drop(&mut STORE, &mut TABLE, handle);
49 +
}
50 +
51 +
/// Transfers keep both domains exposed without duplicating page handles.
52 +
@test unsafe fn transferExposure() throws (testing::TestError) {
53 +
    initialize();
54 +
    let receiver = abi::Ref { index: 1, generation: 1 };
55 +
    capability::initialize(&mut TARGET, receiver);
56 +
    try! backing::registerDomain(&mut STORE.backings, receiver);
57 +
    let original = try! pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 8);
58 +
    let entry = try! capability::get(&TABLE, original);
59 +
    let moved = try! pages::transfer(&mut STORE, &mut TABLE, &mut TARGET, original, abi::READ as u64);
60 +
    let page = try! pages::get(&STORE, entry.object);
61 +
    try testing::expect(page.handles == 1 and STORE.backings.records[page.backing.index].exposed == 2);
62 +
    let actual = try! capability::get(&TARGET, moved);
63 +
    try testing::expect(*actual.rights == abi::READ);
64 +
    try! pages::drop(&mut STORE, &mut TARGET, moved);
65 +
    try! backing::endDomain(&mut STORE.backings, receiver);
66 +
    try testing::expect(not STORE.backings.pool.free[0]);
67 +
    try! backing::endDomain(&mut STORE.backings, TABLE.owner);
68 +
    try testing::expect(STORE.backings.pool.free[0]);
69 +
}
70 +
71 +
/// Grants acquire references only after capacity and receiver checks succeed.
72 +
@test unsafe fn grantRollback() throws (testing::TestError) {
73 +
    initialize();
74 +
    let receiver = abi::Ref { index: 1, generation: 1 };
75 +
    capability::initialize(&mut TARGET, receiver);
76 +
    let original = try! pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 2);
77 +
    let entry = try! capability::get(&TABLE, original);
78 +
    let page = try! pages::get(&STORE, entry.object);
79 +
    let mut dead = false;
80 +
    try pages::grant(&mut STORE, &TABLE, &mut TARGET, original, abi::READ as u64) catch err {
81 +
        try testing::expect(err == abi::Error::BadHandle); set dead = true;
82 +
    };
83 +
    try testing::expect(dead and TARGET.slots[0].state == slots::State::Free);
84 +
    let mut rejected = false;
85 +
    try pages::transfer(&mut STORE, &mut TABLE, &mut TARGET, original, abi::READ as u64) catch err {
86 +
        try testing::expect(err == abi::Error::BadHandle); set rejected = true;
87 +
    };
88 +
    try testing::expect(rejected and TARGET.slots[0].state == slots::State::Free);
89 +
    try testing::expect(try! capability::get(&TABLE, original) == entry);
90 +
    try! backing::registerDomain(&mut STORE.backings, receiver);
91 +
    for i in 0..limits::HANDLES {
92 +
        let filler = try! capability::install(&mut TARGET, capability::Entry {
93 +
            kind: abi::Kind::Domain, object: receiver, rights: abi::Rights(0),
94 +
        });
95 +
    }
96 +
    let mut full: u32 = 0;
97 +
    try pages::grant(&mut STORE, &TABLE, &mut TARGET, original, abi::READ as u64) catch err {
98 +
        try testing::expect(err == abi::Error::InvalidArg); set full += 1;
99 +
    };
100 +
    try pages::transfer(&mut STORE, &mut TABLE, &mut TARGET, original, abi::READ as u64) catch err {
101 +
        try testing::expect(err == abi::Error::InvalidArg); set full += 1;
102 +
    };
103 +
    try testing::expect(full == 2 and STORE.records[entry.object.index].handles == 1);
104 +
    try testing::expect(STORE.backings.records[page.backing.index].exposed == 1);
105 +
    try testing::expect(try! capability::get(&TABLE, original) == entry);
106 +
    let filler = try! capability::at(&TARGET, 0, abi::Kind::Domain);
107 +
    let removed = try! capability::invalidate(&mut TARGET, filler);
108 +
    let granted = try! pages::grant(&mut STORE, &TABLE, &mut TARGET, original, abi::READ as u64);
109 +
    try testing::expect(STORE.records[entry.object.index].handles == 2);
110 +
    try testing::expect(STORE.backings.records[page.backing.index].exposed == 2);
111 +
    drop(original);
112 +
    try! pages::drop(&mut STORE, &mut TARGET, granted);
113 +
    try! backing::endDomain(&mut STORE.backings, TABLE.owner);
114 +
    try testing::expect(not STORE.backings.pool.free[0]);
115 +
    try! backing::endDomain(&mut STORE.backings, receiver);
116 +
    try testing::expect(STORE.backings.pool.free[0]);
117 +
}
118 +
119 +
/// Self-attenuation adds one reference, preserves authority, and prevents splitting.
120 +
@test unsafe fn selfAttenuation() throws (testing::TestError) {
121 +
    initialize();
122 +
    let original = try! pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 2);
123 +
    let copy = try! pages::selfGrant(&mut STORE, &mut TABLE, original, abi::READ as u64);
124 +
    let entry = try! capability::get(&TABLE, copy);
125 +
    try testing::expect(*entry.rights == abi::READ and STORE.records[entry.object.index].handles == 2);
126 +
    let narrowed = try! pages::selfGrant(&mut STORE, &mut TABLE, copy, abi::WRITE as u64);
127 +
    let none = try! capability::get(&TABLE, narrowed);
128 +
    try testing::expect(*none.rights == 0);
129 +
    drop(narrowed); drop(copy);
130 +
    let right = try! pages::split(&mut STORE, &mut TABLE, original, 1);
131 +
    drop(original); drop(right);
132 +
    try! backing::endDomain(&mut STORE.backings, TABLE.owner);
133 +
    try testing::expect(STORE.backings.pool.free[0] and STORE.backings.pool.free[1]);
48 134
}
49 135
50 136
/// New allocations are zeroed completely and carry the documented default rights.
51 137
@test unsafe fn allocation() throws (testing::TestError) {
52 138
    initialize();
kernel/kernel/tests/transactions.rad added +171 -0
1 +
//! Atomic capability publication and delegation rules.
2 +
3 +
use std::testing;
4 +
use kernel::abi;
5 +
use kernel::limits;
6 +
use kernel::slots;
7 +
use kernel::capability;
8 +
use kernel::transactions;
9 +
10 +
/// Sender table used by serialized transactions.
11 +
unsafe static SOURCE: capability::Table = undefined;
12 +
/// Receiver table used by serialized transactions.
13 +
unsafe static TARGET: capability::Table = undefined;
14 +
15 +
/// Initialize two independent domain tables.
16 +
unsafe fn initialize() {
17 +
    capability::initialize(&mut SOURCE, abi::Ref { index: 0, generation: 1 });
18 +
    capability::initialize(&mut TARGET, abi::Ref { index: 1, generation: 1 });
19 +
}
20 +
21 +
/// Install test authority whose object lifetime belongs to the caller.
22 +
unsafe fn install(kind: abi::Kind, rights: u16) -> abi::Handle {
23 +
    return try! capability::install(&mut SOURCE, capability::Entry {
24 +
        kind, object: abi::Ref { index: 7, generation: 1 }, rights: abi::Rights(rights),
25 +
    });
26 +
}
27 +
28 +
/// Transfer failure preserves source authority; successful publication invalidates it.
29 +
@test unsafe fn atomicTransfer() throws (testing::TestError) {
30 +
    initialize();
31 +
    let source = install(abi::Kind::Budget, abi::READ | abi::TRANSFER);
32 +
    let entry = try! capability::get(&SOURCE, source);
33 +
    for i in 0..limits::HANDLES {
34 +
        let filler = try! capability::install(&mut TARGET, entry);
35 +
    }
36 +
    let mut full = false;
37 +
    try move(source) catch err {
38 +
        try testing::expect(err == abi::Error::InvalidArg);
39 +
        set full = true;
40 +
    };
41 +
    try testing::expect(full and try! capability::get(&SOURCE, source) == entry);
42 +
    let first = try! capability::at(&TARGET, 0, abi::Kind::Budget);
43 +
    let removed = try! capability::invalidate(&mut TARGET, first);
44 +
    let moved = try! move(source);
45 +
    let actual = try! capability::get(&TARGET, moved);
46 +
    try testing::expect(actual.object == entry.object and *actual.rights == abi::READ);
47 +
    let mut stale = false;
48 +
    try capability::get(&SOURCE, source) catch err {
49 +
        try testing::expect(err == abi::Error::BadHandle); set stale = true;
50 +
    };
51 +
    try testing::expect(stale);
52 +
}
53 +
54 +
/// Complete a transfer after any required object-specific ownership update.
55 +
unsafe fn move(source: abi::Handle) -> abi::Handle throws (abi::Error) {
56 +
    let entry = try capability::get(&SOURCE, source);
57 +
    let owner = SOURCE.owner;
58 +
    let pending = try transactions::reserve(&mut TARGET, owner, source, entry, abi::READ as u64, true);
59 +
    return transactions::transfer(&mut SOURCE, &mut TARGET, pending);
60 +
}
61 +
62 +
/// Self-grants narrow rights without requiring Grant; budgets remain exclusive.
63 +
@test unsafe fn selfGrant() throws (testing::TestError) {
64 +
    initialize();
65 +
    let source = install(abi::Kind::Events, abi::READ | abi::WRITE);
66 +
    let entry = try! capability::get(&SOURCE, source);
67 +
    let owner = SOURCE.owner;
68 +
    let pending = try! transactions::reserve(&mut SOURCE, owner, source, entry, abi::READ as u64, false);
69 +
    let copy = transactions::grant(&mut SOURCE, pending);
70 +
    let narrowed = try! capability::get(&SOURCE, copy);
71 +
    try testing::expect(*narrowed.rights == abi::READ);
72 +
    try testing::expect(try! capability::get(&SOURCE, source) == entry);
73 +
    let budget = install(abi::Kind::Budget, abi::GRANT | abi::TRANSFER);
74 +
    let budgetEntry = try! capability::get(&SOURCE, budget);
75 +
    let mut denied = false;
76 +
    try probe(owner, budget, budgetEntry, false) catch err {
77 +
        try testing::expect(err == abi::Error::Denied); set denied = true;
78 +
    };
79 +
    try testing::expect(denied);
80 +
}
81 +
82 +
/// Reserve and cancel a policy probe without leaving an outstanding token.
83 +
unsafe fn probe(owner: abi::Ref, handle: abi::Handle, entry: capability::Entry, moving: bool) throws (abi::Error) {
84 +
    let pending = try transactions::reserve(&mut TARGET, owner, handle, entry, abi::RIGHTS_MASK as u64, moving);
85 +
    transactions::cancel(&mut TARGET, pending);
86 +
}
87 +
88 +
/// Nondelegable kinds and missing rights fail before consuming target capacity.
89 +
@test unsafe fn delegation() throws (testing::TestError) {
90 +
    initialize();
91 +
    let mut failures: u32 = 0;
92 +
    for kind in &[abi::Kind::Events, abi::Kind::Interrupt, abi::Kind::Budget, abi::Kind::Page] {
93 +
        let source = install(kind, 0);
94 +
        let entry = try! capability::get(&SOURCE, source);
95 +
        try probe(SOURCE.owner, source, entry, false) catch err {
96 +
            try testing::expect(err == abi::Error::Denied); set failures += 1;
97 +
        };
98 +
    }
99 +
    let events = install(abi::Kind::Events, abi::TRANSFER);
100 +
    let entry = try! capability::get(&SOURCE, events);
101 +
    try probe(SOURCE.owner, events, entry, true) catch err {
102 +
        try testing::expect(err == abi::Error::Denied); set failures += 1;
103 +
    };
104 +
    try testing::expect(failures == 5 and TARGET.slots[0].generation == 1);
105 +
    let interrupt = install(abi::Kind::Interrupt, abi::TRANSFER);
106 +
    let moved = try! move(interrupt);
107 +
    let actual = try! capability::get(&TARGET, moved);
108 +
    try testing::expect(actual.kind == abi::Kind::Interrupt);
109 +
}
110 +
111 +
/// Cancelled object work leaves no published capability and restores capacity.
112 +
@test unsafe fn cancellation() throws (testing::TestError) {
113 +
    initialize();
114 +
    let source = install(abi::Kind::Page, abi::GRANT | abi::READ);
115 +
    let entry = try! capability::get(&SOURCE, source);
116 +
    let owner = SOURCE.owner;
117 +
    let pending = try! transactions::reserve(&mut TARGET, owner, source, entry, abi::READ as u64, false);
118 +
    try testing::expect(TARGET.slots[0].state == slots::State::Reserved);
119 +
    transactions::cancel(&mut TARGET, pending);
120 +
    try testing::expect(TARGET.slots[0].state == slots::State::Free);
121 +
    try testing::expect(try! capability::get(&SOURCE, source) == entry);
122 +
}
123 +
124 +
/// Invalid masks, self-transfers, and budget self-grants preserve table state.
125 +
@test unsafe fn policyBoundaries() throws (testing::TestError) {
126 +
    initialize();
127 +
    let source = install(abi::Kind::Budget, abi::GRANT | abi::TRANSFER);
128 +
    let entry = try! capability::get(&SOURCE, source);
129 +
    let mut failures: u32 = 0;
130 +
    try selfProbe(source, entry, 0, true) catch err {
131 +
        try testing::expect(err == abi::Error::InvalidArg); set failures += 1;
132 +
    };
133 +
    try selfProbe(source, entry, 0, false) catch err {
134 +
        try testing::expect(err == abi::Error::Denied); set failures += 1;
135 +
    };
136 +
    try selfProbe(source, entry, 0xffffffffffffffff, false) catch err {
137 +
        try testing::expect(err == abi::Error::InvalidArg); set failures += 1;
138 +
    };
139 +
    try testing::expect(failures == 3 and SOURCE.slots[1].generation == 1);
140 +
    try testing::expect(try! capability::get(&SOURCE, source) == entry);
141 +
}
142 +
143 +
/// Consume a successful self-directed policy probe without publishing it.
144 +
unsafe fn selfProbe(handle: abi::Handle, entry: capability::Entry, rights: u64, moving: bool) throws (abi::Error) {
145 +
    let owner = SOURCE.owner;
146 +
    let pending = try transactions::reserve(&mut SOURCE, owner, handle, entry, rights, moving);
147 +
    transactions::cancel(&mut SOURCE, pending);
148 +
}
149 +
150 +
/// Only the installed Events handle is protected from drop; attenuated copies can end.
151 +
@test unsafe fn installedEvents() throws (testing::TestError) {
152 +
    initialize();
153 +
    let installed = install(abi::Kind::Events, abi::READ | abi::WRITE);
154 +
    let entry = try! capability::get(&SOURCE, installed);
155 +
    let owner = SOURCE.owner;
156 +
    let pending = try! transactions::reserve(&mut SOURCE, owner, installed, entry, abi::READ as u64, false);
157 +
    let copy = transactions::grant(&mut SOURCE, pending);
158 +
    let mut denied = false;
159 +
    try transactions::dropping(&SOURCE, installed, installed) catch err {
160 +
        try testing::expect(err == abi::Error::Denied); set denied = true;
161 +
    };
162 +
    try testing::expect(denied);
163 +
    let allowed = try! transactions::dropping(&SOURCE, copy, installed);
164 +
    let removed = try! capability::invalidate(&mut SOURCE, copy);
165 +
    try testing::expect(removed == allowed and try! capability::get(&SOURCE, installed) == entry);
166 +
    let mut stale = false;
167 +
    try transactions::dropping(&SOURCE, copy, installed) catch err {
168 +
        try testing::expect(err == abi::Error::BadHandle); set stale = true;
169 +
    };
170 +
    try testing::expect(stale);
171 +
}
kernel/kernel/transactions.rad added +119 -0
1 +
//! Capability reservations held within one serialized object transaction.
2 +
3 +
use super::abi;
4 +
use super::slots;
5 +
use super::capability;
6 +
7 +
/// Metadata for unpublished authority and its source.
8 +
export record Pending: Copy {
9 +
    /// Destination domain generation.
10 +
    target: abi::Ref,
11 +
    /// Reserved slot in the destination table.
12 +
    slot: abi::Ref,
13 +
    /// Source domain generation.
14 +
    owner: abi::Ref,
15 +
    /// Source capability to invalidate on transfer.
16 +
    source: abi::Handle,
17 +
    /// Validated source entry before attenuation.
18 +
    original: capability::Entry,
19 +
    /// Attenuated entry to publish after object work succeeds.
20 +
    entry: capability::Entry,
21 +
    /// True when publication also consumes the source capability.
22 +
    moving: bool,
23 +
}
24 +
25 +
/// Unpublished capacity that must be committed or cancelled in the same table.
26 +
export union Reservation: Once {
27 +
    /// One destination slot and the authority it will receive.
28 +
    Held(Pending),
29 +
}
30 +
31 +
/// Reserve target capacity after delegation checks and rights validation.
32 +
/// The caller supplies a live source entry and keeps both tables serialized
33 +
/// through object validation, lifetime changes, and commit or cancellation.
34 +
export fn reserve(target: &mut capability::Table, owner: abi::Ref, source: abi::Handle,
35 +
    original: capability::Entry, rights: u64, moving: bool) -> Reservation throws (abi::Error)
36 +
{
37 +
    let requested = try abi::rights(rights);
38 +
    let own = owner == target.owner;
39 +
    if moving {
40 +
        if own {
41 +
            throw abi::Error::InvalidArg;
42 +
        }
43 +
        if original.kind == abi::Kind::Events or not abi::permits(original.rights, abi::Rights(abi::TRANSFER)) {
44 +
            throw abi::Error::Denied;
45 +
        }
46 +
    } else {
47 +
        if original.kind == abi::Kind::Interrupt or original.kind == abi::Kind::Budget {
48 +
            throw abi::Error::Denied;
49 +
        }
50 +
        if not own and (original.kind == abi::Kind::Events or not abi::permits(original.rights, abi::Rights(abi::GRANT))) {
51 +
            throw abi::Error::Denied;
52 +
        }
53 +
    }
54 +
    let reservation = try slots::reserve(&mut target.slots[..]) catch err {
55 +
        throw abi::Error::InvalidArg;
56 +
    };
57 +
    match reservation {
58 +
        case slots::Reservation::Held(slot) => return Reservation::Held(Pending {
59 +
            target: target.owner, slot, owner, source, original,
60 +
            entry: capability::Entry { kind: original.kind, object: original.object,
61 +
                rights: abi::Rights(*original.rights & *requested) },
62 +
            moving,
63 +
        }),
64 +
    }
65 +
}
66 +
67 +
/// Check the reserved destination before any publication or cancellation.
68 +
fn require(target: &capability::Table, pending: &Pending) {
69 +
    assert target.owner == pending.target;
70 +
    assert slots::matches(&target.slots[..], pending.slot, slots::State::Reserved);
71 +
}
72 +
73 +
/// Restore target capacity after object validation or lifetime acquisition fails.
74 +
export fn cancel(target: &mut capability::Table, reservation: Reservation) {
75 +
    match reservation {
76 +
        case Reservation::Held(pending) => {
77 +
            require(target, &pending);
78 +
            try! slots::cancel(&mut target.slots[..], slots::Reservation::Held(pending.slot));
79 +
        },
80 +
    }
81 +
}
82 +
83 +
/// Publish a grant after acquiring its object reference and lifetime exposure.
84 +
export fn grant(target: &mut capability::Table, reservation: Reservation) -> abi::Handle {
85 +
    match reservation {
86 +
        case Reservation::Held(pending) => {
87 +
            require(target, &pending);
88 +
            assert not pending.moving;
89 +
            return capability::publish(target, slots::Reservation::Held(pending.slot), pending.entry);
90 +
        },
91 +
    }
92 +
}
93 +
94 +
/// Publish a transfer after object ownership and exposure updates succeed.
95 +
/// The caller holds serialization over both tables for the complete transaction.
96 +
export fn transfer(source: &mut capability::Table, target: &mut capability::Table, reservation: Reservation) -> abi::Handle {
97 +
    match reservation {
98 +
        case Reservation::Held(pending) => {
99 +
            require(target, &pending);
100 +
            assert pending.moving and source.owner == pending.owner;
101 +
            assert try! capability::get(source, pending.source) == pending.original;
102 +
            let result = capability::publish(target, slots::Reservation::Held(pending.slot), pending.entry);
103 +
            let removed = try! capability::invalidate(source, pending.source);
104 +
            return result;
105 +
        },
106 +
    }
107 +
}
108 +
109 +
/// Validate drop authority before object-specific lifetime release.
110 +
/// installedEvents is the domain's original queue handle, or zero before installation.
111 +
export fn dropping(table: &capability::Table, handle: abi::Handle, installedEvents: abi::Handle)
112 +
    -> capability::Entry throws (abi::Error)
113 +
{
114 +
    let entry = try capability::get(table, handle);
115 +
    if handle == installedEvents {
116 +
        throw abi::Error::Denied;
117 +
    }
118 +
    return entry;
119 +
}