Files
Agent-System-Backend/pkg/extension/project_work.go
T
weichen 7cee124b8f Capture actual project work sources before reuse
Why: Project retrieval hits, tool results, and saved visible analysis must be individually traceable and fully analyzed before later project actions consume them.

Evidence: RED tests first showed missing run-source capture, tool-result capture, and run-wide preflight; go test -p 1 ./... and go build ./... passed; isolated PostgreSQL source, migration, audit, and context tests passed.

Impact: Persist actual run-bound sources with current-status checks and exact manifests behind the disabled X3 assembly gate; no live product path, migration, deployment, or release is enabled.

Refs: TT-N03, TT-R01; approved X3 stage-five plan r1
2026-10-14 16:25:22 +00:00

131 lines
6.0 KiB
Go

package extension
import (
"context"
"time"
core "agent-system-core"
)
// WorkObservation is built by the host from a committed message or an actual
// retrieval, tool, or visible analysis result. Callers and models cannot supply
// Actor, ProjectID, source identity, or the source authorization epoch.
// RunID may be empty for a newly committed user message or chat command.
type WorkObservation struct {
EventID string `json:"eventId"`
Phase string `json:"phase"`
Actor string `json:"actor"`
Owner string `json:"owner"`
ProjectID core.ID `json:"projectId"`
ConversationID core.ID `json:"conversationId,omitempty"`
RunID core.ID `json:"runId,omitempty"`
SourceKind string `json:"sourceKind"`
SourceID string `json:"sourceId"`
SourceVersion string `json:"sourceVersion"`
SourceSHA256 string `json:"sourceSha256"`
PartIndex int `json:"partIndex"`
SpanStart int `json:"spanStart"` // UTF-8 byte offset in the exact source
SpanEnd int `json:"spanEnd"`
Text string `json:"text,omitempty"` // only the actual authorized fragment
Scope string `json:"scope"` // private|project|controlled
Delivery string `json:"delivery"` // complete|summary-only|partial
DeliveryReason string `json:"deliveryReason,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
AuthorizationEpoch string `json:"authorizationEpoch"`
ObservedAt time.Time `json:"observedAt"`
}
type WorkObservationState string
const (
WorkObservationAccepted WorkObservationState = "accepted" // durable intake, analysis may be pending
WorkObservationPending WorkObservationState = "pending"
WorkObservationDenied WorkObservationState = "denied"
WorkObservationPartial WorkObservationState = "partial"
)
type WorkObservationResult struct {
ReceiptID string `json:"receiptId"`
State WorkObservationState `json:"state"`
Reason string `json:"reason,omitempty"`
}
// ProjectWorkSource is one exact part consumed by the pending action. A hash
// mismatch or missing part must block Preflight rather than use a stale receipt.
type ProjectWorkSource struct {
EventID string `json:"eventId"`
SourceKind string `json:"sourceKind"`
SourceID string `json:"sourceId"`
SourceVersion string `json:"sourceVersion"`
SourceSHA256 string `json:"sourceSha256"`
PartIndex int `json:"partIndex"`
}
// ProjectWorkAction is constructed by the host immediately before model use,
// project command dispatch, or another project-affecting action. ProjectID is
// checked against the stored conversation or run, never trusted from Params.
type ProjectWorkAction struct {
Actor string `json:"actor"`
Owner string `json:"owner"`
ProjectID core.ID `json:"projectId"`
ConversationID core.ID `json:"conversationId,omitempty"`
RunID core.ID `json:"runId,omitempty"`
Action string `json:"action"`
TargetID string `json:"targetId,omitempty"`
TargetVersion string `json:"targetVersion,omitempty"`
Manifest []ProjectWorkSource `json:"manifest"`
SourceWatermark string `json:"sourceWatermark"`
}
type ProjectWorkDecisionState string
const (
ProjectWorkProceed ProjectWorkDecisionState = "proceed"
ProjectWorkClarify ProjectWorkDecisionState = "clarify"
ProjectWorkPending ProjectWorkDecisionState = "pending"
ProjectWorkDenied ProjectWorkDecisionState = "denied"
)
// ProjectWorkDecision is a domain decision, not a replacement for a controlled
// document's separate one-time human approval. A successful Observe ACK cannot
// be interpreted as ProjectWorkProceed.
type ProjectWorkDecision struct {
State ProjectWorkDecisionState `json:"state"`
DecisionID string `json:"decisionId"`
RiskID string `json:"riskId,omitempty"`
RiskRevision string `json:"riskRevision,omitempty"`
SourceWatermark string `json:"sourceWatermark,omitempty"`
Reason string `json:"reason,omitempty"`
Question string `json:"question,omitempty"`
Options []string `json:"options,omitempty"`
}
// ProjectRiskAnswer comes from a current authenticated session and persisted
// risk card. An answer is applied only after the provider commits a CAS receipt.
type ProjectRiskAnswer struct {
Actor string `json:"actor"`
Owner string `json:"owner"`
ProjectID core.ID `json:"projectId"`
RiskID string `json:"riskId"`
RiskRevision string `json:"riskRevision"`
Answer string `json:"answer"`
IdempotencyKey string `json:"idempotencyKey"`
}
// ProjectWorkObserver is owned by the project domain. Backend must check a
// returned decision against the exact current source manifest and must fail
// closed on missing provider, timeout, or partial analysis when project facts
// are required. RDM remains the sole persistent risk-state owner.
type ProjectWorkObserver struct {
Observe func(context.Context, WorkObservation) (WorkObservationResult, error)
Preflight func(context.Context, ProjectWorkAction) (ProjectWorkDecision, error)
Respond func(context.Context, ProjectRiskAnswer) (ProjectWorkDecision, error)
}
// ProjectWorkObservationRegistry is optional so older in-process extensions do
// not acquire a mandatory Registry method. Declarative plugins cannot register
// a callback. The host accepts one trusted RDM provider.
type ProjectWorkObservationRegistry interface {
RegisterProjectWorkObserver(ProjectWorkObserver) error
}