Merge remote-tracking branch 'origin/main' into feature/svc-plugin-host-options
ci / vet-and-test (pull_request) Successful in 1m2s

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	pkg/extension/extension.go
This commit is contained in:
2026-10-15 01:58:24 +00:00
177 changed files with 15848 additions and 1137 deletions
+93
View File
@@ -0,0 +1,93 @@
package extension
import "context"
const AccountDirectorySchemaVersion = 1
const (
AccountDirectoryPurposeSystemUsers = "system.users"
AccountDirectoryPurposeProjectMembers = "project.member-candidate"
AccountDirectoryPurposeDocumentDuties = "document.duty-candidate"
AccountDirectoryModeExact = "exact"
AccountDirectoryModeFuzzy = "fuzzy"
)
// AccountDirectoryQuery is a request for account candidates. Actor and field
// permissions are deliberately absent: the host supplies the authenticated
// dispatch actor and obtains current domain authorization from its registrar.
type AccountDirectoryQuery struct {
SchemaVersion int `json:"schemaVersion"`
Purpose string `json:"purpose"`
ProjectID string `json:"projectId,omitempty"`
DocumentID string `json:"documentId,omitempty"`
Mode string `json:"mode"`
Query string `json:"query"`
SearchFields []string `json:"searchFields,omitempty"`
Status string `json:"status,omitempty"` // enabled|disabled|all; domain candidates require enabled
Cursor string `json:"cursor,omitempty"`
Limit int `json:"limit,omitempty"`
}
// AccountDirectoryEntry contains only identity, effective state and fields
// specifically authorized for the current purpose. Contact details never use
// this candidate DTO.
type AccountDirectoryEntry struct {
UserID string `json:"userId"`
Owner string `json:"owner"`
Name string `json:"name,omitempty"`
StaffNo string `json:"staffNo,omitempty"`
Valid bool `json:"valid"`
}
type AccountDirectoryPage struct {
Entries []AccountDirectoryEntry `json:"entries"`
DirectoryVersion string `json:"directoryVersion"`
NextCursor string `json:"nextCursor,omitempty"`
}
type AccountDirectoryLookup struct {
Entry AccountDirectoryEntry `json:"entry"`
DirectoryVersion string `json:"directoryVersion"`
}
// AccountDirectoryProvider is optional on trusted dispatch Host handles.
// Missing capability is a denial, not permission to use EXT-19 as a fallback.
type AccountDirectoryProvider interface {
QueryAccountDirectory(context.Context, AccountDirectoryQuery) (AccountDirectoryPage, error)
LookupAccountDirectory(context.Context, AccountDirectoryQuery, string) (AccountDirectoryLookup, error)
// LookupAccountDirectoryOwner is an explicit legacy-owner compatibility
// lookup; it resolves to the same stable user ID and applies the same scope.
LookupAccountDirectoryOwner(context.Context, AccountDirectoryQuery, string) (AccountDirectoryLookup, error)
}
// AccountDirectoryScopeRequest is constructed by the Backend from the trusted
// dispatch actor, registered plugin and untrusted locator. A resolver must
// validate the locator and current member.manage / duty.assign authority.
type AccountDirectoryScopeRequest struct {
Actor string
PluginID string
Purpose string
ProjectID string
DocumentID string
}
// AccountDirectoryScope is returned by the trusted in-process domain resolver.
// AllCandidates is explicit: a nil candidate list never means global access.
// CandidateIDs or CandidateOwners are applied before search and pagination;
// domain membership stores may use owners while the provider returns stable IDs.
type AccountDirectoryScope struct {
AllowedFields []string
MatchFields []string
AllCandidates bool
CandidateIDs []string
CandidateOwners []string
ScopeVersion string
}
type AccountDirectoryScopeResolver func(context.Context, AccountDirectoryScopeRequest) (AccountDirectoryScope, error)
// AccountDirectoryRegistrar is optional on a compiled in-process Registry.
// Only the trusted RDM plugin may register the domain authorization resolver.
type AccountDirectoryRegistrar interface {
RegisterAccountDirectoryScopeResolver(AccountDirectoryScopeResolver) error
}
+13
View File
@@ -0,0 +1,13 @@
package extension
import "testing"
func TestAccountDirectoryContractIsOptional(t *testing.T) {
var host Host
_, _ = host.(AccountDirectoryProvider)
var registry Registry
_, _ = registry.(AccountDirectoryRegistrar)
if AccountDirectorySchemaVersion < 1 {
t.Fatal("directory schema version must be explicit")
}
}
+8 -8
View File
@@ -36,9 +36,11 @@ import (
// 分区组件自身的命令面 fail-closed)。
// 2.12.0-experimental: optional ProjectHost/ProjectLifecycleRegistry and trusted
// ResourceAuthzQuery.Source for per-document publication authorization.
// 2.13.0-experimental:InProc.HostOptions / CLIHook,以及 MergeHostOptions、
// 2.13.0-experimental: optional AccountDirectoryProvider and trusted in-process
// AccountDirectoryRegistrar for scoped, current account candidate decisions.
// 2.14.0-experimental:InProc.HostOptions / CLIHook,以及 MergeHostOptions、
// RegisterCLIFlags、RunCLIHooks(组合二进制把插件声明并进基座启动,不覆盖显式参数)。
const SurfaceVersion = "2.13.0-experimental"
const SurfaceVersion = "2.14.0-experimental"
// ErrNotLinked 表示进程没有空白导入 link 包,Main 无法启动服务。
var ErrNotLinked = errors.New("extension: runner not linked; blank-import agent-system-backend/pkg/extension/link")
@@ -101,17 +103,15 @@ type AccountEntry struct {
Valid bool `json:"valid"`
}
// AccountLister EXT-19 可选能力接口(实现由宿主注入):游标分页列举系统账号。
// 未实现时下游回落现行行为(成员添加不做账号有效性校验);实现后仅用于
// 授权管理读面——不授予任何写权。
// AccountLister EXT-19 旧接口:仅有受信系统管理员分发上下文时可读取。
// 新领域消费者必须使用 AccountDirectoryProvider,不得以本接口作为降级路径。
type AccountLister interface {
ListAccounts(cursor string, limit int) (entries []AccountEntry, next string, err error)
}
// AccountSearcher EXT-19 扩展可选能力(P3,2026-10-13):按账号或姓名检索账号
// 目录,供人员选择器的输入搜索。query 匹配账号子串或姓名子串(大小写不敏感;
// 空串=不限,返回首页候选)。仅授权管理读面使用;未实现时下游回落
// AccountLister 分页浏览。
// 目录。与 AccountLister 相同,它不含领域定位,只允许受信系统管理员上下文;
// 新领域消费者不得回落到此接口。
type AccountSearcher interface {
SearchAccounts(query string, limit int) (entries []AccountEntry, err error)
}
+11 -1
View File
@@ -7,7 +7,9 @@ import (
// ProjectHost is an optional actor-scoped host projection capability. Metadata
// can be shared by an authorizer; registered-lifecycle metadata masks credentials.
// OwnedProject remains an owner-only management lookup; mutations remain owner-only.
// OwnedProject remains an owner-only management lookup. A request-bound
// delegated projection may carry a separate real operator and fixed creator;
// its final Store transaction checks both accounts and the persisted owner.
type ProjectHost interface {
Owner() string
OwnedProjects() ([]core.Project, error)
@@ -18,6 +20,14 @@ type ProjectHost interface {
ProjectNameAvailable(name, exceptID string) (bool, error)
}
// DelegatedProjectProjectionHost is an optional capability of a current,
// authenticated RDM request Host. It derives a single-project creator
// projection while retaining the original operator for final account fencing.
// A globally reconstructed ScopedHost does not implement this capability.
type DelegatedProjectProjectionHost interface {
ProjectProjectionForCreator(creator, projectID string) (ProjectHost, 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.
+21
View File
@@ -1,3 +1,22 @@
AccountDirectoryEntry
AccountDirectoryLookup
AccountDirectoryModeExact
AccountDirectoryModeFuzzy
AccountDirectoryPage
AccountDirectoryProvider
AccountDirectoryProvider.LookupAccountDirectory
AccountDirectoryProvider.LookupAccountDirectoryOwner
AccountDirectoryProvider.QueryAccountDirectory
AccountDirectoryPurposeDocumentDuties
AccountDirectoryPurposeProjectMembers
AccountDirectoryPurposeSystemUsers
AccountDirectoryQuery
AccountDirectoryRegistrar
AccountDirectoryRegistrar.RegisterAccountDirectoryScopeResolver
AccountDirectorySchemaVersion
AccountDirectoryScope
AccountDirectoryScopeRequest
AccountDirectoryScopeResolver
AccountEntry
AccountLister
AccountLister.ListAccounts
@@ -8,6 +27,8 @@ AdminProvider.IsAdmin
AdminZoneSpec
CLIHook
Compiled
DelegatedProjectProjectionHost
DelegatedProjectProjectionHost.ProjectProjectionForCreator
ErrNotLinked
Handler
Host