Documentation
¶
Overview ¶
Package http is the R5 review collection endpoint: the HTTP arm of the §2A surface→transport map. It builds a plain net/http handler (Mount) that the R3 service runtime stitches onto its HTTP/SSE edge via internal/service/http.(*Server).RegisterMount(mount.Prefix(), mount) — this package never touches the R3 host, middleware chain, or lifecycle (spec D5.5: route ownership lives here, hosting lives in R3). No EventBus access is needed in v1 (r3 §D4.3).
Route trees, rooted at the mount prefix (default /api/reviews):
GET {prefix}/runs/{iteration}/labels list labels (labels:read)
POST {prefix}/runs/{iteration}/labels submit label (labels:write, audited)
PATCH {prefix}/runs/{iteration}/labels/{label_id} edit label (labels:write, audited)
GET {prefix}/audit audit view (audit:read, admin)
GET {prefix}/users list users (users:manage, admin)
POST {prefix}/users create user (users:manage, audited)
PATCH {prefix}/users/{email} change role (users:manage, audited)
DELETE {prefix}/users/{email} delete user (users:manage, audited)
net/http mounts do not strip prefixes, so the full route paths (prefix included) are registered on the internal mux at construction time; R3 simply forwards requests whose path lives under the prefix.
Auth is bearer-token via the pluggable internal/review/auth Authenticator (missing/invalid token → 401, insufficient role → 403 — spec R8). Every mutating call is recorded in the internal/review/audit chained log by the audit middleware, which is FAIL-CLOSED: each mutating request's [read → mutate → audit] section runs under a per-target lock (in-process mutex + agentslock file lock, so concurrent requests and CLI processes cannot interleave read-modify-write cycles and drop each other's writes), with the target file's pre-image captured up front — if the audit append fails, the mutation is rolled back to the pre-image and the client gets a 500 carrying the X-Request-Id. A persisted-but-unaudited mutation can only survive in the doubly-degraded case where the rollback write also fails (reported loudly with the request id; audit.Append's at-least-once note applies to that residual only — reconcile by request id, never blind-retry).
The package name collides with the standard library, so net/http is imported under the alias nethttp (matching internal/service/http).
Index ¶
- Constants
- Variables
- type AuditLog
- type Deps
- type FileUserStore
- type LabelStore
- type Mount
- type SidecarLabelStore
- func (s SidecarLabelStore) Add(iteration int, in labels.AddInput) (labels.Label, error)
- func (s SidecarLabelStore) Edit(iteration int, labelID string, in labels.EditInput) (labels.Label, error)
- func (s SidecarLabelStore) Get(iteration int, labelID string) (labels.Label, error)
- func (s SidecarLabelStore) List(iteration int) ([]labels.Label, error)
- func (s SidecarLabelStore) SidecarPath(iteration int) string
- type UserStore
Constants ¶
const DefaultPrefix = "/api/reviews"
DefaultPrefix is the mount point R3 registers this handler under (see internal/service/http server docs: "R5 its review queue at /api/reviews").
const HeaderRequestID = "X-Request-Id"
HeaderRequestID is the request/response header carrying the per-request id used as the audit idempotency key. A client-supplied value is honored (so a reconciling retry can reuse it); otherwise the audit middleware generates one. It is echoed on every mutating response, success or failure.
Variables ¶
var ( // ErrInvalidPrefix is returned when the mount prefix is not a rooted, // non-root path. ErrInvalidPrefix = errors.New("review/http: mount prefix must be a rooted path below /") // ErrNilDependency is returned when a required dependency is missing. ErrNilDependency = errors.New("review/http: missing dependency") )
Errors returned by New.
Functions ¶
This section is empty.
Types ¶
type AuditLog ¶
type AuditLog interface {
Append(e audit.Event) (audit.Record, error)
Records() ([]audit.Record, error)
}
AuditLog is the audit surface the mount consumes: chained append for the audit middleware, record read-back for the admin audit view. *audit.Log satisfies it.
type Deps ¶
type Deps struct {
// Auth resolves bearer tokens to identities (401/403 enforcement).
Auth auth.Authenticator
// Labels persists label submissions and edits.
Labels LabelStore
// Users backs the admin user-management routes.
Users UserStore
// Audit records every mutating call (spec R6).
Audit AuditLog
}
Deps carries the collaborators a Mount is built from. All fields are required.
type FileUserStore ¶
type FileUserStore struct {
Path string
}
FileUserStore is the production UserStore over the users file at Path.
func (FileUserStore) FilePath ¶
func (s FileUserStore) FilePath() string
type LabelStore ¶
type LabelStore interface {
List(iteration int) ([]labels.Label, error)
Get(iteration int, labelID string) (labels.Label, error)
Add(iteration int, in labels.AddInput) (labels.Label, error)
Edit(iteration int, labelID string, in labels.EditInput) (labels.Label, error)
// SidecarPath is the file a mutation on the iteration will modify — the
// mutation guard locks it and captures its pre-image before the handler
// runs. An empty string disables the guard (stores with no file target).
SidecarPath(iteration int) string
}
LabelStore is the label persistence surface the handlers consume. The production implementation is SidecarLabelStore (iter-N.labels.yaml sidecars via internal/review/labels); tests inject failures through this seam.
type Mount ¶
type Mount struct {
// contains filtered or unexported fields
}
Mount is the review collection endpoint as a plain http.Handler. Construct it with New; R3 registers it via RegisterMount(m.Prefix(), m). It is safe for concurrent use — all mutable state lives in the backing stores, which serialize their own writes.
func New ¶
New builds a Mount whose routes are rooted at prefix (typically DefaultPrefix). The prefix must be rooted and below "/"; a trailing slash is normalized away, mirroring the R3 side's RegisterMount normalization so the registered mux patterns and the mount registration always agree.
type SidecarLabelStore ¶
type SidecarLabelStore struct {
// Dir is the iteration-log directory holding iter-N.labels.yaml sidecars.
Dir string
}
SidecarLabelStore is the production LabelStore, delegating to the internal/review/labels sidecar CRUD rooted at the iteration-log directory.
func (SidecarLabelStore) List ¶
func (s SidecarLabelStore) List(iteration int) ([]labels.Label, error)
func (SidecarLabelStore) SidecarPath ¶
func (s SidecarLabelStore) SidecarPath(iteration int) string
type UserStore ¶
type UserStore interface {
Load() (*auth.UsersFile, error)
Save(uf *auth.UsersFile) error
// FilePath is the users file a mutation will modify — the mutation guard
// locks it and captures its pre-image before the handler runs. An empty
// string disables the guard (stores with no file target).
FilePath() string
}
UserStore is the users-file surface the admin handlers consume. The production implementation is FileUserStore over the local users file (~/.config/da/review/users.yaml — auth.DefaultUsersPath).