Why: Allow consumers to download an immutable module revision without nested source checkouts or local path replacements. Evidence: SELF package tests and the prior canonical-path rejection are recorded in the Dev dependency-workspace task card; remote consumption is replayed against the committed candidate. Impact: Change the module identity and its Go imports while preserving API behavior. Existing historical submodule revisions remain buildable. Refs: Versioned dependencies U1/U3; feature/dependency-workspace
125 lines
4.0 KiB
Go
125 lines
4.0 KiB
Go
package app
|
|
|
|
import (
|
|
"git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git/internal/projectscope"
|
|
"git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git/internal/server"
|
|
"git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git/internal/store"
|
|
"git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git/pkg/extension"
|
|
core "git-ssh.u1680832.nyat.app/weichen/Agent-System-Core.git"
|
|
"context"
|
|
"errors"
|
|
"sync"
|
|
)
|
|
|
|
type projectLifecycleSlot struct {
|
|
mu sync.Mutex
|
|
holder string
|
|
provider *extension.ProjectLifecycle
|
|
}
|
|
|
|
func (r compiledRegistry) RegisterProjectLifecycle(extension.ProjectLifecycle) error {
|
|
return errors.New("extension: declarative plugins cannot register project lifecycle")
|
|
}
|
|
func (r pluginRegistry) RegisterProjectLifecycle(p extension.ProjectLifecycle) error {
|
|
if r.caps.projects == nil || r.pluginID == "" {
|
|
return errors.New("extension: project lifecycle slot unavailable")
|
|
}
|
|
if p.List == nil || p.Create == nil || p.Rename == nil || p.Archive == nil || p.Work == nil {
|
|
return errors.New("extension: project lifecycle requires all callbacks")
|
|
}
|
|
slot := r.caps.projects
|
|
slot.mu.Lock()
|
|
defer slot.mu.Unlock()
|
|
if slot.provider != nil {
|
|
return errors.New("extension: project lifecycle already registered")
|
|
}
|
|
slot.holder, slot.provider = r.pluginID, &p
|
|
return nil
|
|
}
|
|
func (r recordRegistry) RegisterProjectLifecycle(p extension.ProjectLifecycle) error {
|
|
reg, ok := r.Registry.(extension.ProjectLifecycleRegistry)
|
|
if !ok {
|
|
return errors.New("extension: project lifecycle registration unavailable")
|
|
}
|
|
return reg.RegisterProjectLifecycle(p)
|
|
}
|
|
func (slot *projectLifecycleSlot) wire(srv *server.Server, caps hostcaps) {
|
|
slot.mu.Lock()
|
|
defer slot.mu.Unlock()
|
|
if slot.provider == nil {
|
|
return
|
|
}
|
|
holder := slot.holder
|
|
srv.SetProjectLifecycle(*slot.provider, func(owner string) extension.Host {
|
|
return inprocHost{caps: caps, owner: owner, actor: owner, pluginID: holder}
|
|
})
|
|
}
|
|
|
|
func (slot *projectLifecycleSlot) active() bool {
|
|
slot.mu.Lock()
|
|
defer slot.mu.Unlock()
|
|
return slot.provider != nil
|
|
}
|
|
|
|
func (slot *projectLifecycleSlot) work() func(context.Context, string, core.ID) (bool, error) {
|
|
slot.mu.Lock()
|
|
defer slot.mu.Unlock()
|
|
if slot.provider == nil {
|
|
return nil
|
|
}
|
|
return slot.provider.Work
|
|
}
|
|
|
|
func (slot *projectLifecycleSlot) resolve(ctx context.Context, caps hostcaps, owner string, id core.ID) (core.ID, error) {
|
|
slot.mu.Lock()
|
|
if slot.provider == nil {
|
|
slot.mu.Unlock()
|
|
return id, nil
|
|
}
|
|
p, holder := *slot.provider, slot.holder
|
|
slot.mu.Unlock()
|
|
return projectscope.Resolve(ctx, inprocHost{caps: caps, owner: owner, actor: owner, pluginID: holder}, p, id)
|
|
}
|
|
|
|
var _ extension.ProjectHost = inprocHost{}
|
|
|
|
func (h inprocHost) OwnedProjects() ([]core.Project, error) {
|
|
ps, err := h.caps.st.ListProjects(h.owner)
|
|
for i := range ps {
|
|
ps[i].Owner = h.owner
|
|
}
|
|
return ps, err
|
|
}
|
|
func (h inprocHost) OwnedProject(id string) (core.Project, error) {
|
|
return h.caps.st.GetProject(h.owner, id)
|
|
}
|
|
func (h inprocHost) ProjectMetadata(id string) (core.Project, error) {
|
|
p, err := h.caps.st.ProjectMetadata(id)
|
|
if err != nil {
|
|
return core.Project{}, err
|
|
}
|
|
if h.caps.projects != nil && h.caps.projects.active() {
|
|
p.Settings = h.caps.set.MaskedProjectMetadata(p.Settings)
|
|
}
|
|
if p.Owner == h.owner {
|
|
return p, nil
|
|
}
|
|
if h.caps.authz == nil {
|
|
return core.Project{}, store.ErrNotFound
|
|
}
|
|
ok, err := h.caps.authz.AuthorizeResource(extension.ResourceAuthzQuery{Actor: h.owner, Action: "read", ResourceType: "project", ResourceID: core.ID(id), Owner: p.Owner, ProjectID: core.ID(id)})
|
|
if err != nil || !ok {
|
|
return core.Project{}, store.ErrNotFound
|
|
}
|
|
return p, nil
|
|
}
|
|
func (h inprocHost) EnsureProject(id, name string) (core.Project, error) {
|
|
return h.caps.st.EnsureProject(h.owner, id, name)
|
|
}
|
|
func (h inprocHost) RenameOwnedProject(id, name string) (core.Project, error) {
|
|
return h.caps.st.RenameOwnedProject(h.owner, id, name)
|
|
}
|
|
func (h inprocHost) ProjectNameAvailable(name, exceptID string) (bool, error) {
|
|
return h.caps.st.ProjectNameAvailable(name, exceptID)
|
|
}
|