chore: remove completed implementation plans — RFC.md is the single source
Plans 1-5 are implemented. Plan 6 (ecosystem sweep) is in consumer RFCs.
RFC.plan.md, RFC.plan.1.md, RFC.plan.2.md served their purpose as
session continuity docs — the work they described is done.
RFC.md (1935 lines) is now the single source of truth for core/go.
Removed:
- RFC.implementation.{1-6}.md
- RFC.plan.md, RFC.plan.1.md, RFC.plan.2.md
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
fe46e33ddf
commit
390b392dec
9 changed files with 0 additions and 783 deletions
|
|
@ -1,78 +0,0 @@
|
|||
# Implementation Plan 1 — Critical Bug Fixes (Phase 1)
|
||||
|
||||
> Ship as v0.7.1. Zero consumer breakage. Fix the 3 critical bugs.
|
||||
|
||||
## P4-3: ACTION !OK Stops Broadcast Chain
|
||||
|
||||
**File:** `ipc.go`
|
||||
**Change:** ACTION dispatch must call ALL handlers regardless of individual results.
|
||||
|
||||
```go
|
||||
// Current (broken):
|
||||
for _, h := range handlers {
|
||||
if r := h(c, msg); !r.OK {
|
||||
return r // STOPS — remaining handlers never called
|
||||
}
|
||||
}
|
||||
|
||||
// Fix:
|
||||
for _, h := range handlers {
|
||||
h(c, msg) // call all, ignore individual results for broadcast
|
||||
}
|
||||
```
|
||||
|
||||
**Test:** `TestIpc_Action_Ugly` — register 3 handlers, second returns !OK, verify third still fires.
|
||||
|
||||
## P7-2: No Cleanup on Startup Failure
|
||||
|
||||
**File:** `core.go` — `Run()`
|
||||
**Change:** Call ServiceShutdown before exit on startup failure.
|
||||
|
||||
```go
|
||||
// Current:
|
||||
if !r.OK { os.Exit(1) }
|
||||
|
||||
// Fix:
|
||||
if !r.OK {
|
||||
c.ServiceShutdown(context.Background())
|
||||
os.Exit(1)
|
||||
}
|
||||
```
|
||||
|
||||
## P7-3: ACTION Handlers No Panic Recovery
|
||||
|
||||
**File:** `ipc.go`
|
||||
**Change:** Wrap each handler in defer/recover.
|
||||
|
||||
```go
|
||||
for _, h := range handlers {
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
Error("ACTION handler panicked", "panic", r)
|
||||
}
|
||||
}()
|
||||
h(c, msg)
|
||||
}()
|
||||
}
|
||||
```
|
||||
|
||||
**Test:** `TestIpc_Action_Ugly` — handler that panics, verify other handlers still execute.
|
||||
|
||||
## P7-4: Run() Needs defer ServiceShutdown
|
||||
|
||||
**File:** `core.go`
|
||||
**Change:** Add defer as first line of Run.
|
||||
|
||||
```go
|
||||
func (c *Core) Run() {
|
||||
defer c.ServiceShutdown(context.Background())
|
||||
// ... rest unchanged, but remove os.Exit calls
|
||||
}
|
||||
```
|
||||
|
||||
## Additional Phase 1 (safe)
|
||||
|
||||
- **I3:** Remove `Embed()` accessor (0 consumers)
|
||||
- **I15:** Fix stale comment on `New()` — update to show `*Core` return
|
||||
- **P9-1:** Remove `os/exec` import from `app.go` — move `App.Find()` to go-process
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
# Implementation Plan 2 — Registry[T] Primitive (Section 20)
|
||||
|
||||
> Foundation brick. Most other plans depend on this.
|
||||
|
||||
## The Type
|
||||
|
||||
**New file:** `registry.go`
|
||||
|
||||
```go
|
||||
type Registry[T any] struct {
|
||||
items map[string]T
|
||||
order []string // insertion order (fixes P4-1)
|
||||
mu sync.RWMutex
|
||||
mode registryMode // open, sealed, locked
|
||||
}
|
||||
|
||||
type registryMode int
|
||||
const (
|
||||
registryOpen registryMode = iota // anything goes
|
||||
registrySealed // update existing, no new keys
|
||||
registryLocked // fully frozen
|
||||
)
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
```go
|
||||
func NewRegistry[T any]() *Registry[T]
|
||||
func (r *Registry[T]) Set(name string, item T) Result
|
||||
func (r *Registry[T]) Get(name string) Result
|
||||
func (r *Registry[T]) Has(name string) bool
|
||||
func (r *Registry[T]) Names() []string // insertion order
|
||||
func (r *Registry[T]) List(pattern string) []T // glob match
|
||||
func (r *Registry[T]) Each(fn func(string, T))
|
||||
func (r *Registry[T]) Len() int
|
||||
func (r *Registry[T]) Delete(name string) Result
|
||||
func (r *Registry[T]) Disable(name string) // soft — exists but skipped
|
||||
func (r *Registry[T]) Lock() // fully frozen
|
||||
func (r *Registry[T]) Seal() // no new, updates OK
|
||||
func (r *Registry[T]) Open() // default
|
||||
```
|
||||
|
||||
## Migration
|
||||
|
||||
Replace internal registries one at a time:
|
||||
|
||||
1. `serviceRegistry` → `ServiceRegistry` embedding `Registry[*Service]`
|
||||
2. `commandRegistry` → `CommandRegistry` embedding `Registry[*Command]`
|
||||
3. `Drive.handles` → embed `Registry[*DriveHandle]`
|
||||
4. `Data.mounts` → embed `Registry[*Embed]`
|
||||
5. `Lock.locks` → `Registry[*sync.RWMutex]` (fixes P4-8 allocation)
|
||||
|
||||
Each migration is a separate commit. Tests before and after.
|
||||
|
||||
## Core Accessor
|
||||
|
||||
```go
|
||||
func (c *Core) Registry(name string) *Registry[any]
|
||||
```
|
||||
|
||||
Returns named registries for cross-cutting queries.
|
||||
|
||||
## Resolves
|
||||
|
||||
P4-1 (startup order), P4-8 (lock allocation), I6 (serviceRegistry unexported), I12 (Ipc data-only), I13 (Lock allocation), I14 (Startables return type).
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
# Implementation Plan 3 — Action/Task System (Section 18)
|
||||
|
||||
> Depends on: Plan 2 (Registry). The execution primitive.
|
||||
|
||||
## Phase A: Action Registry
|
||||
|
||||
**New file:** `action.go` (renamed from `task.go`)
|
||||
|
||||
```go
|
||||
type ActionDef struct {
|
||||
Name string
|
||||
Handler ActionHandler
|
||||
Description string
|
||||
Schema Options // expected input keys
|
||||
enabled bool // for Disable()
|
||||
}
|
||||
|
||||
type ActionHandler func(context.Context, Options) Result
|
||||
```
|
||||
|
||||
**Core accessor:**
|
||||
|
||||
```go
|
||||
// Dual-purpose (like Service):
|
||||
c.Action("process.run", handler) // register
|
||||
c.Action("process.run").Run(ctx, opts) // invoke
|
||||
c.Action("process.run").Exists() // check
|
||||
c.Action("process.run").Def() // metadata
|
||||
```
|
||||
|
||||
Internally uses `Registry[*ActionDef]`.
|
||||
|
||||
## Phase B: Move Registration to IPC
|
||||
|
||||
Move from `task.go`:
|
||||
- `RegisterAction` → `ipc.go` (registers in `Registry[ActionHandler]`)
|
||||
- `RegisterActions` → `ipc.go`
|
||||
- `RegisterTask` → `ipc.go`
|
||||
|
||||
Keep in `action.go`:
|
||||
- `Perform` → becomes `c.Action("name").Run()`
|
||||
- `PerformAsync` → becomes `c.Action("name").RunAsync()`
|
||||
- `Progress` → stays
|
||||
|
||||
## Phase C: Panic Recovery on All Actions
|
||||
|
||||
Every `Action.Run()` wraps in recover:
|
||||
|
||||
```go
|
||||
func (a *ActionDef) Run(ctx context.Context, opts Options) (result Result) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
result = Result{E("action.Run", Sprint("panic: ", r), nil), false}
|
||||
}
|
||||
}()
|
||||
return a.Handler(ctx, opts)
|
||||
}
|
||||
```
|
||||
|
||||
## Phase D: Task Composition (v0.8.0 stretch)
|
||||
|
||||
```go
|
||||
type TaskDef struct {
|
||||
Name string
|
||||
Steps []Step
|
||||
}
|
||||
|
||||
type Step struct {
|
||||
Action string
|
||||
With Options
|
||||
Async bool // run in background
|
||||
Input string // "previous" = output of last step
|
||||
}
|
||||
```
|
||||
|
||||
`c.Task("name", def)` registers. `c.Task("name").Run(ctx)` executes steps in order.
|
||||
|
||||
## Resolves
|
||||
|
||||
I16 (task.go concerns), P6-1 (cascade → Task pipeline), P6-2 (O(N×M) → direct dispatch), P7-3 (panic recovery), P7-8 (circuit breaker via Disable), P10-2 (PERFORM not ACTION for request/response).
|
||||
|
||||
## Migration in core/agent
|
||||
|
||||
After Actions exist, refactor `handlers.go`:
|
||||
|
||||
```go
|
||||
// Current: nested c.ACTION() cascade
|
||||
// Target:
|
||||
c.Task("agent.completion", TaskDef{
|
||||
Steps: []Step{
|
||||
{Action: "agentic.qa"},
|
||||
{Action: "agentic.auto-pr"},
|
||||
{Action: "agentic.verify"},
|
||||
{Action: "agentic.ingest", Async: true},
|
||||
{Action: "agentic.poke", Async: true},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
# Implementation Plan 4 — c.Process() Primitive (Section 17)
|
||||
|
||||
> Depends on: Plan 2 (Registry), Plan 3 (Actions).
|
||||
> go-process v0.7.0 update required.
|
||||
|
||||
## Phase A: Update go-process to v0.7.0 Core API
|
||||
|
||||
**Repo:** core/go-process
|
||||
|
||||
1. Change `NewService` factory to return `core.Result` (not `(any, error)`)
|
||||
2. Add `Register` function matching `WithService` signature
|
||||
3. Remove `ProcessRegister` bridge from core/agent
|
||||
4. Register process Actions during OnStartup:
|
||||
|
||||
```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)
|
||||
return core.Result{OK: true}
|
||||
}
|
||||
```
|
||||
|
||||
## Phase B: Add Process Primitive to core/go
|
||||
|
||||
**New file in core/go:** `process.go`
|
||||
|
||||
```go
|
||||
type Process struct {
|
||||
core *Core
|
||||
}
|
||||
|
||||
func (c *Core) Process() *Process { return c.process }
|
||||
|
||||
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},
|
||||
))
|
||||
}
|
||||
|
||||
func (p *Process) RunIn(ctx context.Context, dir, 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},
|
||||
))
|
||||
}
|
||||
```
|
||||
|
||||
No new dependencies in core/go — Process is just Action sugar.
|
||||
|
||||
## Phase C: Migrate core/agent proc.go
|
||||
|
||||
Replace standalone helpers with Core methods:
|
||||
|
||||
```go
|
||||
// Current: proc.go standalone with ensureProcess() hack
|
||||
out, err := runCmd(ctx, dir, "git", "log")
|
||||
|
||||
// Target: method on PrepSubsystem using Core
|
||||
out := s.core.Process().RunIn(ctx, dir, "git", "log")
|
||||
```
|
||||
|
||||
Delete `proc.go`. Delete `ensureProcess()`. Delete `ProcessRegister`.
|
||||
|
||||
## Phase D: Replace syscall.Kill calls
|
||||
|
||||
5 call sites in core/agent use `syscall.Kill(pid, 0)` and `syscall.Kill(pid, SIGTERM)`.
|
||||
|
||||
Replace with:
|
||||
```go
|
||||
processIsRunning(st.ProcessID, st.PID) // already in proc.go
|
||||
processKill(st.ProcessID, st.PID) // already in proc.go
|
||||
```
|
||||
|
||||
These use `process.Get(id).IsRunning()` when ProcessID is available.
|
||||
|
||||
## Phase E: Atomic File Writes (P4-10)
|
||||
|
||||
Add to core/go `fs.go`:
|
||||
|
||||
```go
|
||||
func (m *Fs) WriteAtomic(p, content string) Result {
|
||||
tmp := p + ".tmp." + strconv.FormatInt(time.Now().UnixNano(), 36)
|
||||
if r := m.Write(tmp, content); !r.OK { return r }
|
||||
if err := os.Rename(tmp, m.path(p)); err != nil {
|
||||
m.Delete(tmp)
|
||||
return Result{err, false}
|
||||
}
|
||||
return Result{OK: true}
|
||||
}
|
||||
```
|
||||
|
||||
Migrate `writeStatus` in core/agent to use `WriteAtomic`. Fixes P4-9 (51 race sites).
|
||||
|
||||
## Resolves
|
||||
|
||||
I7 (no c.Process), P9-1 (os/exec in core), P4-9 (status.json race), P4-10 (Fs.Write not atomic), P11-2 (unsafe.Pointer Fs bypass — add Fs.NewUnrestricted instead).
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
# Implementation Plan 5 — Missing Primitives + AX-7 (Phase 1-2)
|
||||
|
||||
> Independent of Plans 2-4. Can ship alongside or before.
|
||||
|
||||
## core.ID() — Unique Identifier
|
||||
|
||||
**File:** `utils.go` or new `id.go`
|
||||
|
||||
```go
|
||||
var idCounter atomic.Uint64
|
||||
|
||||
func ID() string {
|
||||
return Sprintf("id-%d-%s", idCounter.Add(1), shortRand())
|
||||
}
|
||||
|
||||
func shortRand() string {
|
||||
b := make([]byte, 3)
|
||||
crand.Read(b)
|
||||
return hex.EncodeToString(b)
|
||||
}
|
||||
```
|
||||
|
||||
Replaces 3 different patterns: crypto/rand hex, atomic counter, fmt.Sprintf.
|
||||
|
||||
## core.ValidateName() / core.ValidatePath()
|
||||
|
||||
**File:** `utils.go` or new `validate.go`
|
||||
|
||||
```go
|
||||
func ValidateName(name string) Result {
|
||||
if name == "" || name == "." || name == ".." {
|
||||
return Result{E("validate", "invalid name: "+name, nil), false}
|
||||
}
|
||||
if Contains(name, "/") || Contains(name, "\\") {
|
||||
return Result{E("validate", "name contains path separator: "+name, nil), false}
|
||||
}
|
||||
return Result{name, true}
|
||||
}
|
||||
|
||||
func SanitisePath(path string) string {
|
||||
safe := PathBase(path)
|
||||
if safe == "." || safe == ".." || safe == "" {
|
||||
return "invalid"
|
||||
}
|
||||
return safe
|
||||
}
|
||||
```
|
||||
|
||||
Replaces copy-pasted validation in prep.go, plan.go, command.go.
|
||||
|
||||
## Fs.WriteAtomic()
|
||||
|
||||
See Plan 4 Phase E. Fixes P4-9 and P4-10.
|
||||
|
||||
## Fs.NewUnrestricted()
|
||||
|
||||
**File:** `fs.go`
|
||||
|
||||
```go
|
||||
func (m *Fs) NewUnrestricted() *Fs {
|
||||
return m.New("/")
|
||||
}
|
||||
```
|
||||
|
||||
Gives consumers a legitimate door instead of unsafe.Pointer crowbar.
|
||||
Fixes P11-2. Add go vet / linter rule to flag unsafe.Pointer on Core types.
|
||||
|
||||
## AX-7 for core/go
|
||||
|
||||
Currently 14% AX-7 (83.6% statement coverage, wrong naming).
|
||||
|
||||
Steps:
|
||||
1. Run the rename script from core/agent (same Python script)
|
||||
2. Gap analysis: find functions missing Good/Bad/Ugly
|
||||
3. Fill gaps — 212 functions × 3 categories = 636 target
|
||||
4. Currently 91/636 filled → need 545 more
|
||||
|
||||
Prioritise by section:
|
||||
- Section 3 (services): ServiceFor, RegisterService, Service
|
||||
- Section 4 (IPC): Action, Query, RegisterAction
|
||||
- Section 8 (Fs): Read, Write, WriteAtomic, Delete
|
||||
- Section 5 (Config): Get, Set, Enable, ConfigGet
|
||||
|
||||
## RunE() — Backwards-Compatible Run Fix
|
||||
|
||||
**File:** `core.go`
|
||||
|
||||
```go
|
||||
func (c *Core) RunE() error {
|
||||
defer c.ServiceShutdown(context.Background())
|
||||
|
||||
r := c.ServiceStartup(c.context, nil)
|
||||
if !r.OK {
|
||||
if err, ok := r.Value.(error); ok {
|
||||
return err
|
||||
}
|
||||
return E("core.Run", "startup failed", nil)
|
||||
}
|
||||
|
||||
if cli := c.Cli(); cli != nil {
|
||||
r = cli.Run()
|
||||
}
|
||||
|
||||
if !r.OK {
|
||||
if err, ok := r.Value.(error); ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run keeps backwards compatibility
|
||||
func (c *Core) Run() {
|
||||
if err := c.RunE(); err != nil {
|
||||
Error(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Fixes P7-5 (os.Exit bypasses defer) without breaking 15 main.go files.
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
# Implementation Plan 6 — Ecosystem Sweep (Phase 3)
|
||||
|
||||
> Depends on: Plans 1-5 complete. 44 repos need updating.
|
||||
> Use Codex dispatch with RFC as the spec.
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
### StartableV2 (if P3-1 adopted)
|
||||
|
||||
```go
|
||||
type StartableV2 interface {
|
||||
OnStartup(ctx context.Context) Result
|
||||
}
|
||||
```
|
||||
|
||||
Core checks V2 first, falls back to V1. No breakage. Deprecation notice on V1.
|
||||
|
||||
**Codex template:** "If this repo implements OnStartup returning error, add a V2 variant returning core.Result alongside it."
|
||||
|
||||
### RunE() exists alongside Run()
|
||||
|
||||
No breakage — Run() still works. New code uses RunE().
|
||||
|
||||
### Command.Managed replaces CommandLifecycle
|
||||
|
||||
```go
|
||||
// Old:
|
||||
Command{Lifecycle: myLifecycle}
|
||||
|
||||
// New:
|
||||
Command{Managed: "process.daemon"}
|
||||
```
|
||||
|
||||
**Codex template:** "If this repo uses Command.Lifecycle, replace with Managed field."
|
||||
|
||||
## Per-Repo Sweep Checklist
|
||||
|
||||
For each of 44 repos:
|
||||
|
||||
1. [ ] Update `go.mod` to core v0.8.0
|
||||
2. [ ] Replace `OnStartup() error` with `OnStartup() Result` (if V2 adopted)
|
||||
3. [ ] Replace `os/exec` with `c.Process()` calls
|
||||
4. [ ] Replace manual `unsafe.Pointer` Fs hacks with `Fs.NewUnrestricted()`
|
||||
5. [ ] Run AX-7 gap analysis, fill missing Good/Bad/Ugly
|
||||
6. [ ] Rename tests to `TestFile_Function_{Good,Bad,Ugly}`
|
||||
7. [ ] Add `llm.txt` and `AGENTS.md`
|
||||
8. [ ] Verify `go test ./...` passes
|
||||
9. [ ] Create PR to dev
|
||||
|
||||
## Dispatch Strategy
|
||||
|
||||
```
|
||||
Batch 1 (core packages — we control):
|
||||
core/go, core/agent, core/mcp, go-process, go-io, go-log
|
||||
|
||||
Batch 2 (utility packages):
|
||||
go-config, go-git, go-scm, go-cache, go-store, go-session
|
||||
|
||||
Batch 3 (domain packages):
|
||||
go-forge, go-api, go-build, go-devops, go-container
|
||||
|
||||
Batch 4 (application packages):
|
||||
gui, cli, ts, php, ide, lint
|
||||
|
||||
Batch 5 (research/experimental):
|
||||
go-ai, go-ml, go-mlx, go-inference, go-rag, go-rocm
|
||||
```
|
||||
|
||||
Each batch is one Codex sweep. Verify batch N before starting N+1.
|
||||
|
||||
## Success Criteria for v0.8.0
|
||||
|
||||
- [ ] All 3 critical bugs fixed (Plan 1)
|
||||
- [ ] Registry[T] implemented and migrated (Plan 2)
|
||||
- [ ] Action system working (Plan 3 Phase A-C)
|
||||
- [ ] c.Process() primitive working (Plan 4 Phase A-C)
|
||||
- [ ] Missing primitives added (Plan 5)
|
||||
- [ ] core/go AX-7 at 100%
|
||||
- [ ] core/agent AX-7 at 100%
|
||||
- [ ] Zero os/exec in core/go
|
||||
- [ ] Zero unsafe.Pointer on Core types in ecosystem
|
||||
- [ ] All Phase 1-2 changes shipped
|
||||
- [ ] Phase 3 sweep at least started (batch 1-2)
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
# RFC Plan 1 — First Session Priorities (COMPLETED 2026-03-25)
|
||||
|
||||
> All items shipped. See RFC.plan.md "What Was Shipped" section.
|
||||
|
||||
## Priority 1: Fix the 3 Critical Bugs (Plan 1)
|
||||
|
||||
These are one-line to five-line changes. Ship as v0.7.1.
|
||||
|
||||
### Bug 1: ACTION stops on !OK (ipc.go line ~33)
|
||||
|
||||
```go
|
||||
// CURRENT (broken — handler 3 failing silences handlers 4 and 5):
|
||||
for _, h := range handlers {
|
||||
if r := h(c, msg); !r.OK { return r }
|
||||
}
|
||||
|
||||
// FIX:
|
||||
for _, h := range handlers {
|
||||
func() {
|
||||
defer func() { if r := recover(); r != nil { Error("handler panic", "err", r) } }()
|
||||
h(c, msg)
|
||||
}()
|
||||
}
|
||||
```
|
||||
|
||||
This also fixes P7-3 (no panic recovery) in the same change.
|
||||
|
||||
### Bug 2: Run() leaks on startup failure (core.go Run method)
|
||||
|
||||
Add one line:
|
||||
```go
|
||||
func (c *Core) Run() {
|
||||
defer c.ServiceShutdown(context.Background()) // ADD THIS
|
||||
// ... rest unchanged
|
||||
}
|
||||
```
|
||||
|
||||
### Bug 3: Remove stale Embed() and fix comment
|
||||
|
||||
Delete `func (c *Core) Embed() Result` from core.go.
|
||||
Fix the `New()` comment to show `*Core` return.
|
||||
|
||||
### Test all 3 with AX-7 naming:
|
||||
```
|
||||
TestIpc_Action_Ugly_HandlerFailsChainContinues
|
||||
TestIpc_Action_Ugly_HandlerPanicsChainContinues
|
||||
TestCore_Run_Ugly_StartupFailureCallsShutdown
|
||||
```
|
||||
|
||||
## Priority 2: AX-7 Rename for core/go
|
||||
|
||||
Run the same Python rename script used on core/agent:
|
||||
|
||||
```python
|
||||
# Same script from core/agent session — applies to any Go package
|
||||
# Changes TestFoo_Good to TestFile_Foo_Good
|
||||
```
|
||||
|
||||
This is mechanical. No logic changes. Just naming.
|
||||
|
||||
Then run gap analysis:
|
||||
```bash
|
||||
python3 -c "... same gap analysis script ..."
|
||||
```
|
||||
|
||||
## Priority 3: Start Registry[T] (Plan 2)
|
||||
|
||||
Create `registry.go` with the type. Write tests FIRST (AX-7 complete from day one):
|
||||
|
||||
```
|
||||
TestRegistry_Set_Good
|
||||
TestRegistry_Set_Bad
|
||||
TestRegistry_Set_Ugly
|
||||
TestRegistry_Get_Good
|
||||
...
|
||||
```
|
||||
|
||||
Then migrate `serviceRegistry` first (most tested, most used).
|
||||
|
||||
## What Was Skipped (shipped in same session instead)
|
||||
|
||||
All items originally marked "skip" were shipped because Registry and Actions were built in the same session:
|
||||
- Plan 3 (Actions) — DONE: ActionDef, TaskDef, c.Action(), c.Task()
|
||||
- Plan 4 (Process) — DONE for core/go: c.Process() sugar over Actions
|
||||
- Breaking changes — DONE: Startable returns Result, CommandLifecycle removed
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
# RFC Plan 2 — Registry + Actions Sessions (COMPLETED 2026-03-25)
|
||||
|
||||
> All core/go items shipped. core/agent migration and go-process v0.7.0 are separate repo scope.
|
||||
|
||||
## Session Goal: Registry[T] + First Migration
|
||||
|
||||
1. Build `registry.go` with full AX-7 tests
|
||||
2. Migrate `serviceRegistry` → `ServiceRegistry` embedding `Registry[*Service]`
|
||||
3. Verify all existing tests still pass
|
||||
4. Commit + push
|
||||
|
||||
## Session Goal: Action System
|
||||
|
||||
1. Rename `task.go` → `action.go`
|
||||
2. Move `RegisterAction`/`RegisterActions`/`RegisterTask` to `ipc.go`
|
||||
3. Build `ActionDef` type with `Run()`, `Exists()`, `Def()`
|
||||
4. Wire `c.Action("name")` dual-purpose accessor
|
||||
5. Full AX-7 tests
|
||||
6. Commit + push
|
||||
|
||||
## Session Goal: Migrate core/agent Handlers
|
||||
|
||||
1. Register named Actions in `agentic.Register()`
|
||||
2. Replace nested `c.ACTION()` cascade with Task pipeline
|
||||
3. Test that queue drains properly after agent completion
|
||||
4. This is the P6-1 fix — the queue starvation bug
|
||||
|
||||
## Session Goal: c.Process() + go-process v0.7.0
|
||||
|
||||
1. Update go-process factory to return `core.Result`
|
||||
2. Add `process.Register` direct factory
|
||||
3. Remove `agentic.ProcessRegister` bridge
|
||||
4. Add `Process` primitive to core/go (sugar over Actions)
|
||||
5. Migrate core/agent `proc.go` → `s.core.Process()` calls
|
||||
6. Delete `proc.go` and `ensureProcess()`
|
||||
|
||||
## Between Sessions
|
||||
|
||||
Each session should produce:
|
||||
- Working code (all tests pass)
|
||||
- A commit with conventional message
|
||||
- Updated coverage numbers
|
||||
- Any new findings added to RFC.md passes
|
||||
110
docs/RFC.plan.md
110
docs/RFC.plan.md
|
|
@ -1,110 +0,0 @@
|
|||
# RFC Plan — How to Work With This Spec
|
||||
|
||||
> For future Claude sessions. Read this FIRST before touching code.
|
||||
|
||||
## What Exists
|
||||
|
||||
- `docs/RFC.md` — 3,845-line API spec with 108 findings across 13 passes
|
||||
- `docs/RFC.implementation.{1-6}.md` — ordered implementation plans
|
||||
- `llm.txt` — agent entry point
|
||||
- `CLAUDE.md` — session-specific instructions
|
||||
|
||||
## The 108 Findings Reduce to 5 Root Causes
|
||||
|
||||
1. **Type erasure** (16 findings) — `Result{Value: any}` loses compile-time safety. Mitigate with typed methods + AX-7 tests. Not fixable without abandoning Result.
|
||||
|
||||
2. **No internal boundaries** (14 findings) — `*Core` grants God Mode. Solved by Section 21 (Entitlement primitive). v0.8.0 scope — designed, implementation pending.
|
||||
|
||||
3. **Synchronous everything** (12 findings) — IPC dispatch blocks. ACTION cascade in core/agent blocks queue for minutes. Fixed by Action/Task system (Plan 3).
|
||||
|
||||
4. **No recovery path** (10 findings) — `os.Exit` bypasses defer. No cleanup on failure. Fixed by Plan 1 (defer + RunE + panic recovery).
|
||||
|
||||
5. **Missing primitives** (8 findings) — No ID, validation, health, atomic writes. Fixed by Plan 5.
|
||||
|
||||
## Implementation Order
|
||||
|
||||
```
|
||||
Plan 1 → v0.7.1 (ship immediately, zero breakage)
|
||||
Plan 2 → Registry[T] (foundation — Plans 3-4 depend on this)
|
||||
Plan 3 → Action/Task (execution primitive — Plan 4 depends on this)
|
||||
Plan 4 → c.Process() (needs go-process v0.7.0 update first)
|
||||
Plan 5 → Missing primitives + AX-7 (independent, do alongside 2-4)
|
||||
Plan 6 → Ecosystem sweep (after 1-5, dispatched via Codex)
|
||||
```
|
||||
|
||||
## 3 Critical Bugs — Fix First
|
||||
|
||||
1. **P4-3:** `ipc.go` — ACTION handler returning `!OK` stops entire broadcast chain. Other handlers never fire. Fix: call all handlers, don't stop on failure.
|
||||
|
||||
2. **P6-1:** core/agent `handlers.go` — Nested `c.ACTION()` calls create synchronous cascade 4 levels deep. QA → PR → Verify → Merge blocks Poke handler for minutes. Queue doesn't drain. Fix: replace with Task pipeline (needs Plan 3).
|
||||
|
||||
3. **P7-2:** `core.go` — `Run()` calls `os.Exit(1)` on startup failure without calling `ServiceShutdown()`. Running services leak. Fix: add `defer c.ServiceShutdown()` + replace `os.Exit` with error return.
|
||||
|
||||
## Key Design Decisions Already Made
|
||||
|
||||
- **CamelCase = primitive** (brick), **UPPERCASE = convenience** (sugar)
|
||||
- **Core is Lego bricks** — export the bricks, hide the safety mechanisms
|
||||
- **Fs.root is the ONE exception** — security boundaries stay unexported
|
||||
- **Registration IS permission** — no handler = no capability
|
||||
- **`error` at Go interface boundary, `Result` at Core contract boundary**
|
||||
- **Dual-purpose methods** (Service, Command, Action) — keep as sugar, Registry has explicit Get/Set
|
||||
- **Array[T] and ConfigVar[T] are guardrail primitives** — model-proof, not speculative
|
||||
- **ServiceRuntime[T] and manual `.core = c` are both valid** — document both
|
||||
- **Startable returns Result** — clean break, no V2 compat shim (pre-v1, breaking is expected)
|
||||
- **`RunE()` alongside `Run()`** — no breakage
|
||||
- **CommandLifecycle removed** — replaced with `Command.Managed` string field
|
||||
|
||||
## Existing RFCs That Solve Open Problems
|
||||
|
||||
| Problem | RFC | Core Provides | Consumer Implements |
|
||||
|---------|-----|---------------|-------------------|
|
||||
| Permissions | RFC-004 Entitlements | `c.Entitlement()` interface | go-entitlements package |
|
||||
| Config context | RFC-003 Config Channels | `c.Config()` with channel | config channel service |
|
||||
| Secrets | RFC-012 SMSG | `c.Secret()` interface | go-smsg / env fallback |
|
||||
| Validation | RFC-009 Sigil | Transform chain interface | validator implementations |
|
||||
| Containers | RFC-014 TIM | `c.Fs()` sandbox | TIM = OS isolation |
|
||||
| In-memory fs | RFC-013 DataNode | `c.Data()` mounts fs.FS | DataNode / Borg |
|
||||
| Lazy startup | RFC-002 Event Modules | Event declaration | Lazy instantiation |
|
||||
|
||||
Core stays stdlib-only. Consumers bring implementations via WithService.
|
||||
|
||||
## What NOT to Do
|
||||
|
||||
- Don't add dependencies to core/go (it's stdlib + go-io + go-log only)
|
||||
- Don't use `os/exec` — go-process is the only allowed user (P9-1: core/go itself violates this in app.go — fix it)
|
||||
- Don't use `unsafe.Pointer` on Core types — add legitimate APIs instead
|
||||
- Don't call `os.Exit` inside Core — return errors, let main() exit
|
||||
- Don't create global mutable state — use Core's Registry
|
||||
- Don't auto-discover via reflect — use explicit registration (HandleIPCEvents is the last magic method)
|
||||
|
||||
## AX-7 Status
|
||||
|
||||
- core/agent: 92% (840 tests, 79.9% coverage)
|
||||
- core/go: **100%** (457 tests, 84.4% coverage) — renamed 2026-03-25
|
||||
- All 457 tests have `TestFile_Function_{Good,Bad,Ugly}` naming
|
||||
|
||||
## What Was Shipped (2026-03-25 session)
|
||||
|
||||
Plans 1-5 complete for core/go scope. 457 tests, 84.4% coverage, 100% AX-7 naming.
|
||||
|
||||
- P4-3 + P7-3: ACTION broadcast — calls all handlers, panic recovery per handler
|
||||
- P7-2 + P7-4: `RunE()` with `defer ServiceShutdown`, `Run()` delegates
|
||||
- P3-1: Startable/Stoppable return `Result` (breaking, clean — no V2)
|
||||
- P9-1: Zero `os/exec` in core/go — `App.Find()` rewritten with `os.Stat` + PATH
|
||||
- P11-2: `Fs.NewUnrestricted()` — legitimate door replaces unsafe.Pointer
|
||||
- P4-10: `Fs.WriteAtomic()` — write-to-temp-then-rename
|
||||
- I3: `Embed()` removed, I15: `New()` comment fixed
|
||||
- I9: `CommandLifecycle` interface removed → `Command.Managed` string field
|
||||
- Section 17: `c.Process()` primitive (Action sugar, no deps)
|
||||
- Section 18: `c.Action("name")` + `ActionDef` + `c.Task("name", TaskDef{Steps})` composition
|
||||
- Section 20: `Registry[T]` + all 5 migrations (services, commands, drive, data, lock)
|
||||
- Section 20.4: `c.RegistryOf("name")` cross-cutting accessor
|
||||
- Plan 5: `core.ID()`, `ValidateName()`, `SanitisePath()`
|
||||
|
||||
## Session Context That Won't Be In Memory
|
||||
|
||||
- The ACTION cascade (P6-1) — core/go now has TaskDef for the fix, core/agent needs to wire it
|
||||
- status.json has 51 unprotected read-modify-write sites (P4-9) — `WriteAtomic` exists, core/agent needs to use it
|
||||
- `core.Env("DIR_HOME")` is cached at init — `t.Setenv` doesn't override it (P2-5) — use `CORE_WORKSPACE` in tests
|
||||
- go-process `NewService` returns `(any, error)` not `core.Result` — needs v0.7.0 update (go-process repo)
|
||||
- Multiple Core instances share global state (assetGroups, systemInfo, defaultLog)
|
||||
Loading…
Add table
Reference in a new issue