@knpkv/capability
v0.1.0
Published
Typed capability contracts: Effect Schema input, output and failure, defined once and projected to every surface
Readme
@knpkv/capability
Typed capability contracts: one definition of something a product can do, with Effect Schema input, output and failure, shared by every surface that exposes it (Relay tools, MCP, HTTP, CLI).
Define, implement, invoke
import { defineContract, implement, invoke, ObjectRef } from "@knpkv/capability"
import { Effect, Schema } from "effect"
class PullRequestNotFound extends Schema.TaggedError<PullRequestNotFound>()("PullRequestNotFound", {
id: Schema.String,
fix: Schema.String
}) {}
export const getPullRequestContract = defineContract({
name: "get_pull_request",
description: "Read one pull request.",
access: "read",
input: Schema.Struct({ id: Schema.String }),
output: PullRequestSummary,
failure: PullRequestNotFound,
cites: (pullRequest) => [pullRequest.ref]
})
export const getPullRequest = implement(getPullRequestContract, ({ id }) => repository.get(id))
// A surface calls it with untrusted JSON:
const result = invoke(getPullRequest, { id: "42" }) // Effect<{ output: Json; cites: ObjectRef[] }, ...>Rules the types enforce
- Names must match
^[a-zA-Z0-9_-]{1,64}$(snake_case by convention).defineContractthrowsContractNameInvalidwhen the module loads otherwise. - Input is a
Schema.Structof service-free fields, so tool arguments, CLI flags and HTTP queries all get named fields. - Failures are declared. Each is a tagged error with a
messageand afix. A handler that fails with anything else does not typecheck, andinvokechecks the declared union at runtime too: a failure outside it (from untyped code, or a value the failure schema rejects) is a defect carrying the original value, which surfaces report as a generic internal error without its message. - Access:
readruns.write(a person confirms) andhost(a host approval) must carryreversibleand a puredescribe(input), which returns the exactPendingActionthe person deciding sees.describeCallaccepts only those.
What a surface gets
invoke(capability, args)decodesargswith the input schema, runs the handler, and encodes the output as JSON with its citations. A declared failure becomesCapabilityFailedwithtag,reason,fixand the encoded failure; invalid arguments becomeCapabilityInputInvalid.describeCall(contract, args)returns the pending action for decoded arguments.- What a contract's own callbacks return (
cites,describe) is checked againstObjectRefandPendingAction; an empty reference, an empty verb or a non-JSON argument is a bug in the capability and fails withCapabilityEncodingFailed. inputJsonSchema(contract)returns the input's JSON Schema for a tool list.
Gating (confirmations, approvals) belongs to the surface: call invoke only after a gated action has been agreed to.
