From 7c6dff4a01fb90c7404d3ec145ccb44164f2aaac Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Tue, 4 Aug 2026 10:45:28 +0200 Subject: [PATCH] rename EDSL constructors for clarity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Leaf → FileNode (file node) - Dir → DirNode (directory node) - Blueprint → Dynamic (state-dependent directory) The names now clearly indicate what they create: - FileNode: a file - DirNode: a directory - Dynamic: a directory whose children depend on runtime state --- fs/{blueprint_test.go => dynamic_test.go} | 98 ++++++++++---------- fs/fsnode.go | 20 ++-- fs/spec.go | 108 +++++++++++----------- 3 files changed, 113 insertions(+), 113 deletions(-) rename fs/{blueprint_test.go => dynamic_test.go} (78%) diff --git a/fs/blueprint_test.go b/fs/dynamic_test.go similarity index 78% rename from fs/blueprint_test.go rename to fs/dynamic_test.go index 777ddd4..3eb6e11 100644 --- a/fs/blueprint_test.go +++ b/fs/dynamic_test.go @@ -5,11 +5,11 @@ import ( "testing" ) -// TestBlueprintBasic tests the basic Blueprint pattern: +// TestDynamicBasic tests the basic Blueprint pattern: // - Declares structure statically // - Instances function provides identity (name, aliases, UID/GID) // - Handlers receive context with bound data -func TestBlueprintBasic(t *testing.T) { +func TestDynamicBasic(t *testing.T) { // Test data that instances will bind to ctx type testData struct { ID string @@ -22,8 +22,8 @@ func TestBlueprintBasic(t *testing.T) { } // Create a Blueprint with static structure and dynamic instances - spec := Dir("/", - Blueprint("{id}", func(ctx HandlerCtx) ([]Instance, error) { + spec := DirNode("/", + Dynamic("{id}", func(ctx HandlerCtx) ([]Instance, error) { var out []Instance for _, d := range instances { data := d // capture @@ -36,10 +36,10 @@ func TestBlueprintBasic(t *testing.T) { } return out, nil }, - Leaf("id", 0444, Read(func(ctx HandlerCtx) ([]byte, error) { + FileNode("id", 0444, Read(func(ctx HandlerCtx) ([]byte, error) { return []byte("test-id\n"), nil })), - Leaf("name", 0666, + FileNode("name", 0666, Read(func(ctx HandlerCtx) ([]byte, error) { return []byte("test-name\n"), nil }), @@ -138,8 +138,8 @@ func TestBlueprintBasic(t *testing.T) { } } -// TestBlueprintNestedStructure tests nested Blueprint nodes -func TestBlueprintNestedStructure(t *testing.T) { +// TestDynamicNestedStructure tests nested Blueprint nodes +func TestDynamicNestedStructure(t *testing.T) { // Outer instances (like sessions) outerInstances := []struct { ID string @@ -157,8 +157,8 @@ func TestBlueprintNestedStructure(t *testing.T) { {ID: "agent-b", Name: "agentB"}, } - spec := Dir("/", - Blueprint("{outer}", func(ctx HandlerCtx) ([]Instance, error) { + spec := DirNode("/", + Dynamic("{outer}", func(ctx HandlerCtx) ([]Instance, error) { var out []Instance for _, d := range outerInstances { data := d @@ -169,11 +169,11 @@ func TestBlueprintNestedStructure(t *testing.T) { } return out, nil }, - Leaf("outer-file", 0444, Read(func(ctx HandlerCtx) ([]byte, error) { + FileNode("outer-file", 0444, Read(func(ctx HandlerCtx) ([]byte, error) { return []byte("outer\n"), nil })), - Dir("inner-dir", - Blueprint("{inner}", func(ctx HandlerCtx) ([]Instance, error) { + DirNode("inner-dir", + Dynamic("{inner}", func(ctx HandlerCtx) ([]Instance, error) { var out []Instance for _, d := range innerInstances { data := d @@ -184,7 +184,7 @@ func TestBlueprintNestedStructure(t *testing.T) { } return out, nil }, - Leaf("inner-file", 0444, Read(func(ctx HandlerCtx) ([]byte, error) { + FileNode("inner-file", 0444, Read(func(ctx HandlerCtx) ([]byte, error) { return []byte("inner\n"), nil })), ), @@ -230,13 +230,13 @@ func TestBlueprintNestedStructure(t *testing.T) { } } -// TestBlueprintContextPropagation tests that context is properly propagated -func TestBlueprintContextPropagation(t *testing.T) { +// TestDynamicContextPropagation tests that context is properly propagated +func TestDynamicContextPropagation(t *testing.T) { // Use a channel to capture the context value seen by the handler seen := make(chan string, 1) - spec := Dir("/", - Blueprint("{id}", func(ctx HandlerCtx) ([]Instance, error) { + spec := DirNode("/", + Dynamic("{id}", func(ctx HandlerCtx) ([]Instance, error) { return []Instance{ { Name: "test-instance", @@ -245,7 +245,7 @@ func TestBlueprintContextPropagation(t *testing.T) { }, }, nil }, - Leaf("check", 0444, Read(func(ctx HandlerCtx) ([]byte, error) { + FileNode("check", 0444, Read(func(ctx HandlerCtx) ([]byte, error) { seen <- "handler-called" return []byte("ok\n"), nil })), @@ -273,16 +273,16 @@ func TestBlueprintContextPropagation(t *testing.T) { } } -// TestBlueprintUIDGIDInheritance tests UID/GID resolution -func TestBlueprintUIDGIDInheritance(t *testing.T) { - spec := Dir("/", UID("root-uid"), GID("root-gid"), - Blueprint("{id}", func(ctx HandlerCtx) ([]Instance, error) { +// TestDynamicUIDGIDInheritance tests UID/GID resolution +func TestDynamicUIDGIDInheritance(t *testing.T) { + spec := DirNode("/", UID("root-uid"), GID("root-gid"), + Dynamic("{id}", func(ctx HandlerCtx) ([]Instance, error) { return []Instance{ {Name: "with-uid", UID: "custom-uid", GID: "custom-gid"}, {Name: "without-uid"}, // should inherit }, nil }, - Leaf("file", 0444, Read(func(ctx HandlerCtx) ([]byte, error) { + FileNode("file", 0444, Read(func(ctx HandlerCtx) ([]byte, error) { return []byte("data\n"), nil })), ), @@ -324,12 +324,12 @@ func TestBlueprintUIDGIDInheritance(t *testing.T) { } } -// TestBlueprintRename tests the rename callback on instances -func TestBlueprintRename(t *testing.T) { +// TestDynamicRename tests the rename callback on instances +func TestDynamicRename(t *testing.T) { renamed := "" - spec := Dir("/", - Blueprint("{id}", func(ctx HandlerCtx) ([]Instance, error) { + spec := DirNode("/", + Dynamic("{id}", func(ctx HandlerCtx) ([]Instance, error) { return []Instance{ { Name: "original", @@ -340,7 +340,7 @@ func TestBlueprintRename(t *testing.T) { }, }, nil }, - Leaf("file", 0444, Read(func(ctx HandlerCtx) ([]byte, error) { + FileNode("file", 0444, Read(func(ctx HandlerCtx) ([]byte, error) { return []byte("data\n"), nil })), ), @@ -359,13 +359,13 @@ func TestBlueprintRename(t *testing.T) { } } -// TestBlueprintEmptyInstances tests behavior when no instances exist -func TestBlueprintEmptyInstances(t *testing.T) { - spec := Dir("/", - Blueprint("{id}", func(ctx HandlerCtx) ([]Instance, error) { +// TestDynamicEmptyInstances tests behavior when no instances exist +func TestDynamicEmptyInstances(t *testing.T) { + spec := DirNode("/", + Dynamic("{id}", func(ctx HandlerCtx) ([]Instance, error) { return nil, nil // empty }, - Leaf("file", 0444, Read(func(ctx HandlerCtx) ([]byte, error) { + FileNode("file", 0444, Read(func(ctx HandlerCtx) ([]byte, error) { return []byte("data\n"), nil })), ), @@ -389,24 +389,24 @@ func TestBlueprintEmptyInstances(t *testing.T) { } } -// TestBlueprintMixedStaticAndDynamic tests Blueprint alongside static children -func TestBlueprintMixedStaticAndDynamic(t *testing.T) { - spec := Dir("/", - Leaf("static-file", 0444, Read(func(ctx HandlerCtx) ([]byte, error) { +// TestDynamicMixedStaticAndDynamic tests Blueprint alongside static children +func TestDynamicMixedStaticAndDynamic(t *testing.T) { + spec := DirNode("/", + FileNode("static-file", 0444, Read(func(ctx HandlerCtx) ([]byte, error) { return []byte("static\n"), nil })), - Dir("static-dir", - Leaf("nested", 0444, Read(func(ctx HandlerCtx) ([]byte, error) { + DirNode("static-dir", + FileNode("nested", 0444, Read(func(ctx HandlerCtx) ([]byte, error) { return []byte("nested-static\n"), nil })), ), - Blueprint("{id}", func(ctx HandlerCtx) ([]Instance, error) { + Dynamic("{id}", func(ctx HandlerCtx) ([]Instance, error) { return []Instance{ {Name: "dynamic1"}, {Name: "dynamic2"}, }, nil }, - Leaf("dynamic-file", 0444, Read(func(ctx HandlerCtx) ([]byte, error) { + FileNode("dynamic-file", 0444, Read(func(ctx HandlerCtx) ([]byte, error) { return []byte("dynamic\n"), nil })), ), @@ -453,16 +453,16 @@ func TestBlueprintMixedStaticAndDynamic(t *testing.T) { } } -// TestBlueprintStatMode tests that blueprint instances get correct file mode -func TestBlueprintStatMode(t *testing.T) { - spec := Dir("/", - Blueprint("{id}", func(ctx HandlerCtx) ([]Instance, error) { +// TestDynamicStatMode tests that blueprint instances get correct file mode +func TestDynamicStatMode(t *testing.T) { + spec := DirNode("/", + Dynamic("{id}", func(ctx HandlerCtx) ([]Instance, error) { return []Instance{{Name: "instance"}}, nil }, - Leaf("readable", 0444, Read(func(ctx HandlerCtx) ([]byte, error) { + FileNode("readable", 0444, Read(func(ctx HandlerCtx) ([]byte, error) { return []byte("r\n"), nil })), - Leaf("writable", 0666, + FileNode("writable", 0666, Read(func(ctx HandlerCtx) ([]byte, error) { return []byte("rw\n"), nil }), @@ -470,7 +470,7 @@ func TestBlueprintStatMode(t *testing.T) { return nil }), ), - Leaf("executable", 0555, Read(func(ctx HandlerCtx) ([]byte, error) { + FileNode("executable", 0555, Read(func(ctx HandlerCtx) ([]byte, error) { return []byte("#!/bin/sh\n"), nil })), ), diff --git a/fs/fsnode.go b/fs/fsnode.go index 9649e0c..b2d5cea 100644 --- a/fs/fsnode.go +++ b/fs/fsnode.go @@ -166,8 +166,8 @@ type NodeOption func(*FsNodeDecl) // ── EDSL constructors ──────────────────────────────────────────── -// Dir declares a static directory node, optionally with NodeOptions (GID, UID). -func Dir(name string, args ...any) FsNodeDecl { +// DirNode declares a static directory node, optionally with NodeOptions (GID, UID). +func DirNode(name string, args ...any) FsNodeDecl { d := FsNodeDecl{Name: name} for _, a := range args { switch v := a.(type) { @@ -182,22 +182,22 @@ func Dir(name string, args ...any) FsNodeDecl { // TemplateDir declares a dynamic directory node whose children are // resolved at runtime by list. The list function is a factory that -// returns fully-built subtrees — no blueprint children needed here. +// returns fully-built subtrees — no Dynamic children needed here. func TemplateDir(name string, list ListFn) FsNodeDecl { return FsNodeDecl{Name: name, List: list} } -// Blueprint declares a directory template with static structure. +// Dynamic declares a state-dependent directory with static structure. // The instances function returns identity info for each instance; // handlers access instance data via ctx.Session/ctx.Agent/ctx.AgentLog. // // Example: // -// Blueprint("{id}", sessionInstances, -// Leaf("name", 0666, Read(readSessionName), Write(writeSessionName)), -// Leaf("state", 0444, Read(readSessionState)), +// Dynamic("{id}", sessionInstances, +// FileNode("name", 0666, Read(readSessionName), Write(writeSessionName)), +// FileNode("state", 0444, Read(readSessionState)), // ) -func Blueprint(name string, instances InstanceFn, args ...any) FsNodeDecl { +func Dynamic(name string, instances InstanceFn, args ...any) FsNodeDecl { d := FsNodeDecl{Name: name, Instances: instances} for _, a := range args { switch v := a.(type) { @@ -210,8 +210,8 @@ func Blueprint(name string, instances InstanceFn, args ...any) FsNodeDecl { return d } -// Leaf declares a file node (non-directory). -func Leaf(name string, mode os.FileMode, opts ...NodeOption) FsNodeDecl { +// FileNode declares a file node (non-directory). +func FileNode(name string, mode os.FileMode, opts ...NodeOption) FsNodeDecl { d := FsNodeDecl{Name: name, Mode: mode} for _, o := range opts { o(&d) diff --git a/fs/spec.go b/fs/spec.go index 4c9bd63..a1ffec7 100644 --- a/fs/spec.go +++ b/fs/spec.go @@ -27,30 +27,30 @@ import ( var treeSpecUID, treeSpecGID string -var treeSpec = Dir("/", +var treeSpec = DirNode("/", // Root ownership: UID/GID set at NewRoot() time. // Every child inherits from root unless overridden. // ── Root-level files (owned by system user, group "agent") ── - Leaf("backends", 0444, Read(readBackends), GID("agent")), - Leaf("help", 0444, Read(readHelp), GID("agent")), - Leaf("models", 0444, Read(readModels), GID("agent")), - Leaf("agents", 0444, Read(readAgents), GID("agent")), - Leaf("tools", 0444, Read(readTools), GID("agent")), - Leaf("ctl", 0666, Write(writeRootCtl), GID("agent")), - Leaf("eventwait", 0444, + FileNode("backends", 0444, Read(readBackends), GID("agent")), + FileNode("help", 0444, Read(readHelp), GID("agent")), + FileNode("models", 0444, Read(readModels), GID("agent")), + FileNode("agents", 0444, Read(readAgents), GID("agent")), + FileNode("tools", 0444, Read(readTools), GID("agent")), + FileNode("ctl", 0666, Write(writeRootCtl), GID("agent")), + FileNode("eventwait", 0444, Read(readEventwait), Stream(blockEventwait), GID("agent"), ), - Leaf("complete", 0666, Request(requestComplete), GID("agent")), - Leaf("generate", 0666, Request(requestGenerate), GID("agent")), - Leaf("route", 0666, Request(requestRoute), GID("agent")), - Leaf("aliases", 0444, Read(readAliases), GID("agent")), + FileNode("complete", 0666, Request(requestComplete), GID("agent")), + FileNode("generate", 0666, Request(requestGenerate), GID("agent")), + FileNode("route", 0666, Request(requestRoute), GID("agent")), + FileNode("aliases", 0444, Read(readAliases), GID("agent")), // ── elevate/ (owned by system user, group "agent") ────────── - Dir("elevate", GID("agent"), - Leaf("policy", 0666, + DirNode("elevate", GID("agent"), + FileNode("policy", 0666, Read(readElevatePolicy), Write(writeElevatePolicy), ), @@ -59,66 +59,66 @@ var treeSpec = Dir("/", ), // ── session/ ──────────────────────────────────────────────── - Dir("session", GID("agent"), + DirNode("session", GID("agent"), // session/new and session/idx are owned by system user - Leaf("new", 0666, Request(requestSessionNew)), - Leaf("idx", 0444, Read(readSessionIdx)), + FileNode("new", 0666, Request(requestSessionNew)), + FileNode("idx", 0444, Read(readSessionIdx)), // {id} — dynamic per-session directory. // Handlers access session via ctx.Session. - Blueprint("{id}", sessionInstances, + Dynamic("{id}", sessionInstances, // env — session environment - Leaf("env", 0444, Read(readSessionEnv)), + FileNode("env", 0444, Read(readSessionEnv)), // paused — session pause status - Leaf("paused", 0444, Read(readSessionPaused)), + FileNode("paused", 0444, Read(readSessionPaused)), // connected — RPC connection status - Leaf("connected", 0444, Read(readSessionConnected)), + FileNode("connected", 0444, Read(readSessionConnected)), // ctl — session control - Leaf("ctl", 0666, Read(nopRead), Write(writeSessionCtl)), + FileNode("ctl", 0666, Read(nopRead), Write(writeSessionCtl)), // name — mutable session name - Leaf("name", 0666, Read(readSessionName), Write(writeSessionName)), + FileNode("name", 0666, Read(readSessionName), Write(writeSessionName)), // id — immutable session ID - Leaf("id", 0444, Read(readSessionID)), + FileNode("id", 0444, Read(readSessionID)), // elevate — session elevation policy - Leaf("elevate", 0666, Read(readSessionElevate), Write(writeSessionElevate)), + FileNode("elevate", 0666, Read(readSessionElevate), Write(writeSessionElevate)), // agent/ — contains per-agent subdirectories - Dir("agent", - Leaf("new", 0666, Request(requestAgentNew)), + DirNode("agent", + FileNode("new", 0666, Request(requestAgentNew)), // {aid} — dynamic per-agent directory. // Handlers access agent via ctx.Agent and ctx.AgentLog. - Blueprint("{aid}", agentInstances, - Leaf("prompt", 0666, Write(writeAgentPrompt)), - Leaf("prompt.prev", 0444, Read(readAgentPromptPrev)), - Leaf("fifo.in", 0222, Write(writeAgentFifoIn)), - Leaf("fifo.out", 0444, Read(readAgentFifoOut)), - Leaf("chat", 0444, Stat(statAgentChat), Read(readAgentChat), Stream(streamAgentChat)), - Leaf("connection", 0444, Read(readAgentConnection)), - Leaf("state", 0444, Read(readAgentState)), - Leaf("statewait", 0444, Read(readAgentState), BlockOnce(blockAgentStateWait)), - Leaf("log", 0444, Read(readAgentLog)), - Leaf("offset", 0444, Read(readAgentOffset)), - Leaf("plan", 0666, Read(readAgentPlan), Write(writeAgentPlan)), - Leaf("cfg", 0666, Read(readAgentCfg), Write(writeAgentCfg)), - Leaf("ctl", 0666, Write(writeAgentCtl)), - Leaf("cwd", 0666, Read(readAgentCwd), Write(writeAgentCwd)), - Leaf("usage", 0444, Read(readAgentUsage)), - Leaf("cost", 0444, Read(readAgentCost)), - Leaf("ctxsz", 0444, Read(readAgentCtxsz)), - Leaf("models", 0444, Read(readAgentModels)), - Leaf("systemprompt", 0444, Read(readAgentSystemprompt)), - Leaf("context", 0444, Read(readAgentContext)), - Leaf("tail", 0555, Read(readAgentTail)), - Leaf("tools", 0666, Read(readAgentTools), Write(writeAgentTools)), - Leaf("name", 0666, Read(readAgentName), Write(writeAgentName)), - Leaf("id", 0444, Read(readAgentID)), + Dynamic("{aid}", agentInstances, + FileNode("prompt", 0666, Write(writeAgentPrompt)), + FileNode("prompt.prev", 0444, Read(readAgentPromptPrev)), + FileNode("fifo.in", 0222, Write(writeAgentFifoIn)), + FileNode("fifo.out", 0444, Read(readAgentFifoOut)), + FileNode("chat", 0444, Stat(statAgentChat), Read(readAgentChat), Stream(streamAgentChat)), + FileNode("connection", 0444, Read(readAgentConnection)), + FileNode("state", 0444, Read(readAgentState)), + FileNode("statewait", 0444, Read(readAgentState), BlockOnce(blockAgentStateWait)), + FileNode("log", 0444, Read(readAgentLog)), + FileNode("offset", 0444, Read(readAgentOffset)), + FileNode("plan", 0666, Read(readAgentPlan), Write(writeAgentPlan)), + FileNode("cfg", 0666, Read(readAgentCfg), Write(writeAgentCfg)), + FileNode("ctl", 0666, Write(writeAgentCtl)), + FileNode("cwd", 0666, Read(readAgentCwd), Write(writeAgentCwd)), + FileNode("usage", 0444, Read(readAgentUsage)), + FileNode("cost", 0444, Read(readAgentCost)), + FileNode("ctxsz", 0444, Read(readAgentCtxsz)), + FileNode("models", 0444, Read(readAgentModels)), + FileNode("systemprompt", 0444, Read(readAgentSystemprompt)), + FileNode("context", 0444, Read(readAgentContext)), + FileNode("tail", 0555, Read(readAgentTail)), + FileNode("tools", 0666, Read(readAgentTools), Write(writeAgentTools)), + FileNode("name", 0666, Read(readAgentName), Write(writeAgentName)), + FileNode("id", 0444, Read(readAgentID)), // proc/ — detached processes (still uses TemplateDir for simplicity) TemplateDir("proc", listProcesses), @@ -310,7 +310,7 @@ func listElevatePending(ctx HandlerCtx) ([]FsNodeDecl, error) { out := make([]FsNodeDecl, len(pending)) for i, r := range pending { id, req := r.ID, r - out[i] = Leaf(id, 0666, + out[i] = FileNode(id, 0666, Read(func(_ HandlerCtx) ([]byte, error) { return []byte(req.Summary() + "\n"), nil }), @@ -867,7 +867,7 @@ func listProcesses(ctx HandlerCtx) ([]FsNodeDecl, error) { for i, p := range procs { pid := p.PID pidStr := strconv.Itoa(pid) - out[i] = Leaf(pidStr, 0444, + out[i] = FileNode(pidStr, 0444, Read(func(ctx HandlerCtx) ([]byte, error) { output, err := ctx.Agent.GetDetachedOutput(pid) if err != nil {