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>
96 lines
3.1 KiB
Go
96 lines
3.1 KiB
Go
// 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()
|
|
}
|