Bindings GitHub issue
- Why bindings exist
- Install: operator-only, not runtime
- How a Caspian class uses a binding
- Naming convention
- Core bindings (ship with every engine)
- Non-core bindings
- Common driver grammar (planned)
- Open questions
vibecode
{"vibecode": { "doc": "bindings", "role": "spec for the binding mechanism that lets Caspian classes reach Lua-backed functionality (HTTP, filesystem, SQLite, signing, parsing) without breaching the Caspian/Lua security floor", "key_concepts": ["binding_mechanism", "lua_bridge", "operator_install_only", "sandbox_seal", "engine_startup_load", "controlled_capability_surface"] }}
A binding is the mechanism by which a Caspian class reaches Lua-level functionality (a parsed HTML tree, an HTTP client, a SQLite connection, etc.) without breaching the security floor that prevents arbitrary Caspian code from accessing Lua directly.
Why bindings exist GitHub issue
Caspian is designed so user-level code cannot reach into Lua. That seal is a load-bearing security property — without it, untrusted code could escape the Caspian sandbox into arbitrary Lua and from there into the host process.
But Puck's framework needs Lua-backed functionality everywhere: HTTP, filesystem, JSON, SQLite, signing, markup parsing, etc. The question is how user-level Caspian classes (Uma, mikobase, Sammy, etc.) get to use these Lua libraries without breaking the seal.
Bindings are the answer. They're a controlled bridge: each binding wraps a specific Lua library, exposes a narrow Caspian-callable surface, and is loaded by the engine at startup — not at runtime. Caspian classes consume bindings the same way they consume any other capability.
Install: operator-only, not runtime GitHub issue
A binding includes Lua code. Loading it gives the engine new Lua-level functionality. This is a privileged operation — the engine operator decides what bindings are installed; Caspian code cannot install or fetch bindings at runtime.
This is intentional and matches how privileged things work elsewhere in the framework (engine capability grants, role definitions, etc.). Operator-controlled install means:
- A deployment's binding surface is a known set, established at deploy time.
- Untrusted Caspian code cannot pull in new Lua at runtime.
- Third-party bindings are real — anyone can write one — but installing them is a deliberate operator action, like installing a system service.
This is not equivalent to pip install or gem install. Treat binding install as more like adding a system service.
How a Caspian class uses a binding GitHub issue
A class declares its binding dependency, and the engine raises a clear error at class-load time if the binding isn't installed. The class's Caspian code then calls the binding's exposed surface to reach the Lua library.
(Exact mechanism — declaration syntax, error shape, discovery API — to be specified.)
The flow:
- Operator installs bindings the engine supports.
- Engine starts up, registers installed bindings.
- Caspian class loads; if it depends on a binding, the engine checks the binding is present. If not, error.
- Class methods call the binding's surface as needed; binding internals call Lua.
- Bound Lua functionality is exposed to Caspian only through the binding's surface — never directly.
Naming convention GitHub issue
Bindings live under puck.uno/binding/...:
puck.uno/binding/httppuck.uno/binding/fspuck.uno/binding/json- etc.
Named after the capability, not the implementation. The implementation can change (swap libcurl for something else, swap gumbo for another HTML parser) without renaming the binding.
Core bindings (ship with every engine) GitHub issue
Every engine ships with this baseline set:
| Binding | Purpose | Backing |
|---|---|---|
puck.uno/binding/http |
HTTP client + server | libcurl + libmicrohttpd (or similar) |
puck.uno/binding/fs |
Filesystem operations including flock/fcntl locking |
Lua io + LuaFileSystem or similar |
puck.uno/binding/json |
Parse/serialize JSON | lua-cjson or similar |
puck.uno/binding/sqlite |
SQLite driver (used by mikobase) | LuaSQLite3 or similar |
puck.uno/binding/markup |
HTML + XML parsing/serialization | gumbo bindings or similar |
puck.uno/binding/fork |
Process spawning, IPC, signals | luaposix or similar |
puck.uno/binding/sodium |
Cryptographic primitives — hashing (SHA-256, BLAKE2b), signing (Ed25519), secure random, constant-time comparison | luasodium / libsodium |
puck.uno/binding/time |
Clock, timestamp parsing/formatting, timezone math | OS time + Lua wrappers |
Probably ship (still TBD):
| Binding | Purpose |
|---|---|
puck.uno/binding/compress |
gzip/zlib (HTTP content encoding, log compression) |
puck.uno/binding/encoding |
base64, hex, URL encoding, UTF-8 utilities |
HTTP client is non-negotiable GitHub issue
Puck's remote-object IPC depends on HTTP client. Every engine needs this binding — without it, %puck lookups against remote objects don't work. It's not optional.
Filesystem includes locking GitHub issue
puck.uno/binding/fs must include POSIX-style file locking (flock/fcntl). Jasmine's directory store and several other parts of the framework depend on it for concurrency.
What the sodium binding covers GitHub issue
Puck uses these primitives for:
- Blockchain signing (see blockchain.md).
- Mikobase file deduplication via SHA-256.
- Secure random for UUIDs (Jasmine entries, tokens, identifiers).
- Constant-time secret comparison where needed.
The binding is named puck.uno/binding/sodium after libsodium, the underlying C library. Naming it that way is intentional — it's specific (sodium is a library, not a category) and sidesteps the ambiguity of the word "crypto" (which has been colloquially hijacked by cryptocurrency contexts).
Non-core bindings GitHub issue
Bindings that don't ship by default but the operator can install:
Postgres driver GitHub issue
puck.uno/binding/postgres — Postgres driver for mikobase backends and other database needs. Non-core but top-priority. Building any real site without Postgres is going to be painful; expect this to be one of the first non-core bindings most deployments install.
A working Ruby Pg implementation already exists and serves as the reference for the Caspian binding.
Others GitHub issue
Anything else outside the core set: DNS-specific work (SRV/MX/TXT records), email (SMTP), WebSockets standalone, image processing, PDF generation, gRPC, etc. — all third-party binding territory.
Common driver grammar (planned) GitHub issue
Inspired by Perl's DBI (Database Interface), where drivers (DBD::SQLite, DBD::mysql, DBD::Pg) all implement a uniform interface, so application code is portable across backends.
Puck will adopt the same approach for relational drivers. A common grammar for puck.uno/binding/sqlite, puck.uno/binding/postgres, and any future relational binding — each implements the shared surface; mikobase (and other consumers) codes against the shared surface, not the specific driver.
Start now, while only SQLite is in scope. The grammar gets shaped by one concrete driver and is ready when Postgres lands. Trying to unify after multiple drivers have diverged is the hard way (Ruby went that route — ActiveRecord, Sequel, etc. all built their own driver models, fragmenting the ecosystem; the dbi gem never caught on).
The shape of this grammar is TBD — connect, prepare, execute, fetch, commit, rollback, etc. — to be designed alongside the SQLite binding spec.
Open questions GitHub issue
Deferred from the initial design conversation; will be addressed as bindings get spec'd:
- How does a class declare its binding dependency? Probably something like
requires_binding 'puck.uno/binding/markup'at class load time. - What's the discovery API? How does a deployment enumerate installed bindings so authors can reason about availability?
- What's the binding API shape? Probably thin Caspian-callable functions and primitive data exchange. Full object passing across the boundary gets messy.
- Versioning. How do bindings declare and respect their own version?
- Install mechanics. What does the operator-level install actually look like — a directory? a config file? a registry?