Skip to main content

Authority Confinement

Give a component the services it needs, and have the compiler check how it uses them.

An order processor needs to read orders. Passing it an input service makes that dependency explicit. Adding #[confined] lets the compiler check that the processor and the code it calls stay within supplied services, including when a helper or callback is involved.

Start with an in-memory service

Define the operation your component needs as an ordinary trait. Here, OrderInput supplies order bytes. The memory implementation holds those bytes directly. The function read_order uses the service, and the actor stores it when constructed.

Memory service — complete program
trait OrderInput {
    fn[suspends] load(self) -> Result<bytes, string>;
}
 
type MemoryOrders {
    data: bytes;
}
 
impl OrderInput for MemoryOrders {
    fn load(self) -> Result<bytes, string> {
        .Ok(self.data)
    }
}
 
#[confined]
fn read_order<I: OrderInput>(input: I) -> Result<bytes, string> {
    input.load()
}
 
#[confined]
actor OrderReader<I: OrderInput> {
    let input: I;
 
    receive fn read() -> Result<bytes, string> {
        read_order(input)
    }
}
 
fn main() {
    let reader = spawn OrderReader {
        input: MemoryOrders { data: "order-41".to_bytes() }
    };
    let order = reader.read().expect("actor reply").expect("load order");
    assert(order == "order-41".to_bytes());
    println(order.len());
    stop(reader);
    stopped(reader);
}

This prints 8. The launcher constructs the input and passes it to the actor once. Inside the actor, ordinary method calls use that input. #[confined] takes no arguments and can annotate an actor declaration or a free function. Helpers called from a confined body are checked transitively even when they have no annotation themselves.

fn[suspends] allows the trait operation to wait for I/O. It describes suspension; authority is checked separately. A generic library can be checked before its clients exist. The compiler records obligations for supplied services, then checks the concrete implementations at calls and actor constructions.

Supply access to specific files

On native targets, the launcher can open a file and supply the resulting resource. fs.open_read(path) returns an owning ReadFile; fs.open_write(path) returns an owning WriteFile. Both return Result so the launcher can handle acquisition failures.

  • ReadFile.read() reads the remaining bytes from its opened file.
  • WriteFile.write(bytes) writes to its opened file and advances the cursor. Opening for writing creates or truncates the file.
  • Each resource provides only its direction of access and cannot open another path. Renaming or replacing the original path does not redirect the opened resource.

An adapter lets the file satisfy the same OrderInput interface. This complete native program copies an order to a receipt file. Run it in a scratch directory: it creates orders.txt and creates or overwrites receipts.txt.

File services — complete native program
import std.fs;
 
trait OrderInput {
    fn[suspends] load(self) -> Result<bytes, string>;
}
 
type FileOrders {
    file: fs.ReadFile;
}
 
impl OrderInput for FileOrders {
    fn load(self) -> Result<bytes, string> {
        match self.file.read() {
            .Ok(data) => .Ok(data),
            .Err(_) => .Err("read order file"),
        }
    }
}
 
#[confined]
fn copy_order<I: OrderInput>(input: I, output: fs.WriteFile) -> i64 fails string {
    let order = input.load()?;
    let size = order.len();
    match output.write(order) {
        .Ok(()) => size,
        .Err(_) => { return error "write receipt file"; }
    }
}
 
#[confined]
actor OrderCopier<I: OrderInput> {
    let input: I;
    let output: fs.WriteFile;
 
    receive fn copy() -> Result<i64, string> {
        copy_order(input, output)
    }
}
 
fn main() {
    fs.write("orders.txt", "order-41").expect("prepare order");
    let input = fs.open_read("orders.txt").expect("open input");
    let output = fs.open_write("receipts.txt").expect("open output");
    let copier = spawn OrderCopier {
        input: FileOrders { file: input }, output: output
    };
    println(copier.copy().expect("actor reply").expect("copy order"));
    stop(copier);
    stopped(copier);
    assert(fs.read("receipts.txt").expect("inspect receipt") == "order-41");
}

This also prints 8. Opening files happens in main, before construction. The confined function reads and writes through the supplied resources. To use memory instead, keep the input trait and supply MemoryOrders; an output trait can similarly wrap either a WriteFile or an in-memory stream sink. Native file resources are unavailable in the browser VM. Memory services use the same language pattern once its compiler includes confinement support.

Follow authority through calls and cleanup

Ambient authority is access acquired from the surrounding process, such as opening an arbitrary path, using standard input or output directly, or looking up a host or global-registry service. A confined component uses services it receives. For example, return a result to the launcher for printing, or accept a logging service.

The compiler follows imported helpers, captured callables, selected trait implementations, constructed actors, and implicit operations such as iteration, hashing, equality, and rendering. Supplying a callback or trait implementation that opens an unrelated file fails the check. Definite ambient acquisition is rejected even in an unused annotated body.

For an actor, the check covers field defaults, initialization, receive handlers, ordinary methods, lifecycle hooks, and terminal field cleanup. Calling a supplied actor service does not transfer that service's private construction authority to the caller. Creating an actor inside a confined body also checks the new actor's construction and callbacks.

Ownership includes cleanup

ReadFile and WriteFile follow the resource cleanup rules: ownership moves with the resource, and its consuming close runs when required, including when an owning actor terminates. You can also close a resource explicitly. The stopped calls above wait for actor termination and cleanup.

A custom resource's close method is part of the authority check. Wrapping a resource in a record, collection, pending result, or queued stream value also carries its cleanup effects. Scope exit, explicit close, and destructive updates cannot hide an ambient operation in cleanup.

Resolve compiler diagnostics

Run hew check example.hew with the main compiler. For example, this complete program is expected to fail because a helper acquires a file inside the confined boundary:

Rejected ambient acquisition
// hew-sample: partial — intentionally rejected; checked by check-confinement-examples.mjs
import std.fs;
 
fn acquire() {
    _ = fs.open_read("ungranted.txt");
}
 
#[confined]
fn process_order() {
    acquire();
}
 
fn main() {
    process_order();
}

E_CONFINED_AUTHORITY names the confined root and reached operation, explains the refusal, and asks you to supply a narrow resource or service at construction. Move the open into the launcher and pass its result to the component, as in the native example.

Diagnostic or reasonWhat to check
E_CONFINED_AUTHORITY: ambient authority acquisitionLook through the named call path for a file open, direct standard I/O, or host-service acquisition. Supply the needed service from the launcher.
E_CONFINED_AUTHORITY: an opaque user extern/raw escapeA user extern has no checked authority summary. Use a supported operation with a known contract; adding unsafe does not bypass confinement.
E_CONFINED_INDIRECT_CALLSupply a callable whose origin and transitive authority the compiler can establish at the boundary.
Incomplete implementation or cleanup authorityKeep concrete service and cleanup information available. Erased generator execution or captured cleanup, and some opaque iterator adapters or associated-type rendering paths, are currently refused when their authority cannot be established.

Understand the checked boundary

Actor isolation governs shared state and message processing; Send and Frozen govern which values cross actor boundaries. Confinement checks which authority code can acquire. It is independent of suspension and deterministic execution, and permits I/O through supplied services.

Application source roots and the configured standard library remain trusted. This is a compiler check on source authority, not an operating-system sandbox for arbitrary native code. Remote calls and distributed ambient acquisition are currently refused inside confinement; authenticated cross-node capability delegation requires a separate service-reference protocol.

For the full contract, see Resource construction and confinement in the released specification. The v0.6.1 release notes preserve the feature's original release context.