2. Contracts — @get-bb/plugin-sdk/contracts
@get-bb/plugin-sdk/contracts is what a plugin's contracts module imports (02 §9). It is browser-safe: no Node import reaches the entry, and the app tier loads it through the import map. It adds no runtime of its own; defineService, method and defineEvent wrap @bb/kernel-contract and @bb/kernel-core with the SDK's pre-checks, and everything else is a re-export. A contracts module exports defineService objects, defineEvent objects, defineHostCommands objects and declare module merges; it contains no handlers. 02 §9 places renderText in the tier entry; the built example keeps it on the method in the contracts module, and contractJsonOf drops it. The built source wins over the spec where they differ; each row says so.
Path shorthand below: <pkg>:<file> means packages/<pkg>/src/<file>. Every value and type the lock lists for this entry appears in a table in this section.
2.1 The published-contracts convention
| Member | Signature (abridged) | What it does | Spec | As built |
|---|---|---|---|---|
CONTRACTS_SUBPATH |
"./contracts" |
The package.json#exports key a contracts module publishes under. |
01 §1, 02 §9 | plugin-sdk:contracts/convention.ts |
pluginPackageName |
(pluginId, trust = "published") => string |
@get-bb/plugin-<id>; @bb-local/<id> for trust: "local" (forks, bb plugin new). |
01 §1 | same |
contractsSpecifier |
(pluginId, trust = "published") => string |
${pluginPackageName(id, trust)}/contracts: the specifier a sibling imports. |
02 §9 | same |
A sibling plugin imports only this subpath; bb plugin build fails any other import of a sibling (D19, plugin-build:externals.ts). bb plugin build imports the module named by package.json#bb.contracts, runs contractJsonOf (§2.9) and writes dist/contract.json; nothing is written back into package.json (03 §2.4).
// examples/plugins/hello-slot/src/contracts.ts (abridged)
export const greeter = defineService({
id: "hello-slot/greeter", version: "1.0.0", summary: "Greet by name and count greetings",
cli: { group: ["hello"] },
methods: {
greet: method({
kind: "query", summary: "Greet someone by name",
input: z.strictObject({ name: nameSchema, salutation: z.string().min(1).max(32).default("Hello") }),
output: greetingSchema, expose: { tool: true },
cli: { fields: { name: { positional: 0 } }, examples: [{ argv: "bb hello greet bb", note: "prints `Hello, bb!` and the counter" }] },
tool: { presentation: { label: { pending: "Greeting…", completed: "Greeted" }, icon: "hello-slot/wave", intent: "generic" } },
renderText: (g) => `${g.greeting} (count ${g.count})`,
}),
count: method({ kind: "mutation", summary: "Record a greeting in the plugin's data.db counter",
input: z.strictObject({ name: nameSchema }), output: greetingSchema, cli: { fields: { name: { positional: 0 } } } }),
},
});2.2 defineService / defineContract and method
| Member | Signature (abridged) | What it does | Spec | As built |
|---|---|---|---|---|
defineService |
<const M>(def: ServiceInput<M>) => ServiceDef<M> |
Normalizes a service: checks the id, fills cli.group, command, cli.name, tool.name, builds .contract. The SDK adds: version must be a full semver; multi needs factsKey and facts; factsKey only with multi. |
03 §2.1 | plugin-sdk:contracts/define.ts → kernel-contract:define.ts |
defineContract |
= defineService |
The brief's spelling; one function. | — | plugin-sdk:contracts/define.ts |
method |
(def: Omit<MethodInput<I,O,"query">,"kind">) => MethodDef<I,O,"query"> / (def: MethodInput<I,O,K> & {kind: K}) => MethodDef<I,O,K> |
Normalizes one method with every default filled once; runs the invalid_contract checks below. Two overloads: no kind means "query". |
03 §2.1 (one signature) | D4: two overloads; kernel-contract:define.ts |
ServiceInput<M> |
{id, version, summary, methods: M, cli?, facts?, multi?, factsKey?} |
Author input to defineService. cli is {group?, aliases?, hidden?, extensible?}. |
03 §2.1 | kernel-contract:define.ts |
ServiceDef<M> |
{id, version, summary, cli: ServiceCliMeta, methods: M, facts, multi, factsKey, contract} |
The normalized service. contract is the 02 ServiceContract the container registers; a custom method is query-shaped over the HTTP pair there. |
03 §2.1 (extends ServiceContract) |
carries .contract instead of extending; kernel-contract:README |
ServiceCliMeta |
{group: string[], aliases: string[][], hidden, extensible} |
Service-level CLI group. Default group [kebab(serviceName)]; at most two levels; extensible opens a kernel-owned group to other plugins' words (a non-kernel group is open to method-level mounts regardless). |
03 §2.1, §5.3 | kernel-contract:define.ts, cli/tree.ts |
MethodInput<I,O,K> |
{kind?, input, output, command?, summary, description?, expose?, cli?, tool?, target?, auth?, local?, actors?, destructive?, blocking?, resumable?, path?, renderText?, jsonSchema?, errors?} |
Author input to method; every optional is a default, filled once. |
03 §2.1 | kernel-contract:define.ts |
MethodDef<I,O,K> |
the normalized method, every field present (table below) | What every derivation reads. Read command, cli.name, tool.name from a ServiceDef, never from a bare method() result. |
03 §2.1 | D4 |
MethodKind |
"query" | "mutation" | "stream" | "custom" |
Verb, transport and handler shape per kind. Query or mutation: the kernel applies no rule to a query's side effects — a query is a plain GET that bypasses the command bus, so it gets no command name, before/after hooks, deadlineMs, or CLI y/N; choose mutation when you want those (hello-slot: greet is a query that only reads data.db; count writes and publishes, and is a mutation). |
03 §2.1 | same; no side-effect check in kernel-contract:define.ts or invoke.ts |
Target |
"core" | "host" |
Where the handler runs. host answers 503 service_unavailable today. |
03 §5.9 | D5 |
Auth |
"operator" | "machine" | "none" |
Which callers are admitted (§8.2). | 03 §9.2 | kernel-contract:http/auth.ts |
Expose |
{http, sdk, cli, tool, ui} (all boolean) |
Which surfaces derive. MethodInput.expose is Partial<Expose>: give only the gates you change (expose: { tool: true }); the rest keep their defaults http/sdk/cli/ui: true, tool: false. sdk/ui are X-BB-Client.kind gates on the one HTTP route, not security. |
03 §2.1 | D5; kernel-contract:define.ts { ...defaults, ...def.expose } |
CliFieldMeta |
{positional: number|null, flag, alias: string|null, ambient: Ambient|null, hidden, variadic} |
One input field's CLI form. One row exists for every input property (defaults filled). | 03 §2.1, §5.4 | kernel-contract:define.ts |
CliMethodMeta |
{name, aliases, group: string[]|null, flagAlias, fields, examples: {argv: string, note: string}[], hidden, offline} |
The method's CLI word. group: null means the service group; flagAlias routes bb <group> <name> --<flag> here; examples items are { argv, note } (default []), printed in help. |
03 §2.1, §5.3 | same |
ToolMeta |
{name, description, instructions, presentation: ToolPresentation, ui: {renderer}|null} |
Agent-tool metadata; presentation is required when expose.tool. |
03 §2.1, §6.1 | same |
ToolPresentation |
{label: {pending, completed}, icon: string, intent: RenderIntent, suppress} |
Becomes DynamicTool.presentation verbatim; suppress filled false. icon is an @bb/ui icon name (§4.11: a core name such as Zap, an extended name, or <pluginId>/<name>; only the first two render in the slice). intent ∈ generic | terminal | diff | search | read | list | web | image (RenderIntent, bridge-kit/src/protocol/common.ts renderIntentSchema; §7.7 repeats the list). |
03 §2.1, 06 §4.3 | same; icon is typed string in kernel-contract:define.ts |
RenderText<I,O> |
(value: InferOutput<O>, ctx: RenderCtx<InferOutput<I>>) => string |
Text renderer for CLI text mode and tool content. Runs where the plugin code is loaded; not in contract.json. |
03 §2.1, §5.5 | bivariant method type; kernel-contract:define.ts |
RenderCtx<In> |
{width, color, tty, input} |
From Bb-Render: text; width=; color=; tty= or defaults {80, false, false}. |
03 §2.1, §5.5 | kernel-contract:context.ts |
CallContext |
02's {callId, actor, session, scope, signal, command, traceId} + {caller, ambient, render, deadline, since, log} |
What a handler receives as its second argument. | 03 §2.3 | extension rides a side table; kernel-contract:context.ts |
CustomHandler |
{http(req: Request, ctx), cli?(argv, ctx)} | {httpSerializable(req: PluginHttpRequest, ctx), cli?} |
Handler of a custom method: Fetch-shaped in-process, or the serializable pair in either placement. |
03 §2.2 | kernel-contract:define.ts |
MethodHandler<D> |
stream → (input, ctx) => AsyncIterable<O>; custom → CustomHandler; else (input, ctx) => Promise<O> |
Handler type per kind, from a MethodDef. |
03 §2.2 | same |
ServiceHandlers<D> |
{[N in keyof D["methods"]]: MethodHandler<…>} |
What ctx.provide(def, handlers) takes. |
03 §2.2 | same |
PluginHttpRequest / PluginHttpResponse |
{method, path, query, headers, body} / {status, headers, body}; body is {kind: "text", text} | {kind: "bytes", base64} |
The serializable HTTP pair; validated once by kernel-core's handle. | 03 §2.2 (adds {kind: "stream"}) |
no stream body; >1 MiB is 413; kernel-contract:README |
StandardSchemaV1 |
@standard-schema/spec |
Every input, output, payload, facts is one. zod 4 is the first-party validator. |
03 §2.1 | re-exported from kernel-core:schema.ts |
SchemaOf<T> |
StandardSchemaV1<unknown, T> |
A schema whose output is T; the SDK's schema parameter type. |
— | plugin-sdk:contracts/handles.ts |
JsonSchema |
JsonObject |
The JSON Schema carrier read from ~standard.jsonSchema.input/output; drives CLI, tool, types and docs. |
03 §2.1 | kernel-core:schema.ts |
MethodDef fields, as method() and defineService fill them (kernel-contract:define.ts):
| Field | Type | Default | Meaning |
|---|---|---|---|
kind |
MethodKind |
"query" |
query GET, mutation POST through the command bus, stream SSE, custom raw HTTP. |
input |
StandardSchemaV1 |
required | Root must be a closed object (additionalProperties: false, i.e. z.strictObject) unless custom (D4). |
output |
StandardSchemaV1 |
required | Result schema; for stream, the schema of one item. |
command |
CommandName | null |
mutation: <pluginId>/<service>.<method>; else null |
The command-bus name; only a mutation may declare one. |
deadlineMs |
number | null |
mutation: blocking.maxMs else 60_000; else null |
Whole-dispatch deadline the command bus enforces. |
actors |
ReadonlyArray<Actor["kind"]> |
all four | Actor kinds admitted; others get forbidden. Accepted on every kind (03 says mutations only). |
destructive |
boolean |
false |
CLI asks y/N unless --yes; tool description appends a warning. |
summary |
string |
required | One line, 1–80 characters. |
description |
string | null |
null |
Markdown paragraphs for docs and the tool description. |
expose |
Expose |
{http: true, sdk: true, cli: true, tool: false, ui: true} |
Surface gates. |
cli |
CliMethodMeta |
name: kebab(method), aliases: [], group: null, flagAlias: null, fields: one row per property (flag: kebab(field)), examples: [], hidden: false, offline: false |
CLI derivation input; offline: true is accepted and ignored until the cli tier ships (Appendix B). |
tool |
ToolMeta | null |
null; name: <plugin>_<service>_<method> (snake) when given |
Must be present with expose.tool and absent without it. |
target |
Target |
"core" |
"host" runs in the plugin's host tier; 503 today (D5). |
auth |
Auth |
"operator" |
Admitted callers (§8.2). "none" on a mutation requires local: true. |
local |
boolean |
false |
Served only to loopback callers, never through the gate. |
blocking |
{maxMs} | null |
null |
May park on an interaction; 1..3_600_000; becomes deadlineMs and Bb-Deadline. |
resumable |
boolean |
false |
Streams only; the handler gets ctx.since from Last-Event-ID. |
path |
string | null |
null |
custom + kernel services only: an absolute mount outside /api/v1. |
renderText |
RenderText | null |
null |
Text renderer; genericText otherwise. |
jsonSchema |
{input, output} |
from ~standard.jsonSchema (draft 2020-12) |
Set it only when the validator has no converter. |
errors |
Record<string, {status, summary}> |
{} |
Declared plugin codes <pluginId>/<snake_code> with a 4xx/5xx status; an undeclared code is rewritten to plugin_error. |
invalid_contract is thrown at definition time for: a summary outside 1–80 chars; command on a non-mutation; blocking.maxMs outside 1..3_600_000; resumable off a stream; path off a custom method or off a kernel service; tool.instructions over 4096 bytes; auth: "none" on a non-local mutation; expose.tool without tool.presentation, tool without expose.tool, or expose.tool on custom; a non-strict input root; a cli.fields key that is not an input property; a multi-letter alias; a repeated positional; variadic on a non-positional or non-array field, or more than one; an errors code outside <pluginId>/<snake> or status outside 4xx/5xx; a tool name outside [a-zA-Z0-9_-]+; a service id not <pluginId>/<kebab-service>; a CLI group deeper than two levels; a non-semver version; multi without factsKey + facts; factsKey without multi; a multi contract with a mutation (kernel-core, D2).
2.3 Handles and handlers
| Member | Signature (abridged) | What it does | Spec | As built |
|---|---|---|---|---|
AnyContract |
ServiceDef | ServiceContract |
What provide / inject accept: a defineService object or a bare kernel contract. |
02 §3 | plugin-sdk:contracts/handles.ts |
ServiceDefHandle<D> |
HandleInfo & {[M]: (input, opts?: CallOverrides) => Promise<O> | AsyncIterable<O>} |
The handle methods of a ServiceDef; a custom method is (PluginHttpRequest) => Promise<PluginHttpResponse>. |
02 §3.6 | same |
HandleOf<C> |
C extends ServiceDef ? ServiceDefHandle<C> : ServiceHandle<C> |
The typed handle ctx.inject(def) and useService(def) return. |
02 §3.6 | same |
HandlersOf<C> |
C extends ServiceDef ? ServiceHandlers<C> : ServiceImpl<C> |
The handler map ctx.provide(def, …) takes. |
03 §2.2 | same |
FactsOf<C> / FactsOutputOf<C> |
InferInput<C["facts"]> / InferOutput<C["facts"]> |
The facts a provider passes / a consumer reads. | 02 §3.5 | same |
isServiceDef |
(c: AnyContract) => c is ServiceDef |
True when c.contract is an object. |
— | same |
contractOf |
(c: AnyContract) => ServiceContract |
The 02 view either shape binds by. | 02 §3 | same |
ServiceContract |
{id, version, methods, facts, multi, factsKey} |
kernel-core's normalized contract; ServiceDef.contract is one. |
02 §3.1 | kernel-core:contract.ts |
ServiceId |
`${string}/${string}` |
<pluginId>/<service>; pluginId [a-z0-9][a-z0-9-]*, service [a-z][a-zA-Z0-9.-]* (the dot admits kernel/secrets.backend); defineService narrows the service part to kebab-case. |
02 §3.1 | kernel-core:names.ts |
2.4 Events: defineEvent
| Member | Signature (abridged) | What it does | Spec | As built |
|---|---|---|---|---|
defineEvent |
<Name extends keyof Events>(input: EventDefinitionInput<Name>) => EventDefinition<Name> |
Normalizes an event (wire: null, persist: null unless given). Rejects a name that is not <owner>/<name> and wire on a non-emit mode (invalid_contract). |
02 §5.1 | plugin-sdk:contracts/define.ts |
Events |
interface Events {} (merged) |
Declaration-merging hook: declare module "@get-bb/plugin-sdk/contracts" { interface Events { "<id>/<name>": EventDecl<P, M, R> } }. Kernel events are pre-declared. |
02 §5.1 | kernel-core:events.ts |
EventDecl<P, M, R = void> |
{payload: P, mode: M, result: R} |
The type-level declaration of one event. | 02 §5.1 | same |
EventDefinitionInput<N> |
{name, mode, payload: StandardSchemaV1, scope: "server"|"client"|"host", wire?: {to: ("client"|"server")[], key: {path, prefix}|null}, persist?: {retention: "forever"|{days}}} |
Author input; wire.key is data (<prefix>:<payload[path]>), never code. key: null publishes every frame under key null; a client target with key: null (invalidateOn, events.on) matches every frame of that name, keyed or not (keyOf in kernel-core:schema.ts; define-query.ts t.key === null || t.key === frame.key). |
02 §5.1 | same |
EventDefinition<N> |
same fields, wire and persist nullable and present |
What ctx.events.define / ctx.realtime.declare take. |
02 §5.1 | same |
EventMode |
"emit" | "waterfall" | "parallel" |
emit: fire-and-forget, the only mode that crosses the wire; waterfall: around-middleware with next; parallel: all listeners, Array<R | KernelError>. There is no serial (02 §1.1 cut it). |
02 §5.2 | kernel-core:events.ts |
EventName |
`${string}/${string}` |
<owner>/<name>; a plugin may define only its own owner; kernel/ is reserved_name. |
02 §5.1 | kernel-core:names.ts |
Listener<N> |
emit (payload, meta) => void; waterfall (payload, next, meta) => Promise<R>; parallel (payload, meta) => Promise<R> |
Listener signature per mode. | 02 §5.2 | kernel-core:events.ts |
// hello-slot: the merge, then the definition the server tier passes to ctx.realtime.declare / events.define
declare module "@get-bb/plugin-sdk/contracts" {
interface Events { "hello-slot/greeted": EventDecl<GreetedPayload, "emit"> }
}
export const greeted = defineEvent({
name: "hello-slot/greeted", mode: "emit", payload: greetedPayloadSchema, scope: "server",
wire: { to: ["client"], key: null },
});2.5 Commands: defineCommand
| Member | Signature (abridged) | What it does | Spec | As built |
|---|---|---|---|---|
defineCommand |
(input: ClientCommandInput<In, Out>) => CommandDefinition<In, Out> |
A side: "local" client command (origin: {kind: "client"}, deadlineMs: null). Server commands are never written by hand: provide() derives one per mutation. Rejects a name outside the grammar. |
02 §6.3 | plugin-sdk:contracts/define.ts |
ClientCommandInput<In, Out> |
{name, input, output, actors?, destructive?} |
Author input; actors defaults to all four kinds, destructive to false. |
02 §6.3 | same |
CommandDefinition<In, Out> |
{name, input, output, actors, side, destructive, deadlineMs, origin} |
The normalized command the bus stores. | 02 §6.3 | kernel-core:commands.ts |
CommandName |
`${string}.${string}` |
<noun>.<segment>(.<segment>)*; noun [a-z][a-z0-9-]*(/[a-z][a-z0-9-]*)?; nounOf is everything before the last dot. Reserved nouns: kernel, composition, plugin, host, preference, secret. A plugin always owns <pluginId>/… nouns; other server nouns come from contributes.commands.nouns, by composition order. |
02 §6.2 | D2; kernel-core:names.ts |
Commands |
interface Commands {} (merged) |
declare module … { interface Commands { "<name>": { input; output } } } types CommandInput/CommandOutput and the bus. |
02 §6.3 | kernel-core:commands.ts |
CommandInput<N> / CommandOutput<N> |
Commands[N]["input"] / ["output"], else unknown |
The typed input/output of a named command. | 02 §6.3 | same |
2.6 Host commands: defineHostCommands
| Member | Signature (abridged) | What it does | Spec | As built |
|---|---|---|---|---|
defineHostCommands |
<const C, const S = {}>(input: HostCommandsInput<C, S>) => HostCommandsContract<C, S> |
The one object both ends validate against: the server's ctx.hostClient(contract, name) and the role process. Checks the plugin id and kebab-case names. Manifest hostCommands/hostSignals list names only. |
05 §4.7 | plugin-sdk:contracts/define.ts |
HostCommandsInput<C, S> |
{id, commands: C, signals?: S} |
Author input; omitting signals means the plugin emits none. |
05 §4.7 | same |
HostCommandsContract<C, S> |
{id, commands: C, signals: S} |
Normalized; wire methods are <id>/<command>. |
05 §4.7 | same |
HostCommandDef<In, Out> |
{input: In, output: Out} |
One command's schemas. | 05 §4.7 | same |
HostSignalDef<P> |
{payload: P} |
One signal's payload schema. | 05 §4.7 | same |
export const helloHost = defineHostCommands({
id: "hello-slot",
commands: { "host-greet": { input: z.strictObject({ name: nameSchema }), output: hostGreetingSchema } },
});2.7 Kernel hook service contracts
These are kernel-core ServiceContract objects (not ServiceDefs); inject them by object. All are version 1.0.0.
| Member | Signature (abridged) | What it does | Spec | As built |
|---|---|---|---|---|
preferences |
kernel/preferences: get {key, scope, threadId} → {value, updatedAt}; set {…, value, expectedUpdatedAt} → {updatedAt} (mutation preference.set); clear {key, scope, threadId} → null (mutation preference.clear); list {prefix} → {entries} |
Profile/thread preferences with compare-and-set; owner check on writes. | 02 §3.7, #29 | set returns {updatedAt}; kernel-core:services.ts |
PrefScope |
"profile" | "thread" |
The scopes that reach core; threadId is non-null exactly for thread. |
02 §3.7 | same |
secrets |
kernel/secrets: resolve {name} → {value}; put {name, value} → null (mutation secret.set, actors: ["human"]); delete {name} → null (mutation secret.delete); list {} → {names, backend} |
Secrets through the active backend. | 02 §3.7 | same |
secretsBackend |
kernel/secrets.backend (multi, factsKey: "backendId"): get {name} → {value}; set {name, value} → null; delete {name} → null; list {} → {names} |
A backend a plugin provides; query-shaped because multi contracts carry no mutations. | 02 §3.7 | D2 |
SecretsBackendImpl |
ServiceImpl<typeof secretsBackend> |
The handler map a backend provides. | 02 §3.7 | same |
telemetry |
kernel/telemetry: record {name, props} → null |
Fire-and-forget fan-out to every sink; a no-op without sinks. | 02 §3.7 | same |
telemetrySink |
kernel/telemetry.sink (multi, factsKey: "sinkId"): record {name, props} → null |
A sink a plugin provides. | 02 §3.7 | same |
TelemetrySinkImpl |
ServiceImpl<typeof telemetrySink> |
The handler map a sink provides. | 02 §3.7 | same |
2.8 Errors, envelope, actors, JSON
| Member | Signature (abridged) | What it does | Spec | As built |
|---|---|---|---|---|
KernelError |
new KernelError({code, message, issues?, data?, retryable?}); .toJSON(): KernelErrorShape; .withData(extra) |
The one error class on every edge. Plugins throw <pluginId>/<code> codes declared in method.errors. retryable defaults to true for timeout and service_unavailable. |
02 §8 | kernel-core:errors.ts |
NeedsConfigurationError |
new NeedsConfigurationError(message, data?) |
code: "needs_configuration"; surfaces through plugin status. |
02 §8, §4.4 | same |
KernelErrorShape |
{code, message, issues: Issue[]|null, data: JsonValue|null, retryable} |
The wire shape; every field present. | 02 §8 | same |
Envelope<T> / Result<T> |
{ok: true, result: T} | {ok: false, error: KernelErrorShape} |
The one envelope (in-process, worker, HTTP); two spellings. The CLI --json prints the bare result; only a failure prints the {ok: false, error} half (§8.4, D6). |
02 §8 | same |
Issue |
{path: (string|number)[], message} |
One validation issue. | 02 §8 | same |
Actor |
{kind: "human"|"agent"|"plugin"|"system", id} |
human "local"; agent threadId; plugin pluginId; system a closed id set. |
02 §7 | kernel-core:actor.ts |
Session |
{id, actor, client: {kind, surface, version}, createdAt, credentialId} |
Minted once at the boundary; client.kind ∈ app|cli|sdk|bridge|worker|internal. |
02 §7 | same |
JsonValue / JsonObject |
recursive JSON types | The payload vocabulary. | 02 §8 | kernel-core:names.ts |
jsonValueSchema / jsonObjectSchema |
z.ZodType<JsonValue> / z.ZodType<JsonObject> |
The one zod JSON schema pair; numbers must be finite. | — | kernel-core:schema.ts |
2.9 contract.json and the catalog
| Member | Signature (abridged) | What it does | Spec | As built |
|---|---|---|---|---|
contractJsonOf |
(defs: readonly ServiceDef[], guide: string | null = null) => ContractJson |
The dist/contract.json block: services sorted by id, every method() default filled, JSON Schemas, guide = contributes.guide path. Pure data; renderText does not travel. |
03 §2.4 | plugin-sdk:contracts/contract-json.ts |
contractJsonText |
(defs, guide?) => string |
The same block as pretty JSON. | 03 §2.4 | same |
ContractJson |
{services: ContractJsonService[], guide: string | null} |
Root of the block. | 03 §2.4 | same |
ContractJsonService |
{id, version, summary, multi, factsKey, facts: JsonSchema, cli: {group, aliases, hidden, extensible}, methods} |
One service. | 03 §2.4 | same |
ContractJsonMethod |
{kind, command, deadlineMs, actors, destructive, input, output, summary, description, expose, cli, tool, target, auth, local, blocking, resumable, path, errors} |
One method; cli.fields/examples/flagAlias and tool.presentation/ui included. |
03 §2.4 | same |
ContractJsonCliField |
{positional, flag, alias, ambient, hidden, variadic} |
CliFieldMeta on disk. |
03 §2.4 | same |
parsePluginCatalog |
(value: unknown) => PluginCatalog |
Parses kernel/catalog.get's plugins / bb ui catalog --json at the boundary. |
Q38 | sdk:catalog.ts |
pluginCatalogSchema / pluginCatalogEntrySchema |
zod objects | {version: 1, plugins, services, slots, eventKinds}; unknown keys stripped (I20), version: 1 is the only hard gate. |
Q38 | same |
PluginCatalog / PluginCatalogEntry |
z.output<…> |
The parsed types. | Q38 | same |
2.10 The remaining declaration-merging hooks
| Member | Signature (abridged) | What it does | Spec | As built |
|---|---|---|---|---|
SlotMap |
interface SlotMap {} (merged) |
{"<slot>": {kind: SlotKind, scope: SlotScope, props: P}}; narrows app.slots.register and SlotComponentProps. |
07 §5.9 | plugin-sdk:app/slots.ts |
SlotEntry<P> |
{kind, scope, props: P} |
The shape of one SlotMap entry. |
07 §5.9 | same |
BbServices |
interface BbServices {} (merged) |
{"<pluginId>/<service>": ServiceClient<typeof def>} types sdk.plugins.<plugin>.<service>.<method> for @bb/sdk consumers. A plugin does not write this merge: ServiceClient is exported by @bb/sdk (packages/sdk/src/client.ts), not by any @get-bb/plugin-sdk entry, and a plugin may not import @bb/sdk (§2.1, D19); bb sdk types emits the merge from JSON Schema for scripts. |
03 §4.1 | sdk:client.ts |
Events / Commands |
see §2.4 / §2.5 | The other two merge targets, all augmented through @get-bb/plugin-sdk/contracts. |
02 §5.1, §6.3 | plugin-sdk:contracts/index.ts |