threads

package
v1.89.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 25, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

Documentation

Overview

Package threads holds the portal feedback-thread data layer: the Thread types and constants, the ThreadStore interface, its PostgreSQL implementation, and the filter/query helpers. It was split out of the portal package (issue #594, package-size gate) so the bulk of the thread substrate lives in a cohesive, independently-reasoned package; the HTTP handlers that drive it remain in portal, which re-exports these symbols under their original names.

Index

Constants

View Source
const (
	ThreadKindComment    = "comment"
	ThreadKindQuestion   = "question"
	ThreadKindCorrection = "correction"
	ThreadKindRating     = "rating"
	ThreadKindApproval   = "approval"
	ThreadKindRejection  = "rejection"
	ThreadKindSuggestion = "suggestion"
)

Thread kinds. A kind is the human-facing classification of a feedback thread; it is stored on the thread and also determines the event_type of the thread's first event (see DeriveFirstEventType).

View Source
const (
	ThreadStatusOpen         = "open"
	ThreadStatusAnswered     = "answered"
	ThreadStatusResolved     = "resolved"
	ThreadStatusWontFix      = "wont_fix"
	ThreadStatusAcknowledged = "acknowledged"
)

Thread statuses.

View Source
const (
	EventTypeComment           = "comment"
	EventTypeStatusChange      = "status_change"
	EventTypeResolution        = "resolution"
	EventTypeRating            = "rating"
	EventTypeApproval          = "approval"
	EventTypeRejection         = "rejection"
	EventTypeValidationRequest = "validation_request"
	EventTypeValidationResult  = "validation_result"
	EventTypeInsightLinked     = "insight_linked"
	EventTypeChangesetLinked   = "changeset_linked"
)

Thread event types. comment is one event type among many; status changes, resolutions, ratings, approvals, and the Phase 2 knowledge-link events all share the same timeline.

View Source
const (
	ValidationStateNone      = "none"
	ValidationStatePending   = "pending"
	ValidationStateValidated = "validated"
	ValidationStateDisputed  = "disputed"
)

Thread validation states (validation/signoff lifecycle lands in Phase 3; the column and these values exist so the substrate is forward-compatible).

View Source
const (
	DefaultThreadLimit = defaultThreadLimit
	MaxThreadLimit     = maxThreadLimit
)

DefaultThreadLimit and MaxThreadLimit are exported for the portal list handlers, which parse and clamp the page size before calling the store.

Variables

This section is empty.

Functions

func DeriveFirstEventType

func DeriveFirstEventType(kind string) string

DeriveFirstEventType maps a thread kind to the event_type its initial event carries: rating/approval/rejection keep their own semantic; everything else (comment/question/correction/suggestion) is a plain comment event. Exported for the portal createThread handler, which owns thread creation.

func NewThreadEventID

func NewThreadEventID() string

NewThreadEventID returns a unique id for a thread event. Exported so callers outside the package (the portal toolkit) can mint event ids for AppendEvent.

func NewThreadID

func NewThreadID(prefix string) string

NewThreadID returns a prefixed unique id (e.g. "thr_<uuid>", "evt_<uuid>").

func ValidThreadKind

func ValidThreadKind(kind string) bool

ValidThreadKind reports whether kind is one of the seven authoring kinds.

func ValidThreadStatus

func ValidThreadStatus(status string) bool

ValidThreadStatus reports whether status is a recognized thread status.

func ValidThreadValidationState

func ValidThreadValidationState(s string) bool

ValidThreadValidationState reports whether s is a recognized validation state.

Types

type Thread

type Thread struct {
	ID                 string          `json:"id" example:"thr_01HK7R8Z"`
	Kind               string          `json:"kind" example:"correction"`
	TargetType         string          `json:"target_type" example:"asset"`
	AssetID            string          `json:"asset_id,omitempty"`
	CollectionID       string          `json:"collection_id,omitempty"`
	PromptID           string          `json:"prompt_id,omitempty"`
	KnowledgePageID    string          `json:"knowledge_page_id,omitempty"`
	Anchor             json.RawMessage `json:"anchor,omitempty" swaggertype:"object"`
	TargetVersion      int             `json:"target_version,omitempty"`
	Title              string          `json:"title,omitempty"`
	AuthorID           string          `json:"author_id"`
	AuthorEmail        string          `json:"author_email" example:"sme@example.com"`
	Status             string          `json:"status" example:"open"`
	RequiresResolution bool            `json:"requires_resolution"`
	ValidationState    string          `json:"validation_state" example:"none"`
	InsightID          string          `json:"insight_id,omitempty"`
	CreatedAt          time.Time       `json:"created_at"`
	UpdatedAt          time.Time       `json:"updated_at"`
	DeletedAt          *time.Time      `json:"deleted_at,omitempty"`
}

Thread is a tracked feedback work item with a target, a lifecycle, and a typed event timeline. Exactly one of AssetID/CollectionID/PromptID is set for asset/collection/prompt target types; all are empty for standalone.

type ThreadEvent

type ThreadEvent struct {
	ID            string          `json:"id" example:"evt_01HK7R8Z"`
	ThreadID      string          `json:"thread_id"`
	EventType     string          `json:"event_type" example:"comment"`
	AuthorID      string          `json:"author_id"`
	AuthorEmail   string          `json:"author_email"`
	Body          string          `json:"body,omitempty"`
	Rating        *int            `json:"rating,omitempty"`
	ParentEventID string          `json:"parent_event_id,omitempty"`
	Metadata      json.RawMessage `json:"metadata,omitempty" swaggertype:"object"`
	CreatedAt     time.Time       `json:"created_at"`
}

ThreadEvent is one entry in a thread's timeline.

type ThreadFilter

type ThreadFilter struct {
	TargetType         string
	AssetID            string
	CollectionID       string
	PromptID           string
	KnowledgePageID    string
	Kind               string
	Status             string
	RequiresResolution *bool
	ValidationState    string
	// AuthorID / AuthorEmail restrict to threads opened by this user (used by the
	// SME "awaiting my validation" worklist). When both are set the match is an
	// OR (id or case-insensitive email), matching how respond-permission resolves
	// the author, so a thread answerable by the caller cannot be missing from
	// their worklist.
	AuthorID    string
	AuthorEmail string
	// TargetAssetIDs / TargetCollectionIDs / TargetPromptIDs restrict to threads
	// on any of the given assets, collections, OR prompts. The practitioner
	// worklist sets assets+collections (artifacts the caller owns or can edit);
	// the feedback activity feed sets all three (every artifact the caller can
	// view). When any is set the match is an OR across the populated target
	// types.
	TargetAssetIDs      []string
	TargetCollectionIDs []string
	TargetPromptIDs     []string
	// IncludeStandalone adds standalone-channel threads to the target-id OR group
	// (used by the agent feedback feed, which spans the caller's artifacts AND the
	// shared general channel). On its own it matches all standalone threads.
	IncludeStandalone bool
	// Unresolved restricts to threads in a non-terminal status (anything other
	// than resolved or wont_fix), i.e. feedback that still needs attention.
	Unresolved bool
	// ExcludeAuthorID / ExcludeAuthorEmail drop threads opened by this user, so a
	// caller's own threads are not surfaced as feedback awaiting their action.
	ExcludeAuthorID    string
	ExcludeAuthorEmail string
	Limit              int
	Offset             int
}

ThreadFilter selects threads for listing.

func (*ThreadFilter) EffectiveLimit

func (f *ThreadFilter) EffectiveLimit() int

EffectiveLimit returns the clamped page size, applying the default when unset.

type ThreadStore

type ThreadStore interface {
	// CreateThread inserts a thread and its first event atomically and returns
	// the stored thread with server-assigned timestamps.
	CreateThread(ctx context.Context, t Thread, first ThreadEvent) (*Thread, error)
	// ListThreads returns matching threads (with timeline aggregates) plus the
	// total count for pagination.
	ListThreads(ctx context.Context, filter ThreadFilter) ([]ThreadWithMeta, int, error)
	// GetThread returns a single non-deleted thread by id.
	GetThread(ctx context.Context, id string) (*Thread, error)
	// ListEvents returns a thread's events oldest-first.
	ListEvents(ctx context.Context, threadID string) ([]ThreadEvent, error)
	// AppendEvent inserts an event and bumps the thread's updated_at.
	AppendEvent(ctx context.Context, e ThreadEvent) (*ThreadEvent, error)
	// UpdateThread applies the update and, when the status changes, records a
	// status_change/resolution event in the same transaction.
	UpdateThread(ctx context.Context, id string, u ThreadUpdate, actorID, actorEmail string) error
	// SoftDeleteThread marks a thread deleted.
	SoftDeleteThread(ctx context.Context, id string) error
	// LinkInsight links one or more threads to a captured insight: it sets
	// insight_id, appends an insight_linked event, and transitions the thread to
	// resolved, atomically per thread. Threads that are missing or already
	// deleted are skipped. It returns the IDs of the threads that were actually
	// linked, so the caller can detect (and report) thread_ids that matched
	// nothing. This is the bridge from feedback into the knowledge loop
	// (Phase 2 / #602).
	LinkInsight(ctx context.Context, threadIDs []string, insightID, actorID, actorEmail string) ([]string, error)
	// RequestValidation moves a thread to validation_state=pending and records a
	// validation_request event, atomically.
	RequestValidation(ctx context.Context, id, actorID, actorEmail string) error
	// RespondValidation records an SME's validation outcome (validated/disputed),
	// appends a validation_result event, and re-opens the thread when disputed.
	RespondValidation(ctx context.Context, id string, resp ValidationResponse, actorID, actorEmail string) error
	// CountOpenByTargets returns, per target id, the number of open (non-deleted)
	// threads. targetType must be asset/collection/prompt. Callers are
	// responsible for restricting ids to those the requester may see.
	CountOpenByTargets(ctx context.Context, targetType string, ids []string) (map[string]int, error)
	// CountSignoffs returns the number of distinct users who left an approval
	// (signoff) event on any thread targeting the given asset/collection.
	CountSignoffs(ctx context.Context, targetType, targetID string) (int, error)
}

ThreadStore persists and queries feedback threads and their event timelines.

func NewPostgresThreadStore

func NewPostgresThreadStore(db *sql.DB) ThreadStore

NewPostgresThreadStore creates a PostgreSQL-backed thread store.

type ThreadUpdate

type ThreadUpdate struct {
	Status             *string
	RequiresResolution *bool
	ValidationState    *string
}

ThreadUpdate carries optional thread mutations. A nil field is left unchanged.

type ThreadWithMeta

type ThreadWithMeta struct {
	Thread
	EventCount    int       `json:"event_count"`
	LastEventAt   time.Time `json:"last_event_at"`
	LastEventType string    `json:"last_event_type,omitempty"`
}

ThreadWithMeta is a thread list row enriched with timeline aggregates so the feedback panel can render activity without an N+1 fan-out over events.

type ValidationResponse

type ValidationResponse struct {
	Result string // ValidationStateValidated or ValidationStateDisputed
	Reason string // optional; recorded on the validation_result event
}

ValidationResponse is an SME's answer to a validation request (Phase 3 / #603).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL