agent-exec transport 8315c523cef26780543b9b2130f4f4a4/5c8333ec4e92
governance / governance (pull_request) Successful in 14s
ci / vet-and-test (pull_request) Successful in 1m18s

This commit is contained in:
agent-exec-transport
2026-10-19 12:21:32 +00:00
parent afb65c5678
commit e942a35b73
13 changed files with 1357 additions and 7 deletions
+158
View File
@@ -0,0 +1,158 @@
package extension
import (
"context"
"errors"
"net/http"
)
const CapabilityExplainSchemaVersion = 1
// Capability sources name where an allowed action comes from.
const (
CapabilitySourceRole = "role"
CapabilitySourceSystemGrant = "system-grant"
CapabilitySourceBootstrapAdmin = "bootstrap-admin"
)
// Capability scopes name where an allowed action applies.
const (
CapabilityScopeSystem = "system"
CapabilityScopeOwnProjects = "own-projects"
CapabilityScopeMemberProjects = "member-projects"
)
// CapabilitySubject identifies the Host-bound actor and the authority snapshot
// an explanation was computed from. It never names another account.
type CapabilitySubject struct {
SchemaVersion int `json:"schemaVersion"`
UserID string `json:"userId"`
Owner string `json:"owner"`
AuthorityEpoch int64 `json:"authorityEpoch"`
RoleID string `json:"roleId,omitempty"`
RoleName string `json:"roleName,omitempty"`
RoleEnabled bool `json:"roleEnabled"`
}
// CapabilityActionExplain reports one action's effective decision. Source is
// set only when Allowed; LinkedVia names the action that links it; Conflict
// marks a linked action that the enabled role caps off.
type CapabilityActionExplain struct {
ActionID string `json:"actionId"`
Scope string `json:"scope"`
Allowed bool `json:"allowed"`
CapAllowed bool `json:"capAllowed"`
Source string `json:"source,omitempty"`
LinkedVia string `json:"linkedVia,omitempty"`
Conflict bool `json:"conflict,omitempty"`
Reason string `json:"reason,omitempty"`
}
// CapabilityExplain is the creator explanation: project.create, the linked
// own-project management actions, their conflicts and the migration note.
type CapabilityExplain struct {
CapabilitySubject
ProjectCreate CapabilityActionExplain `json:"projectCreate"`
Linked []CapabilityActionExplain `json:"linked"`
Conflicts []string `json:"conflicts"`
MigrationNote string `json:"migrationNote,omitempty"`
}
// MemberBasicCollaborate explains the role defaults a member brings to the
// projects they are a member of. Bootstrap admin is not a content source.
type MemberBasicCollaborate struct {
CapabilitySubject
Actions []CapabilityActionExplain `json:"actions"`
}
// DocumentDutyQualify reports whether the role cap lets a document duty be
// assigned to the actor and whether the role grants it by default.
type DocumentDutyQualify struct {
ActionID string `json:"actionId"`
Qualified bool `json:"qualified"`
DefaultAllowed bool `json:"defaultAllowed"`
Source string `json:"source,omitempty"`
Reason string `json:"reason,omitempty"`
}
type DocumentQualify struct {
CapabilitySubject
Duties []DocumentDutyQualify `json:"duties"`
}
// CapabilityExplainProvider is optional on a trusted dispatch Host. Every
// method explains only the Host-bound actor; callers cannot name another one.
type CapabilityExplainProvider interface {
CurrentCapabilityExplain(context.Context) (CapabilityExplain, error)
CurrentMemberBasicCollaborate(context.Context) (MemberBasicCollaborate, error)
CurrentDocumentQualify(context.Context) (DocumentQualify, error)
}
// Work operations whose failures are categorized for clients.
const (
WorkOperationProjectCreate = "project.create"
WorkOperationMemberAdd = "project.member.add"
WorkOperationConversation = "conversation.work"
)
// Work failure reasons. Each maps to exactly one HTTP status.
const (
WorkFailureDuplicateName = "duplicate-name"
WorkFailurePolicyRejected = "policy-rejected"
WorkFailureMappingNotReady = "mapping-not-ready"
WorkFailureServiceUnavailable = "service-unavailable"
)
// WorkFailure is a categorized refusal a provider may return from project
// create, member add or conversation work. Its text carries only the operation
// and reason, never an account, project or name.
type WorkFailure struct {
Operation string `json:"operation"`
Reason string `json:"reason"`
}
func (f WorkFailure) Error() string { return f.Operation + ": " + f.Reason }
// StatusCode returns 0 for an unknown reason so it is never trusted as a status.
func (f WorkFailure) StatusCode() int {
switch f.Reason {
case WorkFailureDuplicateName:
return http.StatusConflict
case WorkFailurePolicyRejected:
return http.StatusForbidden
case WorkFailureMappingNotReady, WorkFailureServiceUnavailable:
return http.StatusServiceUnavailable
}
return 0
}
// CategorizeWorkFailure classifies a provider error for operation. A known
// WorkFailure keeps its reason; a coded 403/503 (and 409 outside conversation
// work) maps to its category; a deadline is service-unavailable. Anything else
// is left uncategorized so the caller keeps its existing not-found handling.
func CategorizeWorkFailure(operation string, err error) (WorkFailure, bool) {
if err == nil {
return WorkFailure{}, false
}
var failure WorkFailure
if errors.As(err, &failure) && failure.StatusCode() != 0 {
return WorkFailure{Operation: operation, Reason: failure.Reason}, true
}
var coded interface{ StatusCode() int }
if errors.As(err, &coded) {
switch coded.StatusCode() {
case http.StatusForbidden:
return WorkFailure{Operation: operation, Reason: WorkFailurePolicyRejected}, true
case http.StatusConflict:
if operation != WorkOperationConversation {
return WorkFailure{Operation: operation, Reason: WorkFailureDuplicateName}, true
}
case http.StatusServiceUnavailable:
return WorkFailure{Operation: operation, Reason: WorkFailureServiceUnavailable}, true
}
}
if errors.Is(err, context.DeadlineExceeded) {
return WorkFailure{Operation: operation, Reason: WorkFailureServiceUnavailable}, true
}
return WorkFailure{}, false
}
+56
View File
@@ -0,0 +1,56 @@
package extension
import (
"context"
"errors"
"fmt"
"net/http"
"strings"
"testing"
)
type codedTestError int
func (e codedTestError) Error() string { return "provider detail about other-owner" }
func (e codedTestError) StatusCode() int { return int(e) }
func TestWorkFailureStatusAndTextCarryNoIdentifiers(t *testing.T) {
for reason, code := range map[string]int{
WorkFailureDuplicateName: http.StatusConflict,
WorkFailurePolicyRejected: http.StatusForbidden,
WorkFailureMappingNotReady: http.StatusServiceUnavailable,
WorkFailureServiceUnavailable: http.StatusServiceUnavailable,
"invented": 0,
} {
failure := WorkFailure{Operation: WorkOperationMemberAdd, Reason: reason}
if failure.StatusCode() != code || failure.Error() != WorkOperationMemberAdd+": "+reason {
t.Fatalf("%s: status=%d text=%q", reason, failure.StatusCode(), failure.Error())
}
}
}
func TestCategorizeWorkFailure(t *testing.T) {
cases := []struct {
op string
err error
reason string
ok bool
}{
{WorkOperationProjectCreate, fmt.Errorf("wrap: %w", WorkFailure{Reason: WorkFailureMappingNotReady}), WorkFailureMappingNotReady, true},
{WorkOperationProjectCreate, codedTestError(409), WorkFailureDuplicateName, true},
{WorkOperationMemberAdd, codedTestError(403), WorkFailurePolicyRejected, true},
{WorkOperationConversation, codedTestError(503), WorkFailureServiceUnavailable, true},
{WorkOperationConversation, codedTestError(409), "", false},
{WorkOperationConversation, context.DeadlineExceeded, WorkFailureServiceUnavailable, true},
{WorkOperationMemberAdd, codedTestError(404), "", false},
{WorkOperationMemberAdd, WorkFailure{Reason: "invented"}, "", false},
{WorkOperationMemberAdd, errors.New("plain"), "", false},
{WorkOperationMemberAdd, nil, "", false},
}
for _, tc := range cases {
failure, ok := CategorizeWorkFailure(tc.op, tc.err)
if ok != tc.ok || failure.Reason != tc.reason || (ok && failure.Operation != tc.op) || strings.Contains(failure.Error(), "other-owner") {
t.Fatalf("%s %v: failure=%+v ok=%v", tc.op, tc.err, failure, ok)
}
}
}
+5 -1
View File
@@ -67,7 +67,11 @@ import (
// reject stale role snapshots.
// 2.26.0-experimental: IAM link U1 RolePolicySnapshot.Linked carries the
// Backend-computed project.create own-project management link and its source.
const SurfaceVersion = "2.26.0-experimental"
// 2.27.0-experimental: IAM link U3 optional CapabilityExplainProvider
// (CapabilityExplain / MemberBasicCollaborate / DocumentQualify for the
// Host-bound actor) and categorized WorkFailure for create, member add and
// conversation work.
const SurfaceVersion = "2.27.0-experimental"
// ErrNotLinked 表示进程没有空白导入 link 包,Main 无法启动服务。
var ErrNotLinked = errors.New("extension: runner not linked; blank-import agent-system-backend/pkg/extension/link")
+1
View File
@@ -49,6 +49,7 @@ type Handler func(ctx context.Context, h Host, req Request) (any, error)
// - WorkTaskHost — X1:分发期建立可恢复的受信项目任务绑定
// - HumanDecisionVerifier — 本次分发是否为认证人对确切待确认卡的响应
// - RolePolicyProvider — Backend 权威职位与动作默认/上限
// - CapabilityExplainProvider — 当前分发主体的创建者/成员/文档职责能力解释
type Host interface {
// PluginID 返回注册处理器的插件标识(形如 rdm)。
PluginID() string
+26
View File
@@ -26,9 +26,26 @@ AdminProvider
AdminProvider.IsAdmin
AdminZoneSpec
CLIHook
CapabilityActionExplain
CapabilityExplain
CapabilityExplainProvider
CapabilityExplainProvider.CurrentCapabilityExplain
CapabilityExplainProvider.CurrentDocumentQualify
CapabilityExplainProvider.CurrentMemberBasicCollaborate
CapabilityExplainSchemaVersion
CapabilityScopeMemberProjects
CapabilityScopeOwnProjects
CapabilityScopeSystem
CapabilitySourceBootstrapAdmin
CapabilitySourceRole
CapabilitySourceSystemGrant
CapabilitySubject
CategorizeWorkFailure
Compiled
DelegatedProjectProjectionHost
DelegatedProjectProjectionHost.ProjectProjectionForCreator
DocumentDutyQualify
DocumentQualify
ErrNotLinked
ErrProjectAuditDenied
ErrProjectAuditLinkConflict
@@ -66,6 +83,7 @@ InProc
IntentSpec
LinkedCapabilities
Main
MemberBasicCollaborate
MergeHostOptions
NewHostClient
NewScopedHost
@@ -240,6 +258,11 @@ WorkCompletionHost
WorkCompletionHost.CompleteForWork
WorkCompletionRequest
WorkCompletionResult
WorkFailure
WorkFailureDuplicateName
WorkFailureMappingNotReady
WorkFailurePolicyRejected
WorkFailureServiceUnavailable
WorkObservation
WorkObservationAccepted
WorkObservationDenied
@@ -247,5 +270,8 @@ WorkObservationPartial
WorkObservationPending
WorkObservationResult
WorkObservationState
WorkOperationConversation
WorkOperationMemberAdd
WorkOperationProjectCreate
WorkTaskHost
WorkTaskHost.TaskCreateForWork