docs: v0.7.0 implementation plan — align with core/go v0.8.0
7-step plan to update factory signature, register process.* Actions, remove global singleton, and align with Startable returning Result. Written with full core/go domain context from RFC implementation session. Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
4ff0d0b745
commit
87f53ad8dd
1 changed files with 151 additions and 0 deletions
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
|
||||
Loading…
Add table
Reference in a new issue