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.
This commit is contained in:
parent
084244941b
commit
06135954ff
88 changed files with 25611 additions and 5285 deletions
144
.kiro/steering/api-standards.md
Normal file
144
.kiro/steering/api-standards.md
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
# 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:
|
||||
|
||||
```go
|
||||
// {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`:
|
||||
|
||||
```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
|
||||
|
||||
```c
|
||||
// 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue