Skip to content

Connector author contract

Kata external-root connectors are executables that implement the versioned kata.connector.v1 JSON protocol. Provider-specific credentials and APIs stay inside the connector.

This page is the contract for connector authors: the wire protocol, the public Go SDK, and the conformance kit. Operators configure connector instances with [[connector]] tables in <KATA_HOME>/config.toml (see the configuration reference) and manage bindings with the kata connector and kata bridge commands (see the CLI reference).

Process and framing

Kata starts one fresh connector process for each RPC. It writes exactly one JSON request to standard input, closes the request stream, and expects exactly one JSON response on standard output. Extra JSON, malformed responses, mismatched request IDs, an unsupported protocol version, or output beyond the bounded response limit fail the call. Diagnostic output is not part of the protocol.

Every request contains:

{
  "protocol": "kata.connector.v1",
  "id": "opaque-request-id",
  "method": "read_root",
  "instance": "notes",
  "settings": {"workspace": "example-workspace"},
  "params": {"root_key": "opaque-root-key"}
}

The response repeats protocol and id, then returns exactly one of result or error:

{
  "protocol": "kata.connector.v1",
  "id": "opaque-request-id",
  "error": {
    "code": "root_unavailable",
    "message": "The external root is temporarily unavailable"
  }
}

Errors must be safe, bounded operator text: a lowercase structured code and a message without credentials, absolute paths, command lines, stack traces, standard-output/error dumps, or control characters. The process client redacts values supplied through the instance's explicit connector.env mapping. The connector remains responsible for returning safe errors.

Methods and capabilities

All handlers implement these methods:

Method Purpose
describe Return stable connector/account identity, protocol, capabilities, and optional settings schema.
resolve_root Turn an operator-provided locator into a canonical root.
read_root Read title, body, lifecycle state, revision, timestamps, actor, and optional fields.
list_comments Return the root's canonical comment observations.
complete_root Complete the external root and return verified readback.
publish_comment Publish one comment and return its provider identity and timestamps.
list_fields Discover canonical external field descriptors.
read_fields Read selected canonical field values.
write_fields Write selected fields, optionally with compare-and-set preconditions, then return canonical readback.

publish_comment is advertised with the publish_comment capability. The field methods are advertised with fields. Compare-and-set field writes are advertised separately with conditional_fields. Kata rejects a conditional write when the connector does not advertise that capability; it does not retry the write without its preconditions. A connector may return a structured unsupported error for optional methods whose capability it does not advertise.

Root keys, account identities, connector IDs, actor IDs, comment IDs, field IDs, and schema revisions are opaque stable identities. Do not encode Kata issue UIDs, refs, project IDs, binding IDs, branches, or other Kata-owned identity inside provider metadata. Field descriptor IDs and schema revisions must already be canonical: surrounding whitespace is invalid.

Root and comment revisions are opaque change tokens. A revision advances when the canonical root or comment changes and remains stable while that observation is unchanged; clients compare revisions for equality rather than ordering. Timestamps use RFC 3339. Field values use the portable date, local_datetime, instant, string, string_set, and null kinds accepted by the conformance transcripts; a descriptor lists the exact kinds it accepts and whether it is nullable and writable.

Protocol v1 write_fields requests always contain fields. Connectors that advertise conditional_fields may also receive expected; when present, the two objects have identical field-ID keysets. The connector writes only when every current value equals its canonical expected value. Otherwise it changes nothing and returns a field_conflict error. A request without expected is an unconditional write.

Text and set values

string uses the existing scalar value member and preserves its exact text, including an empty string. string_set uses values, an array of unique strings sorted in byte order. Set elements may be empty strings. Strings and set elements must be valid UTF-8 without NUL. Neither text kind carries a timezone; sets cannot carry a scalar value. Date/time/null values cannot carry values.

{"kind": "string", "value": "123"}
{"kind": "string_set", "values": ["12", "45"]}
{"kind": "string_set"}
{"kind": "null"}

An omitted or empty values array represents an empty set, which is distinct from null. Wire validation rejects duplicate or unsorted elements. Go authors can call connector.CanonicalizeFieldValue before sending a value; it returns a validated copy with sorted, deduplicated set elements without changing the caller's slice. connector.ValidateFieldValue checks already canonical values.

These wire kinds extend protocol v1 without changing existing date requests. They let adapters expose tags, assignees, or provider selections, but do not register new native Kata mapping fields. Kata still maps only scheduled_on and deadline_on. Each future mapping needs its own native ownership and validation contract.

The built-in Forgejo connector exposes text/set fields but does not advertise conditional_fields: its provider API has no atomic compare-and-set field operation. A read followed by a write does not meet the conditional-write contract. Kata therefore refuses automatic mappings for it. Unconditional batches may require several provider calls and partially apply before failure; callers must read current values before recovery.

Go SDK

Go connectors can implement pkg/connector.Handler and serve one request with connector.ServeOne:

func main() {
    if err := connector.ServeOne(context.Background(), os.Stdin, os.Stdout, handler); err != nil {
        os.Exit(1)
    }
}

connector.InvocationFromContext returns the configured instance ID and a copy of its raw settings for the current call. Treat settings as non-secret operator configuration; read credentials from the explicitly mapped child environment.

Conformance

The public pkg/connector/conformance package runs the same behavior against a disposable provider root through the connector's real protocol boundary. A fixture implements Exchange, Invocation, RootLocator, Reset, ExternalState, MutateComment, and InjectFault, then calls:

func TestConnectorConformance(t *testing.T) {
    conformance.Run(t, newConnectorFixture(t))
}

Invocation returns the configured instance ID and raw JSON settings used for every transcript request. This lets a fixture exercise settings required by its declared configuration schema through the real protocol boundary.

A fixture with provider-specific value restrictions may additionally implement conformance.FieldSampleFixture, whose FieldSamples(fieldID string) returns canonical []connector.FieldValue samples. Returning nil uses the portable transcript defaults. An override must include a sample for every accepted kind; nullable clears are checked separately. This lets a milestone field use real numeric provider IDs while the generic fixture still exercises arbitrary and empty text values. Conditional-write cases run only for connectors advertising conditional_fields.

The committed transcripts under pkg/connector/conformance/testdata/protocol-v1/ are language-neutral. Other languages should drive those JSON cases against their executable and preserve the same identity, mutation, error-safety, ordering, replay, and readback invariants. The publication case injects a process exit after the provider mutation but before the response, retries the same operation_id, and requires provider state to change only once. The required-methods case edits an existing provider comment and requires its stable ID to keep a changed revision across subsequent unchanged reads.

Current boundaries

  • The external root owns title and body while a binding is active.
  • Kata planning-field mappings are limited to scheduled_on and deadline_on in protocol v1.
  • Kata uses one process per RPC for isolation and bounded cleanup.
  • The browser UI has no bridge indicator yet.