Documentation
¶
Overview ¶
Package syncserver is the hub half of Remote Workspace sync (sync-abi-v2; the accept surface is the grant scope, §4): push adjudication against the append-only sync_remote_commits log, pull serving with the ONE scope clamp, and the status counters. It is extracted from the runtime so the standalone hub binary (mnemon-hub) can host the same wire without the runtime: it imports ONLY contract + store (+stdlib) — never channel / runtime / app / hostsurface (the trust-domain import boundary, pinned by a test). Replica authorization enters through the Grants seam; the co-hosted runtime adapts its channel bindings to grants, mnemon-hub builds grants from replicas.json — same fields, same semantics (dual-form rule).
Index ¶
- func IsBadRequest(err error) bool
- func NewHTTPHandler(hub *Server, auth Authenticator, audit io.Writer) http.Handler
- type Authenticator
- type BadRequestError
- type BearerAuthenticator
- type GrantMap
- type Grants
- type ReplicaGrant
- type Server
- func (s *Server) Pull(principal contract.ActorID, req contract.SyncPullRequest) (contract.SyncPullResponse, error)
- func (s *Server) Push(principal contract.ActorID, req contract.SyncPushRequest) (contract.SyncPushResponse, error)
- func (s *Server) Status(principal contract.ActorID) (contract.SyncStatusResponse, error)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func IsBadRequest ¶
IsBadRequest reports whether err is (or wraps) a request-validation failure.
func NewHTTPHandler ¶
NewHTTPHandler serves the three sync verbs over the hub Server. Every request emits ONE audit line to audit (ts, principal, verb, result): result is "unauthorized" (401, principal "-"), "bad_request" (400 — bad JSON / missing field / wrong method), "denied" (403 — no grant / out-of-scope), or "ok". The "result" is the REQUEST-level outcome (auth/parse/method); the per-commit accept/reject/conflict verdicts ride the 200 response body, NOT the audit line — a request whose every commit is rejected still audits result=ok (LOW-13). nil audit discards.
Method gates pin the frozen verb→method mapping (sync-abi-v1 §1): push/pull are POST, status is GET or POST. A violation is 405 before auth (the method is request-shape, not identity).
Types ¶
type Authenticator ¶
Authenticator resolves the authenticated principal from a request. syncserver carries its OWN seam (not channel's) so the standalone hub never imports channel; mnemon-hub plugs in BearerAuthenticator, tests may plug fakes.
type BadRequestError ¶
type BadRequestError struct {
// contains filtered or unexported fields
}
BadRequestError marks a request-VALIDATION failure (a malformed/missing field) as distinct from an AUTHORIZATION failure (no grant / out-of-scope). The HTTP layer maps it to 400; everything else from Push/Pull/Status (no replica grant, out-of-scope clamp) stays 403 (LOW-10). It is the wire layer's only error-class signal — the per-commit accept/reject/conflict verdicts ride the 200 body.
func (*BadRequestError) Error ¶
func (e *BadRequestError) Error() string
func (*BadRequestError) Unwrap ¶
func (e *BadRequestError) Unwrap() error
type BearerAuthenticator ¶
BearerAuthenticator resolves the principal from a static bearer-token map — the mnemon-hub authenticator built from replicas.json credential_refs. A missing, empty, or unknown token is rejected; the request body never names identity.
func (BearerAuthenticator) Authenticate ¶
type GrantMap ¶
type GrantMap map[contract.ActorID]ReplicaGrant
GrantMap is the static Grants form mnemon-hub builds from replicas.json: every listed replica holds all three sync verbs (a replica credential is sync-only by construction; per-verb narrowing is the co-hosted binding form's concern).
type Grants ¶
type Grants interface {
Grant(principal contract.ActorID, verb string) (ReplicaGrant, bool)
}
Grants resolves an authenticated principal's replica grant for ONE sync verb (a contract.SyncVerb* string). Implementations MUST be fail-closed: an unknown principal, a non-replica principal, or an ungranted verb returns false — there is no anonymous or default grant.
type ReplicaGrant ¶
type ReplicaGrant = contract.ReplicaGrant
ReplicaGrant aliases the contract type (the grant record is ABI surface, sync-abi-v1 §2).
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is one hub over one open store. It holds no other state: adjudication and counters are durable in the store, so a restart (or a concurrent co-hosted runtime surface) sees the same hub.
func New ¶
New wires a hub Server over an OPEN store (the caller owns the store's single-writer lock and its lifecycle). now stamps received_at on accepted commits.
func (*Server) Pull ¶
func (s *Server) Pull(principal contract.ActorID, req contract.SyncPullRequest) (contract.SyncPullResponse, error)
Pull serves accepted commits after the request cursor, excluding the puller's own origin and clamped to the grant scope (requested scopes may only narrow). It also advances the hub's serve bookkeeping (last cursor per principal + served total) for the status counters.
func (*Server) Push ¶
func (s *Server) Push(principal contract.ActorID, req contract.SyncPushRequest) (contract.SyncPushResponse, error)
Push adjudicates one batch per commit (sync-abi-v2 §4): accepted (appended, first sight of the idempotency key), rejected (structural-validation or scope-clamp failure, with diagnostic), or conflict (idempotency-key reuse with DIFFERENT content only). The accept surface is the grant scope — the ref-level clamp is the sole kind/ref gate (no global syncable-kind set; v2). Replaying an identical batch repeats the accepted results and appends nothing. The request replica_id must match every commit's origin — a mismatch rejects the whole request before any adjudication.