myxce/.kiro/steering/api-standards.md
Rongsong Shen 06135954ff chore: baseline snapshot — prior-session work + ha-consistency-invariants spec family
Pre-existing uncommitted work: ha-repl-hardening payload data plane (7.x),
heartbeat liveness fix (Req 1.3), connection-reuse ASAN fixes, doc cleanup.
New: ha-consistency-invariants umbrella (requirements/design/research) and
its four implementation sub-specs (specs generated via batch, cross-spec
review applied), steering roadmap + product.md Raft+CP decision.
2026-06-11 13:21:50 +00:00

5.1 KiB

API Standards

Philosophy

Two API surfaces: Go REST API (apiserver) and C daemon messaging (Cap'n Proto over TCP). Both follow a consistent pattern: validate at the boundary, delegate business logic, return structured responses. Non-blocking I/O throughout the daemon layer.

Go REST API (apiserver)

Handler Pattern

Every resource follows a single consistent pattern:

// {Resource}Controller struct
type JobController struct{}

// {Action}{Resource} handler method
func (j JobController) SubmitJob(ctx *gin.Context) {
    // 1. Validate input at the boundary
    req, err := preCheckSubmitJobRequest(ctx)
    if err != nil {
        ReturnError(ctx, common.INVALID_PARAMS_CODE, err.Error(), nil)
        return
    }

    // 2. Delegate to service layer
    result, err := j.svc.Submit(req)
    if err != nil {
        ReturnError(ctx, common.ERROR_CODE, err.Error(), nil)
        return
    }

    // 3. Return structured success
    ReturnSuccess(ctx, common.SUCCESS_CODE, "submitted", result)
}

Rules:

  • Early return on every error — ReturnError(...); return
  • Keep preCheck* functions in the same file as the handler
  • Controllers delegate to services — business logic never lives in handlers
  • Dependencies flow inward: controllers → services → store/backend

Response Format

Every HTTP response uses a consistent envelope via two helpers in controllers/response.go:

// Success (always HTTP 200)
ReturnSuccess(ctx, code, msg, data)
// → {code: 0, msg: "success", data: {...}}

// Error (HTTP status == code parameter)
ReturnError(ctx, code, msg, details)
// → {code: 401, msg: "access forbidden", details: nil}

Standard Error Codes

All codes defined in common/common.go:

Code Constant Usage
0 SUCCESS_CODE All success responses
400 INVALID_PARAMS_CODE Malformed/missing input
401 ERROR_AUTH_CHECK_TOKEN_CODE Missing/invalid JWT
403 ERROR_AUTH_CODE Valid JWT, insufficient permission
404 ERROR_NOT_FOUND_CODE Resource not found
429 ERROR_TOO_MANY_REQUESTS_CODE Rate limit exceeded
500 ERROR_CODE Unexpected server error

Middleware Chain

Order is defined in router/routers.go and is non-negotiable:

WhitelistURL → WhitelistIP → RateLimit → ValidateHandler (JWT) → CORS → RBAC
  • WhitelistURL: filter by allowed paths (applied first for efficiency)
  • WhitelistIP: abort 403 if client IP not in whitelist
  • RateLimit: 10 req/sec, burst 20 (global config)
  • ValidateHandler: parse and validate JWT from Authorization: Bearer header; validate body content-type
  • CORS: origin checks via config
  • RBAC: RequirePermission("action:scope") per-route, enforced by controllers/rbac_check.go

Permission Format

"action:scope" — scopes: all, self, cluster, hostgroup, self_ug, self_hg. Defined in config/rbac.yaml. Roles support inheritance via includes:.

C Daemon Messaging

Cap'n Proto Wire Format

All inter-daemon messages are Cap'n Proto serialized. Schema: src/schema/controller.capnp.

Request (xce_request_t):

  • opcode — OPCode enum (38 values)
  • seq — request sequence number
  • from — sender peer info (host/key)
  • body — union of 27 message types

Reply (xce_reply_t):

  • opcode — always OPCode_reply or OPCode_redirect
  • status — error code (0 = success)
  • seq — echoes request seq
  • data — union of reply payloads (clusterconf, jobid, stateReport, walackreplymsg, etc.)

Key OPCodes

Group OPCodes Purpose
Control jobsubmit (11), jobdispatch (8), jobcontrol (15) Job lifecycle
Cluster hello (2), join (3), clusterconf (17), heartbeat (25) Membership and liveness
Replication replConfig (29), replConfigPush (30), walCatchupEntry (37), snapshotTransfer (36) Config + WAL replication
Vote requestVote, reply Raft election
Data push (6), get (5), notify (10) Data exchange

Daemon I/O Functions

// Enqueue request to endpoint send buffer (non-blocking, always)
int xce_request(endpoint_t *ep, xce_request_t *req);

// Enqueue reply to inbound request sender (non-blocking)
int xce_reply(endpoint_t *ep, xce_peerinfo_t *host, xce_reply_t *reply);

// Cap'n Proto serialize request to packet
int xce_encode(xce_request_t *req, io_packet_t *pkt);

// Cap'n Proto deserialize packet to request
int xce_decode(io_packet_t *pkt, xce_request_t *req);

msg_handler callback: Each endpoint has ep->msg_handler — a function pointer int (*)(void*) called by iomgr when a packet arrives. Handlers must return quickly (vtask context). Registered per-endpoint during connection setup.

Adding a New Opcode

  1. Add the opcode constant to src/include/request.h (OPCode enum)
  2. Add the message struct to src/schema/controller.capnp (Request/Reply union arms)
  3. Rebuild capnp schemas (automatic via meson)
  4. Wire the handler in the dispatch gate (ha_dispatch_gate in ha.c)
  5. Add encode/decode paths in src/lib/request.c if using raw payloads