notices

package
v1.138.2 Latest Latest
Warning

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

Go to latest
Published: Sep 30, 2026 License: Apache-2.0 Imports: 15 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 AutomationNotice added in v1.137.1

type AutomationNotice struct {
	Name        string `json:"name"`
	DisplayName string `json:"display_name,omitempty"`
	// Reference dereferences the automation, for `fetch`.
	Reference string `json:"reference"`
	// Version is the version the failed run executed.
	Version int `json:"version"`
	// RunID is the failed run, for manage_script get_run.
	RunID string `json:"run_id"`
	// Cause is why it failed (script, upstream, transient, memory, ...), and
	// Retryable whether running it again is expected to succeed.
	Cause     string `json:"cause"`
	Retryable bool   `json:"retryable"`
	// Error is the failure's last line, which names what went wrong.
	Error string `json:"error,omitempty"`
	// ConsecutiveFailures is how many finished runs in a row have failed.
	ConsecutiveFailures int `json:"consecutive_failures"`
	// FailedAt is when the failed run finished, RFC3339.
	FailedAt string `json:"failed_at,omitempty"`
	// LastSucceededAt is when a run of it last succeeded, RFC3339; absent
	// when none has.
	LastSucceededAt string `json:"last_succeeded_at,omitempty"`
	// Scheduled is whether a schedule is set to fire it again.
	Scheduled bool `json:"scheduled"`
	// New is whether it failed since the caller was last briefed.
	New bool `json:"new"`
}

AutomationNotice is one automation the caller owns whose latest finished run failed (#1934). It is listed for as long as the automation keeps failing, because the problem it reports is not solved by having been told; New marks the ones whose latest failure arrived since the caller was last briefed.

type AutomationSource added in v1.137.1

type AutomationSource interface {
	List(ctx context.Context, filter script.ListFilter) ([]script.Script, error)
	ListSchedules(ctx context.Context, filter script.ScheduleFilter) ([]script.Schedule, error)
	runstate.FailureStreakReader
}

AutomationSource is what the briefing reads a caller's automations from: their scripts, which of those are scheduled, and how each one's recent runs stand.

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"`
	// FailingAutomations lists the automations the caller owns whose latest
	// finished run failed, newest failure first, capped at
	// maxAutomationNotices (#1934). Unlike the other lists it is not bounded
	// by the watermark: an automation that is still failing is still listed.
	//
	// It is not part of the notices block's JSON (#1971): releases up to
	// v1.137.1 advertised that block as a closed object, and a client still
	// holding one of those tool lists rejects a key it does not declare.
	// platform_info carries the list at its top level, which every release
	// since v1.102.0 has advertised open; see Failing.
	FailingAutomations []AutomationNotice `json:"-"`
	// FailingAutomationsTotal is how many there are, which exceeds the list
	// when the cap cut it.
	FailingAutomationsTotal int `json:"-"`
}

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, automations int)

Counts returns how many feedback threads, new shares and failing automations the digest reports. Feedback and automations are true totals; shares is what the list holds, which NewSharesTruncated marks as a floor. It is what the agent instructions are sized from, and answers zeros for a nil digest so the caller needs no nil check.

func (*Digest) Failing added in v1.138.1

func (d *Digest) Failing() (list []AutomationNotice, total int)

Failing returns the failing automations and how many there are, and answers none for a nil digest so the caller needs no nil check.

func (*Digest) Notices added in v1.138.1

func (d *Digest) Notices() *Digest

Notices returns the digest as the notices block of platform_info carries it: nil when it holds nothing but failing automations, which are carried beside it, so an empty block is never sent.

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 portal input is missing, so a caller wires it unconditionally and every deployment that lacks a piece simply carries no notices. A nil scripts briefs no failing automations.

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