feat: client sync core — config, pull, inventory, manifest, OpenCode target

- admin-protected client config (flag > AGENT_LIB_CONFIG > platform default),
  token used for basic auth only, redacted from all errors and files
- work-repo pull via go-git (bare cache clone + fetch, remote re-pointed on
  config change), clean failure keeps previous state
- inventory: own items + external areas from lockfile; MCP inventoried but
  never deployed
- manifest v1 records name/type/origin/revision/content-hash per item,
  deterministic bytes; folder hashes over sorted relpath+filehash lines so
  tree and disk hashes agree
- shared deploy package (atomic temp+rename, exec-bit preserving) now backs
  both curator materialization and client deployment
- sync validates and hashes everything before the first mutation; second run
  is a byte-level no-op
This commit is contained in:
2026-08-23 10:31:00 +02:00
parent a755d138da
commit 49dbcf469e
17 changed files with 1211 additions and 52 deletions
+66
View File
@@ -0,0 +1,66 @@
package discovery
import (
"os"
"strings"
)
// PrefixTree narrows a Tree to the subtree below Prefix; paths passed in and
// reported back are relative to that subtree.
type PrefixTree struct {
Tree Tree
Prefix string
}
func (p *PrefixTree) Walk(fn func(rel string, isDir bool) error) error {
prefix := strings.TrimSuffix(p.Prefix, "/") + "/"
return p.Tree.Walk(func(rel string, isDir bool) error {
if rel == p.Prefix || strings.HasPrefix(rel, prefix) {
sub := strings.TrimPrefix(rel, prefix)
if sub == "" {
return nil
}
return fn(sub, isDir)
}
return nil
})
}
func (p *PrefixTree) ReadFile(rel string) ([]byte, error) {
return p.Tree.ReadFile(p.Prefix + "/" + rel)
}
// FilesUnder delegates to the underlying tree with the prefix reapplied.
func (p *PrefixTree) FilesUnder(prefix string) ([]string, error) {
lister, ok := p.Tree.(interface {
FilesUnder(prefix string) ([]string, error)
})
if !ok {
return nil, os.ErrNotExist
}
full := prefix
if p.Prefix != "" {
full = p.Prefix + "/" + strings.TrimSuffix(prefix, "/")
}
files, err := lister.FilesUnder(full)
if err != nil {
return nil, err
}
trim := p.Prefix + "/"
out := make([]string, 0, len(files))
for _, f := range files {
out = append(out, strings.TrimPrefix(f, trim))
}
return out, nil
}
// MaterializeFile delegates to the underlying tree with the prefix reapplied.
func (p *PrefixTree) MaterializeFile(rel, dst string) error {
mt, ok := p.Tree.(interface {
MaterializeFile(rel, dst string) error
})
if !ok {
return os.ErrNotExist
}
return mt.MaterializeFile(p.Prefix+"/"+rel, dst)
}