notices

package
v1.128.0 Latest Latest
Warning

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

Go to latest
Published: Sep 1, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

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"`
	// NewShares lists artifacts shared with the caller since the watermark,
	// newest first, capped at maxShareNotices.
	NewShares []ShareNotice `json:"new_shares,omitempty"`
	// NewSharesTruncated records that more shares arrived than the list holds.
	// 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

func (d *Digest) Counts() (feedback, shares int)

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

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

func (s *PostgresWatermarkStore) Get(ctx context.Context, userKey string) (*time.Time, error)

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.

func (*PostgresWatermarkStore) Set

func (s *PostgresWatermarkStore) Set(ctx context.Context, userKey string, at time.Time) error

Set advances this caller's watermark to at. It never moves the watermark backwards: two sessions can brief the same person concurrently, and the later delivery is the one that decides what the next session has already seen.

type ShareNotice

type ShareNotice struct {
	// Kind is asset, collection, or prompt.
	Kind string `json:"kind"`
	ID   string `json:"id"`
	Name string `json:"name,omitempty"`
	// Reference dereferences the artifact, for `fetch`.
	Reference string `json:"reference"`
	// SharedBy is the person who made the grant, falling back to the artifact's
	// owner for a legacy row that recorded no creator.
	SharedBy string `json:"shared_by,omitempty"`
	// SharedAt is when the grant was made, RFC3339.
	SharedAt string `json:"shared_at"`
	// Permission is viewer or editor.
	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.

Jump to

Keyboard shortcuts

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