4.8 KiB
EDSL for 9P Filesystem Specification
The entire 9P namespace is declared as a single tree using an embedded
domain-specific language (EDSL) in Go. The declaration lives in
fs/spec.go and is the single source of truth for every file
and directory in the system.
Core Type
type FsNodeDecl struct {
Name string
UID string // "" = inherit from parent
GID string // "" = inherit from parent
Mode os.FileMode // 0 = inherit parent default
Stat func() os.FileInfo // metadata override
Children []FsNodeDecl // static children (implies directory)
List func(HandlerCtx) ([]FsNodeDecl, error) // dynamic children
Read func(HandlerCtx) ([]byte, error)
Write func(HandlerCtx, []byte) error
BlockOnce func(HandlerCtx, context.Context, string) ([]byte, string, error)
Stream func(HandlerCtx, context.Context, string) ([]byte, string, error)
Request func(HandlerCtx, []byte) ([]byte, error)
Create func(HandlerCtx, string) error
Remove func(HandlerCtx) error
}
Nodes are either directories (have Children or List) or leaf files
(have read/write/blocking handlers). At most one blocking variant
(BlockOnce, Stream, Request) may be set per node.
Constructors
Dir(name, children_or_options...)
Declares a static directory. Accepts a mix of child FsNodeDecl values
and NodeOption functions:
Dir("session",
Leaf("new", 0666, Request(requestSessionNew)),
Leaf("idx", 0444, Read(readSessionIdx)),
GID("agent"),
)
TemplateDir(name, listFn)
Declares a dynamic directory whose children are resolved at runtime by
listFn. The list function receives a HandlerCtx and returns fully
built []FsNodeDecl:
TemplateDir("{id}", listSessions)
Leaf(name, mode, options...)
Declares a file node. Options specify handlers and ownership:
Leaf("env", 0444, Read(readSessionEnv))
Leaf("ctl", 0666, Read(nopRead), Write(writeSessionCtl))
Leaf("chat", 0444, Read(readAgentChat), Stream(blockAgentChat))
Node Options
| Option | Signature | Sets |
|---|---|---|
Read(fn) |
func(HandlerCtx) ([]byte, error) |
d.Read |
Write(fn) |
func(HandlerCtx, []byte) error |
d.Write |
Stream(fn) |
func(HandlerCtx, ctx, base) (data, base, err) |
d.Stream |
BlockOnce(fn) |
func(HandlerCtx, ctx, base) (data, base, err) |
d.BlockOnce |
Request(fn) |
func(HandlerCtx, []byte) ([]byte, error) |
d.Request |
CreateFile(fn) |
func(HandlerCtx, string) error |
d.Create |
Remove(fn) |
func(HandlerCtx) error |
d.Remove |
Stat(fn) |
func() os.FileInfo |
d.Stat |
UID(s) |
string | d.UID |
GID(s) |
string | d.GID |
Handler Context
All handler functions receive a HandlerCtx that carries runtime
dependencies. The context is populated per scope by the wire step:
type HandlerCtx struct {
// Root-level (always available)
Root *Tree
Log *olog.Logger
ToolReg *toolsrv.Registry
Skills *skills.Registry
Models *ModelCache
Elevate *elevate.Broker
Invalidate func()
Shutdown func()
// Session scope
Session *Session
Kill func()
Rename func(string) error
SaveFn func([]byte) error
// Agent scope
Agent *agent.Agent
AgentLog *AgentLog
}
Template Directories (Factories)
Template directories use the List function as a factory. The factory
receives the current HandlerCtx, builds fully-wired subtrees using the
EDSL constructors, and returns them:
func listSessions(ctx HandlerCtx) ([]FsNodeDecl, error) {
rs := ctx.Root.Data.(*rootState)
for _, sess := range rs.sessions {
// capture session deps
sessionDir := Dir(sess.Name(),
UID(sess.Uname()), GID("agent"),
Leaf("env", 0444, Read(func(_ HandlerCtx) ([]byte, error) {
return []byte(sessionEnvContent(sess)), nil
})),
// ... more files
)
out = append(out, sessionDir)
}
return out, nil
}
Rules
- Directories cannot have leaf handlers. A node with
ChildrenorListmust not setRead,Write,BlockOnce,Stream, orRequest. - At most one blocking handler.
BlockOnce,Stream, andRequestare mutually exclusive. - Template nodes must have a
Listfunction. Names starting with{and ending with}require a dynamic child resolver. CreateandRemoveare directory-only.- Ownership inherits. Empty
UID/GIDvalues inherit from the parent node, all the way up to root (set from the system user atNewRoottime). - Mode defaults.
0inherits from parent; if still 0 at the root, directories default to0755, files to0444.