Documentation
¶
Overview ¶
Package notices assembles the per-user session-start briefing that platform_info carries (#1278).
The platform already tells a session about pending insights. Nothing told a person that feedback had been left on work they own, or that something new had been shared with them: the portal activity feed was the only surface that carried either, so a user who works through MCP sessions and never opens the portal discovered neither. This package answers one question for one caller — what is waiting for you that you have not been shown — and platform_info attaches the answer to the first call of the session.
"Have not been shown" is a watermark, not a read receipt: the digest advances it as it is delivered, so what a session is told about is not repeated to the next one. That makes delivery a single shot, which is why the agent instructions that ship alongside tell the agent to relay the digest rather than act on it silently.
Construction takes explicit inputs — the portal asset, share and thread stores plus a watermark store — so the digest is buildable and testable without a Platform. New returns nil when any of them is missing (no database, no portal), and every method on a nil Handle answers empty, so a deployment without a portal simply carries no notices.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Digest ¶
type Digest struct {
// Since is the watermark this digest covers, RFC3339. Everything reported
// arrived after it.
Since string `json:"since"`
// Feedback lists unresolved threads on assets the caller owns, newest
// activity first, capped at maxFeedbackNotices.
Feedback []FeedbackNotice `json:"feedback,omitempty"`
// FeedbackTotal is how many such threads exist. It exceeds len(Feedback)
// when the cap truncated the list, so the agent can say how many were not
// named rather than implying the list is complete.
FeedbackTotal int `json:"feedback_total,omitempty"`
// newest first, capped at maxShareNotices.
NewShares []ShareNotice `json:"new_shares,omitempty"`
// The share query is bounded rather than counted, so the digest reports
// "at least this many" rather than presenting a truncated list as the whole
// set -- an unknown remainder is its own state, not zero.
NewSharesTruncated bool `json:"new_shares_truncated,omitempty"`
}
Digest is one caller's session-start briefing: feedback left on work they own and artifacts newly shared with them, both bounded to what has arrived since they were last briefed. It is nil, rather than empty, when there is nothing to say, so platform_info omits the block entirely.
func (*Digest) Counts ¶
Counts returns how many feedback threads and new shares the digest reports. Feedback is the true total (the thread query counts what it did not return); shares is what the list holds, which NewSharesTruncated marks as a floor. It is what the agent instructions are sized from, and answers zero, zero for a nil digest so the caller needs no nil check.
type FeedbackNotice ¶
type FeedbackNotice struct {
ThreadID string `json:"thread_id"`
// Kind is the thread's classification (comment, question, correction, ...).
Kind string `json:"kind"`
Status string `json:"status"`
Title string `json:"title,omitempty"`
// AuthorEmail is who opened the thread; never the caller.
AuthorEmail string `json:"author_email,omitempty"`
AssetID string `json:"asset_id"`
AssetName string `json:"asset_name,omitempty"`
// AssetReference dereferences the asset the thread is on, for `fetch`.
AssetReference string `json:"asset_reference"`
// LastActivityAt is when the thread last moved, RFC3339.
LastActivityAt string `json:"last_activity_at"`
}
FeedbackNotice is one unresolved feedback thread on an asset the caller owns, left by someone else. It carries enough to relay the thread without a further call, and the reference needed to read it in full.
type Handle ¶
type Handle struct {
// contains filtered or unexported fields
}
Handle assembles a caller's session-start digest from the portal stores and the notice watermark. A nil Handle builds nothing, which is what a deployment without a database or without the portal holds.
func New ¶
func New(db *sql.DB, assets portaldomain.AssetStore, shares portaldomain.ShareStore, ts threads.ThreadStore) *Handle
New builds the digest assembler over db and the portal stores. It returns nil when any input is missing, so a caller wires it unconditionally and every deployment that lacks a piece simply carries no notices.
func (*Handle) Build ¶
func (h *Handle) Build(ctx context.Context, pc *middleware.PlatformContext) *Digest
Build returns the caller's digest, or nil when there is nothing to brief them on. Delivering a digest advances the caller's watermark, so what one session is told is not repeated to the next; a digest assembled from a failed query is still returned but does NOT advance the watermark, so a transient database error delays a notice rather than swallowing it.
Orientation must not fail because a notice could not be assembled, so every error here is logged and swallowed.
type PostgresWatermarkStore ¶
type PostgresWatermarkStore struct {
// contains filtered or unexported fields
}
PostgresWatermarkStore is the user_notice_watermarks-backed WatermarkStore.
func NewPostgresWatermarkStore ¶
func NewPostgresWatermarkStore(db *sql.DB) *PostgresWatermarkStore
NewPostgresWatermarkStore returns a watermark store over db.
func (*PostgresWatermarkStore) Get ¶
Get returns when this caller was last briefed, or nil if never.
func (*PostgresWatermarkStore) Now ¶ added in v1.123.0
Now returns the database server's current time, which is the clock every timestamp a digest compares against was stamped from.
type ShareNotice ¶
type ShareNotice struct {
// Kind is asset, collection, or prompt.
Reference string `json:"reference"`
// owner for a legacy row that recorded no creator.
SharedBy string `json:"shared_by,omitempty"`
SharedAt string `json:"shared_at"`
Permission string `json:"permission,omitempty"`
}
ShareNotice is one artifact newly shared with the caller.
type WatermarkStore ¶
type WatermarkStore interface {
// Now returns the clock the watermark is kept on.
//
// A digest compares its boundary against timestamps the database stamped
// (portal_shares.created_at defaults to NOW()), so the boundary has to be
// read from that same clock. Stamping it from the process clock instead
// compares two clocks: a few milliseconds of skew between the application
// host and the database server is enough to place an already-delivered
// share after the watermark and read it out to the caller a second time.
Now(ctx context.Context) (time.Time, error)
Get(ctx context.Context, userKey string) (*time.Time, error)
Set(ctx context.Context, userKey string, at time.Time) error
}
WatermarkStore records, per caller, when a notice digest was last delivered to them. Get answers nil (not an error) for a caller who has never been briefed; Set is what makes delivery single-shot.