Add project lifecycle delegation and private member work

Why:
Shared project reads previously doubled as write permission, project mutations
bypassed the domain lifecycle, and coarse retrieval shared private resources.

Evidence:
SELF red-before-implementation logs cover projection, lifecycle, private work,
resource filtering, canonical retrieval scope, publication source and namespace.
SELF go test ./..., isolated PostgreSQL go test ./internal/store -count=1,
and CGO_ENABLED=0 go build ./cmd/agent-system passed; task owner owns replay/CI.

Impact:
Add optional actor-scoped projection/lifecycle capabilities; registered providers
control public lifecycle and live work. Preserve owned conversation history and
ordinary unregistered behavior. Authorize knowledge by stored ID/source, map
current host scopes through trusted service metadata, and filter artifact lists.

Refs:
Dev project-member-access implementation plan r1 Task 1; approved design r0.1;
2026-10-13 trusted publication Source hardening ruling
This commit is contained in:
2026-10-13 08:43:02 +00:00
parent 15566f9a33
commit 7cf8f02399
24 changed files with 1572 additions and 78 deletions
+9 -2
View File
@@ -5,14 +5,21 @@ package extension
// 不含姓名/邮箱/联系方式)。红:类型未定义,编译红先行。
import (
"encoding/json"
"strconv"
"strings"
"testing"
)
func TestAccountListerSurface(t *testing.T) {
if !strings.HasPrefix(SurfaceVersion, "2.11.0") {
t.Fatalf("SurfaceVersion 应升至 2.11.0-experimental(新增导出符号须升版),got %s", SurfaceVersion)
parts := strings.Split(SurfaceVersion, ".")
minor := 0
if len(parts) > 1 {
minor, _ = strconv.Atoi(parts[1])
}
if len(parts) < 2 || parts[0] != "2" || minor < 11 {
t.Fatalf("AccountLister requires experimental version 2.11 or later, got %s", SurfaceVersion)
}
// 接口方法签名即契约:游标分页 + 最小字段。
var _ AccountLister = (AccountLister)(nil)
e := AccountEntry{Owner: "u1", Valid: true}
+12 -8
View File
@@ -32,7 +32,9 @@ import (
// 2.9.0-experimental:Registry.AddAdminZone 与 AdminZoneSpec(WP3-I3/KTD4:编译
// 插件向基座管理区声明展示分区;分区入口不做基座级权限判定,数据能力由
// 分区组件自身的命令面 fail-closed)。
const SurfaceVersion = "2.11.0-experimental"
// 2.12.0-experimental: optional ProjectHost/ProjectLifecycleRegistry and trusted
// ResourceAuthzQuery.Source for per-document publication authorization.
const SurfaceVersion = "2.12.0-experimental"
// ErrNotLinked 表示进程没有空白导入 link 包,Main 无法启动服务。
var ErrNotLinked = errors.New("extension: runner not linked; blank-import agent-system-backend/pkg/extension/link")
@@ -43,15 +45,17 @@ var ErrNotLinked = errors.New("extension: runner not linked; blank-import agent-
type ResourceAuthorizer func(owner string, projectID core.ID) (bool, error)
// ResourceAuthzQuery 是 EXT-8b 资源级授权查询(U9):字段由执行端从受信上下文
//(认证会话、受信存储)构造,不采信请求自报的归属;下游只回答布尔,不接触基座
// (认证会话、受信存储)构造,不采信请求自报的归属;下游只回答布尔,不接触基座
// 内部状态;返回 error 视为拒绝(fail-closed)。读取路径专用;写路径一律 owner 自有。
type ResourceAuthzQuery struct {
Actor string `json:"actor"` // 认证主体(发起读取的会话)
Action string `json:"action"` // read|download|list|search|stream|model-context
ResourceType string `json:"resourceType"` // 基座资源类型(artifact/attachment/...)
ResourceID core.ID `json:"resourceId"` // 资源稳定 ID
Owner string `json:"owner"` // 资源属主(受信存储解析,非请求自报)
ProjectID core.ID `json:"projectId,omitempty"` // 所属项目;空=无项目私域
Actor string `json:"actor"` // 认证主体(发起读取的会话)
Action string `json:"action"` // read|download|list|search|stream|model-context
ResourceType string `json:"resourceType"` // 基座资源类型(artifact/attachment/...)
ResourceID core.ID `json:"resourceId"` // 资源稳定 ID
Owner string `json:"owner"` // 资源属主(受信存储解析,非请求自报)
// Source is optional trusted resource provenance, populated from stored knowledge rows.
Source string `json:"source,omitempty"`
ProjectID core.ID `json:"projectId,omitempty"` // 所属项目;空=无项目私域
}
// ResourceAuthorizerV2 EXT-8b 资源级授权适配器:V1(owner×project 布尔)之上的
+35
View File
@@ -0,0 +1,35 @@
package extension
import (
core "agent-system-core"
"context"
)
// ProjectHost is an optional actor-scoped host projection capability. Metadata
// can be shared by an authorizer; owned lookups and mutations remain owner-only.
type ProjectHost interface {
Owner() string
OwnedProjects() ([]core.Project, error)
ProjectMetadata(projectID string) (core.Project, error)
OwnedProject(projectID string) (core.Project, error)
EnsureProject(projectID, name string) (core.Project, error)
RenameOwnedProject(projectID, name string) (core.Project, error)
ProjectNameAvailable(name, exceptID string) (bool, error)
}
// ProjectLifecycle supplies the domain's public project lifecycle and live work
// authorization. Each Host is scoped to the authenticated actor. Errors with a
// StatusCode() int method may report 400, 403, 404, 409, or 503 to HTTP clients.
type ProjectLifecycle struct {
List func(context.Context, Host) ([]core.Project, error)
Create func(context.Context, Host, string) (core.Project, error)
Rename func(context.Context, Host, string, string) (core.Project, error)
Archive func(context.Context, Host, string) error
Work func(context.Context, string, core.ID) (bool, error)
}
// ProjectLifecycleRegistry is optional so older Registry implementations retain
// their contract. Only in-process plugins can register one complete provider.
type ProjectLifecycleRegistry interface {
RegisterProjectLifecycle(ProjectLifecycle) error
}
+11
View File
@@ -32,6 +32,17 @@ Main
NewHostClient
NewScopedHost
Options
ProjectHost
ProjectHost.EnsureProject
ProjectHost.OwnedProject
ProjectHost.OwnedProjects
ProjectHost.Owner
ProjectHost.ProjectMetadata
ProjectHost.ProjectNameAvailable
ProjectHost.RenameOwnedProject
ProjectLifecycle
ProjectLifecycleRegistry
ProjectLifecycleRegistry.RegisterProjectLifecycle
RegisterCompiled
Registry
Registry.AddAdminZone