go/process.go

97 lines
3.1 KiB
Go
Raw Permalink Normal View History

feat: implement RFC plans 1-5 — Registry[T], Action/Task, Process, primitives Plans 1-5 complete for core/go scope. 456 tests, 84.4% coverage, 100% AX-7 naming. Critical bugs (Plan 1): - P4-3+P7-3: ACTION broadcast calls all handlers with panic recovery - P7-2+P7-4: RunE() with defer ServiceShutdown, Run() delegates - P3-1: Startable/Stoppable return Result (breaking, clean) - P9-1: Zero os/exec — App.Find() rewritten with os.Stat+PATH - I3: Embed() removed, I15: New() comment fixed - I9: CommandLifecycle removed → Command.Managed field Registry[T] (Plan 2): - Universal thread-safe named collection with 3 lock modes - All 5 registries migrated: services, commands, drive, data, lock - Insertion order preserved (fixes P4-1) - c.RegistryOf("name") cross-cutting accessor Action/Task system (Plan 3): - Action type with Run()/Exists(), ActionHandler signature - c.Action("name") dual-purpose accessor (register/invoke) - TaskDef with Steps — sequential chain, async dispatch, previous-input piping - Panic recovery on all Action execution - broadcast() internal, ACTION() sugar Process primitive (Plan 4): - c.Process() returns Action sugar — Run/RunIn/RunWithEnv/Start/Kill/Exists - No deps added — delegates to c.Action("process.*") - Permission-by-registration: no handler = no capability Missing primitives (Plan 5): - core.ID() — atomic counter + crypto/rand suffix - ValidateName() / SanitisePath() — reusable validation - Fs.WriteAtomic() — write-to-temp-then-rename - Fs.NewUnrestricted() / Fs.Root() — legitimate sandbox bypass - AX-7: 456/456 tests renamed to TestFile_Function_{Good,Bad,Ugly} Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 15:18:25 +00:00
// SPDX-License-Identifier: EUPL-1.2
// Process is the Core primitive for managed execution.
// Methods emit via named Actions — actual execution is handled by
// whichever service registers the "process.*" actions (typically go-process).
//
// If go-process is NOT registered, all methods return Result{OK: false}.
// This is permission-by-registration: no handler = no capability.
//
// Usage:
//
// r := c.Process().Run(ctx, "git", "log", "--oneline")
// if r.OK { output := r.Value.(string) }
//
// r := c.Process().RunIn(ctx, "/path/to/repo", "go", "test", "./...")
//
// Permission model:
//
// // Full Core — process registered:
// c := core.New(core.WithService(process.Register))
// c.Process().Run(ctx, "git", "log") // works
//
// // Sandboxed Core — no process:
// c := core.New()
// c.Process().Run(ctx, "git", "log") // Result{OK: false}
package core
import "context"
// Process is the Core primitive for process management.
// Zero dependencies — delegates to named Actions.
type Process struct {
core *Core
}
// Process returns the process management primitive.
//
// c.Process().Run(ctx, "git", "log")
func (c *Core) Process() *Process {
return &Process{core: c}
}
// Run executes a command synchronously and returns the output.
//
// r := c.Process().Run(ctx, "git", "log", "--oneline")
// if r.OK { output := r.Value.(string) }
func (p *Process) Run(ctx context.Context, command string, args ...string) Result {
return p.core.Action("process.run").Run(ctx, NewOptions(
Option{Key: "command", Value: command},
Option{Key: "args", Value: args},
))
}
// RunIn executes a command in a specific directory.
//
// r := c.Process().RunIn(ctx, "/repo", "go", "test", "./...")
func (p *Process) RunIn(ctx context.Context, dir string, command string, args ...string) Result {
return p.core.Action("process.run").Run(ctx, NewOptions(
Option{Key: "command", Value: command},
Option{Key: "args", Value: args},
Option{Key: "dir", Value: dir},
))
}
// RunWithEnv executes with additional environment variables.
//
// r := c.Process().RunWithEnv(ctx, dir, []string{"GOWORK=off"}, "go", "test")
func (p *Process) RunWithEnv(ctx context.Context, dir string, env []string, command string, args ...string) Result {
return p.core.Action("process.run").Run(ctx, NewOptions(
Option{Key: "command", Value: command},
Option{Key: "args", Value: args},
Option{Key: "dir", Value: dir},
Option{Key: "env", Value: env},
))
}
// Start spawns a detached/background process.
//
// r := c.Process().Start(ctx, ProcessStartOptions{Command: "docker", Args: []string{"run", "..."}})
func (p *Process) Start(ctx context.Context, opts Options) Result {
return p.core.Action("process.start").Run(ctx, opts)
}
// Kill terminates a managed process by ID or PID.
//
// c.Process().Kill(ctx, core.NewOptions(core.Option{Key: "id", Value: processID}))
func (p *Process) Kill(ctx context.Context, opts Options) Result {
return p.core.Action("process.kill").Run(ctx, opts)
}
// Exists returns true if any process execution capability is registered.
//
// if c.Process().Exists() { /* can run commands */ }
func (p *Process) Exists() bool {
return p.core.Action("process.run").Exists()
}