kotoba
capability-safe

Kotoba

A Clojure-shaped language that compiles to WebAssembly components — where a program can only touch what it was explicitly handed.

Three things to know

1 · Identity by content

A definition is named by what it means, not by what it was called.

The identity is computed after desugar, type checking, effect inference and ability elaboration — and it seals the effect row, so a pure definition and one requiring network authority can never share a name. Unison's idea; not Unison's syntax and not a global codebase.

2 · Memory safety, as a consequence

The thesis is confinement. Memory safety falls out of it.

Admitted components cannot address runtime or native memory, and component memory operations are bounded or trap. That holds without a general ownership/borrow system: affine consumption is scoped to capability values alone, because what a program must not forge is authority, not pointers.

3 · Component-first execution

The unit that runs is a Wasm component, not a process.

Each component gets its own WIT world built from its declared effects. Undeclared imports are rejected and there is no ambient WASI. The tender links and instantiates, binding only what policy granted. Native AOT for ordinary applications is an explicit non-goal — the boundary is the point.

Kotoba source is an EDN/Lisp subset: .kotoba is the canonical extension and .cljc is common source across Clojure, ClojureScript and Kotoba. It is a source profile with its own compatibility contract — not "any JVM Clojure program runs".

Memory safety is a consequence, not the thesis

Most safe-language pitches begin and end with memory. Kotoba treats that as necessary and insufficient: a program that cannot corrupt memory but can still open a socket it was never given has not been contained. The safest program is not the one written in the strongest type system — it is the one that, when it is fully compromised, can still do nothing. So confinement is ranked above ownership:

S capability sandbox + deny-by-default + reproducible, verified build
what Kotoba targets
A a small Wasm language with Rust-style ownership and borrowing
B Clojure-shaped syntax + a safe subset + a borrow checker
a Clojure/ClojureScript guarded by a linter
last place
Against a mythos-class adversarial agent, a linter's red underline is a polite signpost. When something comes through the wall, the only thing that works is to have kept nothing outside it.

This is why there is no borrow checker over every value. T1 — admitted components cannot address runtime or native memory, and component memory operations are bounded or trap — is met by the admitted grammar and the runtime, not by an ownership system. Affine consumption exists, but only where forging a duplicate would create authority: a capability value may be consumed at most once per execution path.

The ladder is the language's own accepted design position (ADR — safe capability language). What it does not claim: the Wasm runtime engine stays inside the trusted computing base, and native loaders still require a second OS isolation boundary.

Source, and the policy that admits it

A module

(ns example.greet)

(defn greeting [name :string] :string
  (string-concat "hello, " name))

(defn main [] :string
  (greeting "kotoba"))

Types are inline. No macros, no interop, no eval — those forms are not "discouraged", they are absent from the admitted grammar.

A policy

;; effective scope =
;;   requested ∩ delegated ∩ local policy
{:policy/allow
 #{{:cap/kind     :host/http
    :cap/resource "https://api.example.com/"
    :cap/expires  "2026-12-31T00:00:00Z"}}
 :policy/forbid-wildcard true}

what denies

empty-intersectionexpired-grantmissing-grantunknown-kind

Scope may only attenuate, never widen, and the handler receives a concrete post-intersection capability. Production policy must forbid wildcard scope; every attempt is receipted whether or not it succeeds.

Eight claims, and what each still risks

These are the language's qualification claims, read directly out of lang/safety-claims.edn when this page was built. Each one ships with its trusted computing base and its residual risk, because a safety claim without a stated boundary is marketing.

T1 · memory

Admitted components cannot address runtime/native memory and component memory operations are bounded or trap.


trusted computing base

bounded reader · frontend admission · artifact verifier · Wasm/native runtime

residual risk

  • runtime-engine vulnerabilities remain in the TCB
  • native loaders require a second OS isolation boundary

T2 · effect

Every transitive component effect is declared and admitted before emission, including effects used by Kotoba-written providers.


trusted computing base

effect inference · capability catalog · frontend call graph

residual risk

  • kotoba and compiler grammar/effect parity must be continuously compared

T3 · confinement

An ungranted capability is absent or unbound and cannot reach a provider or native handler.


trusted computing base

policy intersection · compiler import emission · tender import binding · host guard

residual risk

  • provider and native implementations must independently validate resource scope
  • production effective grants must forbid wildcard scope

T4 · determinism

The same admitted source, target, policy and lock produce the same observable pure result and artifact bytes.


trusted computing base

canonical reader · deterministic lowering · pinned toolchain

residual risk

  • host effects are deterministic only where their capability contract says so

T5 · resource bounds

Source, admission, execution, memory and output use explicit finite bounds.


trusted computing base

admission limits · fuel meter · runtime quota · supervisor timeout

residual risk

  • platform supervisors do not yet have equal production isolation evidence

T6 · supply chain

Release admission binds artifact identity, trusted signer, validity and reproducible evidence.


trusted computing base

signature verifier · trusted signer configuration · clock · revocation set

residual risk

  • key custody and external revocation distribution remain operational TCB

T7 · backend parity

A shared portable component has equal acceptance, result and effect trace across qualified backends.


trusted computing base

shared conformance manifest · backend adapters · comparison runner

residual risk

  • compiler-only features are not portable and must be rejected by portable profiles

T8 · host resource scope

A component import reaches its provider or native handler only with a concrete post-intersection resource scope and emits a receipt.


trusted computing base

capability intersection · host guard · provider handler · receipt sink

residual risk

  • provider-specific path, redirect, symlink and tenant checks require Q5 kits

Qualification level Q1, as of 2026-07-18.

Deliberately absent

Every entry below is a security constraint, not an unfinished feature. The distinction is tracked in lang/surface-status.edn so that "not implemented yet" can never be quietly confused with "refused on purpose".

eval load load-file ns-resolve require resolve use

Components cannot manufacture code or authority from ambient process state.

. .. import new

Arbitrary JVM/JS object and method access bypasses capability admission.

alter-var-root atom binding dosync ref reset! set! swap! var volatile!

External mutable state is provider-owned and capability/policy mediated; component-local state must use an explicitly bounded model.

agent future locking pmap send send-off

Component scheduling and resources must remain tender-controlled and bounded.

defmacro

The safe component surface must be statically inspectable before execution.

catch throw try

Component/provider effects use explicit result/error values rather than hidden exception paths.

Bounded admission

Untrusted and generated programs must fail closed under finite admission and execution resources.

  • max-bindings 4096
  • max-expression-nodes 50000
  • max-functions 1024
  • max-list-items 128
  • max-parameters 5
  • max-reader-depth 512
  • max-source-bytes 1048576
  • max-symbol-chars 128

Profile version 5, as of 2026-08-03. One more intentional simplification: affine consumption is scoped to capability values only — a general ownership/borrow/lifetime system is intentionally absent.

From source to an admitted artifact

Ten stages, each with a named owner and its own fail-closed rules. Note where effects enter: they are inferred across the call graph, and a declaration is a ceiling, not a floor — you cannot widen your own authority by writing a larger annotation.

closed-reader

closed-edn-subset · fail-closed-on-unknown-reader-macro

kotoba-lang/compiler + kotoba-lang/kotoba

bounded-pure-desugar

no-host-ops · node-depth-bounded · registered-defdesugar-only

kotoba-lang/compiler

name-module-resolution

closed-require-export · no-ambient-require

kotoba-lang/compiler

type-schema-inference

closed-schemas · fail-closed-on-mismatch

kotoba-lang/compiler

interprocedural-effect-inference

declaration-is-ceiling-not-floor · fixpoint-over-calls

kotoba-lang/compiler

implicit-ability-elaboration

named-ops-to-typed-abilities · no-user-facing-numeric-ids

kotoba-lang/compiler

typed-hir-kir

deterministic-encoding

kotoba-lang/compiler

definition-cid

alpha-rename-independent · contract-versions-sealed

kotoba-lang/kotoba-lang + kotoba-lang/kotoba

target-lowering

deny-by-default-admission · exact-imports

kotoba-lang/compiler

host-binding

grant-policy-intersection · quota-deadline-revocation-receipt

kototama + selected host

What you write

bounded-pure-desugardefdefndestructuringhigher-order-functionsifletloop-recurmapnsprotocolsrecordssetvector

What you never write

host-objectnumeric-capability-idportable-effect-envelopeprovider-callbackwit-import-syntax

Numeric capability IDs and WIT import syntax are a wire ABI, not source vocabulary. Explicit capability values are for attenuation, delegation, resource scope, quota and deadline — not for ordinary calls.

Component-first, and what that rules out

WebAssembly components

  • Component Model pinned at e6bb1e456e94
  • WASI 0.3.0 baseline
  • world construction: per-component
  • undeclared imports: reject
  • ambient WASI: false

Async functions, futures and streams are explicit bounded effects with cancellation, deadline and budgets — never ambient authority.

Two lowering targets

The same checked intermediate representation lowers to either restricted-esm, wasm-component — under the same rules: exact imports, deny-by-default admission.

Ordinary-application native AOT is an explicit non-goal: the execution boundary is the component.

Who does what

  • tender — Runtime that verifies/instantiates already-emitted Wasm components, links declared imports and exports, and binds only granted capabilities. kototama owns this role.
  • broker — Authority above the tender that decides which principal may receive which scoped grant. aiueos is the named OS/capability broker.
  • native-primitive — Minimal trusted boundary from typed component imports to the Wasm engine, WASI, OS syscall, device or root secret store. It revalidates concrete resource scope and cannot be removed by changing an extension.

Identity by content, without the Unison surface

Kotoba takes Unison's idea — a definition is named by what it is, not by the label someone typed above it — and deliberately leaves the rest. The identity is computed after desugar, type checking, effect inference and ability elaboration, so it names normalized semantics rather than text. Source formatting, package name and git ref are excluded.

What the identity seals:

typed-kirprofile-versiondesugar-contract-versioneffect-rowinterfacedirect-definition-dependencies

The effect row is in that list for a concrete reason. Without it, a pure definition and one requiring :host/http with identical KIR hash to the same identity — so a lock pinning the pure one would admit the effectful one. There is a negative fixture for exactly that substitution.

What is explicitly not adopted:

ambient-ioambient-loadingbroad-wasi-grantsframework-specific-application-dslgeneral-effect-handlersglobal-codebase-namespaceimplicit-durable-executionordinary-app-native-aotruntime-evalunison-syntax

Delivery stages

CI0 — the contract

implemented

CI1 — canonical typed-KIR encoding and identity test vectors

Canonical DAG-CBOR encoding over a closed, injective, tagged value domain; 10 frozen vectors are recomputed by the test suite.

implemented

CI2 — definition-addressed manifest fields and positive fixtures

:registry/definition-cids resolves through the signed record to :dep/definition-cids, so a name is an alias and never a substitution point.

implemented

CI3 — negative fixtures for every sealed input

One negative fixture per sealed canonical input (body, profile, interface, dependency, effect-row, desugar-contract) plus the linking rules themselves.

implemented

CI4 — safe-build verifies identity against the package lock

admit-build closes the omission hole: verify-locked-definitions alone returns ok? true when a build presents nothing, which is the mutable-name fallback reached by omission. A dependency pinning definition CIDs must now actually resolve all of them.

implemented

CI5 — typed ability/effect checking and the narrow WIT ABI

Host half done: bind-component-imports is now the admissible way to bind a compiled component's effectful imports. It refuses a non-component receipt target, a handler for an undeclared import, and a declared import left unbound, and every bound entry supplies :call and :handler itself so a caller cannot substitute a weaker guard. This matters because guard-ability-call falls through to guard-call for a request that is not a capability value, skipping the effect gate; guard-component-ability-call denies that as :component-ability-invalid, and a negative fixture pins the difference.

partial

CI6 — cross-implementation conformance

The identity half holds: all 10 frozen vectors produce identical canonical bytes and CIDs under ClojureScript (nbb) and Clojure. This runner is what found that integer literals past 2^53-1 are rounded by the cljs reader before any encoder runs, which the i64 exact form now refuses rather than hides.

partial

CI7 — friendly source operations elaborate identically

Friendly source operations elaborate to the same typed ability/effect KIR across project and single-source builds.

pending

Read from lang/code-identity.edn at build time, so this table cannot claim more than the repository does. The ADR's own rule is that until CI4 and CI5 both land the design is described as proposed — CI4 has landed and CI5 has not, so proposed is what it is. Hash-native authoring, browse-by-hash and a deployed codebase network are not claimed at all.

Where this actually is

Kotoba is a working compiler and a qualified bounded slice — not a finished general-purpose platform. The distinction is kept in the repo rather than softened here:

  • Q1–Q8 pass for the bounded reference slice, including a CLJC-shadowed pure port, a denied/allowed capability port, and guarded native OS-isolation conformance.
  • Q9 fleet migration is authorized only for bounded Wave 1 tranches. Later waves and production deployment are not authorized, and the ClojureScript oracle is retained.
  • Runtime-engine vulnerabilities remain inside the trusted computing base; native loaders still require a second OS isolation boundary.
  • Key custody and revocation distribution remain operational, not linguistic, guarantees.

If a claim is not on this page, assume it is not being made.

Read the source

kotoba-lang/kotoba-lang

The language authority: profile, grammar, capability semantics, safety claims, conformance fixtures.

github.com/kotoba-lang/kotoba-lang

kotoba-lang/compiler

Frontend admission, effect inference, KIR, and the emit backends.

github.com/kotoba-lang/compiler

kotoba-lang/kotoba

Language and library substrate, host implementations, semantic-code identity, integration tests.

github.com/kotoba-lang/kotoba

kotoba-lang/kototama

The tender: admits emitted components, links imports and exports, binds granted capabilities, enforces limits.

github.com/kotoba-lang/kototama

This page is generated by site/generate.cljs in kotoba-lang/kotoba-lang, from the authority files it cites: lang/safety-claims.edn, lang/surface-status.edn, lang/capability-semantics.edn, lang/elaboration-pipeline.edn, lang/wasm-component-platform.edn, lang/code-identity.edn, lang/safety-qualification.edn. Change the spec and the page changes; it cannot claim more than the spec claims.