Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ebfb3c8
fix: use uint16 for isSymlink mode type on darwin
Snider Apr 7, 2026
50fe511
feat(io): add MockMedium for in-memory testing
Snider Apr 8, 2026
338db85
refactor: AX compliance sweep — replace banned stdlib imports with co…
Snider Apr 13, 2026
43aa98c
feat(io): add Cube Medium and Named Actions registry
Snider Apr 14, 2026
8c8305e
chore: go mod tidy
Snider Apr 24, 2026
df720d9
feat(go-io): add CLI test Taskfile for build and unit test validation…
codex Apr 24, 2026
2c6bfef
chore(go-io): migrate module path to dappco.re/go/io
Snider Apr 24, 2026
3c4e876
fix(go-io): go mod tidy — add missing go.sum entries for non-workspac…
Snider Apr 25, 2026
133c701
fix(io): annotate text/template as AX-6 intrinsic in store.go (#294)
Snider Apr 25, 2026
71cc8a7
docs(io): annotate sync as AX-6 structural exception per RFC §5.1 (#291)
Snider Apr 25, 2026
670ed51
docs(io): annotate io as AX-6 intrinsic in workspace/service.go (HKDF…
Snider Apr 25, 2026
b88a771
fix(io): AX-6 sweep on cube/cube.go
Snider Apr 25, 2026
cc39192
feat(io): add ChaChaPolySigil + register in NewSigil factory (#628)
Snider Apr 25, 2026
280c137
fix(io/sigil): remove fixed-nonce path — AEAD nonce-uniqueness invari…
Snider Apr 25, 2026
ff862dd
fix(io/sigil): remove half-built NewSigil chacha20poly1305 factory entry
Snider Apr 25, 2026
9718398
feat(go-io/api): scaffold pkg/api/provider.go + 5 routes + 18-action …
Snider Apr 25, 2026
fad5682
feat(go-io/medium): SFTP + WebDAV Medium backends per RFC §12 (#634)
Snider Apr 25, 2026
642b714
feat(go-io/workspace): implement Workspace service per RFC §5 + wire …
Snider Apr 25, 2026
612593e
feat(go-io): register 11 missing RFC §15 actions (sftp/s3/cube wired;…
Snider Apr 25, 2026
35eff93
feat(go-io/medium): GitHub Medium + PWA stub backends per RFC §12 + §…
Snider Apr 25, 2026
72ea438
fix(go-io): purge banned stdlib imports + AX-6 exception annotations …
Snider Apr 25, 2026
0891b7b
fix(io): address all CodeRabbit + SonarCloud findings on PR #3
Snider Apr 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
240 changes: 240 additions & 0 deletions actions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
// SPDX-License-Identifier: EUPL-1.2

// Example: io.RegisterActions(c)
// Example: result := c.Action("core.io.local.read").Run(ctx, core.NewOptions(
// Example: core.Option{Key: "root", Value: "/srv/app"},
// Example: core.Option{Key: "path", Value: "config/app.yaml"},
// Example: ))
package io

import (
"context"
"io/fs"

core "dappco.re/go/core"
)

// Named action identifiers used by Core consumers. Each maps to a Medium
// operation with a predictable path name.
//
// Example: result := c.Action(io.ActionLocalRead).Run(ctx, opts)
const (
ActionLocalRead = "core.io.local.read"
ActionLocalWrite = "core.io.local.write"
ActionLocalList = "core.io.local.list"
ActionLocalDelete = "core.io.local.delete"

ActionMemoryRead = "core.io.memory.read"
ActionMemoryWrite = "core.io.memory.write"

ActionGitHubClone = "core.io.github.clone"
ActionGitHubRead = "core.io.github.read"

ActionPWAScrape = "core.io.pwa.scrape"

ActionSFTPRead = "core.io.sftp.read"
ActionSFTPWrite = "core.io.sftp.write"

ActionS3Read = "core.io.s3.read"
ActionS3Write = "core.io.s3.write"

ActionCubeRead = "core.io.cube.read"
ActionCubeWrite = "core.io.cube.write"
ActionCubePack = "core.io.cube.pack"
ActionCubeUnpack = "core.io.cube.unpack"

ActionCopy = "core.io.copy"
)

// memoryActionStore is the shared in-memory backing for
// core.io.memory.read/core.io.memory.write. Keeping it package-level lets the
// two actions agree on state without the caller supplying a backend.
var memoryActionStore = NewMemoryMedium()

// Example: io.RegisterActions(c)
//
// RegisterActions installs the named actions listed in the go-io RFC §15 on
// the given Core. Consumers call this at service registration time so that any
// agent or CLI can dispatch Medium operations by name.
func RegisterActions(c *core.Core) {
if c == nil {
return
}
c.Action(ActionLocalRead, localReadAction)
c.Action(ActionLocalWrite, localWriteAction)
c.Action(ActionLocalList, localListAction)
c.Action(ActionLocalDelete, localDeleteAction)
c.Action(ActionMemoryRead, memoryReadAction)
c.Action(ActionMemoryWrite, memoryWriteAction)
c.Action(ActionGitHubClone, githubNotImplementedAction)
c.Action(ActionGitHubRead, githubNotImplementedAction)
c.Action(ActionPWAScrape, pwaNotImplementedAction)
c.Action(ActionSFTPRead, mediumReadAction("io.sftp.readAction"))
c.Action(ActionSFTPWrite, mediumWriteAction("io.sftp.writeAction"))
c.Action(ActionS3Read, mediumReadAction("io.s3.readAction"))
c.Action(ActionS3Write, mediumWriteAction("io.s3.writeAction"))
c.Action(ActionCopy, copyAction)
}

// Example: opts := core.NewOptions(core.Option{Key: "root", Value: "/srv/app"}, core.Option{Key: "path", Value: "config/app.yaml"})
func localReadAction(_ context.Context, opts core.Options) core.Result {
medium, err := localMediumFromOptions(opts)
if err != nil {
return core.Result{}.New(err)
}
content, err := medium.Read(opts.String("path"))
if err != nil {
return core.Result{}.New(err)
}
return core.Result{Value: content, OK: true}
}

// Example: opts := core.NewOptions(core.Option{Key: "root", Value: "/srv/app"}, core.Option{Key: "path", Value: "log.txt"}, core.Option{Key: "content", Value: "event"})
func localWriteAction(_ context.Context, opts core.Options) core.Result {
medium, err := localMediumFromOptions(opts)
if err != nil {
return core.Result{}.New(err)
}
if err := medium.Write(opts.String("path"), opts.String("content")); err != nil {
return core.Result{}.New(err)
}
return core.Result{OK: true}
}

// Example: opts := core.NewOptions(core.Option{Key: "root", Value: "/srv/app"}, core.Option{Key: "path", Value: "config"})
func localListAction(_ context.Context, opts core.Options) core.Result {
medium, err := localMediumFromOptions(opts)
if err != nil {
return core.Result{}.New(err)
}
entries, err := medium.List(opts.String("path"))
if err != nil {
return core.Result{}.New(err)
}
return core.Result{Value: entries, OK: true}
}

// Example: opts := core.NewOptions(core.Option{Key: "root", Value: "/srv/app"}, core.Option{Key: "path", Value: "tmp/old.log"})
func localDeleteAction(_ context.Context, opts core.Options) core.Result {
medium, err := localMediumFromOptions(opts)
if err != nil {
return core.Result{}.New(err)
}
path := opts.String("path")
recursive := opts.Bool("recursive")
if recursive {
if err := medium.DeleteAll(path); err != nil {
return core.Result{}.New(err)
}
} else {
if err := medium.Delete(path); err != nil {
return core.Result{}.New(err)
}
}
return core.Result{OK: true}
}

// Example: opts := core.NewOptions(core.Option{Key: "path", Value: "config/app.yaml"})
func memoryReadAction(_ context.Context, opts core.Options) core.Result {
content, err := memoryActionStore.Read(opts.String("path"))
if err != nil {
return core.Result{}.New(err)
}
return core.Result{Value: content, OK: true}
}

// Example: opts := core.NewOptions(core.Option{Key: "path", Value: "config/app.yaml"}, core.Option{Key: "content", Value: "port: 8080"})
func memoryWriteAction(_ context.Context, opts core.Options) core.Result {
if err := memoryActionStore.Write(opts.String("path"), opts.String("content")); err != nil {
return core.Result{}.New(err)
}
return core.Result{OK: true}
}

func githubNotImplementedAction(context.Context, core.Options) core.Result {
return core.Result{
OK: false,
Value: core.E("io.github", "not implemented — see #633 for backend tracking", nil),
}
}

func pwaNotImplementedAction(context.Context, core.Options) core.Result {
return core.Result{
OK: false,
Value: core.E("io.pwa", "not implemented — see #633 for backend tracking", nil),
}
}

func mediumReadAction(operation string) core.ActionHandler {
return func(_ context.Context, opts core.Options) core.Result {
medium, err := mediumFromOptions(opts, operation)
if err != nil {
return core.Result{}.New(err)
}
content, err := medium.Read(opts.String("path"))
if err != nil {
return core.Result{}.New(err)
}
return core.Result{Value: content, OK: true}
}
}

func mediumWriteAction(operation string) core.ActionHandler {
return func(_ context.Context, opts core.Options) core.Result {
medium, err := mediumFromOptions(opts, operation)
if err != nil {
return core.Result{}.New(err)
}
if err := medium.Write(opts.String("path"), opts.String("content")); err != nil {
return core.Result{}.New(err)
}
return core.Result{OK: true}
}
}

// Example: opts := core.NewOptions(
// Example: core.Option{Key: "source", Value: sourceMedium},
// Example: core.Option{Key: "sourcePath", Value: "input.txt"},
// Example: core.Option{Key: "destination", Value: destinationMedium},
// Example: core.Option{Key: "destinationPath", Value: "backup/input.txt"},
// Example: )
func copyAction(_ context.Context, opts core.Options) core.Result {
source, ok := opts.Get("source").Value.(Medium)
if !ok {
return core.Result{}.New(core.E("io.copyAction", "source medium is required", fs.ErrInvalid))
}
destination, ok := opts.Get("destination").Value.(Medium)
if !ok {
return core.Result{}.New(core.E("io.copyAction", "destination medium is required", fs.ErrInvalid))
}
if err := Copy(source, opts.String("sourcePath"), destination, opts.String("destinationPath")); err != nil {
return core.Result{}.New(err)
}
return core.Result{OK: true}
}

// localMediumFromOptions constructs a sandboxed local Medium using the
// "root" option.
func localMediumFromOptions(opts core.Options) (Medium, error) {
root := opts.String("root")
if root == "" {
return nil, core.E("io.localMediumFromOptions", "root is required", fs.ErrInvalid)
}
return NewSandboxed(root)
}

func mediumFromOptions(opts core.Options, operation string) (Medium, error) {
medium, ok := opts.Get("medium").Value.(Medium)
if !ok {
return nil, core.E(operation, "medium is required", fs.ErrInvalid)
}
return medium, nil
}

// ResetMemoryActionStore clears the in-memory state used by memory action
// handlers. Tests call this to isolate runs from each other.
//
// Example: io.ResetMemoryActionStore()
func ResetMemoryActionStore() {
memoryActionStore = NewMemoryMedium()
}
Loading
Loading