Commit graph

22 commits

Author SHA1 Message Date
Snider
693dde08a9 feat: implement Section 19 — API remote streams primitive
c.API() manages remote streams to endpoints configured in c.Drive().
Stream interface (Send/Receive/Close) implemented by protocol handlers.
Consumer packages register handlers via c.API().RegisterProtocol().

- API struct with protocols Registry[StreamFactory]
- Stream interface — bidirectional, transport-agnostic
- c.API().Stream("name") — opens connection via Drive config
- c.API().Call("endpoint", "action", opts) — remote Action invocation
- c.RemoteAction("host:action", ctx, opts) — transparent local/remote dispatch
- extractScheme() parses transport URLs without net/url import
- 11 AX-7 tests with mock stream factory

Drive is the phone book. API is the phone.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 16:21:04 +00:00
Snider
ec423cfe46 feat: implement Section 21 — Entitlement permission primitive
c.Entitled("action", quantity) checks permission before execution.
Default: everything permitted (trusted conclave).
Consumer packages replace checker via c.SetEntitlementChecker().

- Entitlement struct: Allowed, Unlimited, Limit, Used, Remaining, Reason
- NearLimit(threshold), UsagePercent() convenience methods
- EntitlementChecker function type — registered by go-entitlements/commerce-matrix
- UsageRecorder for consumption tracking after gated actions succeed
- Enforcement wired into Action.Run() — one gate for all capabilities
- Security audit logging on denials (P11-6)
- 16 AX-7 tests including full SaaS gating pattern simulation

Maps 1:1 to RFC-004 EntitlementResult and RFC-005 PermissionResult.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 16:17:16 +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
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
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
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
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
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
Snider
001e90ed13 feat: WithName for explicit service naming
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
b03c1a3a3c feat: WithService with v0.3.3 name discovery + IPC handler auto-registration
WithService now: calls factory, discovers service name from instance's
package path via reflect.TypeOf, discovers HandleIPCEvents method,
calls RegisterService. If factory returns nil Value, assumes self-registered.

Also fixes: Cli() accessor uses ServiceFor, test files updated for Options struct.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
177f73cc99 feat: WithService with v0.3.3 name discovery + IPC handler auto-registration
- WithService now calls factory, discovers service name from package path via
  reflect/runtime (last path segment, _test suffix stripped, lowercased), and
  calls RegisterService — which handles Startable/Stoppable/HandleIPCEvents
- If factory returns nil Value (self-registered), WithService returns OK without
  a second registration
- Add contract_test.go with _Good/_Bad tests covering all three code paths
- Fix core.go Cli() accessor: use ServiceFor[*Cli](c, "cli") (was cli.New())
- Fix pre-existing })) → }}) syntax errors in command_test, service_test, lock_test
- Fix pre-existing Options{...} → NewOptions(...) in core_test, data_test,
  drive_test, i18n_test (Options is a struct, not a slice)

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
198ab839a8 wip: checkpoint before v0.3.3 parity rewrite
Cli as service with ServiceRuntime, incomplete.
Need to properly port v0.3.3 service_manager, message_bus,
WithService with full name/IPC discovery.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
f69be963bc feat: Cli.New(c) constructor — Core uses it during construction
Cli{}.New(c) replaces &Cli{core: c} in contract.go.
9 tests passing.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
a49bc46bc7 feat: Options struct + Result methods + WithOption convenience
Options is now a proper struct with New(), Set(), Get(), typed accessors.
Result gains New(), Result(), Get() methods on the struct.
WithOption("key", value) convenience for core.New().

options_test.go: 22 tests passing against the new contract.
Other test files mechanically updated for compilation.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
74f78c83a2 feat: RegisterService with instance storage + interface discovery
Restores v0.3.3 service manager capabilities:
- RegisterService(name, instance) stores the raw instance
- Auto-discovers Startable/Stoppable interfaces → wires lifecycle
- Auto-discovers HandleIPCEvents → wires to IPC bus
- ServiceFor[T](c, name) for typed instance retrieval
- Service DTO gains Instance field for instance tracking

WithService is a simple factory call — no reflect, no magic.
discoverHandlers removed — RegisterService handles it inline.
No double-registration: IPC wired once at registration time.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
64e6a26ea8 fix: move HandleIPCEvents discovery to New() post-construction
WithService is now a simple factory call — no reflect, no auto-registration.
New() calls discoverHandlers() after all opts run, scanning Config for
service instances that implement HandleIPCEvents.

This eliminates both double-registration and empty-placeholder issues:
- Factories wire their own lifecycle via c.Service()
- HandleIPCEvents discovered once, after all services are registered
- No tension between factory-registered and auto-discovered paths

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
9b5f6df6da fix: prevent double IPC registration + empty service placeholder
- HandleIPCEvents only auto-registered for services the factory didn't
  register itself (prevents double handler registration)
- Auto-discovery only creates Service{} placeholder when factory didn't
  call c.Service() — factories that register themselves keep full lifecycle

Addresses Codex review findings 1 and 2 from third pass.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
2d017980dd fix: address Codex review findings on PR #28
- WithOptions copies the Options slice (constructor isolation regression)
- WithService auto-discovers service name from package path via reflect
- WithService auto-registers HandleIPCEvents if present (v0.3.3 parity)
- Add test for failing option short-circuit in New()

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 22:09:02 +00:00
Snider
c45b22849f feat: restore functional option pattern for New()
New() returns Result, accepts CoreOption functionals.
Restores v0.3.3 service registration contract:
- WithService(factory func(*Core) Result) — service factory receives Core
- WithOptions(Options) — key-value configuration
- WithServiceLock() — immutable after construction

Services registered in New() form the application conclave with
shared IPC access. Each Core instance has its own bus scope.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-24 16:23:33 +00:00
Snider
8c2b9c2506 feat: add core.Env() — read-only system information registry
Env is environment, Config is ours. Provides centralised access to
system facts (OS, ARCH, hostname, user, directories, timestamps)
via string key lookup, populated once at package init.

Keys: OS, ARCH, GO, DS, PS, HOSTNAME, USER, PID, NUM_CPU,
DIR_HOME, DIR_CONFIG, DIR_CACHE, DIR_DATA, DIR_TMP, DIR_CWD,
DIR_DOWNLOADS, DIR_CODE, CORE_START.

17 tests covering all keys + unknown key + Core instance accessor.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:26 +00:00
Snider
fbd646456a refactor: flatten polyglot layout to standard Go module
Move source from go/core/ to root, tests from go/tests/ to tests/.
Module path dappco.re/go/core resolves cleanly — builds and tests pass.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-21 08:27:14 +00:00
Renamed from go/core/contract.go (Browse further)