Files
Agent-System-Backend/pkg/extension/project_audit.go
T
agent-exec-transport 97f60c1df6
governance / governance (pull_request) Successful in 15s
ci / vet-and-test (pull_request) Successful in 1m20s
agent-exec transport f461d74086dd6a80db72f83748f34b04/128fee1609ad
2026-10-22 03:41:15 +00:00

211 lines
9.8 KiB
Go

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")
ErrProjectAuditLinkConflict = errors.New("extension: project audit link conflict")
ErrProjectAuditPending = errors.New("extension: committed host audit receipt pending")
)
// ProjectAuditLinkRequest binds one host command receipt to an already
// committed domain fact. Owner is never caller supplied; the host handle and
// stored audit row establish it. HostAuditOffset=0 asks the host to resolve a
// unique committed row by its trusted owner/project/OperationID, which lets an
// outbox wait for the bus audit commit after the domain handler returns. This
// first slice supports domain-commit only.
type ProjectAuditLinkRequest struct {
ProjectID core.ID `json:"projectId"`
HostAuditOffset int64 `json:"hostAuditOffset"`
SourceKind string `json:"sourceKind"`
SourceID string `json:"sourceId"`
OperationID string `json:"operationId"`
ResourceKind string `json:"resourceKind"`
ResourceID string `json:"resourceId"`
RunID string `json:"runId,omitempty"`
DisclosureClass string `json:"disclosureClass"`
IdempotencyKey string `json:"idempotencyKey"`
}
// ProjectAuditLinkProof contains the caller request plus identity and source
// fields re-read from trusted host storage. The domain verifier must check its
// committed source event and operation/resource attribution, not a model claim.
type ProjectAuditLinkProof struct {
ProjectAuditLinkRequest
Owner string
}
type ProjectAuditLinkVerifier func(context.Context, ProjectAuditLinkProof) (bool, error)
// ProjectAuditLinkedResource is the stored, immutable link presented to the
// current domain disclosure policy. A caller ID alone never authorizes it.
type ProjectAuditLinkedResource struct {
ProjectID core.ID
Owner string
SourceKind string
SourceID string
OperationID string
ResourceKind string
ResourceID string
RunID string
DisclosureClass string
}
type ProjectAuditDisclosure struct {
Metadata bool
Body bool
}
// ProjectAuditDisclosureAuthorizer answers one per-object decision for the
// authenticated actor on a stored, immutable link. Metadata and Body are
// separate authorities: the host never substitutes one for the other, and it
// never answers a body request from a metadata-only right.
//
// TODO(RDM consumer, out of this repository): the decision itself — the current
// project audit.read grant, the document read duty plus stage scope that carries
// 正文权, and any export authority — belongs to the domain plugin that registers
// this callback. The Backend only refuses, trims, and reports coverage; it must
// not grow a local account/project policy to answer in the domain's place. A
// later RDM unit implements the callbacks and can then widen Coverage
// (Export, ResourceBodies, RunRelations) from this base's current honest
// "unsupported" statements.
type ProjectAuditDisclosureAuthorizer func(context.Context, string, ProjectAuditLinkedResource) (ProjectAuditDisclosure, error)
// ProjectAuditPublisherFactory is issued only by successful verifier
// registration. A domain outbox may retain it and bind an owner for each job;
// owner and committed-source attribution are checked again when publishing.
// NewScopedHost(owner, pluginID) cannot mint this capability.
type ProjectAuditPublisherFactory interface {
ForOwner(owner string) (ProjectAuditPublisher, error)
}
// ProjectAuditLinkRegistry is optional. Only the registering compiled domain
// plugin receives a publisher factory; string plugin IDs carry no authority.
type ProjectAuditLinkRegistry interface {
RegisterProjectAuditLinkVerifier(ProjectAuditLinkVerifier) (ProjectAuditPublisherFactory, error)
RegisterProjectAuditDisclosureAuthorizer(ProjectAuditDisclosureAuthorizer) error
}
type ProjectAuditLinkReceipt struct {
LinkID int64 `json:"linkId"`
HostAuditOffset int64 `json:"hostAuditOffset"`
}
type ProjectAuditPublisher interface {
PublishProjectAuditLink(context.Context, ProjectAuditLinkRequest) (ProjectAuditLinkReceipt, error)
}
// 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. PostgreSQL
// pages are served only while the installed generator, sequence, and all-writer
// database guards prove a
// committed prefix; otherwise the host rejects the read instead of claiming a
// fixed snapshot. See Coverage.CutoffConsistency.
//
// A read is keyed by the trusted project, the authenticated actor, the verified
// resource and run, and the source offset plus cutoff. Every caller-supplied
// field is a locator, never an authorization: the host re-checks it against
// stored host facts and the domain callbacks before a receipt is projected.
type ProjectAuditQuery struct {
ProjectID core.ID `json:"projectId"`
BeforeOffset int64 `json:"beforeOffset,omitempty"`
CutoffOffset int64 `json:"cutoffOffset,omitempty"`
Limit int `json:"limit,omitempty"`
// Linked* select one exact committed link. This avoids enumerating hidden
// private links or leaking their counts before cursor policy is complete.
LinkedSourceKind string `json:"linkedSourceKind,omitempty"`
LinkedSourceID string `json:"linkedSourceId,omitempty"`
LinkedHostOffset int64 `json:"linkedHostOffset,omitempty"`
// LinkedResourceKind/LinkedResourceID/LinkedRunID narrow the same exact
// locator to the verified binding. A value that disagrees with stored host
// state denies the read; it never widens it and never stands in for the
// domain's current decision on that object.
LinkedResourceKind string `json:"linkedResourceKind,omitempty"`
LinkedResourceID string `json:"linkedResourceId,omitempty"`
LinkedRunID string `json:"linkedRunId,omitempty"`
// IncludeBody asks for the per-object body projection of one verified link.
// The host asks the domain disclosure authorizer again for that object: a
// metadata-only right serves metadata only, and a request without a verified
// object binding is reported unsupported instead of a silently trimmed page.
IncludeBody bool `json:"includeBody,omitempty"`
// Export asks for a whole-history export. No base capability serves one, so
// the host refuses instead of degrading into a partial paginated read that
// would claim a coverage it cannot establish. See Coverage.Export.
Export bool `json:"export,omitempty"`
}
// ProjectAuditReceiptResource names the object one verified link attributes a
// receipt to. The host copies it from the stored binding and only when the
// domain granted the per-object body right; it is never derived from caller
// input and never carries resource content.
type ProjectAuditReceiptResource struct {
Kind string `json:"kind"`
ID string `json:"id"`
RunID string `json:"runId,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.
// Resource appears only for a body-permitted verified link.
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"`
Resource *ProjectAuditReceiptResource `json:"resource,omitempty"`
}
// 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"`
CutoffConsistency string `json:"cutoffConsistency"`
CrossMember string `json:"crossMember"`
LegacyProjectBinding string `json:"legacyProjectBinding"`
RunRelations string `json:"runRelations"`
ModelReceipts string `json:"modelReceipts"`
BackgroundReceipts string `json:"backgroundReceipts"`
ResourceBodies string `json:"resourceBodies"`
// Export states whether a whole-history export is served. An audit grant is
// not an export grant: this slice only pages, and says so.
Export string `json:"export"`
}
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)
}