Commit graph

1116 commits

Author SHA1 Message Date
Snider
1d174a93ce docs(rfc): update RFC.md — consumer RFCs, versioning, v0.8.0 status
- Added Consumer RFCs section pointing to go-process and core/agent RFCs
- Updated versioning to reflect v0.8.0 as current (Plans 1-5 done)
- Updated v0.8.0 requirements checklist — most items done
- Cross-referenced P6-1 fix to core/agent migration plan
- Updated Root Cause 2 to reference Section 21 (Entitlement)

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 16:01:59 +00:00
Snider
028ec84c5e fix: remove type Task any — untyped IPC replaced by named Actions
The old IPC Task system passed `any` through TaskHandler and
PerformAsync. Now that named Actions exist with typed signatures
(ActionHandler func(context.Context, Options) Result), the untyped
layer is dead weight.

Changes:
- type Task any removed (was in contract.go)
- type Task struct is now the composed sequence (action.go)
- PerformAsync takes (action string, opts Options) not (t Task)
- TaskHandler type removed — use c.Action("name", handler)
- RegisterTask removed — use c.Action("name", handler)
- PERFORM sugar removed — use c.Action("name").Run()
- ActionTaskStarted/Progress/Completed carry typed fields
  (Action string, Options, Result) not any

ActionDef → Action rename also in this commit (same principle:
DTOs don't have Run() methods).

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 15:57:36 +00:00
Snider
c5c16a7a21 feat(rfc): Section 21 — Entitlement permission primitive design
Bridges RFC-004 (SaaS feature gating), RFC-005 (Commerce Matrix
hierarchy), and Core Actions into one permission primitive.

Key design: Entitlement struct carries Allowed/Unlimited/Limit/Used/
Remaining/Reason — maps 1:1 to both PHP implementations.
EntitlementChecker is a function registered by consumer packages.
Default is permissive (trusted conclave). Enforcement in Action.Run().

Implementation plan: ~100 lines, zero deps, 11 steps.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 15:23:00 +00:00
Snider
2dff772a40 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
Snider
0704a7a65b feat: session continuity plans — RFC.plan.md + plan.1 + plan.2
RFC.plan.md: master context document for future sessions
  - 5 root causes, 3 critical bugs, key decisions, what NOT to do
  - Session context that won't survive compact
  - Cross-references to existing RFCs that solve problems

RFC.plan.1.md: first session priorities
  - Fix 3 critical bugs (one-line changes)
  - AX-7 rename for core/go
  - Start Registry[T]

RFC.plan.2.md: subsequent session goals
  - Registry + migration
  - Action system
  - core/agent cascade fix
  - c.Process() + go-process v0.7.0

Future sessions: read RFC.plan.md first, then the numbered plan
for that session's scope.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 13:35:14 +00:00
Snider
9cd83daaae feat: 6 implementation plans for v0.8.0
Plan 1: Critical bug fixes (v0.7.1, zero breakage)
  - ACTION chain, panic recovery, defer shutdown, stale code removal

Plan 2: Registry[T] primitive (Section 20)
  - Foundation brick, migration of 5 internal registries

Plan 3: Action/Task system (Section 18)
  - Named callables, task composition, cascade fix

Plan 4: c.Process() primitive (Section 17)
  - go-process v0.7.0, proc.go migration, atomic writes

Plan 5: Missing primitives + AX-7
  - core.ID(), ValidateName, WriteAtomic, RunE(), test coverage

Plan 6: Ecosystem sweep (Phase 3)
  - 44 repos in 5 batches, Codex dispatch with RFC as spec

Each plan lists: files to change, code examples, what it resolves,
dependencies on other plans, and migration strategy.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 13:31:11 +00:00
Snider
f7e91f0970 feat(rfc): cross-reference existing RFCs to open findings
7 existing RFCs solve open problems — Core provides the interface
(stdlib only), consumer packages bring the implementation:

- RFC-002 → lazy startup (P13-5)
- RFC-004 → entitlements/permissions (P11-1)
- RFC-012 → secret storage via SMSG (P11-3)
- RFC-009 → validation via Sigil transforms (P9-6)
- RFC-014 → OS-level isolation via TIM containers (P11-2)
- RFC-013 → in-memory fs via DataNode (P13-2)
- RFC-003 → config channels for surface context (P2-8)

Pattern: core/go defines interface + default. Consumer registers
implementation. c.Secret() defaults to os.Getenv. go-smsg registers
SMSG decryptor. No deps injected into core/go.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 13:26:32 +00:00
Snider
c6403853f1 feat(rfc): Root Cause 2 resolved — Entitlements not CoreView
The boundary model already exists in CorePHP:
- RFC-004 (Entitlements): "can this workspace do this action?"
- RFC-003 (Config Channels): "what settings apply in this context?"

Registration = capability (action exists)
Entitlement = permission (action is allowed)

Port RFC-004 to CoreGO for v0.9.0 instead of inventing CoreView.
The concept is designed, implemented, and production-tested in PHP.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 13:23:19 +00:00
Snider
93c21cfd53 feat(rfc): Synthesis — 108 findings reduce to 5 root causes
With full session context (tests + refactoring + 13 passes + revisit),
the 108 findings cluster into 5 root causes:

1. Type erasure via Result{any} (16 findings)
   → mitigation: typed methods + AX-7 tests, not fixable without abandoning Result

2. No internal boundaries (14 findings)
   → by design for v0.8.0 (trusted conclave), CoreView for v0.9.0

3. Synchronous everything (12 findings)
   → Action/Task system is the fix, PERFORM replaces ACTION for request/response

4. No recovery path (10 findings)
   → one fix: defer ServiceShutdown + return error from Run() + panic recovery

5. Missing primitives (8 findings)
   → ID, Validate, Health needed. JSON/Time are judgment calls.

60 findings clustered, 48 remaining (specific/local).
Priority: recovery > sync > primitives > types > boundaries.

This is the definitive analysis. 3,800+ lines.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 13:21:11 +00:00
Snider
21c1a3e92b feat(rfc): Pass 4 Revisited — 4 deeper concurrency findings
Re-examined concurrency with full context from 13 passes.
Found races that P4 couldn't see:

P4-9:  status.json 51 read-modify-write sites with NO locking.
       spawnAgent goroutine vs status MCP tool vs drainOne vs shutdown
       — classic TOCTOU, status corruption in production.

P4-10: Fs.Write uses os.WriteFile (truncate+write, not atomic).
       Concurrent reader sees empty file during write window.
       Root cause of P4-9. Need WriteAtomic (temp+rename).

P4-11: Config map values shared by reference after Set.
       Goroutine mutates map retrieved from Config — unprotected.

P4-12: Global logger race — Default() may return nil before Core sets it.

P4-9 is likely causing real status corruption in production.
The 51 unprotected read-modify-write sites on status.json
explain workspace status inconsistencies.

108 findings total, 3,700+ lines.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 13:16:05 +00:00
Snider
ef548d07bc feat(rfc): Pass Thirteen — hidden assumptions, final review
P13-1: Single Core per process assumed — 5 globals break with multiple
P13-2: All services are Go — no plugin/remote service boundary
P13-3: Go only — but CorePHP and CoreTS exist (need concept mapping)
P13-4: Unix only — syscall.Kill, PID files, no Windows support
P13-5: Synchronous startup — 30s DB connect blocks everything
P13-6: Static conclave — no hot-loading, no runtime service addition
P13-7: IPC is ephemeral — no persistence, replay, or dead letter
P13-8: Single-perspective review — RFC needs adversarial input

Meta-finding: the RFC is the best single-session analysis possible.
It's not complete. Pass Fourteen starts next session.

FINAL TALLY:
- 13 passes
- 104 findings (3 critical, 12 high, ~50 medium, ~40 low)
- 20 spec sections + 4 planned primitives
- 3-phase migration plan for 44 repos
- 3,600+ lines

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 13:10:34 +00:00
Snider
1ef8846f29 feat(rfc): Pass Twelve — migration risk across 44 repos
44 repos import core/go. Breaking changes categorised into 3 phases:

Phase 1 (zero breakage, ship now):
- New accessors (Process, Action, API, Registry)
- Critical bug fixes (ACTION chain, panic recovery, cleanup)
- Remove dead code (Embed)

Phase 2 (internal refactor):
- task.go → action.go, move RegisterAction
- Remove os/exec from app.go
- Add Fs.NewUnrestricted to replace unsafe.Pointer hacks

Phase 3 (ecosystem sweep, 44 repos):
- Startable returns Result (26 files)
- Run() → RunE() (15 files)
- CommandLifecycle → Managed

Key insight: 3 critical bugs are Phase 1 — can ship as v0.7.1 tomorrow.
Biggest risk (Startable change) can use V2 interface for backwards compat.

Twelve passes, 96 findings, 3,500+ lines.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 13:08:19 +00:00
Snider
caa1dea83d feat(rfc): Pass Eleven — security model, God Mode, sandbox bypass
CRITICAL: P11-2 — The Fs sandbox (P2-2: "correctly unexported") is
bypassed by core/agent using unsafe.Pointer to overwrite Fs.root.
The security boundary exists in theory but is broken in practice.

P11-1: Every service has God Mode — full access to everything
P11-2: Fs.root bypassed via unsafe.Pointer (paths.go, detect.go)
P11-3: core.Env() exposes all secrets (API keys, tokens)
P11-4: ACTION event spoofing — fake AgentCompleted triggers pipeline
P11-5: RegisterAction installs spy handler (sees all IPC)
P11-6: No audit trail — no logging of security-relevant ops
P11-7: ServiceLock has no authentication (anyone can lock)
P11-8: No revocation — services join but can never be ejected

The conclave trust model: all first-party, no isolation.
Acceptable for v0.8.0 (trusted code). Needs capability model
for v0.9.0+ (plugins, third-party services).

Eleven passes, 88 findings, 3,400+ lines.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 13:05:47 +00:00
Snider
20f3ee30b8 feat(rfc): Pass Ten — the spec auditing itself, 80 findings total
P10-1: S17 and S18 contradict on Process return type
P10-2: S17 uses ACTION for request/response — should be PERFORM
P10-3: Subsystem count wrong (22 methods, not 14 subsystems)
P10-4: Four API patterns on one struct — undocumented categories
P10-5: Registry resolves old issues — needs cross-reference table
P10-6: Design Philosophy before reasoning — correct for RFC format
P10-7: v0.8.0 checklist missing 56 findings — added severity classification
       (3 critical, 12 high, 25 medium, 16 low)
P10-8: No section numbers for passes — findings need index

Meta-finding: the spec can now audit itself. Cross-referencing sections
reveals contradictions that weren't visible when each section was written
independently. The RFC is detailed enough to be self-checking.

Ten passes, 80 findings, 3,300+ lines.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 13:02:41 +00:00
Snider
a06af7b6ad feat(rfc): Pass Nine — what's missing, what shouldn't be there
P9-1: core/go imports os/exec in app.go — violates its own rule
P9-2: reflect used for service name — magic, fragile on pkg rename
P9-3: No JSON primitive — every consumer imports encoding/json
P9-4: No time primitive — 3 different timestamp formats
P9-5: No ID generation — 3 different patterns (rand, counter, fmt)
P9-6: No validation primitive — path traversal check copy-pasted 3x
P9-7: Error codes exist but nobody uses them
P9-8: No health/observability primitive — go-process has it per-daemon only

Key finding: core/go imports os/exec (P9-1), violating the rule it
established in Section 17. App.Find() uses exec.LookPath — a process
concern that belongs in go-process.

Missing primitives: ID generation, validation, health checks.
Judgment calls: JSON wrapping (maybe noise), time formatting (convention).

Nine passes, 72 findings, 3,100+ lines.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 12:59:45 +00:00
Snider
c847b5d274 feat(rfc): Pass Eight — type safety analysis, 50 hidden panic sites
CRITICAL: 79% of type assertions on Result.Value are bare (no comma-ok).
50 panic sites in core/go, ~100 in core/agent. Every r.Value.(string)
is a deferred panic that (string, error) would catch at compile time.

P8-1: 50/63 assertions bare — panic on wrong type
P8-2: Result is one type for everything — no compile-time safety
P8-3: Message/Query/Task all 'any' — no type routing
P8-4: Option.Value is 'any' — config becomes untyped bag
P8-5: ServiceFor returns (T,bool) not Result — Go generics limitation
P8-6: Fs validatePath returns Result then callers bare-assert string
P8-7: HandleIPCEvents wrong signature silently fails to register
P8-8: The Guardrail Paradox — Core trades compile-time safety for LOC

The fundamental tension: Result reduces LOC but every type assertion
is a deferred panic. Resolution: AX-7 Ugly tests + typed convenience
methods + accept that Result is a runtime contract.

Eight passes, 64 findings, RFC at 2,930+ lines.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 12:56:29 +00:00
Snider
630f1d5d6b feat(rfc): Pass Seven — failure modes, no recovery on most paths
CRITICAL findings:
P7-1: New() returns half-built Core on option failure (no error return)
P7-2: ServiceStartup fails → os.Exit(1) → no cleanup → resource leak
P7-3: ACTION handlers have NO panic recovery (PerformAsync does)
P7-4: Run() has no defer — panic skips ServiceShutdown
P7-5: os.Exit(1) bypasses ALL defers — even if we add them

Additional:
P7-6: Shutdown context timeout stops remaining service cleanup
P7-7: SafeGo exists but nobody uses it — the safety primitive is unwired
P7-8: No circuit breaker — broken handlers called forever

The error path through Core is: log and crash. No rollback, no cleanup,
no recovery. Every failure mode ends in resource leaks or silent state
corruption. The fix is: defer shutdown always, wrap handlers in recover,
stop calling os.Exit from inside Core.

Seven passes, 56 findings, 2,760+ lines.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 12:52:42 +00:00
Snider
f23e4d2be5 feat(rfc): Pass Six — cascade analysis reveals synchronous pipeline blocking
CRITICAL: P6-1 — The entire agent completion pipeline (QA → PR → Verify
→ Merge → Ingest → Poke) runs synchronously nested inside ACTION dispatch.
A slow Forge API call blocks the queue drainer for minutes. This explains
the observed "agents complete but queue doesn't drain" behaviour.

Resolution: pipeline becomes a Task (Section 18), not nested ACTIONs.

P6-2: O(handlers × messages) fanout — every handler checks every message
P6-3: No dispatch context — can't trace nested cascades
P6-4: Monitor half-migrated (ChannelNotifier + ACTION coexist)
P6-5: Three patterns for service-needs-Core (field, ServiceRuntime, param)
P6-6: Message types untyped — any code can emit any message
P6-7: Aggregator pattern (MCP) has no formal Registry support
P6-8: Shutdown order can kill processes before services finish

Six passes, 48 findings. RFC at 2,600+ lines.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 12:49:57 +00:00
Snider
2167f0c6ab feat(rfc): Pass Five — 8 consumer experience findings
P5-1: ServiceRuntime not used by core/agent — two registration camps exist
P5-2: Register returns Result, OnStartup returns error — consumer confusion
P5-3: No service dependency declaration — implicit order, non-deterministic start
P5-4: HandleIPCEvents auto-discovered via reflect — magic method name
P5-5: Commands registered during OnStartup — invisible timing dependency
P5-6: No service discovery by interface/capability — only lookup by name
P5-7: Factory can see but can't safely USE other services
P5-8: MCP aggregator pattern undocumented — cross-cutting service reads all Actions

Key finding: two camps exist (manual .core vs ServiceRuntime). Both work,
neither documented. HandleIPCEvents is magic — anti-AX.

RFC now 2,436 lines. Five passes, 40 findings.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 12:46:29 +00:00
Snider
6709b0bb1a feat(rfc): Pass Four — 8 concurrency and performance findings
P4-1: ServiceStartup order non-deterministic (map iteration)
P4-2: ACTION dispatch synchronous and blocking
P4-3: ACTION !OK stops chain (wrong for broadcast)
P4-4: IPC clone-and-iterate safe but undocumented
P4-5: PerformAsync has no backpressure (unlimited goroutines)
P4-6: ConfigVar.Set() has no lock (data race)
P4-7: PerformAsync shutdown TOCTOU race
P4-8: Named lock "srv" shared across all service ops

Key finding: ACTION stopping on !OK is a bug for broadcast semantics.
Registry[T] resolves P4-1 (insertion order) and P4-8 (per-registry locks).

RFC now 2,269 lines. Four passes, 32 findings total.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 12:43:16 +00:00
Snider
ecd27e3cc9 feat(rfc): Pass Three — 8 spec contradictions found
P3-1: Startable/Stoppable return error, not Result — change to Result
P3-2: Process returns (string,error), Action returns Result — unify on Result
P3-3: Three getter patterns (Result, typed, tuple) — document the two real ones
P3-4: Dual-purpose methods anti-AX — keep as sugar, Registry has explicit verbs
P3-5: error leaks despite Result — accept at Go stdlib boundary, Result at Core
P3-6: Data has overlapping APIs — split mount management from file access
P3-7: Action has no error propagation — inherit PerformAsync's panic recovery
P3-8: Registry.Lock() one-way door — add Seal() for hot-reload (update yes, new no)

RFC now at 2,194 lines. Three passes complete:
- Pass 1: 16 known issues (all resolved)
- Pass 2: 8 architectural findings
- Pass 3: 8 spec contradictions

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 12:39:27 +00:00
Snider
42fc6fa931 feat(rfc): Pass Two — 8 architectural findings
P2-1: Core struct fully unexported — export bricks, hide safety
P2-2: Fs.root correctly unexported — security boundaries are the exception
P2-3: Config.Settings untyped map[string]any — needs ConfigVar[T] or schema
P2-4: Global assetGroups outside conclave — bootstrap problem, document boundary
P2-5: SysInfo frozen at init() — cached values override test env (known bug)
P2-6: ErrorPanic.onCrash unexported — monitoring can't wire crash handlers
P2-7: Data.mounts unexported — should embed Registry[*Embed]
P2-8: Logging timing gap — global logger unconfigured until New() completes

New rule: export the bricks, hide the safety mechanisms.
Security boundaries (Fs.root) are the ONE exception to Lego Bricks.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 12:35:04 +00:00
Snider
881c8f2ae8 feat(rfc): versioning model + v0.8.0 requirements checklist
Release model:
- v0.7.x = current stable + mechanical fixes
- v0.8.0 = production: all issues resolved, Sections 17-20 implemented
- v0.8.x patches = process gaps (each one = spec missed something)
- Patch count per release IS the quality metric

v0.8.0 checklist: 16 issues, 4 new sections, AX-7 100%, zero os/exec,
AGENTS.md everywhere. Non-blockers documented (cli, Borg, PHP/TS).

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 12:30:03 +00:00
Snider
59dcbc2a31 feat(rfc): resolve ALL 16 known issues
Pass one complete. All 16 issues now have dispositions:

Resolved (design decisions):
- 1: Naming convention — CamelCase=primitive, UPPERCASE=sugar
- 5: RegisterAction location → solved by Issue 16 split
- 6: serviceRegistry → exported via Registry[T] (Section 20)
- 8: NewRuntime → NOT legacy, it's GUI bridge. Update factory signature.
- 9: CommandLifecycle → three-layer CLI (Cli/cli/go-process)
- 10: Array[T] → guardrail primitive, keep
- 11: ConfigVar[T] → promote to documented primitive
- 12: Ipc → owns Action registry, reads from Registry[T]
- 16: task.go → splits into ipc.go + action.go

Resolved (mechanical fixes):
- 2: MustServiceFor → keep, document startup-only usage
- 3: Embed() → remove (dead code)
- 4: Logging → document boundary (global=bootstrap, Core=runtime)
- 13: Lock allocation → use Registry[T], cache Lock struct
- 14: Startables/Stoppables → return []*Service directly
- 15: Stale comment → fix to match *Core return

Blocked (planned):
- 7: c.Process() → spec'd in Section 17, needs go-process v0.7.0

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 12:25:59 +00:00
Snider
b130309c3d feat(rfc): resolve Issues 10+11 — Array[T] and ConfigVar[T] as guardrail primitives
Issue 10 (Resolved): Array[T] is a guardrail primitive, not speculative.
Same role as string helpers — forces single codepath, model-proof,
scannable. Ordered counterpart to Registry[T].

Issue 11 (Resolved): ConfigVar[T] promoted to documented primitive.
Solves "was this explicitly set?" tracking for layered config.
Typed counterpart to Option (which is any-typed).

Both follow the guardrail pattern: the primitive exists not because
Go can't do it, but because weaker models (Gemini, Codex) will
reinvent it inline every time — badly. One import, one pattern.

Added primitive taxonomy table showing the full picture:
strings→core.Contains, paths→core.JoinPath, errors→core.E,
maps→Registry[T], slices→Array[T], config→ConfigVar[T]

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 12:22:10 +00:00
Snider
79fd8c4760 feat(rfc): Section 20 — c.Registry() universal collection primitive
Registry[T] is the brick that all named collections build on:
- map[string]T + mutex + optional locking
- Set/Get/Has/Names/List/Each/Lock/Delete
- c.Registry("services"), c.Registry("actions"), c.Registry("drives")

Resolves Issues 6 + 12:
- serviceRegistry/commandRegistry become exported, embed Registry[T]
- IPC is safe to expose — reads from registry, doesn't own write path
- Registration goes through c.Action(), locking through c.Registry().Lock()

Typed accessors (c.Service, c.Action, c.Drive) are sugar over
c.Registry(name).Get(). Universal query layer on top.

Replaces 5 separate map+mutex+lock implementations with one primitive.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 12:13:19 +00:00
Snider
5211d97d66 feat(rfc): resolve Issue 16 — task.go splits into ipc.go + action.go
task.go has 6 functions mixing registration and execution:
- RegisterAction/RegisterActions/RegisterTask → ipc.go (registry)
- Perform/PerformAsync/Progress → action.go (execution)

contract.go message types (ActionTaskStarted etc) stay — naming
already correct per Issue 1 convention.

Added AX principles 8-9:
8. Naming encodes architecture (CamelCase=brick, UPPERCASE=sugar)
9. File = concern (one file, one job)

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 12:01:39 +00:00
Snider
68b7530072 feat(rfc): resolve Issues 1+12 — naming convention + IPC as registry owner
Issue 1 (Resolved): UPPERCASE vs CamelCase naming convention:
- CamelCase = primitive (the brick): c.Action(), c.Service(), c.Config()
- UPPERCASE = consumer convenience (sugar): c.ACTION(), c.QUERY(), c.PERFORM()
- Current code has this backwards — ACTION is mapped to raw dispatch

Issue 12 (Resolved): IPC owns the Action registry:
- c.IPC() = owns the data (registry, handlers, task flows)
- c.Action() = primitive API for register/invoke/inspect
- c.ACTION() = convenience shortcut for broadcast
- Three layers, one registry — same pattern as Drive/API

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 11:58:27 +00:00
Snider
7a9f9dfbd1 feat(rfc): Section 19 — c.API() remote stream primitive
HTTP/WebSocket/SSE/MCP are all streams. The transport is irrelevant.

- c.IPC() = local conclave (in-process)
- c.API() = remote streams (cross-machine)
- c.Drive() = connection config (WHERE), c.API() = transport (HOW)
- Protocol handlers register like Actions (permission by registration)
- Remote Action dispatch: "charon:agentic.status" → transparent cross-machine
- Maps current manual HTTP/SSE/MCP code in core/agent to single-line calls
- Full 13-subsystem map documented

Proved by: PHP5 stream lib that replaced curl when certs were broken.
Same principle — depend on stream primitive, not transport library.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 11:46:36 +00:00
Snider
773e9ee015 feat(rfc): Issue 9 — three-layer CLI architecture (Cli/cli/go-process)
CommandLifecycle replaced with Managed field on Command struct.
Three layers read the same declaration:
- core.Cli() — primitive: basic parsing, runs Action
- core/cli — extension: rich help, completion, daemon management UI
- go-process — extension: PID, health, signals, registry

Command struct is data not behaviour. Services declare,
packages consume. Lifecycle verbs become process Actions.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 11:40:35 +00:00
Snider
8f7a1223ef feat(rfc): Known Issues 9-16 — recovered ADHD brain dumps
9.  CommandLifecycle — daemon skeleton, never wired to go-process
10. Array[T] — generic collection used nowhere, speculative
11. ConfigVar[T] — typed config var, only used internally
12. Ipc data-only struct — no methods, misleading accessor
13. Lock() allocates wrapper struct on every call
14. Startables/Stoppables return Result instead of []*Service
15. contract.go comment says New() returns Result (stale)
16. task.go mixes execution + registration concerns

Each issue has: what it is, what the intent was, how it relates
to Sections 17-18, and a proposed resolution. These are the
sliding-context ideas saved in quick implementations that need
the care they deserve.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 11:33:16 +00:00
Snider
76714fa292 feat(rfc): Section 18 — Action and Task execution primitives
Action = named, registered callable. The atomic unit of work.
Task = composition of Actions (chain, parallel, conditional, scheduled).

Key design:
- c.Action("name", handler) registers, c.Action("name").Run() invokes
- Services register actions during OnStartup (same as commands)
- Namespace IS capability map (process.*, agentic.*, brain.*)
- Registration IS permission — no action = no capability
- Current IPC verbs (ACTION/QUERY/PERFORM) become invocation modes
- c.Process() is sugar over process.* actions
- Tasks compose Actions into flows (sequential, parallel, conditional, cron)
- Action registry is queryable — agents inspect capabilities before using

Sections: 18.1-18.10 covering concept, API, registration, permission,
task composition (chain/parallel/conditional/scheduled), IPC mapping,
process integration, and registry inspection.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 11:24:44 +00:00
Snider
ec17e3da07 feat(rfc): Section 17 — c.Process() primitive spec
Full design spec for process management as Core subsystem:
- 17.1: Primitive concept (interface in core/go, impl in go-process)
- 17.2: Process struct + accessor
- 17.3: Sync execution (Run, RunIn, RunWithEnv)
- 17.4: Async execution (Start + ProcessOptions)
- 17.5: ProcessHandle (IsRunning, Kill, Done, Wait, Info)
- 17.6: IPC messages (ProcessRun/Start/Kill + Started/Output/Exited/Killed)
- 17.7: Permission by registration (no handler = no capability)
- 17.8: Per-package convenience helpers
- 17.9: go-process implementation (IPC handler registration)
- 17.10: Migration path (current → target)

Key insight: registration IS permission. Sandboxed Core without
go-process cannot execute external commands. No config, no tokens,
no capability files — the service exists or it doesn't.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 11:17:48 +00:00
Snider
f65884075b feat(rfc): add Design Philosophy + Known Issues to API spec
Design Philosophy:
- Core is Lego Bricks — export primitives, reduce downstream LOC
- Export rules: struct fields yes, mutexes no
- Why core/go is minimal (stdlib-only, layers import downward)

Known Issues (8):
1. Dual IPC naming (ACTION vs Action)
2. MustServiceFor uses panic (contradicts Result pattern)
3. Embed() legacy accessor (dead code)
4. Package-level vs Core-level logging (document boundary)
5. RegisterAction in wrong file (task.go vs ipc.go)
6. serviceRegistry unexported (should be Lego brick)
7. No c.Process() accessor (planned)
8. NewRuntime/NewWithFactories legacy (verify usage)

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 11:13:35 +00:00
Snider
1455764e3c feat: add docs/RFC.md — CoreGO API contract specification
Full API spec covering all 16 subsystems: Core container, primitives
(Option/Options/Result), service system, IPC (ACTION/QUERY/PERFORM),
Config, Data, Drive, Fs, CLI, error handling, logging, strings, paths,
utils, locks, ServiceRuntime.

An agent can write a Core service from this document alone.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 11:01:58 +00:00
Snider
e7c3b3a69c feat: add llm.txt — agent entry point for CoreGO framework
Standard llm.txt with package layout, key types, service pattern.
Points to CLAUDE.md and docs/RFC.md for full specs.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 10:52:55 +00:00
f6ed40dfdc Merge pull request 'test: _Bad/_Ugly tests + per-Core lock isolation' (#37) from feat/test-coverage into dev 2026-03-24 22:46:43 +00:00
Snider
d982193ed3 test: add _Bad/_Ugly tests + fix per-Core lock isolation
Tests: Run, RegisterService, ServiceFor, MustServiceFor _Bad/_Ugly variants.
Fix: Lock map is now per-Core instance, not package-level global.
This prevents deadlocks when multiple Core instances exist (e.g. tests).

Coverage: 82.4% → 83.6%

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:44:48 +00:00
5855a6136d Merge pull request 'fix: shutdown context + double IPC registration' (#36) from fix/codex-review-findings into dev 2026-03-24 22:28:42 +00:00
Snider
95076be4b3 fix: shutdown context, double IPC registration
- Run() uses context.Background() for shutdown (c.context is cancelled)
- Stoppable closure uses context.Background() for OnShutdown
- WithService delegates HandleIPCEvents to RegisterService only

Fixes Codex review findings 1, 2, 3.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:28:15 +00:00
f72c5782fd Merge pull request 'feat: restore functional option pattern for New()' (#28) from feat/service-options into dev 2026-03-24 22:09:19 +00:00
Snider
5362a9965c feat: New() returns *Core directly — no Result wrapper needed
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
af1cee244a feat: Core.Run() handles os.Exit on error
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
7608808bb0 feat: Core.Run() — ServiceStartup → Cli → ServiceShutdown lifecycle
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
7f4c4348c0 fix: Service() returns instance, ServiceFor uses type assertion directly
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
9c5cc6ea00 feat: New() constructors for Config, Fs + simplify contract.go init
Config.New() initialises ConfigOptions.
Fs.New(root) sets sandbox root.
ErrorLog uses Default() fallback — no explicit init needed.
contract.go uses constructors instead of struct literals.

All tests green.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
94e1f405fc fix: Result.New handles (value, error) pairs correctly + embed test fixes
Root cause: Result.New didn't mark single-value results as OK=true,
breaking Mount/ReadDir/fs helpers that used Result{}.New(value, err).

Also: data_test.go and embed_test.go updated for Options struct,
doc comments updated across data.go, drive.go, command.go, contract.go.

All tests green. Coverage 82.2%.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
ae4825426f wip: v0.3.3 parity — Tasks 1-7 complete, data/embed tests need fixing
WithService: full name discovery + IPC handler auto-registration via reflect
WithName: explicit service naming
RegisterService: Startable/Stoppable/HandleIPCEvents auto-discovery
MustServiceFor[T]: panics if not found
WithServiceLock: enable/apply split (v0.3.3 parity)
Cli: registered as service via CliRegister, accessed via ServiceFor

@TODO Codex: Fix data_test.go and embed_test.go — embed path resolution
after Options changed from []Option to struct. Mount paths need updating.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
2303c27df0 feat: MustServiceFor[T] + fix service names test for auto-registered cli
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
05d0a64b08 fix: WithServiceLock enables, New() applies after all opts — v0.3.3 parity
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00