Files
mad-skills/ocap-discipline/SKILL.md
T
2026-10-07 01:41:13 -05:00

3.2 KiB


name: ocap-discipline description: Use when writing or reviewing code whose functions touch I/O, clocks, random number generators, process.env, fetch, or filesystem; when someone says "ambient authority", "wash your mouth out", or "that function shouldn't reach for that"; when debating dependency injection or default-arg seams; when asked what OCap discipline is. Source: the Agoric wiki's OCap Discipline page.

OCap Discipline

Agoric's OCap Discipline wiki page, condensed to working rules.

the background, in three lines

Object-capability security = memory safety + encapsulation ("no reference to an object except: create it, be given it at creation, or receive it in a message") combined with primitive effects only via references — all world-touching primitives are embodied by objects, and anything globally accessible is immutable data. No open(filename) in the global namespace: converting a string to file access is like casting an integer to a pointer. JavaScript doesn't enforce this; the discipline is a convention (layerable via SES).

the working rules (from the wiki)

  1. Exported functions depend only on their arguments. I/O, clocks, RNGs, process.env, network — injected as parameters, never imported inside the function body.
    • transitional seam: a default argument the caller may override
    • modules must not export powerful objects (nothing closing over fs or process.env)
    • avoid static mutable state
  2. Program scripts are the exception: they start #!/usr/bin/env node (and +x), are free to import all the authority they need, then delegate it downward.
  3. Test scripts are named test-*.js; tests export nothing.

how this shows up in review

  • crypto.getRandomValues() called inside an exported function → ambient RNG authority: untestable, unreplayable, uncomposable. Fix: the function takes the random bytes (or an Entropy object) as a parameter; the caller at the edge is who holds the ambient right.
  • new Date() inside logic → ambient clock. Same treatment: parameter, default to a live-clock implementation.
  • fetch / fs / env referenced directly inside a helper's body → helpers become untestable without monkey-patching; inject a narrower interface (the caller passes the one URL's worth of fetch, not the whole world).
  • A module's default export closing over bindings/config → a powerful object export. Split: pure logic (argument-only) + a thin program-script-ish edge that wires authority in.

canonical fixes

// ambient: untestable
export function mintKey(): string {
  return b32(crypto.getRandomValues(new Uint8Array(16)));
}

// disciplined: entropy in, key out (caller holds the RNG authority)
export const mintKey = (entropy: Entropy): string => b32(entropy(16));

references