Authoring & Publishing a Module
A module is a unit of custom logic written in
Starlark
(a small, deterministic
Python dialect). Modules have a single entrypoint — main(input) — that
transforms an input dict into an output dict, and a manifest that declares
their identity, version, and capabilities (what side effects they are
allowed to perform). A module that requests no capabilities can have no
side effects: the safest possible default.
This guide writes a module, tests it, runs it locally, then builds, signs,
publishes, and installs it. It uses the kscore-module and
kscore-registry CLIs.
Prerequisites
- Keystone Core installed (
kscore-module versionworks). openssl(for generating a signing key in the publish step).
1. Scaffold a module
kscore-module init keystone/greeter --dir greeterThat writes:
greeter/
├── manifest.yaml # identity, version, type, entrypoint, capabilities
└── main.star # the module: def main(input)The manifest:
name: keystone/greeter
version: 0.1.0
type: starlark
entrypoint: main.star
description: A Keystone Core module
license: Apache-2.02. Write the logic
Edit greeter/main.star. main(input) is the entrypoint; factor out
pure helpers so they are easy to test:
def greet(name):
return "hello, %s!" % (name or "world")
def main(input):
return {"message": greet(input.get("name", ""))}3. Add unit tests
Tests live in *_test.star next to the module. Every top-level
test_* function is a test; the module’s functions are in scope
directly. Create greeter/main_test.star:
def test_default():
assert.eq("hello, world!", main({})["message"])
def test_named():
assert.eq("hello, ada!", main({"name": "ada"})["message"])Tests must be deterministic — they run without any capabilities, so they can’t touch the network or filesystem.
4. Validate, test, run
kscore-module validate greeter # manifest is well-formed
kscore-module test greeter # run the *_test.star files
kscore-module run greeter '{"name":"ada"}' --skip-verificationExpected output:
ok: keystone/greeter@0.1.0
tests: 2 passed, 0 failed
{
"message": "hello, ada!"
}run verifies a module’s signature by default; --skip-verification is
the local-development escape hatch (you sign for real in step 6).
5. Capabilities
The scaffold requests no capabilities, so greeter is a pure
transform. A module that needs side effects declares them in the
manifest, and the runtime denies anything not granted. For example, a
module that fetches a URL declares the http capability:
capabilities:
http:
allow_hosts: ["example.com"]Browse the
example modules
for the capability shapes — httpfetch (http), cmdrun (exec),
secretsync (secrets), fsreport (filesystem).
6. Build & sign
Package the module as a ZIP, then sign it. Signatures are detached and Cosign-compatible.
kscore-module build greeter -o greeter-0.1.0.zip
# A signing key (ECDSA P-256 is fine; SEC1 / PKCS8 / PKCS1 are accepted)
openssl ecparam -name prime256v1 -genkey -noout -out signing.key
kscore-module sign greeter-0.1.0.zip --key signing.key
# → greeter-0.1.0.zip.sigVerify the artifact against the matching public key — what a consumer does before trusting it:
openssl ec -in signing.key -pubout -out signing.pub
kscore-module verify greeter-0.1.0.zip --key signing.pub7. Publish
Run a registry, then publish the signed artifact to it. In one terminal:
kscore-registry serve --addr :8181 --dir ./registry-dataIn another:
kscore-module publish greeter-0.1.0.zip --registry http://localhost:8181The publish uploads the ZIP and its .sig sidecar.
8. Install
Resolve, download, verify, and lock the module (and any dependencies) from the registry:
kscore-module install keystone/greeter@0.1.0 --registry http://localhost:8181Install writes a module.lock pinning the resolved versions + digests, so
re-installs are reproducible and tamper-evident.
Next steps
- Authoring a Blueprint — package a whole deployment.
- The CLI reference
for every
kscore-moduleandkscore-registryflag.