Files
Agent-System/internal/app/docadvance/consumer.go
T
WeiCHEN 381b56dafc refactor(docgov): converge shared lifecycle vocab into contracts
Introduce internal/contracts/vocabulary for state/role/error sentinels,
alias through docgov, and retarget hot production consumers so
coordinator no longer imports docgov for vocabulary-only use.
2026-09-22 21:44:57 +08:00

107 lines
3.0 KiB
Go

package docadvance
import (
"context"
"encoding/json"
"github.com/jackc/pgx/v5"
"chat-agent-system/internal/contracts/vocabulary"
"chat-agent-system/internal/docgov"
"chat-agent-system/internal/events"
"chat-agent-system/internal/notification"
"chat-agent-system/internal/projectinstance"
"chat-agent-system/internal/projectworkflow"
)
// EventConsumer applies document.approved onto workflow task bookkeeping.
// Closures remain the unique writer until live cutover.
type EventConsumer struct {
Inst *projectinstance.Service
Docs DocumentLookup
PW ProjectLookup
Notify *notification.Service
Env string
}
type DocumentLookup interface {
GetDocument(ctx context.Context, docID string) (*docgov.DocumentRecord, error)
}
type ProjectLookup interface {
GetProject(ctx context.Context, projectID, userID int64) (*projectworkflow.Project, error)
}
func (c *EventConsumer) HandleLease(ctx context.Context, lease events.Lease, tx pgx.Tx) error {
if c == nil || c.Inst == nil {
return events.ErrUnknownConsumer
}
principal := events.ServicePrincipal{
ID: "advance-" + c.Env, Environment: c.Env, Consumer: events.ConsumerWorkflowAdvance,
Actions: []string{"workflow.advance"},
}
if err := events.AuthorizePrincipal(principal, lease.Environment, lease.ProjectID, "workflow.advance"); err != nil {
return events.ErrQuarantine
}
if lease.EventName != events.EventApproved {
return nil
}
docID := lease.Envelope.AggregateID
taskKey := ""
var payload map[string]any
_ = json.Unmarshal(lease.Envelope.Payload, &payload)
if payload != nil {
if id, ok := payload["doc_id"].(string); ok && id != "" {
docID = id
}
if k, ok := payload["task_key"].(string); ok {
taskKey = k
}
}
if c.Docs == nil || docID == "" {
return events.ErrQuarantine
}
doc, err := c.Docs.GetDocument(ctx, docID)
if err != nil || doc == nil {
return events.ErrQuarantine
}
if doc.ProjectID != lease.ProjectID {
return events.ErrQuarantine
}
if doc.CurrentState != vocabulary.StateApproved {
return events.ErrQuarantine
}
if taskKey == "" {
taskKey = doc.TaskKey
}
if taskKey == "" {
return events.ErrQuarantine
}
ctx = projectinstance.WithTx(ctx, tx)
result, err := c.Inst.HandleDocumentApproved(ctx, doc.ProjectID, taskKey)
if err != nil {
return err
}
if result == nil || !result.StageReady {
return nil
}
if c.Notify == nil || c.PW == nil {
return nil
}
project, err := c.PW.GetProject(ctx, doc.ProjectID, 0)
if err != nil || project == nil || project.CreatorUserID <= 0 {
return events.ErrQuarantine
}
return c.Notify.EmitVisibleStageReady(ctx, tx, lease.EventID, project.CreatorUserID, notification.StageReadyPayload{
ProjectID: doc.ProjectID,
WorkflowInstanceID: result.InstanceID,
StageInstanceID: result.StageInstanceID,
StageKey: result.StageKey,
StageName: result.StageName,
ConfigRevision: result.ConfigRevision,
RequiredDocCount: result.RequiredDocCount,
ApprovedDocCount: result.ApprovedDocCount,
})
}