feat(rfc-025): update AX spec to v0.8.0 — 3 new principles

Updated all code examples to match core/go v0.8.0 implementation:
- process.NewService → process.Register
- process.RunWithOptions → c.Process().RunIn()
- PERFORM removed from subsystem table
- Startable/Stoppable return Result
- Added Process, API, Action, Task, Entitled, RegistryOf to subsystem table

New principles from validated session patterns:
- Principle 8: RFC as Domain Load — load spec at session start
- Principle 9: Primitives as Quality Gates — imports are the lint rule
- Principle 10: Registration + Entitlement — two-layer permission model

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
user.email 2026-03-25 17:35:21 +00:00
parent fef469ecc9
commit 05c4fc0a78
2 changed files with 85 additions and 33 deletions

View file

@ -2,7 +2,7 @@
- **Status:** Draft
- **Authors:** Snider, Cladius
- **Date:** 2026-03-19
- **Date:** 2026-03-25
- **Applies to:** All Core ecosystem packages (CoreGO, CorePHP, CoreTS, core-agent)
## Abstract
@ -152,16 +152,16 @@ Every component in the ecosystem registers with Core and communicates through Co
```go
c := core.New(
core.WithOption("name", "core-agent"),
core.WithService(process.NewService(process.Options{})),
core.WithService(process.Register),
core.WithService(agentic.Register),
core.WithService(monitor.Register),
core.WithService(brain.Register),
core.WithService(mcp.Register),
)
c.Run()
c.Run() // or: if err := c.RunE(); err != nil { ... }
```
`core.New()` accepts functional options. `WithService` registers a service factory that receives `*Core` and returns `Result`. Services are auto-discovered: name from package path, lifecycle from `Startable`/`Stoppable` interfaces, IPC handlers from `HandleIPCEvents`.
`core.New()` returns `*Core`. `WithService` registers a factory `func(*Core) Result`. Services auto-discover: name from package path, lifecycle from `Startable`/`Stoppable` (return `Result`), IPC from `HandleIPCEvents`.
#### Service Registration Pattern
@ -177,19 +177,25 @@ func Register(c *core.Core) core.Result {
#### Core Subsystem Accessors
| Accessor | Analogy | Purpose |
|----------|---------|---------|
| `c.Options()` | argv | Input configuration used to create this Core |
| `c.App()` | — | Application metadata (name, version) |
| `c.Data()` | /mnt | Embedded assets mounted by packages |
| `c.Drive()` | /dev | Transport handles (API, MCP, SSH, VPN) |
| `c.Config()` | /etc | Configuration, settings, feature flags |
| `c.Fs()` | / | Local filesystem I/O (sandboxable) |
| `c.Error()` | — | Panic recovery (`ErrorPanic`) |
| `c.Log()` | — | Structured logging (`ErrorLog`) |
| `c.Cli()` | — | CLI command framework |
| `c.IPC()` | — | Message bus (ACTION, QUERY, PERFORM) |
| `c.I18n()` | — | Internationalisation |
| Accessor | Purpose |
|----------|---------|
| `c.Options()` | Input configuration |
| `c.App()` | Application metadata (name, version) |
| `c.Config()` | Runtime settings, feature flags |
| `c.Data()` | Embedded assets (Registry[*Embed]) |
| `c.Drive()` | Transport handles (Registry[*DriveHandle]) |
| `c.Fs()` | Filesystem I/O (sandboxable) |
| `c.Process()` | Managed execution (Action sugar) |
| `c.API()` | Remote streams (protocol handlers) |
| `c.Action(name)` | Named callable (register/invoke) |
| `c.Task(name)` | Composed Action sequence |
| `c.Entitled(name)` | Permission check |
| `c.RegistryOf(n)` | Cross-cutting registry queries |
| `c.Cli()` | CLI command framework |
| `c.IPC()` | Message bus (ACTION, QUERY) |
| `c.Log()` | Structured logging |
| `c.Error()` | Panic recovery |
| `c.I18n()` | Internationalisation |
#### Primitive Types
@ -230,16 +236,14 @@ c.RegisterAction(func(c *core.Core, msg core.Message) core.Result {
#### Process Execution — Use Core Primitives
All external command execution MUST go through `go-process`, not raw `os/exec`. This makes process execution testable, observable via IPC, and managed by Core's lifecycle.
All external command execution MUST go through `c.Process()`, not raw `os/exec`. This makes process execution testable, gatable by entitlements, and managed by Core's lifecycle.
```go
// AX-native: go-process via Core
out, err := process.RunWithOptions(ctx, process.RunOptions{
Command: "git", Args: []string{"log", "--oneline", "-20"},
Dir: repoDir,
})
// AX-native: Core Process primitive
r := c.Process().RunIn(ctx, repoDir, "git", "log", "--oneline", "-20")
if r.OK { output := r.Value.(string) }
// Not AX: raw exec.Command — untestable, no IPC, no lifecycle
// Not AX: raw exec.Command — untestable, no entitlement, no lifecycle
cmd := exec.Command("git", "log", "--oneline", "-20")
cmd.Dir = repoDir
out, err := cmd.Output()
@ -247,6 +251,8 @@ out, err := cmd.Output()
**Rule:** If a package imports `os/exec`, it is bypassing Core's process primitive. The only package that should import `os/exec` is `go-process` itself.
**Quality gate:** An agent reviewing a diff can mechanically check: does this import `os/exec`, `unsafe`, or `encoding/json` directly? If so, it bypassed a Core primitive.
#### What This Replaces
| Go Convention | Core AX | Why |
@ -255,7 +261,9 @@ out, err := cmd.Output()
| `func Must*(v) T` | `core.Result` | No hidden panics; errors flow through Result.OK |
| `func *For[T](c) T` | `c.Service("name")` | String lookup is greppable; generics require type context |
| `val, err :=` everywhere | Single return via `core.Result` | Intent not obscured by error handling |
| `exec.Command(...)` | `process.Run(ctx, cmd, args...)` | Testable, observable, lifecycle-managed |
| `exec.Command(...)` | `c.Process().Run(ctx, cmd, args...)` | Testable, gatable, lifecycle-managed |
| `map[string]*T + mutex` | `core.Registry[T]` | Thread-safe, ordered, lockable, queryable |
| untyped `any` dispatch | `c.Action("name").Run(ctx, opts)` | Named, typed, inspectable, entitlement-checked |
### 7. Tests as Behavioural Specification
@ -302,6 +310,42 @@ done
| Unnamed table tests | Explicit Good/Bad/Ugly | Categories are scannable without reading test body |
| Coverage % as metric | Missing categories as metric | 100% coverage with only Good tests is a false signal |
### 8. RFC as Domain Load
An agent's first action in a session should be loading the repo's RFC.md. The full spec in context produces zero-correction sessions — every decision aligns with the design because the design is loaded.
**Validated:** Loading core/go's RFC.md (42k tokens from a 500k token discovery session) at session start eliminated all course corrections. The spec is compressed domain knowledge that survives context compaction.
**Rule:** Every repo that has non-trivial architecture should have a `docs/RFC.md`. The RFC is not documentation for humans — it's a context document for agents. It should be loadable in one read and contain everything needed to make correct decisions.
### 9. Primitives as Quality Gates
Core primitives become mechanical code review rules. An agent reviewing a diff checks:
| Import | Violation | Use Instead |
|--------|-----------|-------------|
| `os/exec` | Bypasses Process primitive | `c.Process().Run()` |
| `unsafe` | Bypasses Fs sandbox | `Fs.NewUnrestricted()` |
| `encoding/json` | Bypasses Core serialisation | Core JSON helpers (future) |
| `fmt.Errorf` | Bypasses error primitive | `core.E()` |
| `errors.New` | Bypasses error primitive | `core.E()` |
| `log.*` | Bypasses logging | `core.Info()` / `c.Log()` |
**Rule:** If a diff introduces a disallowed import, it failed code review. The import list IS the quality gate. No subjective judgement needed — a weaker model can enforce this mechanically.
### 10. Registration IS Capability, Entitlement IS Permission
Two layers of permission, both declarative:
```
Registration = "this action EXISTS" → c.Action("process.run").Exists()
Entitlement = "this Core is ALLOWED" → c.Entitled("process.run").Allowed
```
A sandboxed Core has no `process.run` registered — the action doesn't exist. A SaaS Core has it registered but entitlement-gated — the action exists but the workspace may not be allowed to use it.
**Rule:** Never check permissions with `if` statements in business logic. Register capabilities as Actions. Gate them with Entitlements. The framework enforces both — `Action.Run()` checks both before executing.
## Applying AX to Existing Patterns
### File Structure
@ -371,9 +415,11 @@ c.Command("issue/get", core.Command{
### Process Execution
```go
// AX-native: go-process helpers, testable
// AX-native: Core Process primitive, testable with mock handler
func (s *PrepSubsystem) getGitLog(repoPath string) string {
return gitOutput(context.Background(), repoPath, "log", "--oneline", "-20")
r := s.core.Process().RunIn(context.Background(), repoPath, "git", "log", "--oneline", "-20")
if !r.OK { return "" }
return core.Trim(r.Value.(string))
}
// Not AX: raw exec.Command, untestable without real git
@ -381,9 +427,7 @@ func (s *PrepSubsystem) getGitLog(repoPath string) string {
cmd := exec.Command("git", "log", "--oneline", "-20")
cmd.Dir = repoPath
output, err := cmd.Output()
if err != nil {
return ""
}
if err != nil { return "" }
return strings.TrimSpace(string(output))
}
```
@ -415,6 +459,5 @@ Priority order:
## Changelog
- 2026-03-25: Major update — aligned all examples to v0.7.0 API (Option{Key,Value}, WithService, Result without generics). Added Principle 7 (Tests as Behavioural Specification). Added go-process rule to Principle 6. Updated all code examples to match actual implementation. Added command extraction and process execution patterns.
- 2026-03-20: Updated to match implementation — Option K/V atoms, Options as []Option, Data/Drive split, ErrorPanic/ErrorLog renames, subsystem table
- 2026-03-19: Initial draft
- 2026-03-25: v0.8.0 alignment — all examples match implemented API. Added Principles 8 (RFC as Domain Load), 9 (Primitives as Quality Gates), 10 (Registration + Entitlement). Updated subsystem table (Process, API, Action, Task, Entitled, RegistryOf). Process examples use `c.Process()` not old `process.RunWithOptions`. Removed PERFORM references.
- 2026-03-19: Initial draft — 7 principles

9
go.mod
View file

@ -11,6 +11,15 @@ require (
)
require (
dappco.re/go/core v0.5.0
dappco.re/go/core/api v0.2.0
dappco.re/go/core/i18n v0.2.0
dappco.re/go/core/io v0.2.0
dappco.re/go/core/log v0.1.0
dappco.re/go/core/process v0.3.0
dappco.re/go/core/scm v0.4.0
dappco.re/go/core/store v0.2.0
dappco.re/go/core/ws v0.3.0
forge.lthn.ai/core/go v0.3.3 // indirect
forge.lthn.ai/core/go-i18n v0.1.8 // indirect
forge.lthn.ai/core/go-inference v0.1.7 // indirect