CleanPath existed all along (path.go:118) — earlier grep had a stray
quote that hid it. Example now demonstrates actual behaviour:
redundant separator removal and .. resolution.
Removed duplicate CleanPath_Good test that conflicted with existing.
546 tests, all pass.
Co-Authored-By: Virgil <virgil@lethean.io>
33 new examples across 8 dedicated files. Removed phantom CleanPath
(in RFC spec but never implemented — spec drift caught by examples).
545 tests total, 84.8% coverage. Every major primitive has compilable
examples that serve as test, documentation seed, and godoc content.
Co-Authored-By: Virgil <virgil@lethean.io>
core/go was violating its own RFC-025 Principle 2: every exported
function must have a comment showing HOW with real values.
37 functions had no comments — mostly one-liner accessors on Core,
Config, ServiceRuntime, IPC, and Options. Now every exported function
in every source file has a usage-example comment.
AX Principle 2 compliance: 0/37 → 37/37 (100%).
Co-Authored-By: Virgil <virgil@lethean.io>
Removed ~200 lines of progress-report content:
- Known Issues (16 resolved items — history, not spec)
- Findings Summary (108 findings table — discovery report)
- Synthesis (5 root causes narrative — architectural history)
- What v0.8.0 Requires (Done checklist — project management)
- What Blocks / What Does NOT Block (status tracking)
All preserved in git history. The RFC now describes what v0.8.0 IS,
not how we built it.
4193 → 1278 lines (70% reduction from original).
Co-Authored-By: Virgil <virgil@lethean.io>
All PERFORM/RegisterTask/type Task any references replaced with
named Action patterns. Every code example now uses the v0.8.0 API.
- docs/messaging.md: full rewrite — ACTION/QUERY + named Actions + Task
- docs/primitives.md: full rewrite — added Action, Task, Registry, Entitlement
- docs/index.md: full rewrite — updated surface table, quick example, doc links
- docs/getting-started.md: 2 RegisterTask+PERFORM blocks → Action pattern
- docs/testing.md: 1 RegisterTask+PERFORM block → Action pattern
An agent reading any doc file now gets compilable code.
Co-Authored-By: Virgil <virgil@lethean.io>
README showed core.New(core.Options{...}) (deleted pattern),
RegisterTask (removed), PERFORM (removed), type Task any (removed).
Quick example would not compile.
Also found 6 docs/ files with same stale patterns — tracked for
next session (getting-started, index, messaging, primitives, testing).
Co-Authored-By: Virgil <virgil@lethean.io>
CLAUDE.md was telling agents NOT to use WithService (the actual API).
Tests path was wrong (tests/ vs root *_test.go). core.New() example
showed deleted DTO pattern. PERFORM listed as current. Missing 6
subsystem accessors.
llm.txt had pkg/core/ path (doesn't exist), PERFORM reference,
missing 8 key types.
Both now match v0.8.0 implementation.
Co-Authored-By: Virgil <virgil@lethean.io>
- AX principle 5: PERFORM removed, now ACTION/QUERY + named Actions
- Findings table: TaskDef → Task
- Root Cause 2: removed stale v0.8.0 framing, Entitlement→Entitled method name
- Root Cause 3: TaskDef→Task, linked to core/agent RFC not deleted plan file
- Root Cause 4: Run()→RunE() in code example
- Root Cause 5: Updated to show resolved items vs open items
- Fixed triple blank line, cleaned whitespace
Co-Authored-By: Virgil <virgil@lethean.io>
Section 17 was 240 lines of design spec with old signatures.
Now 30 lines matching the actual Process primitive.
Consumer detail (ProcessHandle, IPC messages) lives in go-process/docs/RFC.md.
Section 18 stale items identified for next pass.
Co-Authored-By: Virgil <virgil@lethean.io>
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>
All 16 Known Issues replaced with resolved summary table.
All 12 passes (108 findings) replaced with findings summary table.
Full discovery detail preserved in git history.
What remains: 21 feature sections (the API contract), Design Philosophy,
AX Principles, root cause synthesis, consumer RFCs, versioning.
No "Planned" tags. No unresolved findings. No v0.9.0 deferrals.
Co-Authored-By: Virgil <virgil@lethean.io>
v0.8.0 IS the production release. There is no v0.9.0 to defer to.
All 8 references to v0.9.0 updated:
- P11-1: Entitlements are Section 21, v0.8.0 scope
- P13-5: Async startup is future enhancement, not version-gated
- P13-6: Registry Seal/Lock enables hot-reload patterns
- Root Cause 2+5: Section 21 designed, implementation pending
- Versioning: v0.8.0 = production, v0.8.* patches = quality metric
- Section 21 header: v0.8.0 boundary model
- Config/Data/Fs gating: same pattern, more integration points
Co-Authored-By: Virgil <virgil@lethean.io>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>