Why: Project work currently reaches model calls without current authorized project facts or a persisted input receipt. Evidence: SELF go test ./... -count=1, targeted go test -race for W6 paths, go vet ./..., and CGO_ENABLED=0 go build ./cmd/agent-system passed on this change set. Impact: Add optional X1 interfaces, trusted persisted work-task binding, request-scoped context injection, evidence reauthorization, and attempted/responded/unknown manifests. Existing non-project behavior stays available. Refs: TT-C01 TT-C02 TT-C03 TT-C04; stage5 W6; Dev stage6 X1 public extension request r1
58 lines
2.3 KiB
Go
58 lines
2.3 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
)
|
|
|
|
type WorkTaskBinding struct {
|
|
TaskID string
|
|
Owner string
|
|
PluginID string
|
|
ConversationID string
|
|
ProjectID string
|
|
Status string
|
|
}
|
|
|
|
// CreateWorkTask atomically saves the legacy task row and its trusted creation
|
|
// scope. A legacy TaskCreate row has no binding and cannot enter work recovery.
|
|
func (s *Store) CreateWorkTask(owner, pluginID, projectID, conversationID, kind, title string) (BackgroundTask, error) {
|
|
if owner == "" || pluginID == "" || projectID == "" || conversationID == "" || kind == "" {
|
|
return BackgroundTask{}, errors.New("work task scope incomplete")
|
|
}
|
|
tx, err := s.db.BeginTx(context.Background(), nil)
|
|
if err != nil {
|
|
return BackgroundTask{}, err
|
|
}
|
|
defer tx.Rollback()
|
|
now := nowISO()
|
|
t := BackgroundTask{ID: NewID("task"), Owner: owner, Kind: kind, Title: title, Status: "queued", ConversationID: conversationID, CreatedAt: now, UpdatedAt: now}
|
|
if _, err := tx.Exec(s.q(`INSERT INTO background_tasks(id,owner,kind,title,status,progress,error,conversation_id,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,?,?)`),
|
|
t.ID, owner, kind, title, t.Status, 0, "", conversationID, now, now); err != nil {
|
|
return BackgroundTask{}, err
|
|
}
|
|
if _, err := tx.Exec(s.q(`INSERT INTO work_task_bindings(task_id,owner,plugin_id,conversation_id,project_id,created_at) VALUES(?,?,?,?,?,?)`),
|
|
t.ID, owner, pluginID, conversationID, projectID, now); err != nil {
|
|
return BackgroundTask{}, err
|
|
}
|
|
if err := tx.Commit(); err != nil {
|
|
return BackgroundTask{}, err
|
|
}
|
|
return t, nil
|
|
}
|
|
|
|
// WorkTaskScope resolves a task only under its original owner and plugin.
|
|
func (s *Store) WorkTaskScope(owner, pluginID, taskID string) (WorkTaskBinding, error) {
|
|
var b WorkTaskBinding
|
|
if owner == "" || pluginID == "" || taskID == "" {
|
|
return b, ErrNotFound
|
|
}
|
|
err := s.db.QueryRow(s.q(`SELECT b.task_id,b.owner,b.plugin_id,b.conversation_id,b.project_id,t.status FROM work_task_bindings b JOIN background_tasks t ON t.id=b.task_id WHERE b.task_id=? AND b.owner=? AND b.plugin_id=? AND t.owner=? AND t.conversation_id=b.conversation_id`),
|
|
taskID, owner, pluginID, owner).Scan(&b.TaskID, &b.Owner, &b.PluginID, &b.ConversationID, &b.ProjectID, &b.Status)
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return WorkTaskBinding{}, ErrNotFound
|
|
}
|
|
return b, err
|
|
}
|