09 — Capabilities

Previous: Effects (the heart of vibe)

日本語版: 09_capabilities.vibe.md

The last chapter's effects were ones you implement, with a handler. Reading a file is not like that: the host already knows how, and the question is whether your program is allowed to.

That is a capability. It rides the same row, but what the row records is permission, and the permission is decided when the program is built — not checked again at every call. The call itself stays an ordinary call.

This is Deno's permission flags composed with Koka's effect system.

Permission is part of the signature

fn greet(name: String) -> Unit with Console {
  println("hi \{name}")
}

fn main with Console {
  greet("vibe")
}
hi vibe

greet writes to the terminal, so it says with Console. main calls greet, so main says it too. The capability does not appear at main by magic — it is inferred from the calls and then checked against what you wrote. A function whose signature omits it will not compile.

Console is the terminal capability. Stdin / Stdout / Stderr are older labels for parts of it that are still accepted; with Console covers them, and they do not cover Console. Ask for the narrow one and you get the narrow one:

// skip: `with Stdout` does not reach a `Console::` operation
fn main with Stdout {
  Console::write_stream("x")
}
effect row mismatch for 'main': missing { Console::write_stream }
(declared { Stdout }, requires { Console::write_stream, Stdout })
hint: add 'with Console::write_stream + Stdout' to 'main'

with and allows are different clauses

A signature can split into what it emits and what it is authorized for:

fn main with () allows Console {
  println("authority is a separate clause")
}
authority is a separate clause

with () is the empty row: nothing algebraic. allows Console is the authority. The bare fn main with Console from chapter 1 is the short form of the same thing.

Once you write the split form, a capability has to be in allows. Put one in with and you are told which clause it belongs to:

// skip: a capability in `with` on a split signature
fn main() -> Int with Console allows Fs::read_file? {
  0
}
`Console` is a capability effect and must appear in the `allows` clause,
not `with` (ADR-0088, #1345)

Authority stays per-operation. allows Console::write_stream does not grant Console::read_stream — a program that may print does not thereby acquire the right to read the terminal.

Optional capability: perform?

A ? on an allows item marks it optional — the program can run whether or not the host granted it. The matching perform? Fs::read_file("p") gives back an Attempt: Granted, NotGranted, or Errored.

The non-interactive compiler has no build/apply grant fact, so it freezes an unresolved optional capability to NotGranted before code generation. The operation and its arguments are not evaluated:

fn main() -> Int with () allows Console + Fs::read_file? {
  let a = perform? Fs::read_file("config.json")
  match a {
    NotGranted => 0,
    Errored(_) => 1,
    Granted(_) => 2
  }
}
0

The frozen-resolution lowering is shared by the linear and wasm-gc backends. Wiring --allow-*, BindingLock, and interactive preflight into production is tracked in #2332; until then production compilation does not select Granted or Errored.

Telling the two kinds apart

Both ride the row, and the spelling says which you are looking at:

| | example | you write | who implements it | |---|---|---|---| | algebraic effect | Ask::Value | perform + a handle | you | | capability | Fs::read_file | an ordinary call | the host |

Effect::CamelCase is an operation you perform; Effect::snake_case is a function you call. That is the rule, and it is why Fs::read_file(p) reads like any other call even though it needs authority.

What denial actually does

--allow-* decides the grant set at build time, and a denied capability is const-folded and removed from the artifact — the code that needed it is not in the wasm, not merely unreachable. A program that never gets Http does not ship networking code, and does not demand a runtime that can do networking (feature levels).

At startup, a required capability the host did not grant aborts before main runs and names the flag that would have granted it.

Next: Option and the railway.