kernel: Implement capability transactions

2d5bac683bea186f4b08f44f33590aca790373291002df8faec96bda4188a86a
Assisted-by: Codex:gpt-6
Alexis Sellier committed ago 1 parent 5a24e04e
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
128 129
    set store.records[object.index].handles -= 1;
129 130
    if page.handles > 1 { return; }
130 131
    try! slots::release(&mut store.slots[..], object);
131 132
    try! backing::releasePage(&mut store.backings, page.backing);
132 133
}
134 +
135 +
/// Acquire page lifetime and publish an attenuated capability into a resolved table.
136 +
unsafe fn receive(store: &mut Store, target: &mut capability::Table, owner: abi::Ref,
137 +
    source: abi::Handle, entry: capability::Entry, rights: u64) -> abi::Handle throws (abi::Error)
138 +
{
139 +
    let receiver = target.owner;
140 +
    let pending = try transactions::reserve(target, owner, source, entry, rights, false);
141 +
    try retain(store, entry.object, receiver) catch err {
142 +
        transactions::cancel(target, pending); throw err;
143 +
    };
144 +
    return transactions::grant(target, pending);
145 +
}
146 +
147 +
/// Grant page authority to a separately resolved domain table.
148 +
/// The caller serializes both tables and page metadata through publication.
149 +
export unsafe fn grant(store: &mut Store, source: &capability::Table, target: &mut capability::Table,
150 +
    handle: abi::Handle, rights: u64) -> abi::Handle throws (abi::Error)
151 +
{
152 +
    let entry = try capability::lookup(source, handle, abi::Kind::Page, abi::Rights(0));
153 +
    return try receive(store, target, source.owner, handle, entry, rights);
154 +
}
155 +
156 +
/// Create a narrower page capability in the calling domain's table.
157 +
export unsafe fn selfGrant(store: &mut Store, table: &mut capability::Table, handle: abi::Handle, rights: u64)
158 +
    -> abi::Handle throws (abi::Error)
159 +
{
160 +
    let entry = try capability::lookup(table, handle, abi::Kind::Page, abi::Rights(0));
161 +
    let owner = table.owner;
162 +
    return try receive(store, table, owner, handle, entry, rights);
163 +
}
164 +
165 +
/// Move page authority without changing its handle count or ending source exposure.
166 +
/// The caller serializes the resolved domain tables and page metadata.
167 +
export unsafe fn transfer(store: &mut Store, source: &mut 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 +
    let page = try get(store, entry.object);
172 +
    let receiver = target.owner;
173 +
    let pending = try transactions::reserve(target, source.owner, handle, entry, rights, true);
174 +
    try backing::expose(&mut store.backings, page.backing, receiver) catch err {
175 +
        transactions::cancel(target, pending); throw err;
176 +
    };
177 +
    return transactions::transfer(source, target, pending);
178 +
}
179 +
180 +
/// Drop a page capability and release its object reference under serialization.
181 +
export fn drop(store: &mut Store, table: &mut capability::Table, handle: abi::Handle) throws (abi::Error) {
182 +
    let entry = try capability::lookup(table, handle, abi::Kind::Page, abi::Rights(0));
183 +
    try release(store, entry.object);
184 +
    let removed = try! capability::invalidate(table, handle);
185 +
}
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
36 38
    });
37 39
}
38 40
39 41
/// Drop the test's handle and its object reference in the same serialized operation.
40 42
unsafe fn drop(handle: abi::Handle) {
41 -
    let entry = try! capability::invalidate(&mut TABLE, handle);
42 -
    try! pages::release(&mut STORE, entry.object);
43 +
    try! pages::drop(&mut STORE, &mut TABLE, handle);
44 +
}
45 +
46 +
/// Transfers keep both domains exposed without duplicating page handles.
47 +
@test unsafe fn transferExposure() throws (testing::TestError) {
48 +
    initialize();
49 +
    let receiver = abi::Ref { index: 1, generation: 1 };
50 +
    capability::initialize(&mut TARGET, receiver);
51 +
    try! backing::registerDomain(&mut STORE.backings, receiver);
52 +
    let original = try! pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 8);
53 +
    let entry = try! capability::get(&TABLE, original);
54 +
    let moved = try! pages::transfer(&mut STORE, &mut TABLE, &mut TARGET, original, abi::READ as u64);
55 +
    let page = try! pages::get(&STORE, entry.object);
56 +
    try testing::expect(page.handles == 1 and STORE.backings.records[page.backing.index].exposed == 2);
57 +
    let actual = try! capability::get(&TARGET, moved);
58 +
    try testing::expect(*actual.rights == abi::READ);
59 +
    try! pages::drop(&mut STORE, &mut TARGET, moved);
60 +
    try! backing::endDomain(&mut STORE.backings, receiver);
61 +
    try testing::expect(not STORE.backings.pool.free[0]);
62 +
    try! backing::endDomain(&mut STORE.backings, TABLE.owner);
63 +
    try testing::expect(STORE.backings.pool.free[0]);
64 +
}
65 +
66 +
/// Grants acquire references only after capacity and receiver checks succeed.
67 +
@test unsafe fn grantRollback() throws (testing::TestError) {
68 +
    initialize();
69 +
    let receiver = abi::Ref { index: 1, generation: 1 };
70 +
    capability::initialize(&mut TARGET, receiver);
71 +
    let original = try! pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 2);
72 +
    let entry = try! capability::get(&TABLE, original);
73 +
    let page = try! pages::get(&STORE, entry.object);
74 +
    let mut dead = false;
75 +
    try pages::grant(&mut STORE, &TABLE, &mut TARGET, original, abi::READ as u64) catch err {
76 +
        try testing::expect(err == abi::Error::BadHandle); set dead = true;
77 +
    };
78 +
    try testing::expect(dead and TARGET.slots[0].state == slots::State::Free);
79 +
    let mut rejected = false;
80 +
    try pages::transfer(&mut STORE, &mut TABLE, &mut TARGET, original, abi::READ as u64) catch err {
81 +
        try testing::expect(err == abi::Error::BadHandle); set rejected = true;
82 +
    };
83 +
    try testing::expect(rejected and TARGET.slots[0].state == slots::State::Free);
84 +
    try testing::expect(try! capability::get(&TABLE, original) == entry);
85 +
    try! backing::registerDomain(&mut STORE.backings, receiver);
86 +
    for i in 0..limits::HANDLES {
87 +
        let filler = try! capability::install(&mut TARGET, capability::Entry {
88 +
            kind: abi::Kind::Domain, object: receiver, rights: abi::Rights(0),
89 +
        });
90 +
    }
91 +
    let mut full: u32 = 0;
92 +
    try pages::grant(&mut STORE, &TABLE, &mut TARGET, original, abi::READ as u64) catch err {
93 +
        try testing::expect(err == abi::Error::InvalidArg); set full += 1;
94 +
    };
95 +
    try pages::transfer(&mut STORE, &mut TABLE, &mut TARGET, original, abi::READ as u64) catch err {
96 +
        try testing::expect(err == abi::Error::InvalidArg); set full += 1;
97 +
    };
98 +
    try testing::expect(full == 2 and STORE.records[entry.object.index].handles == 1);
99 +
    try testing::expect(STORE.backings.records[page.backing.index].exposed == 1);
100 +
    try testing::expect(try! capability::get(&TABLE, original) == entry);
101 +
    let filler = try! capability::at(&TARGET, 0, abi::Kind::Domain);
102 +
    let removed = try! capability::invalidate(&mut TARGET, filler);
103 +
    let granted = try! pages::grant(&mut STORE, &TABLE, &mut TARGET, original, abi::READ as u64);
104 +
    try testing::expect(STORE.records[entry.object.index].handles == 2);
105 +
    try testing::expect(STORE.backings.records[page.backing.index].exposed == 2);
106 +
    drop(original);
107 +
    try! pages::drop(&mut STORE, &mut TARGET, granted);
108 +
    try! backing::endDomain(&mut STORE.backings, TABLE.owner);
109 +
    try testing::expect(not STORE.backings.pool.free[0]);
110 +
    try! backing::endDomain(&mut STORE.backings, receiver);
111 +
    try testing::expect(STORE.backings.pool.free[0]);
112 +
}
113 +
114 +
/// Self-attenuation adds one reference, preserves authority, and prevents splitting.
115 +
@test unsafe fn selfAttenuation() throws (testing::TestError) {
116 +
    initialize();
117 +
    let original = try! pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 2);
118 +
    let copy = try! pages::selfGrant(&mut STORE, &mut TABLE, original, abi::READ as u64);
119 +
    let entry = try! capability::get(&TABLE, copy);
120 +
    try testing::expect(*entry.rights == abi::READ and STORE.records[entry.object.index].handles == 2);
121 +
    let narrowed = try! pages::selfGrant(&mut STORE, &mut TABLE, copy, abi::WRITE as u64);
122 +
    let none = try! capability::get(&TABLE, narrowed);
123 +
    try testing::expect(*none.rights == 0);
124 +
    drop(narrowed); drop(copy);
125 +
    let right = try! pages::split(&mut STORE, &mut TABLE, original, 1);
126 +
    drop(original); drop(right);
127 +
    try! backing::endDomain(&mut STORE.backings, TABLE.owner);
128 +
    try testing::expect(STORE.backings.pool.free[0] and STORE.backings.pool.free[1]);
43 129
}
44 130
45 131
/// New allocations are zeroed completely and carry the documented default rights.
46 132
@test unsafe fn allocation() throws (testing::TestError) {
47 133
    initialize();
kernel/kernel/tests/transactions.rad added +166 -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 { let filler = try! capability::install(&mut TARGET, entry); }
34 +
    let mut full = false;
35 +
    try move(source) catch err { try testing::expect(err == abi::Error::InvalidArg); set full = true; };
36 +
    try testing::expect(full and try! capability::get(&SOURCE, source) == entry);
37 +
    let first = try! capability::at(&TARGET, 0, abi::Kind::Budget);
38 +
    let removed = try! capability::invalidate(&mut TARGET, first);
39 +
    let moved = try! move(source);
40 +
    let actual = try! capability::get(&TARGET, moved);
41 +
    try testing::expect(actual.object == entry.object and *actual.rights == abi::READ);
42 +
    let mut stale = false;
43 +
    try capability::get(&SOURCE, source) catch err {
44 +
        try testing::expect(err == abi::Error::BadHandle); set stale = true;
45 +
    };
46 +
    try testing::expect(stale);
47 +
}
48 +
49 +
/// Complete a transfer after any required object-specific ownership update.
50 +
unsafe fn move(source: abi::Handle) -> abi::Handle throws (abi::Error) {
51 +
    let entry = try capability::get(&SOURCE, source);
52 +
    let owner = SOURCE.owner;
53 +
    let pending = try transactions::reserve(&mut TARGET, owner, source, entry, abi::READ as u64, true);
54 +
    return transactions::transfer(&mut SOURCE, &mut TARGET, pending);
55 +
}
56 +
57 +
/// Self-grants narrow rights without requiring Grant; budgets remain exclusive.
58 +
@test unsafe fn selfGrant() throws (testing::TestError) {
59 +
    initialize();
60 +
    let source = install(abi::Kind::Events, abi::READ | abi::WRITE);
61 +
    let entry = try! capability::get(&SOURCE, source);
62 +
    let owner = SOURCE.owner;
63 +
    let pending = try! transactions::reserve(&mut SOURCE, owner, source, entry, abi::READ as u64, false);
64 +
    let copy = transactions::grant(&mut SOURCE, pending);
65 +
    let narrowed = try! capability::get(&SOURCE, copy);
66 +
    try testing::expect(*narrowed.rights == abi::READ);
67 +
    try testing::expect(try! capability::get(&SOURCE, source) == entry);
68 +
    let budget = install(abi::Kind::Budget, abi::GRANT | abi::TRANSFER);
69 +
    let budgetEntry = try! capability::get(&SOURCE, budget);
70 +
    let mut denied = false;
71 +
    try probe(owner, budget, budgetEntry, false) catch err {
72 +
        try testing::expect(err == abi::Error::Denied); set denied = true;
73 +
    };
74 +
    try testing::expect(denied);
75 +
}
76 +
77 +
/// Reserve and cancel a policy probe without leaving an outstanding token.
78 +
unsafe fn probe(owner: abi::Ref, handle: abi::Handle, entry: capability::Entry, moving: bool) throws (abi::Error) {
79 +
    let pending = try transactions::reserve(&mut TARGET, owner, handle, entry, abi::RIGHTS_MASK as u64, moving);
80 +
    transactions::cancel(&mut TARGET, pending);
81 +
}
82 +
83 +
/// Nondelegable kinds and missing rights fail before consuming target capacity.
84 +
@test unsafe fn delegation() throws (testing::TestError) {
85 +
    initialize();
86 +
    let mut failures: u32 = 0;
87 +
    for kind in &[abi::Kind::Events, abi::Kind::Interrupt, abi::Kind::Budget, abi::Kind::Page] {
88 +
        let source = install(kind, 0);
89 +
        let entry = try! capability::get(&SOURCE, source);
90 +
        try probe(SOURCE.owner, source, entry, false) catch err {
91 +
            try testing::expect(err == abi::Error::Denied); set failures += 1;
92 +
        };
93 +
    }
94 +
    let events = install(abi::Kind::Events, abi::TRANSFER);
95 +
    let entry = try! capability::get(&SOURCE, events);
96 +
    try probe(SOURCE.owner, events, entry, true) catch err {
97 +
        try testing::expect(err == abi::Error::Denied); set failures += 1;
98 +
    };
99 +
    try testing::expect(failures == 5 and TARGET.slots[0].generation == 1);
100 +
    let interrupt = install(abi::Kind::Interrupt, abi::TRANSFER);
101 +
    let moved = try! move(interrupt);
102 +
    let actual = try! capability::get(&TARGET, moved);
103 +
    try testing::expect(actual.kind == abi::Kind::Interrupt);
104 +
}
105 +
106 +
/// Cancelled object work leaves no published capability and restores capacity.
107 +
@test unsafe fn cancellation() throws (testing::TestError) {
108 +
    initialize();
109 +
    let source = install(abi::Kind::Page, abi::GRANT | abi::READ);
110 +
    let entry = try! capability::get(&SOURCE, source);
111 +
    let owner = SOURCE.owner;
112 +
    let pending = try! transactions::reserve(&mut TARGET, owner, source, entry, abi::READ as u64, false);
113 +
    try testing::expect(TARGET.slots[0].state == slots::State::Reserved);
114 +
    transactions::cancel(&mut TARGET, pending);
115 +
    try testing::expect(TARGET.slots[0].state == slots::State::Free);
116 +
    try testing::expect(try! capability::get(&SOURCE, source) == entry);
117 +
}
118 +
119 +
/// Invalid masks, self-transfers, and budget self-grants preserve table state.
120 +
@test unsafe fn policyBoundaries() throws (testing::TestError) {
121 +
    initialize();
122 +
    let source = install(abi::Kind::Budget, abi::GRANT | abi::TRANSFER);
123 +
    let entry = try! capability::get(&SOURCE, source);
124 +
    let mut failures: u32 = 0;
125 +
    try selfProbe(source, entry, 0, true) catch err {
126 +
        try testing::expect(err == abi::Error::InvalidArg); set failures += 1;
127 +
    };
128 +
    try selfProbe(source, entry, 0, false) catch err {
129 +
        try testing::expect(err == abi::Error::Denied); set failures += 1;
130 +
    };
131 +
    try selfProbe(source, entry, 0xffffffffffffffff, false) catch err {
132 +
        try testing::expect(err == abi::Error::InvalidArg); set failures += 1;
133 +
    };
134 +
    try testing::expect(failures == 3 and SOURCE.slots[1].generation == 1);
135 +
    try testing::expect(try! capability::get(&SOURCE, source) == entry);
136 +
}
137 +
138 +
/// Consume a successful self-directed policy probe without publishing it.
139 +
unsafe fn selfProbe(handle: abi::Handle, entry: capability::Entry, rights: u64, moving: bool) throws (abi::Error) {
140 +
    let owner = SOURCE.owner;
141 +
    let pending = try transactions::reserve(&mut SOURCE, owner, handle, entry, rights, moving);
142 +
    transactions::cancel(&mut SOURCE, pending);
143 +
}
144 +
145 +
/// Only the installed Events handle is protected from drop; attenuated copies can end.
146 +
@test unsafe fn installedEvents() throws (testing::TestError) {
147 +
    initialize();
148 +
    let installed = install(abi::Kind::Events, abi::READ | abi::WRITE);
149 +
    let entry = try! capability::get(&SOURCE, installed);
150 +
    let owner = SOURCE.owner;
151 +
    let pending = try! transactions::reserve(&mut SOURCE, owner, installed, entry, abi::READ as u64, false);
152 +
    let copy = transactions::grant(&mut SOURCE, pending);
153 +
    let mut denied = false;
154 +
    try transactions::dropping(&SOURCE, installed, installed) catch err {
155 +
        try testing::expect(err == abi::Error::Denied); set denied = true;
156 +
    };
157 +
    try testing::expect(denied);
158 +
    let allowed = try! transactions::dropping(&SOURCE, copy, installed);
159 +
    let removed = try! capability::invalidate(&mut SOURCE, copy);
160 +
    try testing::expect(removed == allowed and try! capability::get(&SOURCE, installed) == entry);
161 +
    let mut stale = false;
162 +
    try transactions::dropping(&SOURCE, copy, installed) catch err {
163 +
        try testing::expect(err == abi::Error::BadHandle); set stale = true;
164 +
    };
165 +
    try testing::expect(stale);
166 +
}
kernel/kernel/transactions.rad added +111 -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 { throw abi::Error::InvalidArg; }
41 +
        if original.kind == abi::Kind::Events or not abi::permits(original.rights, abi::Rights(abi::TRANSFER)) {
42 +
            throw abi::Error::Denied;
43 +
        }
44 +
    } else {
45 +
        if original.kind == abi::Kind::Interrupt or original.kind == abi::Kind::Budget { throw abi::Error::Denied; }
46 +
        if not own and (original.kind == abi::Kind::Events or not abi::permits(original.rights, abi::Rights(abi::GRANT))) {
47 +
            throw abi::Error::Denied;
48 +
        }
49 +
    }
50 +
    let reservation = try slots::reserve(&mut target.slots[..]) catch err { throw abi::Error::InvalidArg; };
51 +
    match reservation {
52 +
        case slots::Reservation::Held(slot) => return Reservation::Held(Pending {
53 +
            target: target.owner, slot, owner, source, original,
54 +
            entry: capability::Entry { kind: original.kind, object: original.object,
55 +
                rights: abi::Rights(*original.rights & *requested) },
56 +
            moving,
57 +
        }),
58 +
    }
59 +
}
60 +
61 +
/// Check the reserved destination before any publication or cancellation.
62 +
fn require(target: &capability::Table, pending: &Pending) {
63 +
    assert target.owner == pending.target;
64 +
    assert slots::matches(&target.slots[..], pending.slot, slots::State::Reserved);
65 +
}
66 +
67 +
/// Restore target capacity after object validation or lifetime acquisition fails.
68 +
export fn cancel(target: &mut capability::Table, reservation: Reservation) {
69 +
    match reservation {
70 +
        case Reservation::Held(pending) => {
71 +
            require(target, &pending);
72 +
            try! slots::cancel(&mut target.slots[..], slots::Reservation::Held(pending.slot));
73 +
        },
74 +
    }
75 +
}
76 +
77 +
/// Publish a grant after acquiring its object reference and lifetime exposure.
78 +
export fn grant(target: &mut capability::Table, reservation: Reservation) -> abi::Handle {
79 +
    match reservation {
80 +
        case Reservation::Held(pending) => {
81 +
            require(target, &pending);
82 +
            assert not pending.moving;
83 +
            return capability::publish(target, slots::Reservation::Held(pending.slot), pending.entry);
84 +
        },
85 +
    }
86 +
}
87 +
88 +
/// Publish a transfer after object ownership and exposure updates succeed.
89 +
/// The caller holds serialization over both tables for the complete transaction.
90 +
export fn transfer(source: &mut capability::Table, target: &mut capability::Table, reservation: Reservation) -> abi::Handle {
91 +
    match reservation {
92 +
        case Reservation::Held(pending) => {
93 +
            require(target, &pending);
94 +
            assert pending.moving and source.owner == pending.owner;
95 +
            assert try! capability::get(source, pending.source) == pending.original;
96 +
            let result = capability::publish(target, slots::Reservation::Held(pending.slot), pending.entry);
97 +
            let removed = try! capability::invalidate(source, pending.source);
98 +
            return result;
99 +
        },
100 +
    }
101 +
}
102 +
103 +
/// Validate drop authority before object-specific lifetime release.
104 +
/// installedEvents is the domain's original queue handle, or zero before installation.
105 +
export fn dropping(table: &capability::Table, handle: abi::Handle, installedEvents: abi::Handle)
106 +
    -> capability::Entry throws (abi::Error)
107 +
{
108 +
    let entry = try capability::get(table, handle);
109 +
    if handle == installedEvents { throw abi::Error::Denied; }
110 +
    return entry;
111 +
}