Documentation
¶
Index ¶
- Constants
- Variables
- func Mount(mux webx.Router, deps Deps)
- func RegisterTools(s *mcp.Server, deps *Deps)
- type AuditHook
- type Check
- type Deps
- type Doctor
- type JobAttemptError
- type JobDetail
- type JobService
- func (s *JobService) Available() bool
- func (s *JobService) Cancel(ctx context.Context, id int64) error
- func (s *JobService) Get(ctx context.Context, id int64) (*JobDetail, error)
- func (s *JobService) List(ctx context.Context, state, queue, kind string, limit int) ([]JobSummary, error)
- func (s *JobService) Retry(ctx context.Context, id int64) error
- type JobSummary
- type LogService
- type Result
Constants ¶
const ( ScopeObserve = "dev:observe" ScopeOperate = "dev:operate" )
ScopeObserve gates read-only dev endpoints (logs, job list/get, doctor); ScopeOperate gates mutating ones (job retry/cancel). Both follow the PAT scope model (webx.CredentialScope): non-PAT principals (browser sessions) pass untouched, PATs must carry the scope.
Variables ¶
var ( // ErrNotWired means the job service was built without a river client // (events not constructed) — the surface is present but inert. ErrNotWired = errors.New("devx: job service not wired (events not constructed)") // ErrJobNotFound means no river job with that id exists. ErrJobNotFound = errors.New("devx: job not found") // ErrValidation wraps input-shape errors. ErrValidation = errors.New("devx: validation") // ErrScopeDenied is returned to MCP tool calls whose PAT lacks the // required dev scope (the HTTP layer answers 403 via webx.RequireScope // before reaching handlers, so only the MCP path surfaces this). ErrScopeDenied = errors.New("devx: scope denied") )
Sentinel errors surfaced by the service; the HTTP layer and MCP tools map them to statuses/codes once, here.
Functions ¶
func Mount ¶
Mount registers the dev endpoints on the router (webx.Mux or a plain *http.ServeMux — both satisfy webx.Router):
GET /dev/logs?request_id=&level=&limit=
GET /dev/jobs?state=&queue=&kind=&limit=
GET /dev/jobs/{id}
POST /dev/jobs/{id}/retry
POST /dev/jobs/{id}/cancel
GET /dev/doctor
Auth is NOT applied here — mount behind webx.Authenticate + webx.RequireScope(ScopeObserve / ScopeOperate) so PAT callers are scoped. The intended wiring gates the whole /dev/ prefix at observe level and re-gates the mutating subset at operate level.
func RegisterTools ¶
RegisterTools installs the devx built-in tools on an MCP server. Products call this from their mcpx register callback with the same Deps instance Mount received, so both surfaces (HTTP /dev/* and MCP tools) share one service layer and stay behaviorally identical.
Scope enforcement: the mount gates /mcp with webx.Authenticate; in stateless mode the HTTP request context (principal included) is the tool handler's ctx, so tools re-check the caller's PAT scope exactly like the HTTP routes do via webx.RequireScope — session callers pass, PAT callers must carry dev:observe / dev:operate.
Types ¶
type AuditHook ¶
AuditHook is the optional audit sink for mutating dev operations. It is a hook, not an import: platform packages cannot reach the audit domain, so the product injects its Auditor here (mounted endpoints do not auto-audit).
type Check ¶
type Check struct {
// Name is a stable machine identifier (e.g. "river_schema").
Name string
// Summary is a one-line description of what the check verifies.
Summary string
// Run executes the check. Timeout is enforced by the registry; a check
// that outlives it reports Fail with a timeout symptom.
Run func(ctx context.Context) Result
}
Check is one named self-diagnostic. A check answers a question the agent would otherwise have to reverse-engineer from a confusing failure ("is the river schema even migrated?"); it returns Pass with a human-readable detail, or Fail with the symptom AND the fix.
func CookieSecureCheck ¶
CookieSecureCheck catches the production trap of Secure=false cookies behind HTTPS (session cookies leaked over plain HTTP) and the inverse dev trap of forcing Secure on local HTTP (logins fail with an opaque CSRF error).
func DrainWindowCheck ¶
DrainWindowCheck verifies the graceful-shutdown drain window covers the events soft-stop window (SoftStopTimeout + margin). A shorter Drain force-kills in-flight event handlers mid-work.
func HealthProbesCheck ¶
HealthProbesCheck verifies the readiness endpoint actually probes dependencies (a bare-200 readiness check stays green through a database outage and the LB keeps routing to a dead instance).
func RiverSchemaCheck ¶
RiverSchemaCheck verifies the river schema was migrated (events.Migrate was called at boot). Symptom when skipped: the first transactional event dies with `relation "river_job" does not exist`, two layers away from the cause. conn is a plain *sql.DB (or anything running SQL) — devx does not depend on pgx directly here so any driver works.
func SealKeyCheck ¶
SealKeyCheck verifies tenant-credential encryption is configured. Without PLOYKIT_SEAL_KEY, new secret writes are rejected and legacy plaintext rows error on read — surfaced to the product as a confusing runtime failure, never as a boot error.
type Deps ¶
type Deps struct {
// Logs serves /dev/logs. Optional; nil → endpoint reports not enabled.
Logs *LogService
// Jobs serves /dev/jobs. Optional; nil → endpoints report not wired.
Jobs *JobService
// Doctor serves /dev/doctor. Required for that endpoint; nil → not found.
Doctor *Doctor
// Audit, when set, records mutating operations (retry/cancel).
Audit AuditHook
// Log is used for the platform's own operational lines (mount, denials).
Log *slog.Logger
}
Deps wires the dev surface.
type Doctor ¶
type Doctor struct {
// contains filtered or unexported fields
}
Doctor is the check registry. Products register framework checks via the constructor helpers plus their own domain-specific ones; RunDoctor executes all of them concurrently with a per-check timeout.
type JobAttemptError ¶
type JobAttemptError struct {
At time.Time `json:"at"`
Error string `json:"error"`
TraceID string `json:"trace_id,omitempty"`
}
JobAttemptError mirrors rivertype.AttemptError for JSON output.
type JobDetail ¶
type JobDetail struct {
JobSummary
Args json.RawMessage `json:"args"`
}
JobDetail adds the arguments payload to the summary — the single most valuable field when an agent needs to understand what a failed job was trying to do.
type JobService ¶
type JobService struct {
// contains filtered or unexported fields
}
JobService wraps the river client with the list/get/retry/cancel verbs used by both the HTTP surface and the MCP tools.
func NewJobService ¶
func NewJobService(client *river.Client[pgxTx]) *JobService
NewJobService builds the service over an events emitter's client. A nil client (events not wired) yields a service whose queries return ErrNotWired — surfaces gate on Available.
func (*JobService) Available ¶
func (s *JobService) Available() bool
Available reports whether a river client backs this service.
func (*JobService) Cancel ¶
func (s *JobService) Cancel(ctx context.Context, id int64) error
Cancel cancels a job: queued jobs immediately, running jobs cooperatively (via context cancellation inside the worker). Already-finalized jobs error.
func (*JobService) List ¶
func (s *JobService) List(ctx context.Context, state, queue, kind string, limit int) ([]JobSummary, error)
List returns jobs filtered by state/queue/kind, ordered by ID descending (newest first). Default and maximum page size: 50.
type JobSummary ¶
type JobSummary struct {
ID int64 `json:"id"`
Kind string `json:"kind"`
Queue string `json:"queue"`
State string `json:"state"`
Tags []string `json:"tags,omitempty"`
Attempt int `json:"attempt"`
MaxAttempts int `json:"max_attempts"`
ScheduledAt time.Time `json:"scheduled_at"`
FinalizedAt *time.Time `json:"finalized_at,omitempty"`
Errors []JobAttemptError `json:"errors,omitempty"`
}
JobSummary is the list-view shape of a river job.
type LogService ¶
type LogService struct {
// contains filtered or unexported fields
}
LogService adapts a logring Ring for the HTTP surface and MCP tools. A nil ring (logring not wired) yields Available=false and the surfaces answer with a clear degradation message instead of empty results.
func (*LogService) Available ¶
func (s *LogService) Available() bool
Available reports whether a ring backs this service.
func (*LogService) ByRequestID ¶
ByRequestID returns the captured entries for one request, newest last.