docs(ax): add RFC/spec artifacts for AX contract alignment
This commit is contained in:
parent
b0dd22fc5e
commit
e75cb1fc97
7 changed files with 1135 additions and 0 deletions
302
docs/RFC.md
Normal file
302
docs/RFC.md
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
# go-process API Contract — RFC Specification
|
||||
|
||||
> `dappco.re/go/core/process` — Managed process execution for the Core ecosystem.
|
||||
> This package is the ONLY package that imports `os/exec`. Everything else uses
|
||||
> `c.Process()` which delegates to Actions registered by this package.
|
||||
|
||||
**Status:** v0.8.0
|
||||
**Module:** `dappco.re/go/core/process`
|
||||
**Depends on:** core/go v0.8.0
|
||||
|
||||
---
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
go-process provides the implementation behind `c.Process()`. Core defines the primitive (Section 17). go-process registers the Action handlers that make it work.
|
||||
|
||||
```
|
||||
core/go defines: c.Process().Run(ctx, "git", "log")
|
||||
→ calls c.Action("process.run").Run(ctx, opts)
|
||||
|
||||
go-process provides: c.Action("process.run", s.handleRun)
|
||||
→ actually executes the command via os/exec
|
||||
```
|
||||
|
||||
Without go-process registered, `c.Process().Run()` returns `Result{OK: false}`. Permission-by-registration.
|
||||
|
||||
### Current State (2026-03-25)
|
||||
|
||||
The codebase is PRE-migration. The RFC describes the v0.8.0 target. What exists today:
|
||||
|
||||
- `service.go` — `NewService(opts) func(*Core) (any, error)` — **old factory signature**. Change to `Register(c *Core) core.Result`
|
||||
- `OnStartup() error` / `OnShutdown() error` — **Change** to return `core.Result`
|
||||
- `process.SetDefault(svc)` global singleton — **Remove**. Service registers in Core conclave
|
||||
- Own ID generation `fmt.Sprintf("proc-%d", ...)` — **Replace** with `core.ID()`
|
||||
- Custom `map[string]*ManagedProcess` — **Replace** with `core.Registry[*ManagedProcess]`
|
||||
- No named Actions registered — **Add** `process.run/start/kill/list/get` during OnStartup
|
||||
|
||||
### File Layout
|
||||
|
||||
```
|
||||
service.go — main service (factory, lifecycle, process execution)
|
||||
registry.go — daemon registry (PID files, health, restart)
|
||||
daemon.go — DaemonEntry, managed daemon lifecycle
|
||||
health.go — health check endpoints
|
||||
pidfile.go — PID file management
|
||||
buffer.go — output buffering
|
||||
actions.go — WILL CONTAIN Action handlers after migration
|
||||
global.go — global Default() singleton — DELETE after migration
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Registration
|
||||
|
||||
```go
|
||||
// Register is the WithService factory.
|
||||
//
|
||||
// core.New(core.WithService(process.Register))
|
||||
func Register(c *core.Core) core.Result {
|
||||
svc := &Service{
|
||||
ServiceRuntime: core.NewServiceRuntime(c, Options{}),
|
||||
managed: core.NewRegistry[*ManagedProcess](),
|
||||
}
|
||||
return core.Result{Value: svc, OK: true}
|
||||
}
|
||||
```
|
||||
|
||||
### OnStartup — Register Actions
|
||||
|
||||
```go
|
||||
func (s *Service) OnStartup(ctx context.Context) core.Result {
|
||||
c := s.Core()
|
||||
c.Action("process.run", s.handleRun)
|
||||
c.Action("process.start", s.handleStart)
|
||||
c.Action("process.kill", s.handleKill)
|
||||
c.Action("process.list", s.handleList)
|
||||
c.Action("process.get", s.handleGet)
|
||||
return core.Result{OK: true}
|
||||
}
|
||||
```
|
||||
|
||||
### OnShutdown — Kill Managed Processes
|
||||
|
||||
```go
|
||||
func (s *Service) OnShutdown(ctx context.Context) core.Result {
|
||||
s.managed.Each(func(id string, p *ManagedProcess) {
|
||||
p.Kill()
|
||||
})
|
||||
return core.Result{OK: true}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Action Handlers
|
||||
|
||||
### process.run — Synchronous Execution
|
||||
|
||||
```go
|
||||
func (s *Service) handleRun(ctx context.Context, opts core.Options) core.Result {
|
||||
command := opts.String("command")
|
||||
args, _ := opts.Get("args").Value.([]string)
|
||||
dir := opts.String("dir")
|
||||
env, _ := opts.Get("env").Value.([]string)
|
||||
|
||||
cmd := exec.CommandContext(ctx, command, args...)
|
||||
if dir != "" { cmd.Dir = dir }
|
||||
if len(env) > 0 { cmd.Env = append(os.Environ(), env...) }
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return core.Result{Value: err, OK: false}
|
||||
}
|
||||
return core.Result{Value: string(output), OK: true}
|
||||
}
|
||||
```
|
||||
|
||||
> Note: go-process is the ONLY package allowed to import `os` and `os/exec`.
|
||||
|
||||
### process.start — Detached/Background
|
||||
|
||||
```go
|
||||
func (s *Service) handleStart(ctx context.Context, opts core.Options) core.Result {
|
||||
command := opts.String("command")
|
||||
args, _ := opts.Get("args").Value.([]string)
|
||||
|
||||
cmd := exec.Command(command, args...)
|
||||
cmd.Dir = opts.String("dir")
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return core.Result{Value: err, OK: false}
|
||||
}
|
||||
|
||||
id := core.ID()
|
||||
managed := &ManagedProcess{
|
||||
ID: id, PID: cmd.Process.Pid, Command: command,
|
||||
cmd: cmd, done: make(chan struct{}),
|
||||
}
|
||||
s.managed.Set(id, managed)
|
||||
|
||||
go func() {
|
||||
cmd.Wait()
|
||||
close(managed.done)
|
||||
managed.ExitCode = cmd.ProcessState.ExitCode()
|
||||
}()
|
||||
|
||||
return core.Result{Value: id, OK: true}
|
||||
}
|
||||
```
|
||||
|
||||
### process.kill — Terminate by ID or PID
|
||||
|
||||
```go
|
||||
func (s *Service) handleKill(ctx context.Context, opts core.Options) core.Result {
|
||||
id := opts.String("id")
|
||||
if id != "" {
|
||||
r := s.managed.Get(id)
|
||||
if !r.OK {
|
||||
return core.Result{Value: core.E("process.kill", core.Concat("not found: ", id), nil), OK: false}
|
||||
}
|
||||
r.Value.(*ManagedProcess).Kill()
|
||||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
pid := opts.Int("pid")
|
||||
if pid > 0 {
|
||||
proc, err := os.FindProcess(pid)
|
||||
if err != nil { return core.Result{Value: err, OK: false} }
|
||||
proc.Signal(syscall.SIGTERM)
|
||||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
return core.Result{Value: core.E("process.kill", "need id or pid", nil), OK: false}
|
||||
}
|
||||
```
|
||||
|
||||
### process.list / process.get
|
||||
|
||||
```go
|
||||
func (s *Service) handleList(ctx context.Context, opts core.Options) core.Result {
|
||||
return core.Result{Value: s.managed.Names(), OK: true}
|
||||
}
|
||||
|
||||
func (s *Service) handleGet(ctx context.Context, opts core.Options) core.Result {
|
||||
id := opts.String("id")
|
||||
r := s.managed.Get(id)
|
||||
if !r.OK { return r }
|
||||
return core.Result{Value: r.Value.(*ManagedProcess).Info(), OK: true}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. ManagedProcess
|
||||
|
||||
```go
|
||||
type ManagedProcess struct {
|
||||
ID string
|
||||
PID int
|
||||
Command string
|
||||
ExitCode int
|
||||
StartedAt time.Time
|
||||
cmd *exec.Cmd
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func (p *ManagedProcess) IsRunning() bool {
|
||||
select {
|
||||
case <-p.done: return false
|
||||
default: return true
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ManagedProcess) Kill() {
|
||||
if p.cmd != nil && p.cmd.Process != nil {
|
||||
p.cmd.Process.Signal(syscall.SIGTERM)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ManagedProcess) Done() <-chan struct{} { return p.done }
|
||||
|
||||
func (p *ManagedProcess) Info() ProcessInfo {
|
||||
return ProcessInfo{
|
||||
ID: p.ID, PID: p.PID, Command: p.Command,
|
||||
Running: p.IsRunning(), ExitCode: p.ExitCode, StartedAt: p.StartedAt,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Daemon Registry
|
||||
|
||||
Higher-level abstraction over `process.start`:
|
||||
|
||||
```
|
||||
process.start → low level: start a command, get a handle
|
||||
daemon.Start → high level: PID file, health endpoint, restart policy, signals
|
||||
```
|
||||
|
||||
Daemon registry uses `core.Registry[*DaemonEntry]`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Error Handling
|
||||
|
||||
All errors via `core.E()`. String building via `core.Concat()`.
|
||||
|
||||
```go
|
||||
return core.Result{Value: core.E("process.run", core.Concat("command failed: ", command), err), OK: false}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Test Strategy
|
||||
|
||||
AX-7: `TestFile_Function_{Good,Bad,Ugly}`
|
||||
|
||||
```
|
||||
TestService_Register_Good — factory returns Result
|
||||
TestService_OnStartup_Good — registers 5 Actions
|
||||
TestService_HandleRun_Good — runs command, returns output
|
||||
TestService_HandleRun_Bad — command not found
|
||||
TestService_HandleRun_Ugly — timeout via context
|
||||
TestService_HandleStart_Good — starts detached, returns ID
|
||||
TestService_HandleStart_Bad — invalid command
|
||||
TestService_HandleKill_Good — kills by ID
|
||||
TestService_HandleKill_Bad — unknown ID
|
||||
TestService_HandleList_Good — returns managed process IDs
|
||||
TestService_OnShutdown_Good — kills all managed processes
|
||||
TestService_Ugly_PermissionModel — no go-process = c.Process().Run() fails
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Quality Gates
|
||||
|
||||
go-process is the ONE exception — it imports `os` and `os/exec` because it IS the process primitive. All other disallowed imports still apply:
|
||||
|
||||
```bash
|
||||
# Should only find os/exec in service.go, os in service.go
|
||||
grep -rn '"os"\|"os/exec"' *.go | grep -v _test.go
|
||||
|
||||
# No other disallowed imports
|
||||
grep -rn '"io"\|"fmt"\|"errors"\|"log"\|"encoding/json"\|"path/filepath"\|"unsafe"\|"strings"' *.go \
|
||||
| grep -v _test.go
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Consumer RFCs
|
||||
|
||||
| Package | RFC | Role |
|
||||
|---------|-----|------|
|
||||
| core/go | `core/go/docs/RFC.md` | Primitives — Process primitive (Section 17) |
|
||||
| core/agent | `core/agent/docs/RFC.md` | Consumer — `c.Process().RunIn()` for git/build ops |
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
- 2026-03-25: v0.8.0 spec — written with full core/go domain context.
|
||||
151
docs/plans/2026-03-25-v0.7.0-core-alignment.md
Normal file
151
docs/plans/2026-03-25-v0.7.0-core-alignment.md
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
# go-process v0.7.0 — Core Alignment
|
||||
|
||||
> Written by Cladius with full core/go domain context (2026-03-25).
|
||||
> Read core/go docs/RFC.md Section 17 for the full Process primitive spec.
|
||||
|
||||
## What Changed in core/go
|
||||
|
||||
core/go v0.8.0 added:
|
||||
- `c.Process()` — primitive that delegates to `c.Action("process.*")`
|
||||
- `c.Action("name")` — named action registry with panic recovery
|
||||
- `Startable.OnStartup()` returns `core.Result` (not `error`)
|
||||
- `Registry[T]` — universal thread-safe named collection
|
||||
- `core.ID()` — unique identifier primitive
|
||||
|
||||
go-process needs to align its factory signature and register process Actions.
|
||||
|
||||
## Step 1: Fix Factory Signature
|
||||
|
||||
Current (`service.go`):
|
||||
```go
|
||||
func NewService(opts Options) func(*core.Core) (any, error) {
|
||||
```
|
||||
|
||||
Target:
|
||||
```go
|
||||
func Register(c *core.Core) core.Result {
|
||||
svc := &Service{
|
||||
ServiceRuntime: core.NewServiceRuntime(c, Options{}),
|
||||
processes: make(map[string]*ManagedProcess),
|
||||
}
|
||||
return core.Result{Value: svc, OK: true}
|
||||
}
|
||||
```
|
||||
|
||||
This matches `core.WithService(process.Register)` — the standard pattern.
|
||||
|
||||
## Step 2: Register Process Actions During OnStartup
|
||||
|
||||
```go
|
||||
func (s *Service) OnStartup(ctx context.Context) core.Result {
|
||||
c := s.Core()
|
||||
|
||||
// Register named actions — these are what c.Process() calls
|
||||
c.Action("process.run", s.handleRun)
|
||||
c.Action("process.start", s.handleStart)
|
||||
c.Action("process.kill", s.handleKill)
|
||||
|
||||
return core.Result{OK: true}
|
||||
}
|
||||
```
|
||||
|
||||
Note: `OnStartup` now returns `core.Result` not `error`.
|
||||
|
||||
## Step 3: Implement Action Handlers
|
||||
|
||||
```go
|
||||
func (s *Service) handleRun(ctx context.Context, opts core.Options) core.Result {
|
||||
command := opts.String("command")
|
||||
args, _ := opts.Get("args").Value.([]string)
|
||||
dir := opts.String("dir")
|
||||
env, _ := opts.Get("env").Value.([]string)
|
||||
|
||||
// Use existing RunWithOptions internally
|
||||
out, err := s.RunWithOptions(ctx, RunOptions{
|
||||
Command: command,
|
||||
Args: args,
|
||||
Dir: dir,
|
||||
Env: env,
|
||||
})
|
||||
if err != nil {
|
||||
return core.Result{Value: err, OK: false}
|
||||
}
|
||||
return core.Result{Value: out, OK: true}
|
||||
}
|
||||
|
||||
func (s *Service) handleStart(ctx context.Context, opts core.Options) core.Result {
|
||||
// Detached process — returns handle ID
|
||||
command := opts.String("command")
|
||||
args, _ := opts.Get("args").Value.([]string)
|
||||
|
||||
handle, err := s.Start(ctx, StartOptions{
|
||||
Command: command,
|
||||
Args: args,
|
||||
Dir: opts.String("dir"),
|
||||
Detach: opts.Bool("detach"),
|
||||
})
|
||||
if err != nil {
|
||||
return core.Result{Value: err, OK: false}
|
||||
}
|
||||
return core.Result{Value: handle.ID, OK: true}
|
||||
}
|
||||
|
||||
func (s *Service) handleKill(ctx context.Context, opts core.Options) core.Result {
|
||||
id := opts.String("id")
|
||||
pid := opts.Int("pid")
|
||||
|
||||
if id != "" {
|
||||
return s.KillByID(id)
|
||||
}
|
||||
return s.KillByPID(pid)
|
||||
}
|
||||
```
|
||||
|
||||
## Step 4: Remove Global Singleton Pattern
|
||||
|
||||
Current: `process.SetDefault(svc)` and `process.Default()` global state.
|
||||
|
||||
Target: Service registered in Core's conclave. No global state.
|
||||
|
||||
The `ensureProcess()` hack in core/agent exists because go-process doesn't register properly. Once this is done, that bridge can be deleted.
|
||||
|
||||
## Step 5: Update OnShutdown
|
||||
|
||||
```go
|
||||
func (s *Service) OnShutdown(ctx context.Context) core.Result {
|
||||
// Kill all managed processes
|
||||
for _, p := range s.processes {
|
||||
p.Kill()
|
||||
}
|
||||
return core.Result{OK: true}
|
||||
}
|
||||
```
|
||||
|
||||
## Step 6: Use core.ID() for Process IDs
|
||||
|
||||
Current: `fmt.Sprintf("proc-%d", s.idCounter.Add(1))`
|
||||
|
||||
Target: `core.ID()` — consistent format across ecosystem.
|
||||
|
||||
## Step 7: AX-7 Tests
|
||||
|
||||
All tests renamed to `TestFile_Function_{Good,Bad,Ugly}`:
|
||||
- `TestService_Register_Good` — factory returns Result
|
||||
- `TestService_HandleRun_Good` — runs command via Action
|
||||
- `TestService_HandleRun_Bad` — command not found
|
||||
- `TestService_HandleKill_Good` — kills by ID
|
||||
- `TestService_OnStartup_Good` — registers Actions
|
||||
- `TestService_OnShutdown_Good` — kills all processes
|
||||
|
||||
## What This Unlocks
|
||||
|
||||
Once go-process v0.7.0 ships:
|
||||
- `core.New(core.WithService(process.Register))` — standard registration
|
||||
- `c.Process().Run(ctx, "git", "log")` — works end-to-end
|
||||
- core/agent deletes `proc.go`, `ensureProcess()`, `ProcessRegister`
|
||||
- Tests can mock process execution by registering a fake handler
|
||||
|
||||
## Dependencies
|
||||
|
||||
- core/go v0.8.0 (already done — Action system, Process primitive, Result lifecycle)
|
||||
- No other deps change
|
||||
6
exec/doc.go
Normal file
6
exec/doc.go
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// Package exec provides a small command wrapper around `os/exec` with
|
||||
// structured logging hooks.
|
||||
//
|
||||
// ctx := context.Background()
|
||||
// out, err := exec.Command(ctx, "echo", "hello").Output()
|
||||
package exec
|
||||
29
specs/api/RFC.md
Normal file
29
specs/api/RFC.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# api
|
||||
**Import:** `dappco.re/go/core/process/pkg/api`
|
||||
**Files:** 2
|
||||
|
||||
## Types
|
||||
|
||||
### `ProcessProvider`
|
||||
`struct`
|
||||
|
||||
Service provider that wraps the go-process daemon registry and bundled UI entrypoint.
|
||||
|
||||
Exported fields:
|
||||
- None.
|
||||
|
||||
## Functions
|
||||
|
||||
### Package Functions
|
||||
|
||||
- `func NewProvider(registry *process.Registry, hub *ws.Hub) *ProcessProvider`: Returns a `ProcessProvider` for the supplied registry and WebSocket hub. When `registry` is `nil`, it uses `process.DefaultRegistry()`.
|
||||
- `func PIDAlive(pid int) bool`: Returns `false` for non-positive PIDs and otherwise reports whether `os.FindProcess(pid)` followed by signal `0` succeeds.
|
||||
|
||||
### `ProcessProvider` Methods
|
||||
|
||||
- `func (p *ProcessProvider) Name() string`: Returns `"process"`.
|
||||
- `func (p *ProcessProvider) BasePath() string`: Returns `"/api/process"`.
|
||||
- `func (p *ProcessProvider) Element() provider.ElementSpec`: Returns an element spec with tag `core-process-panel` and source `/assets/core-process.js`.
|
||||
- `func (p *ProcessProvider) Channels() []string`: Returns `process.daemon.started`, `process.daemon.stopped`, `process.daemon.health`, `process.started`, `process.output`, `process.exited`, and `process.killed`.
|
||||
- `func (p *ProcessProvider) RegisterRoutes(rg *gin.RouterGroup)`: Registers the daemon list, daemon lookup, daemon stop, and daemon health routes.
|
||||
- `func (p *ProcessProvider) Describe() []api.RouteDescription`: Returns static route descriptions for the registered daemon routes.
|
||||
68
specs/exec/RFC.md
Normal file
68
specs/exec/RFC.md
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# exec
|
||||
**Import:** `dappco.re/go/core/process/exec`
|
||||
**Files:** 3
|
||||
|
||||
## Types
|
||||
|
||||
### `Options`
|
||||
`struct`
|
||||
|
||||
Command execution options used by `Cmd`.
|
||||
|
||||
Fields:
|
||||
- `Dir string`: Working directory.
|
||||
- `Env []string`: Environment entries appended to `os.Environ()` when non-empty.
|
||||
- `Stdin io.Reader`: Reader assigned to command stdin.
|
||||
- `Stdout io.Writer`: Writer assigned to command stdout.
|
||||
- `Stderr io.Writer`: Writer assigned to command stderr.
|
||||
|
||||
### `Cmd`
|
||||
`struct`
|
||||
|
||||
Wrapped command with chainable configuration methods.
|
||||
|
||||
Exported fields:
|
||||
- None.
|
||||
|
||||
### `Logger`
|
||||
`interface`
|
||||
|
||||
Command-execution logger.
|
||||
|
||||
Methods:
|
||||
- `Debug(msg string, keyvals ...any)`: Logs a debug-level message.
|
||||
- `Error(msg string, keyvals ...any)`: Logs an error-level message.
|
||||
|
||||
### `NopLogger`
|
||||
`struct`
|
||||
|
||||
No-op `Logger` implementation.
|
||||
|
||||
Exported fields:
|
||||
- None.
|
||||
|
||||
## Functions
|
||||
|
||||
### Package Functions
|
||||
|
||||
- `func Command(ctx context.Context, name string, args ...string) *Cmd`: Returns a `Cmd` for the supplied context, executable name, and arguments.
|
||||
- `func RunQuiet(ctx context.Context, name string, args ...string) error`: Runs a command with stderr captured into a buffer and returns `core.E("RunQuiet", core.Trim(stderr.String()), err)` on failure.
|
||||
- `func SetDefaultLogger(l Logger)`: Sets the package-level default logger. Passing `nil` replaces it with `NopLogger`.
|
||||
- `func DefaultLogger() Logger`: Returns the package-level default logger.
|
||||
|
||||
### `Cmd` Methods
|
||||
|
||||
- `func (c *Cmd) WithDir(dir string) *Cmd`: Sets `Options.Dir` and returns the same command.
|
||||
- `func (c *Cmd) WithEnv(env []string) *Cmd`: Sets `Options.Env` and returns the same command.
|
||||
- `func (c *Cmd) WithStdin(r io.Reader) *Cmd`: Sets `Options.Stdin` and returns the same command.
|
||||
- `func (c *Cmd) WithStdout(w io.Writer) *Cmd`: Sets `Options.Stdout` and returns the same command.
|
||||
- `func (c *Cmd) WithStderr(w io.Writer) *Cmd`: Sets `Options.Stderr` and returns the same command.
|
||||
- `func (c *Cmd) WithLogger(l Logger) *Cmd`: Sets a command-specific logger and returns the same command.
|
||||
- `func (c *Cmd) Run() error`: Prepares the underlying `exec.Cmd`, logs `"executing command"`, runs it, and wraps failures with `wrapError("Cmd.Run", ...)`.
|
||||
- `func (c *Cmd) Output() ([]byte, error)`: Prepares the underlying `exec.Cmd`, logs `"executing command"`, returns stdout bytes, and wraps failures with `wrapError("Cmd.Output", ...)`.
|
||||
- `func (c *Cmd) CombinedOutput() ([]byte, error)`: Prepares the underlying `exec.Cmd`, logs `"executing command"`, returns combined stdout and stderr, and wraps failures with `wrapError("Cmd.CombinedOutput", ...)`.
|
||||
|
||||
### `NopLogger` Methods
|
||||
|
||||
- `func (NopLogger) Debug(string, ...any)`: Discards the message.
|
||||
- `func (NopLogger) Error(string, ...any)`: Discards the message.
|
||||
207
specs/process-ui.md
Normal file
207
specs/process-ui.md
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
# @core/process-ui
|
||||
**Import:** `@core/process-ui`
|
||||
**Files:** 8
|
||||
|
||||
## Types
|
||||
|
||||
### `DaemonEntry`
|
||||
`interface`
|
||||
|
||||
Daemon-registry row returned by `ProcessApi.listDaemons` and `ProcessApi.getDaemon`.
|
||||
|
||||
Properties:
|
||||
- `code: string`: Application or component code.
|
||||
- `daemon: string`: Daemon name.
|
||||
- `pid: number`: Process ID.
|
||||
- `health?: string`: Optional health-endpoint address.
|
||||
- `project?: string`: Optional project label.
|
||||
- `binary?: string`: Optional binary label.
|
||||
- `started: string`: Start timestamp string from the API.
|
||||
|
||||
### `HealthResult`
|
||||
`interface`
|
||||
|
||||
Result returned by the daemon health endpoint.
|
||||
|
||||
Properties:
|
||||
- `healthy: boolean`: Health outcome.
|
||||
- `address: string`: Health endpoint address that was checked.
|
||||
- `reason?: string`: Optional explanation such as the absence of a health endpoint.
|
||||
|
||||
### `ProcessInfo`
|
||||
`interface`
|
||||
|
||||
Process snapshot shape used by the UI package.
|
||||
|
||||
Properties:
|
||||
- `id: string`: Managed-process identifier.
|
||||
- `command: string`: Executable name.
|
||||
- `args: string[]`: Command arguments.
|
||||
- `dir: string`: Working directory.
|
||||
- `startedAt: string`: Start timestamp string.
|
||||
- `status: 'pending' | 'running' | 'exited' | 'failed' | 'killed'`: Process status string.
|
||||
- `exitCode: number`: Exit code.
|
||||
- `duration: number`: Numeric duration value from the API payload.
|
||||
- `pid: number`: Child PID.
|
||||
|
||||
### `RunResult`
|
||||
`interface`
|
||||
|
||||
Pipeline result row used by `ProcessRunner`.
|
||||
|
||||
Properties:
|
||||
- `name: string`: Spec name.
|
||||
- `exitCode: number`: Exit code.
|
||||
- `duration: number`: Numeric duration value.
|
||||
- `output: string`: Captured output.
|
||||
- `error?: string`: Optional error message.
|
||||
- `skipped: boolean`: Whether the spec was skipped.
|
||||
- `passed: boolean`: Whether the spec passed.
|
||||
|
||||
### `RunAllResult`
|
||||
`interface`
|
||||
|
||||
Aggregate pipeline result consumed by `ProcessRunner`.
|
||||
|
||||
Properties:
|
||||
- `results: RunResult[]`: Per-spec results.
|
||||
- `duration: number`: Aggregate duration.
|
||||
- `passed: number`: Count of passed specs.
|
||||
- `failed: number`: Count of failed specs.
|
||||
- `skipped: number`: Count of skipped specs.
|
||||
- `success: boolean`: Aggregate success flag.
|
||||
|
||||
### `ProcessApi`
|
||||
`class`
|
||||
|
||||
Typed fetch client for `/api/process/*`.
|
||||
|
||||
Public API:
|
||||
- `new ProcessApi(baseUrl?: string)`: Stores an optional URL prefix. The default is `""`.
|
||||
- `listDaemons(): Promise<DaemonEntry[]>`: Fetches `GET /api/process/daemons`.
|
||||
- `getDaemon(code: string, daemon: string): Promise<DaemonEntry>`: Fetches one daemon entry.
|
||||
- `stopDaemon(code: string, daemon: string): Promise<{ stopped: boolean }>`: Sends `POST /api/process/daemons/:code/:daemon/stop`.
|
||||
- `healthCheck(code: string, daemon: string): Promise<HealthResult>`: Fetches `GET /api/process/daemons/:code/:daemon/health`.
|
||||
|
||||
### `ProcessEvent`
|
||||
`interface`
|
||||
|
||||
Event envelope consumed by `connectProcessEvents`.
|
||||
|
||||
Properties:
|
||||
- `type: string`: Event type.
|
||||
- `channel?: string`: Optional channel name.
|
||||
- `data?: any`: Event payload.
|
||||
- `timestamp?: string`: Optional timestamp string.
|
||||
|
||||
### `ProcessPanel`
|
||||
`class`
|
||||
|
||||
Top-level custom element registered as `<core-process-panel>`.
|
||||
|
||||
Public properties:
|
||||
- `apiUrl: string`: Forwarded to child elements through the `api-url` attribute.
|
||||
- `wsUrl: string`: WebSocket endpoint URL from the `ws-url` attribute.
|
||||
|
||||
Behavior:
|
||||
- Renders tabbed daemon, process, and pipeline views.
|
||||
- Opens a process-event WebSocket when `wsUrl` is set.
|
||||
- Shows the last received process channel or event type in the footer.
|
||||
|
||||
### `ProcessDaemons`
|
||||
`class`
|
||||
|
||||
Daemon-list custom element registered as `<core-process-daemons>`.
|
||||
|
||||
Public properties:
|
||||
- `apiUrl: string`: Base URL prefix for `ProcessApi`.
|
||||
|
||||
Behavior:
|
||||
- Loads daemon entries on connect.
|
||||
- Can trigger per-daemon health checks and stop requests.
|
||||
- Emits `daemon-stopped` after a successful stop request.
|
||||
|
||||
### `ProcessList`
|
||||
`class`
|
||||
|
||||
Managed-process list custom element registered as `<core-process-list>`.
|
||||
|
||||
Public properties:
|
||||
- `apiUrl: string`: Declared API prefix property.
|
||||
- `selectedId: string`: Selected process ID, reflected from `selected-id`.
|
||||
|
||||
Behavior:
|
||||
- Emits `process-selected` when a row is chosen.
|
||||
- Currently renders from local state only because the process REST endpoints referenced by the component are not implemented in this package.
|
||||
|
||||
### `ProcessOutput`
|
||||
`class`
|
||||
|
||||
Live output custom element registered as `<core-process-output>`.
|
||||
|
||||
Public properties:
|
||||
- `apiUrl: string`: Declared API prefix property. The current implementation does not use it.
|
||||
- `wsUrl: string`: WebSocket endpoint URL.
|
||||
- `processId: string`: Selected process ID from the `process-id` attribute.
|
||||
|
||||
Behavior:
|
||||
- Connects to the WebSocket when both `wsUrl` and `processId` are present.
|
||||
- Filters for `process.output` events whose payload `data.id` matches `processId`.
|
||||
- Appends output lines and auto-scrolls by default.
|
||||
|
||||
### `ProcessRunner`
|
||||
`class`
|
||||
|
||||
Pipeline-results custom element registered as `<core-process-runner>`.
|
||||
|
||||
Public properties:
|
||||
- `apiUrl: string`: Declared API prefix property.
|
||||
- `result: RunAllResult | null`: Aggregate pipeline result used for rendering.
|
||||
|
||||
Behavior:
|
||||
- Renders summary counts plus expandable per-spec output.
|
||||
- Depends on the `result` property today because pipeline REST endpoints are not implemented in the package.
|
||||
|
||||
## Functions
|
||||
|
||||
### Package Functions
|
||||
|
||||
- `function connectProcessEvents(wsUrl: string, handler: (event: ProcessEvent) => void): WebSocket`: Opens a WebSocket, parses incoming JSON, forwards only messages whose `type` or `channel` starts with `process.`, ignores malformed payloads, and returns the `WebSocket` instance.
|
||||
|
||||
### `ProcessPanel` Methods
|
||||
|
||||
- `connectedCallback(): void`: Calls the LitElement base implementation and opens the WebSocket when `wsUrl` is set.
|
||||
- `disconnectedCallback(): void`: Calls the LitElement base implementation and closes the current WebSocket.
|
||||
- `render(): unknown`: Renders the header, tab strip, active child element, and connection footer.
|
||||
|
||||
### `ProcessDaemons` Methods
|
||||
|
||||
- `connectedCallback(): void`: Instantiates `ProcessApi` and loads daemon data.
|
||||
- `loadDaemons(): Promise<void>`: Fetches daemon entries, stores them in component state, and records any request error message.
|
||||
- `render(): unknown`: Renders the daemon list, loading state, empty state, and action buttons.
|
||||
|
||||
### `ProcessList` Methods
|
||||
|
||||
- `connectedCallback(): void`: Calls the LitElement base implementation and invokes `loadProcesses`.
|
||||
- `loadProcesses(): Promise<void>`: Current placeholder implementation that clears state because the referenced process REST endpoints are not implemented yet.
|
||||
- `render(): unknown`: Renders the process list or an informational empty state explaining the missing REST support.
|
||||
|
||||
### `ProcessOutput` Methods
|
||||
|
||||
- `connectedCallback(): void`: Calls the LitElement base implementation and opens the WebSocket when `wsUrl` and `processId` are both set.
|
||||
- `disconnectedCallback(): void`: Calls the LitElement base implementation and closes the current WebSocket.
|
||||
- `updated(changed: Map<string, unknown>): void`: Reconnects when `processId` or `wsUrl` changes, resets buffered lines on reconnection, and auto-scrolls when enabled.
|
||||
- `render(): unknown`: Renders the output panel, waiting state, and accumulated stdout or stderr lines.
|
||||
|
||||
### `ProcessRunner` Methods
|
||||
|
||||
- `connectedCallback(): void`: Calls the LitElement base implementation and invokes `loadResults`.
|
||||
- `loadResults(): Promise<void>`: Current placeholder method. The implementation is empty because pipeline endpoints are not present.
|
||||
- `render(): unknown`: Renders the empty-state notice when `result` is absent, or the aggregate summary plus per-spec details when `result` is present.
|
||||
|
||||
### `ProcessApi` Methods
|
||||
|
||||
- `listDaemons(): Promise<DaemonEntry[]>`: Returns the `data` field from a successful daemon-list response.
|
||||
- `getDaemon(code: string, daemon: string): Promise<DaemonEntry>`: Returns one daemon entry from the provider API.
|
||||
- `stopDaemon(code: string, daemon: string): Promise<{ stopped: boolean }>`: Issues the stop request and returns the provider's `{ stopped }` payload.
|
||||
- `healthCheck(code: string, daemon: string): Promise<HealthResult>`: Returns the daemon-health payload.
|
||||
372
specs/process.md
Normal file
372
specs/process.md
Normal file
|
|
@ -0,0 +1,372 @@
|
|||
# process
|
||||
**Import:** `dappco.re/go/core/process`
|
||||
**Files:** 11
|
||||
|
||||
## Types
|
||||
|
||||
### `ActionProcessStarted`
|
||||
`struct`
|
||||
|
||||
Broadcast payload for a managed process that has successfully started.
|
||||
|
||||
Fields:
|
||||
- `ID string`: Generated managed-process identifier.
|
||||
- `Command string`: Executable name passed to the service.
|
||||
- `Args []string`: Argument vector used to start the process.
|
||||
- `Dir string`: Working directory supplied at start time.
|
||||
- `PID int`: OS process ID of the child process.
|
||||
|
||||
### `ActionProcessOutput`
|
||||
`struct`
|
||||
|
||||
Broadcast payload for one scanned line of process output.
|
||||
|
||||
Fields:
|
||||
- `ID string`: Managed-process identifier.
|
||||
- `Line string`: One line from stdout or stderr, without the trailing newline.
|
||||
- `Stream Stream`: Output source, using `StreamStdout` or `StreamStderr`.
|
||||
|
||||
### `ActionProcessExited`
|
||||
`struct`
|
||||
|
||||
Broadcast payload emitted after the service wait goroutine finishes.
|
||||
|
||||
Fields:
|
||||
- `ID string`: Managed-process identifier.
|
||||
- `ExitCode int`: Process exit code.
|
||||
- `Duration time.Duration`: Time elapsed since `StartedAt`.
|
||||
- `Error error`: Declared error slot for exit metadata. The current `Service` emitter does not populate it.
|
||||
|
||||
### `ActionProcessKilled`
|
||||
`struct`
|
||||
|
||||
Broadcast payload emitted by `Service.Kill`.
|
||||
|
||||
Fields:
|
||||
- `ID string`: Managed-process identifier.
|
||||
- `Signal string`: Signal name reported by the service. The current implementation emits `"SIGKILL"`.
|
||||
|
||||
### `RingBuffer`
|
||||
`struct`
|
||||
|
||||
Fixed-size circular byte buffer used for captured process output. The implementation is mutex-protected and overwrites the oldest bytes when full.
|
||||
|
||||
Exported fields:
|
||||
- None.
|
||||
|
||||
### `DaemonOptions`
|
||||
`struct`
|
||||
|
||||
Configuration for `NewDaemon`.
|
||||
|
||||
Fields:
|
||||
- `PIDFile string`: PID file path. Empty disables PID-file management.
|
||||
- `ShutdownTimeout time.Duration`: Grace period used by `Stop`. Zero is normalized to 30 seconds by `NewDaemon`.
|
||||
- `HealthAddr string`: Listen address for the health server. Empty disables health endpoints.
|
||||
- `HealthChecks []HealthCheck`: Additional `/health` checks to register on the health server.
|
||||
- `Registry *Registry`: Optional daemon registry used for automatic register/unregister.
|
||||
- `RegistryEntry DaemonEntry`: Base registry payload. `Start` fills in `PID`, `Health`, and `Started` behavior through `Registry.Register`.
|
||||
|
||||
### `Daemon`
|
||||
`struct`
|
||||
|
||||
Lifecycle wrapper around a PID file, optional health server, and optional registry entry.
|
||||
|
||||
Exported fields:
|
||||
- None.
|
||||
|
||||
### `HealthCheck`
|
||||
`type HealthCheck func() error`
|
||||
|
||||
Named function type used by `HealthServer` and `DaemonOptions`. Returning `nil` marks the check healthy; returning an error makes `/health` respond with `503`.
|
||||
|
||||
### `HealthServer`
|
||||
`struct`
|
||||
|
||||
HTTP server exposing `/health` and `/ready` endpoints.
|
||||
|
||||
Exported fields:
|
||||
- None.
|
||||
|
||||
### `PIDFile`
|
||||
`struct`
|
||||
|
||||
Single-instance guard backed by a PID file on disk.
|
||||
|
||||
Exported fields:
|
||||
- None.
|
||||
|
||||
### `ManagedProcess`
|
||||
`struct`
|
||||
|
||||
Service-owned process record for a started child process.
|
||||
|
||||
Fields:
|
||||
- `ID string`: Managed-process identifier generated by `core.ID()`.
|
||||
- `Command string`: Executable name.
|
||||
- `Args []string`: Command arguments.
|
||||
- `Dir string`: Working directory used when starting the process.
|
||||
- `Env []string`: Extra environment entries appended to the command environment.
|
||||
- `StartedAt time.Time`: Timestamp recorded immediately before `cmd.Start`.
|
||||
- `Status Status`: Current lifecycle state tracked by the service.
|
||||
- `ExitCode int`: Exit status after completion.
|
||||
- `Duration time.Duration`: Runtime duration set when the wait goroutine finishes.
|
||||
|
||||
### `Process`
|
||||
`type alias of ManagedProcess`
|
||||
|
||||
Compatibility alias that exposes the same fields and methods as `ManagedProcess`.
|
||||
|
||||
### `Program`
|
||||
`struct`
|
||||
|
||||
Thin helper for finding and running a named executable.
|
||||
|
||||
Fields:
|
||||
- `Name string`: Binary name to look up or execute.
|
||||
- `Path string`: Resolved absolute path populated by `Find`. When empty, `Run` and `RunDir` fall back to `Name`.
|
||||
|
||||
### `DaemonEntry`
|
||||
`struct`
|
||||
|
||||
Serialized daemon-registry record written as JSON.
|
||||
|
||||
Fields:
|
||||
- `Code string`: Application or component code.
|
||||
- `Daemon string`: Daemon name within that code.
|
||||
- `PID int`: Running process ID.
|
||||
- `Health string`: Health endpoint address, if any.
|
||||
- `Project string`: Optional project label.
|
||||
- `Binary string`: Optional binary label.
|
||||
- `Started time.Time`: Start timestamp persisted in RFC3339Nano format.
|
||||
|
||||
### `Registry`
|
||||
`struct`
|
||||
|
||||
Filesystem-backed daemon registry that stores one JSON file per daemon entry.
|
||||
|
||||
Exported fields:
|
||||
- None.
|
||||
|
||||
### `Runner`
|
||||
`struct`
|
||||
|
||||
Pipeline orchestrator that starts `RunSpec` processes through a `Service`.
|
||||
|
||||
Exported fields:
|
||||
- None.
|
||||
|
||||
### `RunSpec`
|
||||
`struct`
|
||||
|
||||
One process specification for `Runner`.
|
||||
|
||||
Fields:
|
||||
- `Name string`: Friendly name used for dependencies and result reporting.
|
||||
- `Command string`: Executable name.
|
||||
- `Args []string`: Command arguments.
|
||||
- `Dir string`: Working directory.
|
||||
- `Env []string`: Additional environment variables.
|
||||
- `After []string`: Dependency names that must complete before this spec can run in `RunAll`.
|
||||
- `AllowFailure bool`: When true, downstream work is not skipped because of this spec's failure.
|
||||
|
||||
### `RunResult`
|
||||
`struct`
|
||||
|
||||
Per-spec runner result.
|
||||
|
||||
Fields:
|
||||
- `Name string`: Spec name.
|
||||
- `Spec RunSpec`: Original spec payload.
|
||||
- `ExitCode int`: Exit code from the managed process.
|
||||
- `Duration time.Duration`: Process duration or start-attempt duration.
|
||||
- `Output string`: Captured output returned from the managed process.
|
||||
- `Error error`: Start or orchestration error. For a started process that exits non-zero, this remains `nil`.
|
||||
- `Skipped bool`: Whether the spec was skipped instead of run.
|
||||
|
||||
### `RunAllResult`
|
||||
`struct`
|
||||
|
||||
Aggregate result returned by `RunAll`, `RunSequential`, and `RunParallel`.
|
||||
|
||||
Fields:
|
||||
- `Results []RunResult`: Collected per-spec results.
|
||||
- `Duration time.Duration`: End-to-end runtime for the orchestration method.
|
||||
- `Passed int`: Count of results where `Passed()` is true.
|
||||
- `Failed int`: Count of non-skipped results that did not pass.
|
||||
- `Skipped int`: Count of skipped results.
|
||||
|
||||
### `Service`
|
||||
`struct`
|
||||
|
||||
Core service that owns managed processes and registers action handlers.
|
||||
|
||||
Fields:
|
||||
- `*core.ServiceRuntime[Options]`: Embedded Core runtime used for lifecycle hooks and access to `Core()`.
|
||||
|
||||
### `Options`
|
||||
`struct`
|
||||
|
||||
Service configuration.
|
||||
|
||||
Fields:
|
||||
- `BufferSize int`: Ring-buffer capacity for captured output. `Register` currently initializes this from `DefaultBufferSize`.
|
||||
|
||||
### `Status`
|
||||
`type Status string`
|
||||
|
||||
Named lifecycle-state type for a managed process.
|
||||
|
||||
Exported values:
|
||||
- `StatusPending`: queued but not started.
|
||||
- `StatusRunning`: currently executing.
|
||||
- `StatusExited`: completed and waited.
|
||||
- `StatusFailed`: start or wait failure state.
|
||||
- `StatusKilled`: terminated by signal.
|
||||
|
||||
### `Stream`
|
||||
`type Stream string`
|
||||
|
||||
Named output-stream discriminator for process output events.
|
||||
|
||||
Exported values:
|
||||
- `StreamStdout`: stdout line.
|
||||
- `StreamStderr`: stderr line.
|
||||
|
||||
### `RunOptions`
|
||||
`struct`
|
||||
|
||||
Execution settings accepted by `Service.StartWithOptions` and `Service.RunWithOptions`.
|
||||
|
||||
Fields:
|
||||
- `Command string`: Executable name. Required by both start and run paths.
|
||||
- `Args []string`: Command arguments.
|
||||
- `Dir string`: Working directory.
|
||||
- `Env []string`: Additional environment entries appended to the command environment.
|
||||
- `DisableCapture bool`: Disables the managed-process ring buffer when true.
|
||||
- `Detach bool`: Starts the child in a separate process group and replaces the parent context with `context.Background()`.
|
||||
- `Timeout time.Duration`: Optional watchdog timeout that calls `Shutdown` after the duration elapses.
|
||||
- `GracePeriod time.Duration`: Delay between `SIGTERM` and fallback kill in `Shutdown`.
|
||||
- `KillGroup bool`: Requests process-group termination. The current service only enables this when `Detach` is also true.
|
||||
|
||||
### `ProcessInfo`
|
||||
`struct`
|
||||
|
||||
Serializable snapshot returned by `ManagedProcess.Info` and `Service` action lookups.
|
||||
|
||||
Fields:
|
||||
- `ID string`: Managed-process identifier.
|
||||
- `Command string`: Executable name.
|
||||
- `Args []string`: Command arguments.
|
||||
- `Dir string`: Working directory.
|
||||
- `StartedAt time.Time`: Start timestamp.
|
||||
- `Running bool`: Convenience boolean derived from `Status`.
|
||||
- `Status Status`: Current lifecycle state.
|
||||
- `ExitCode int`: Exit status.
|
||||
- `Duration time.Duration`: Runtime duration.
|
||||
- `PID int`: Child PID, or `0` if no process handle is available.
|
||||
|
||||
### `Info`
|
||||
`type alias of ProcessInfo`
|
||||
|
||||
Compatibility alias that exposes the same fields as `ProcessInfo`.
|
||||
|
||||
## Functions
|
||||
|
||||
### Package Functions
|
||||
|
||||
- `func Register(c *core.Core) core.Result`: Builds a `Service` with a fresh `core.Registry[*ManagedProcess]`, sets the buffer size to `DefaultBufferSize`, and returns the service in `Result.Value`.
|
||||
- `func NewRingBuffer(size int) *RingBuffer`: Allocates a fixed-capacity ring buffer of exactly `size` bytes.
|
||||
- `func NewDaemon(opts DaemonOptions) *Daemon`: Normalizes `ShutdownTimeout`, creates optional `PIDFile` and `HealthServer` helpers, and attaches any configured health checks.
|
||||
- `func NewHealthServer(addr string) *HealthServer`: Returns a health server with the supplied listen address and readiness initialized to `true`.
|
||||
- `func WaitForHealth(addr string, timeoutMs int) bool`: Polls `http://<addr>/health` every 200 ms until it gets HTTP 200 or the timeout expires.
|
||||
- `func NewPIDFile(path string) *PIDFile`: Returns a PID-file manager for `path`.
|
||||
- `func ReadPID(path string) (int, bool)`: Reads and parses a PID file, then uses signal `0` to report whether that PID is still alive.
|
||||
- `func NewRegistry(dir string) *Registry`: Returns a daemon registry rooted at `dir`.
|
||||
- `func DefaultRegistry() *Registry`: Returns a registry at `~/.core/daemons`, falling back to the OS temp directory if the home directory cannot be resolved.
|
||||
- `func NewRunner(svc *Service) *Runner`: Returns a runner bound to a specific `Service`.
|
||||
|
||||
### `RingBuffer` Methods
|
||||
|
||||
- `func (rb *RingBuffer) Write(p []byte) (n int, err error)`: Appends bytes one by one, advancing the circular window and overwriting the oldest bytes when capacity is exceeded.
|
||||
- `func (rb *RingBuffer) String() string`: Returns the current buffer contents in logical order as a string.
|
||||
- `func (rb *RingBuffer) Bytes() []byte`: Returns a copied byte slice of the current logical contents, or `nil` when the buffer is empty.
|
||||
- `func (rb *RingBuffer) Len() int`: Returns the number of bytes currently retained.
|
||||
- `func (rb *RingBuffer) Cap() int`: Returns the configured capacity.
|
||||
- `func (rb *RingBuffer) Reset()`: Clears the buffer indexes and full flag.
|
||||
|
||||
### `Daemon` Methods
|
||||
|
||||
- `func (d *Daemon) Start() error`: Acquires the PID file, starts the health server, marks the daemon running, and auto-registers it when `Registry` is configured. If a later step fails, it rolls back earlier setup.
|
||||
- `func (d *Daemon) Run(ctx context.Context) error`: Requires a started daemon, waits for `ctx.Done()`, and then calls `Stop`.
|
||||
- `func (d *Daemon) Stop() error`: Sets readiness false, shuts down the health server, releases the PID file, unregisters the daemon, and joins health or PID teardown errors with `core.ErrorJoin`.
|
||||
- `func (d *Daemon) SetReady(ready bool)`: Forwards readiness changes to the health server when one exists.
|
||||
- `func (d *Daemon) HealthAddr() string`: Returns the bound health-server address or `""` when health endpoints are disabled.
|
||||
|
||||
### `HealthServer` Methods
|
||||
|
||||
- `func (h *HealthServer) AddCheck(check HealthCheck)`: Appends a health-check callback under lock.
|
||||
- `func (h *HealthServer) SetReady(ready bool)`: Updates the readiness flag used by `/ready`.
|
||||
- `func (h *HealthServer) Start() error`: Installs `/health` and `/ready` handlers, listens on `addr`, stores the listener and `http.Server`, and serves in a goroutine.
|
||||
- `func (h *HealthServer) Stop(ctx context.Context) error`: Calls `Shutdown` on the underlying `http.Server` when started; otherwise returns `nil`.
|
||||
- `func (h *HealthServer) Addr() string`: Returns the actual bound listener address after `Start`, or the configured address before startup.
|
||||
|
||||
### `PIDFile` Methods
|
||||
|
||||
- `func (p *PIDFile) Acquire() error`: Rejects a live existing PID file, deletes stale state, creates the parent directory when needed, and writes the current process ID.
|
||||
- `func (p *PIDFile) Release() error`: Deletes the PID file.
|
||||
- `func (p *PIDFile) Path() string`: Returns the configured PID-file path.
|
||||
|
||||
### `ManagedProcess` Methods
|
||||
|
||||
- `func (p *ManagedProcess) Info() ProcessInfo`: Returns a snapshot containing public fields plus the current child PID.
|
||||
- `func (p *ManagedProcess) Output() string`: Returns captured output as a string, or `""` when capture is disabled.
|
||||
- `func (p *ManagedProcess) OutputBytes() []byte`: Returns captured output as bytes, or `nil` when capture is disabled.
|
||||
- `func (p *ManagedProcess) IsRunning() bool`: Reports running state by checking whether the `done` channel has closed.
|
||||
- `func (p *ManagedProcess) Wait() error`: Blocks for completion and then returns a wrapped error for failed-start, killed, or non-zero-exit outcomes.
|
||||
- `func (p *ManagedProcess) Done() <-chan struct{}`: Returns the completion channel.
|
||||
- `func (p *ManagedProcess) Kill() error`: Sends `SIGKILL` to the child, or to the entire process group when group killing is enabled.
|
||||
- `func (p *ManagedProcess) Shutdown() error`: Sends `SIGTERM`, waits for the configured grace period, and falls back to `Kill`. With no grace period configured, it immediately calls `Kill`.
|
||||
- `func (p *ManagedProcess) SendInput(input string) error`: Writes to the child's stdin pipe while the process is running.
|
||||
- `func (p *ManagedProcess) CloseStdin() error`: Closes the stdin pipe and clears the stored handle.
|
||||
- `func (p *ManagedProcess) Signal(sig os.Signal) error`: Sends an arbitrary signal while the process is in `StatusRunning`.
|
||||
|
||||
### `Program` Methods
|
||||
|
||||
- `func (p *Program) Find() error`: Resolves `Name` through `exec.LookPath`, stores the absolute path in `Path`, and wraps `ErrProgramNotFound` when lookup fails.
|
||||
- `func (p *Program) Run(ctx context.Context, args ...string) (string, error)`: Executes the program in the current working directory by delegating to `RunDir("", args...)`.
|
||||
- `func (p *Program) RunDir(ctx context.Context, dir string, args ...string) (string, error)`: Runs the program with combined stdout/stderr capture, trims the combined output, and returns that output even when the command fails.
|
||||
|
||||
### `Registry` Methods
|
||||
|
||||
- `func (r *Registry) Register(entry DaemonEntry) error`: Ensures the registry directory exists, defaults `Started` when zero, marshals the entry with the package's JSON writer, and writes one `<code>-<daemon>.json` file.
|
||||
- `func (r *Registry) Unregister(code, daemon string) error`: Deletes the registry file for the supplied daemon key.
|
||||
- `func (r *Registry) Get(code, daemon string) (*DaemonEntry, bool)`: Reads one entry, prunes invalid or stale files, and returns `(nil, false)` when the daemon is missing or dead.
|
||||
- `func (r *Registry) List() ([]DaemonEntry, error)`: Lists all JSON files in the registry directory, prunes invalid or stale entries, and returns only live daemons. A missing registry directory returns `nil, nil`.
|
||||
|
||||
### `RunResult` and `RunAllResult` Methods
|
||||
|
||||
- `func (r RunResult) Passed() bool`: Returns true only when the result is not skipped, has no `Error`, and has `ExitCode == 0`.
|
||||
- `func (r RunAllResult) Success() bool`: Returns true when `Failed == 0`, regardless of skipped count.
|
||||
|
||||
### `Runner` Methods
|
||||
|
||||
- `func (r *Runner) RunAll(ctx context.Context, specs []RunSpec) (*RunAllResult, error)`: Executes dependency-aware waves of specs, skips dependents after failing required dependencies, and marks circular or missing dependency sets as failed results with `ExitCode` 1.
|
||||
- `func (r *Runner) RunSequential(ctx context.Context, specs []RunSpec) (*RunAllResult, error)`: Runs specs in order and marks remaining specs skipped after the first disallowed failure.
|
||||
- `func (r *Runner) RunParallel(ctx context.Context, specs []RunSpec) (*RunAllResult, error)`: Runs all specs concurrently and aggregates counts after all goroutines finish.
|
||||
|
||||
### `Service` Methods
|
||||
|
||||
- `func (s *Service) OnStartup(ctx context.Context) core.Result`: Registers the Core actions `process.run`, `process.start`, `process.kill`, `process.list`, and `process.get`.
|
||||
- `func (s *Service) OnShutdown(ctx context.Context) core.Result`: Iterates all managed processes and calls `Kill` on each one.
|
||||
- `func (s *Service) Start(ctx context.Context, command string, args ...string) core.Result`: Convenience wrapper that builds `RunOptions` and delegates to `StartWithOptions`.
|
||||
- `func (s *Service) StartWithOptions(ctx context.Context, opts RunOptions) core.Result`: Starts a managed process, configures pipes, optional capture, detach and timeout behavior, stores it in the registry, emits `ActionProcessStarted`, streams stdout/stderr lines, and emits `ActionProcessExited` after completion.
|
||||
- `func (s *Service) Get(id string) (*ManagedProcess, error)`: Returns one managed process or `ErrProcessNotFound`.
|
||||
- `func (s *Service) List() []*ManagedProcess`: Returns all managed processes currently stored in the service registry.
|
||||
- `func (s *Service) Running() []*ManagedProcess`: Returns only processes whose `done` channel has not closed yet.
|
||||
- `func (s *Service) Kill(id string) error`: Kills the managed process by ID and emits `ActionProcessKilled`.
|
||||
- `func (s *Service) Remove(id string) error`: Deletes a completed process from the registry and rejects removal while it is still running.
|
||||
- `func (s *Service) Clear()`: Deletes every completed process from the registry.
|
||||
- `func (s *Service) Output(id string) (string, error)`: Returns the managed process's captured output.
|
||||
- `func (s *Service) Run(ctx context.Context, command string, args ...string) core.Result`: Convenience wrapper around `RunWithOptions`.
|
||||
- `func (s *Service) RunWithOptions(ctx context.Context, opts RunOptions) core.Result`: Executes an unmanaged one-shot command with `CombinedOutput`. On success it returns the output string in `Value`; on failure it returns a wrapped error in `Value` and sets `OK` false.
|
||||
Loading…
Add table
Reference in a new issue