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
- func DeriveFirstEventType(kind string) string
- func NewThreadEventID() string
- func NewThreadID(prefix string) string
- func ValidThreadKind(kind string) bool
- func ValidThreadStatus(status string) bool
- func ValidThreadValidationState(s string) bool
- type Thread
- type ThreadEvent
- type ThreadFilter
- type ThreadStore
- type ThreadUpdate
- type ThreadWithMeta
- type ValidationResponse
Constants ¶
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).
const ( ThreadStatusOpen = "open" ThreadStatusAnswered = "answered" ThreadStatusResolved = "resolved" ThreadStatusWontFix = "wont_fix" ThreadStatusAcknowledged = "acknowledged" )
Thread statuses.
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.
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).
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 ¶
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 ¶
NewThreadID returns a prefixed unique id (e.g. "thr_<uuid>", "evt_<uuid>").
func ValidThreadKind ¶
ValidThreadKind reports whether kind is one of the seven authoring kinds.
func ValidThreadStatus ¶
ValidThreadStatus reports whether status is a recognized thread status.
func ValidThreadValidationState ¶
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 ¶
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).