Add scoped project command audit read slice

Why: Project audit cannot use owner-wide logs or current conversation project state to establish trusted attribution.

Evidence: RED cases were observed locally; go test ./pkg/extension ./internal/store ./internal/app -count=1 passed on this tree (SELF).

Impact: Add optional X2 read and audit.read callback, immutable project/name snapshots, safe owner-only projection, and explicit unsupported coverage. Cross-member publication remains pending.

Refs: Stage 5 W9; Stage 6 X2 public extension request
This commit is contained in:
weichen
2026-10-14 03:42:28 +00:00
parent 5d5fc9891c
commit c8dd169a1f
12 changed files with 565 additions and 11 deletions
+3 -1
View File
@@ -34,7 +34,9 @@ import (
// 分区组件自身的命令面 fail-closed)。
// 2.12.0-experimental: optional ProjectHost/ProjectLifecycleRegistry and trusted
// ResourceAuthzQuery.Source for per-document publication authorization.
const SurfaceVersion = "2.12.0-experimental"
// 2.13.0-experimental: optional ProjectAuditHost and ProjectAuditRegistry (X2
// project-bound command-audit read slice and domain audit.read callback).
const SurfaceVersion = "2.13.0-experimental"
// ErrNotLinked 表示进程没有空白导入 link 包,Main 无法启动服务。
var ErrNotLinked = errors.New("extension: runner not linked; blank-import agent-system-backend/pkg/extension/link")
+75
View File
@@ -0,0 +1,75 @@
package extension
import (
"context"
"errors"
"time"
core "agent-system-core"
)
// ProjectAuditAuthorizer answers the domain's audit.read decision for the
// authenticated actor and a project identified by trusted host storage.
// A project ID supplied by a caller is a locator, never an authorization.
type ProjectAuditAuthorizer func(actor string, projectID core.ID) (bool, error)
// ProjectAuditRegistry is optional so older extension registries remain valid.
// Only an in-process domain plugin can register this callback.
type ProjectAuditRegistry interface {
RegisterProjectAuditAuthorizer(ProjectAuditAuthorizer) error
}
var (
ErrProjectAuditDenied = errors.New("extension: project audit denied")
ErrProjectAuditUnsupported = errors.New("extension: project audit unsupported")
)
// ProjectAuditQuery reads one project-bound command-audit slice. Offset is a
// host source offset, not a cross-domain event sequence. A nonzero BeforeOffset
// must be paired with the CutoffOffset returned on the first page.
type ProjectAuditQuery struct {
ProjectID core.ID `json:"projectId"`
BeforeOffset int64 `json:"beforeOffset,omitempty"`
CutoffOffset int64 `json:"cutoffOffset,omitempty"`
Limit int `json:"limit,omitempty"`
}
// HostAuditReceipt is a server-projected receipt, never a raw audit payload.
// ActorName is the name recorded with this command; old rows have no snapshot.
type HostAuditReceipt struct {
SourceOffset int64 `json:"sourceOffset"`
Time time.Time `json:"time"`
ActorID string `json:"actorId,omitempty"`
ActorName string `json:"actorName,omitempty"`
ActorNameStatus string `json:"actorNameStatus"`
CommandID string `json:"commandId"`
State string `json:"state"`
OperationID string `json:"operationId,omitempty"`
ConversationID string `json:"conversationId"`
}
// ProjectAuditCoverage states the exact scope of this first X2 slice. Missing
// host sources and old unbound rows cannot be inferred from owner-wide audit.
type ProjectAuditCoverage struct {
CommandAudit string `json:"commandAudit"`
CrossMember string `json:"crossMember"`
LegacyProjectBinding string `json:"legacyProjectBinding"`
RunRelations string `json:"runRelations"`
ModelReceipts string `json:"modelReceipts"`
BackgroundReceipts string `json:"backgroundReceipts"`
ResourceBodies string `json:"resourceBodies"`
}
type ProjectAuditPage struct {
Receipts []HostAuditReceipt `json:"receipts"`
NextOffset int64 `json:"nextOffset,omitempty"`
CutoffOffset int64 `json:"cutoffOffset"`
Coverage ProjectAuditCoverage `json:"coverage"`
}
// ProjectAuditHost is an optional, actor-bound read capability. A consumer must
// treat a failed type assertion as unsupported; an authorized page still reports
// partial coverage explicitly.
type ProjectAuditHost interface {
QueryProjectAudit(context.Context, ProjectAuditQuery) (ProjectAuditPage, error)
}
+11
View File
@@ -8,6 +8,8 @@ AdminProvider.IsAdmin
AdminZoneSpec
Compiled
ErrNotLinked
ErrProjectAuditDenied
ErrProjectAuditUnsupported
Handler
Host
Host.ArtifactContent
@@ -25,6 +27,7 @@ Host.ParseDocument
Host.PluginID
Host.TaskCreate
Host.TaskUpdate
HostAuditReceipt
HostClient
InProc
IntentSpec
@@ -32,6 +35,14 @@ Main
NewHostClient
NewScopedHost
Options
ProjectAuditAuthorizer
ProjectAuditCoverage
ProjectAuditHost
ProjectAuditHost.QueryProjectAudit
ProjectAuditPage
ProjectAuditQuery
ProjectAuditRegistry
ProjectAuditRegistry.RegisterProjectAuditAuthorizer
ProjectHost
ProjectHost.EnsureProject
ProjectHost.OwnedProject