storage

package
v0.43.0 Latest Latest
Warning

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

Go to latest
Published: Sep 10, 2026 License: Apache-2.0, MIT Imports: 9 Imported by: 0

Documentation

Overview

Package storage

Index

Constants

View Source
const (
	// RawTurnSourceWire marks turns captured on the wire
	// (Envoy → extproc → ingest): request verbatim as sent to the
	// provider, response reduced from the SSE stream by the capture
	// adapter.
	RawTurnSourceWire = "wire"

	// RawTurnSourceTranscript marks turns ingested from a harness
	// on-disk transcript (parentUuid causal records + subagent
	// metadata).
	RawTurnSourceTranscript = "transcript"
)

Raw-turn source discriminators. The raw layer is source-agnostic so every capture origin lands in the same substrate; the deriver treats rows uniformly.

View Source
const DefaultListLimit = 50

DefaultListLimit is the page size used when a list request leaves its limit unset. The keyset-paginated readers (sessions, skills) fall back to it via their own *ListOpts types.

Variables

View Source
var (
	// ErrRawTurnNotFound means the requested raw turn does not exist in the
	// caller's organization (including correlation selectors with no match).
	ErrRawTurnNotFound = errors.New("raw turn not found")

	// ErrRawTurnAmbiguous means a correlation selector matched more than one
	// immutable raw row and therefore cannot be repaired safely.
	ErrRawTurnAmbiguous = errors.New("raw turn selector is ambiguous")

	// ErrRepairProjectionsPending means an attribution correction committed —
	// it is already effective at read time — but at least one synchronous
	// projection rebuild failed afterwards. The affected sessions stay queued
	// for the derive worker (the correction transaction marks both dirty), so
	// the projections converge without operator action. Callers should treat
	// the repair as accepted-but-settling, not failed: the accompanying
	// result is valid and lists the still-stale sessions.
	ErrRepairProjectionsPending = errors.New("attribution correction recorded; projection rebuild pending")
)
View Source
var ErrInvalidContent = errors.New("invalid content for storage")

ErrInvalidContent signals that a write failed because the payload content itself is unstorable — JSON carrying escape sequences or bytes a Postgres JSONB column rejects (SQLSTATE 22P05 unsupported Unicode escape, 22021 invalid byte sequence) — rather than any infrastructure fault.

The distinction drives behavior at the boundaries: an HTTP handler maps it to a client 4xx (the data is the problem; retrying the identical bytes will never succeed) instead of a 502 that reads as a gateway outage, and the derive worker can quarantine such a row instead of re-attempting it on every poll forever. Drivers wrap the underlying pg error with this sentinel; callers test with errors.Is.

Functions

This section is empty.

Types

type Chain added in v0.4.0

type Chain struct {
	// Nodes is the walk output in node-first order: index 0 is the node
	// the walk started from, and the last element is either a real root
	// or the last resolvable node whose parent could not be found.
	Nodes []*merkle.Node

	// Incomplete is true when the walk stopped short of a real root,
	// whether because a parent_hash could not be resolved (see
	// MissingParent) or because a cycle was detected (see CycleDetected).
	Incomplete bool

	// MissingParent is the parent_hash that could not be resolved in this
	// store. Only set when Incomplete is true due to a dangling pointer;
	// left empty for cycle-detected incompletes (the cycle-triggering
	// hash is present in Nodes already).
	MissingParent string

	// CycleDetected is true when the walk stopped because it was about
	// to re-visit a hash already in Nodes. Drivers guard every walk with
	// a per-chain seen-set so a corrupt parent edge can never spin
	// forever; when this flag trips, the chain is the largest
	// acyclic prefix reachable from the starting hash.
	//
	// In practice this never trips on a healthy store. It exists because
	// a single corrupted parent_hash would otherwise hang any endpoint
	// that walks ancestry, which is a blast radius out of proportion to
	// the likelihood.
	CycleDetected bool
}

Chain is the result of walking a node's parent edges back toward a root.

A Chain whose Incomplete field is false reached a legitimate root (a node whose parent_hash is nil or empty). A Chain whose Incomplete field is true stopped because a parent_hash pointed at a node that is not currently present in this store — see MissingParent. The nodes in Nodes are still valid; the store simply can't resolve any higher ancestors from here.

This state is expected on large or long-lived stores that trim/offload older data, merge content from foreign sources, or receive chains whose ancestors live in another store altogether. Callers should treat it as informational (a signal to render a marker, or trigger a future "thaw" lookup against another source), not as corruption.

func (*Chain) Complete added in v0.4.0

func (c *Chain) Complete() bool

Complete reports whether the walk reached a real root.

type DeriveQueue added in v0.16.0

type DeriveQueue interface {
	// MarkDeriveDirty queues (or re-bumps) one harness session.
	MarkDeriveDirty(ctx context.Context, orgID, harnessID, harnessSessionID string) error

	// ListDeriveDirty returns sessions whose dirty mark has settled
	// (DirtiedAt <= dirtiedBefore), oldest first, capped at limit.
	ListDeriveDirty(ctx context.Context, dirtiedBefore, firstDirtiedBefore time.Time, limit int32) ([]DeriveQueueEntry, error)

	// GetDeriveDirty re-reads one queue entry. Returns nil (no error)
	// when the session is clean.
	GetDeriveDirty(ctx context.Context, orgID, harnessID, harnessSessionID string) (*DeriveQueueEntry, error)

	// ClearDeriveDirty removes the entry only if its DirtiedAt is
	// unchanged from e.DirtiedAt. Returns false when the session was
	// re-dirtied (or already cleared) in the meantime.
	ClearDeriveDirty(ctx context.Context, e DeriveQueueEntry) (bool, error)

	// SweepDeriveDirty enqueues every harness session with raw-layer
	// activity at or after activeSince (the worker's slow backstop for
	// lost marks, bounded so a restart doesn't stampede the queue with
	// all of history). The zero time sweeps every session. Sessions
	// already queued keep their DirtiedAt. Returns how many sessions
	// were newly enqueued.
	SweepDeriveDirty(ctx context.Context, activeSince time.Time) (int64, error)

	// DeriveQueueStats reports queue depth and the oldest dirty mark.
	// Cheap (one aggregate over the small dirty-queue table); the
	// worker calls it every poll and the readiness probe leans on it
	// as the "store reachable, queue pollable" check.
	DeriveQueueStats(ctx context.Context) (DeriveQueueStats, error)
}

DeriveQueue is an optional capability for a Driver: the dirty-session queue feeding the derive worker. Marking is at-least-once and idempotent (an upsert that bumps DirtiedAt); deriving is idempotent (re-run prunes 0) — together they make a lost clear or duplicate mark cost only a redundant derive, never lost data.

Only drivers that host the raw layer implement this (Postgres does; in-memory intentionally does not). Callers MUST type-assert.

type DeriveQueueEntry added in v0.16.0

type DeriveQueueEntry struct {
	// OrgID is the canonical UUID string; empty means "no org context"
	// and maps to the nil-UUID sentinel, mirroring raw_turns.org_id.
	OrgID string

	HarnessID        string
	HarnessSessionID string

	// DirtiedAt is when the most recent raw turn dirtied the session.
	// The worker debounces on it and clears the entry only if it is
	// unchanged since read — a bump mid-derive survives the clear.
	DirtiedAt time.Time

	// FirstDirtiedAt is when the session was first marked dirty in this
	// queued window (it survives re-marks, unlike DirtiedAt). The worker
	// derives a continuously-streaming session — whose DirtiedAt never
	// settles past the debounce cutoff — once FirstDirtiedAt crosses the
	// max-lag bound, so live views see bounded lag.
	FirstDirtiedAt time.Time
}

DeriveQueueEntry is one dirty harness session awaiting re-derivation. The key is the deriver's natural unit — the harness triple — NOT a sessions-row id: a sessions row is not guaranteed to exist when a raw turn lands (transcript ingest writes only a raw row).

type DeriveQueueStats added in v0.16.0

type DeriveQueueStats struct {
	// Depth is the number of queued (dirty) sessions.
	Depth int64

	// OldestDirtiedAt is the oldest dirty mark still queued — "derive
	// lag" is now minus this. Zero when the queue is empty.
	OldestDirtiedAt time.Time
}

DeriveQueueStats is a point-in-time summary of the dirty-session queue, feeding the worker's depth/lag gauges and readiness probe.

type Driver

type Driver interface {
	// Open initializes the backing store and makes it ready for use.
	Open(ctx context.Context) error

	// Close closes the store and releases any resources.
	Close() error
}

Driver is the lifecycle handle for a storage backend.

The persisted merkle "node" DAG has been retired: the in-memory merkle chain is still built at derive time for provenance and dedup, but it is no longer written to or read from the store. What remains here is the open/close lifecycle. The actual read/write surfaces (raw-turn capture, session ingest, the derived sessions/traces/spans projection) are exposed as capability interfaces (RawTurnStore, SessionIngester, the derive/read interfaces) that callers type-assert off the concrete driver.

type IngestTurnRequest added in v0.10.0

type IngestTurnRequest struct {
	// Session is the optional session-tracking envelope. nil is a
	// legitimate value (legacy clients): implementations fall back
	// to a Merkle-derived synthetic harness_session_id.
	Session *sessions.IngestEnvelope

	// Nodes is the in-memory chain of nodes for this turn, ordered
	// root-to-leaf. The first element is the conversation root; the
	// last is the assistant response. Consumed for session identity
	// and status folding only — never persisted.
	Nodes []*merkle.Node

	// DerivedTitle folds a title-gen shadow call's output onto the
	// session (sessions.derived_title). Empty for every other call
	// kind; non-empty values overwrite (the harness regenerates the
	// title as the session evolves, so the latest wins).
	DerivedTitle string
}

IngestTurnRequest bundles every input the SessionIngester needs. Built by the worker pool from a single Job + the chain of content-addressed nodes derived from that Job's request/response pair.

type IngestTurnResult added in v0.10.0

type IngestTurnResult struct {
	// SessionID is the resolved/created sessions row id as a 36-char
	// canonical UUID string. Stable across retries (idempotent on
	// natural key).
	SessionID string
}

IngestTurnResult reports what the call resolved.

type ModelUsage added in v0.16.0

type ModelUsage struct {
	Model        string  `json:"model"`
	Calls        int64   `json:"calls"`
	InputTokens  int64   `json:"input_tokens"`
	OutputTokens int64   `json:"output_tokens"`
	CostUSD      float64 `json:"cost_usd"`
}

ModelUsage is one model's contribution to a session: how many llm calls ran on it and what they spent. Cost is priced at derive time, so the per-model share is spend-weighted (a fan-out of cheap subagent calls never out-votes the expensive main-spine model).

type NotFoundError

type NotFoundError struct {
	Hash string
}

NotFoundError is returned when a node doesn't exist in the store.

func (NotFoundError) Error

func (e NotFoundError) Error() string

type PublishedColumnName added in v0.39.0

type PublishedColumnName struct {
	// contains filtered or unexported fields
}

PublishedColumnName is an opaque, SQL-safe column identifier: the claim-declared value column (match.value_column) an EXISTS probe compares against. It exists for the same reason PublishedViewName does — the name reaches SQL in an identifier position and a manifest is remote input — and follows the same discipline: ParsePublishedColumnName is the only constructor, so a value that exists has passed the grammar, and Quoted is the ONE place it renders into SQL.

func ParsePublishedColumnName added in v0.39.0

func ParsePublishedColumnName(column string) (PublishedColumnName, error)

ParsePublishedColumnName validates and constructs a published value-column name: one lower-snake identifier segment of at most 63 bytes — the same grammar admission enforces on match.value_column, restated because storage must not trust its callers.

func (PublishedColumnName) IsZero added in v0.39.0

func (name PublishedColumnName) IsZero() bool

IsZero reports whether the name was never parsed. A zero column must never render into SQL; readers treat it as an evaluation failure, not as a hardcoded default.

func (PublishedColumnName) Quoted added in v0.39.0

func (name PublishedColumnName) Quoted() string

Quoted renders the identifier for SQL, double-quoted. The grammar already excludes every character that could escape a quoted identifier, so quoting is defense in depth on top of the parse.

func (PublishedColumnName) String added in v0.39.0

func (name PublishedColumnName) String() string

String returns the plain spelling, empty for the zero value.

type PublishedFilter added in v0.39.0

type PublishedFilter struct {
	View      PublishedViewName
	TypeValue string
	// Column is the claim-declared value column the probe compares against
	// (match.value_column). Like View it reaches SQL in an identifier
	// position, so it is opaque: only its validating parser constructs it
	// and only its quoting helper renders it.
	Column PublishedColumnName
	Values []string
}

PublishedFilter is a claim-derived filter over a cassette-published attachment view: the view to probe, the primitive-type value that scopes it to this surface, the claim-declared value column to compare, and the already-normalized filter values. It is generic — derived from whatever claim the handler consulted, never named after any particular cassette.

Each value renders one independent EXISTS predicate (repeat is AND), inside the same query that sorts and paginates. Values arrive normalized per the claim's declared profile; storage binds them verbatim, which is what keeps the probe an exact-match index access.

type PublishedViewName added in v0.39.0

type PublishedViewName struct {
	// contains filtered or unexported fields
}

PublishedViewName is an opaque, SQL-safe, schema-qualified view identifier.

It exists for the same reason SortColumn does: this name reaches SQL in an identifier position, and a manifest is remote input. Its fields are unexported and ParsePublishedViewName is the only constructor, so a value that exists has passed the grammar; Quoted is the ONE place a published view identifier is rendered into SQL. No other manifest string may reach an identifier position.

func ParsePublishedViewName added in v0.39.0

func ParsePublishedViewName(qualified string) (PublishedViewName, error)

ParsePublishedViewName validates and constructs a published view name from its qualified "schema.view" spelling.

func (PublishedViewName) IsZero added in v0.39.0

func (name PublishedViewName) IsZero() bool

IsZero reports whether the name was never parsed. A zero name must never render into SQL; readers treat it as an evaluation failure, not as "no filter".

func (PublishedViewName) Quoted added in v0.39.0

func (name PublishedViewName) Quoted() string

Quoted renders the identifier for SQL, both segments double-quoted. The grammar above already excludes every character that could escape a quoted identifier (there is no `"` to double), so quoting here is defense in depth on top of the parse — and the single rendering point reviews are anchored to.

func (PublishedViewName) String added in v0.39.0

func (name PublishedViewName) String() string

String returns the plain qualified spelling, empty for the zero value.

type PublishedViewProber added in v0.40.0

type PublishedViewProber interface {
	// ProbePublishedView reports nil when the view is readable in the shape
	// the claim declared. A non-nil error is either the store's own verdict
	// (the store answered and refused) or a *TransientProbeError when the
	// store could not be asked at all — implementations must keep the two
	// distinguishable, because callers that cache the verdict may only
	// change cached state on an actual answer.
	ProbePublishedView(ctx context.Context, view PublishedViewName, column PublishedColumnName) error
}

PublishedViewProber is the capability a driver exposes when it can verify that a published view is actually readable with the driver's own database role — the same role that later renders the claim's EXISTS predicates, so a probe that succeeds proves exactly what the request path needs.

One probe validates three things in a single round trip: the view exists, the role may SELECT from it, and it carries the contract columns plus the claim-declared value column. It proves readability at probe time, not forever — callers re-probe on their own cadence and must stay loud when an armed filter breaks between probes.

type RawTurnAttribution added in v0.30.0

type RawTurnAttribution struct {
	RawTurnID              int64   `json:"raw_turn_id"`
	HarnessID              string  `json:"harness_id"`
	HarnessSessionID       string  `json:"harness_session_id"`
	ThreadID               string  `json:"thread_id"`
	ParentHarnessSessionID *string `json:"parent_harness_session_id,omitempty"`
}

RawTurnAttribution is the effective, repairable attribution projected over an immutable raw turn. Raw payload and envelope bytes remain untouched.

type RawTurnAttributionRepairRequest added in v0.30.0

type RawTurnAttributionRepairRequest struct {
	OrgID                  string  `json:"-"`
	RawTurnID              int64   `json:"raw_turn_id,omitempty"`
	PaperProxyRequestID    string  `json:"paper_proxy_request_id,omitempty"`
	HarnessID              string  `json:"harness_id"`
	HarnessSessionID       string  `json:"harness_session_id"`
	ThreadID               string  `json:"thread_id"`
	ParentHarnessSessionID *string `json:"parent_harness_session_id,omitempty"`
	Reason                 string  `json:"reason"`
}

RawTurnAttributionRepairRequest selects exactly one raw row and supplies a complete replacement attribution. OrgID is supplied by the trusted caller context, never by an HTTP request body.

type RawTurnAttributionRepairResult added in v0.30.0

type RawTurnAttributionRepairResult struct {
	Recorded  bool               `json:"recorded"`
	Previous  RawTurnAttribution `json:"previous"`
	Effective RawTurnAttribution `json:"effective"`

	// ProjectionsPending lists the sessions whose synchronous rebuild failed
	// after the correction committed. Empty on full success. When set, the
	// correction is effective at read time and the listed projections
	// converge via the derive queue (marked dirty in the correction
	// transaction); the call returns ErrRepairProjectionsPending alongside
	// this result.
	ProjectionsPending []RepairPendingSession `json:"projections_pending,omitempty"`

	// SourceCleanupPending reports that the best-effort removal of the
	// emptied previous-session row failed after the correction and both
	// projection rebuilds applied. The leftover row is cosmetic — it anchors
	// no effective turns — and nothing retries the deletion automatically:
	// the flag makes the response honest about the leftover, it is not a
	// promise of later cleanup. On its own it never accompanies an error;
	// when ProjectionsPending is empty too, the repair succeeded.
	SourceCleanupPending bool `json:"source_cleanup_pending,omitempty"`
}

type RawTurnAttributionRepairer added in v0.30.0

type RawTurnAttributionRepairer interface {
	RepairRawTurnAttribution(context.Context, string, RawTurnAttributionRepairRequest) (RawTurnAttributionRepairResult, error)
}

RawTurnAttributionRepairer is an optional storage capability for audited, append-only corrections to raw-turn attribution.

When the returned error matches ErrRepairProjectionsPending the result is still meaningful: the correction committed, and ProjectionsPending names the sessions the derive worker will converge.

type RawTurnHeader added in v0.16.0

type RawTurnHeader struct {
	ID            int64
	Source        string
	Provider      string
	AgentName     string
	RequestID     string
	ReceivedAt    time.Time
	Meta          json.RawMessage
	RequestBytes  int64
	ResponseBytes int64
}

RawTurnHeader is one wire-log row: capture identity and sizes, no payloads. The operator surface onto the raw layer.

type RawTurnRecord added in v0.16.0

type RawTurnRecord struct {
	// OrgID is the canonical UUID string from the validated session
	// envelope. Empty means "no org context" and maps to the nil-UUID
	// sentinel, mirroring nodes.org_id.
	OrgID string

	// Source is one of the RawTurnSource* constants.
	Source string

	Provider  string
	AgentName string

	// HarnessID / HarnessSessionID echo the session envelope's natural
	// key so a session's raw turns are queryable without decoding the
	// envelope JSONB. Empty when the turn carried no envelope.
	HarnessID        string
	HarnessSessionID string

	// RequestID is the capture adapter's per-call id (extproc forwards
	// the Envoy request id). It dedupes retried POSTs of the same
	// captured turn; empty disables deduplication for the row.
	RequestID string

	// RawRequest is the provider request body, verbatim.
	RawRequest json.RawMessage

	// Response is the reduced provider response, verbatim from the
	// envelope.
	Response json.RawMessage

	// RawResponse is the upstream response body exactly as it arrived on
	// the wire, before any reduction. Response is derived from these bytes
	// and is lossy — a field the reducing adapter didn't model is gone from
	// it, and the raw layer's promise that any future data-model change is
	// a re-derive rather than a re-capture only holds while the source
	// bytes survive.
	//
	// Nil when the capture adapter sent no raw response, or when one
	// arrived and exceeded the ingest cap; RawResponseDropped separates
	// those two cases.
	RawResponse []byte

	// RawResponseEncoding is the Content-Encoding RawResponse is stored
	// under ("identity", "gzip", …), empty when unknown or absent. The
	// bytes are kept as received rather than decompressed, because
	// re-compression is not guaranteed byte-identical and "verbatim" has to
	// mean verbatim for the column to be worth having.
	RawResponseEncoding string

	// RawResponseDropped marks a turn whose raw response existed and was not
	// kept — because it arrived too large to store, or because the producer
	// captured it and withheld it to fit the ingest transport limit. It is
	// the difference between "this turn never had verbatim bytes" and "it
	// did, and we chose not to keep them" — without it a fidelity gap is
	// indistinguishable from an absence.
	//
	// True implies RawResponse is empty: the two say opposite things about
	// the same bytes, so a row asserting both would be unreadable. Ingest
	// enforces it on both paths.
	RawResponseDropped bool

	// Meta is the capture adapter's metadata block (model, stream,
	// upstream status, byte counts, elapsed seconds, …), verbatim.
	Meta json.RawMessage

	// SessionEnvelope is the session-tracking block, verbatim.
	SessionEnvelope json.RawMessage

	// ReceivedAt is populated by the store on read; ignored on write
	// (the database stamps insertion time).
	ReceivedAt time.Time

	// ID is the store-assigned monotonic row id, populated on read.
	ID int64
}

RawTurnRecord is one immutable captured turn, stored verbatim before any parsing or projection. The JSON payloads are the exact bytes from the ingest envelope — never re-marshaled through parsed structs — so fields unknown to the current build survive for later derivers.

type RawTurnStore added in v0.16.0

type RawTurnStore interface {
	// PutRawTurn appends one captured turn. Returns false when the
	// row was deduplicated (same org + request id already stored).
	PutRawTurn(ctx context.Context, rec RawTurnRecord) (bool, error)

	// ListRawTurns scans the raw layer in insertion order, returning
	// up to pageSize rows with id greater than afterID. Pass 0 to
	// start from the beginning.
	ListRawTurns(ctx context.Context, afterID int64, pageSize int32) ([]RawTurnRecord, error)

	// CountRawTurns reports the total number of raw rows.
	CountRawTurns(ctx context.Context) (int64, error)
}

RawTurnStore is an optional capability for a Driver: an append-only, immutable store of full capture envelopes. The derived layer (nodes, edges, typing) is a pure re-runnable function of these rows — see pkg/derive.

Only drivers that can host the raw layer implement this (Postgres does; in-memory intentionally does not). Callers MUST type-assert.

type RepairPendingSession added in v0.30.0

type RepairPendingSession struct {
	HarnessID        string `json:"harness_id"`
	HarnessSessionID string `json:"harness_session_id"`
}

RepairPendingSession names one harness session whose projection rebuild did not complete synchronously during a repair. The session is already queued for the derive worker, which converges it.

type SessionIngester added in v0.10.0

type SessionIngester interface {
	// IngestTurn resolves/UPSERTs the sessions row for the turn
	// (optionally resolving the parent_session_id FK), folds
	// derived_status and tool counts from the in-memory chain, and
	// folds the derived title when present. It persists no nodes.
	IngestTurn(ctx context.Context, req IngestTurnRequest) (IngestTurnResult, error)
}

SessionIngester is an optional capability for a Driver: it resolves (or creates) the sessions row for a captured turn and folds the turn's status onto it in a single transaction.

Only drivers that can host the sessions table implement this. The Postgres driver does; the in-memory driver intentionally does not (it has no concept of sessions, and tests that exercise the classic Put-only path still want the legacy behavior). Callers like the worker pool MUST type-assert against this interface before invoking it.

Implementations MUST satisfy these invariants:

  • All side effects (sessions UPSERT, parent placeholder insert, status fold) commit atomically. A panic or error on any step rolls back every other step.
  • The resolved sessions row is keyed by (org_id, harness_id, harness_session_id). When the inbound envelope is nil, or HarnessID is "unknown", or HarnessSessionID is empty, implementations derive a synthetic harness_session_id from the captured turn's Merkle root prefix.
  • Nothing turn-shaped is persisted here: the in-memory node chain is consumed only to resolve session identity and fold derived_status/tool counts. The token/turn/cost rollups are owned by the derive-time span fold (FoldSessionRollupsFromSpans), so the call is idempotent for retried POSTs.

type SessionListOpts added in v0.16.0

type SessionListOpts struct {
	Limit       int
	Sort        SessionSortField // zero value == SortLastActive
	Dir         SortDirection    // zero value == SortDesc
	CursorVal   *string          // boundary row's sort column, canonical ::text form
	CursorID    *string          // boundary row's id (UUID), the keyset tiebreak
	Since       *time.Time
	Until       *time.Time
	AuthSubject string

	// ClaimedFilters are the claim-derived published-view filters, one per
	// claimed param the request supplied (empty when none was). Values
	// arrive already normalized per each claim's declared profile; every
	// value of every filter renders one EXISTS predicate against that
	// filter's own view and value column, all ANDed inside the same
	// paginating query. Rendering the whole set is the contract: a supplied
	// claimed param that never reached SQL would be silent unfiltering.
	ClaimedFilters []PublishedFilter
}

SessionListOpts parameterizes the sessions-list read: keyset cursor ordered by any sortable column (default last_seen_at DESC, id DESC), an optional activity window, and an optional attribution filter. The since/until window filters on last_seen_at. AuthSubject "" lists every user's sessions; non-empty is an exact match on the gateway-stamped JWT subject.

type SessionRecord added in v0.12.0

type SessionRecord struct {
	ID               string
	HarnessID        string
	HarnessSessionID string
	// Name is the session's display label with the name column taking
	// precedence over the folded title: the value stored on the identity
	// row (a user rename or the harness-supplied session name) when set,
	// otherwise DerivedTitle as the fallback. Empty only when neither
	// exists.
	Name string
	// DisplayName is the user-set display title (sessions.display_name),
	// written only by the Console rename PATCH and never by ingest, so it
	// survives an active session (unlike Name, which the envelope re-sends
	// every turn). Empty means "no user title"; the API resolves the
	// display title as DisplayName -> DerivedTitle -> Preview -> Name
	// (PCC-970).
	DisplayName string
	// DerivedTitle is the deriver's folded session title
	// (sessions.derived_title), generated from the conversation. Empty
	// until title generation produces one. Unlike Name it never falls back
	// to the identity-row name, so it is the pure descriptive title.
	DerivedTitle      string
	Cwd               string // empty when not set
	HarnessVersion    string // empty when not set
	ParentSessionID   string // empty when not set
	StartedAt         time.Time
	LastSeenAt        time.Time
	EndedAt           *time.Time // nil when session is still live
	HarnessMetadata   map[string]any
	TotalInputTokens  int64
	TotalOutputTokens int64
	TotalCostUsd      float64
	TurnCount         int
	// DerivedStatus is the chain-aware session status (completed / failed /
	// abandoned / unknown), denormalized at ingest. 'unknown' until the first
	// turn lands or, for pre-feature rows, until the status backfill runs.
	DerivedStatus string
	// Model is the dominant conversation-spine model, folded at derive
	// time (sessions.derived_model). Empty until the session derives.
	Model string
	// ModelUsage is the per-model spend breakdown folded at derive time
	// across every thread (sessions.model_usage), cost-weighted so the
	// share reflects spend rather than call count. Nil until the session
	// derives; ordered dominant-model-first (by cost).
	ModelUsage []ModelUsage
	// Tasks is the deriver's session-scoped TaskCreate/TaskUpdate fold and
	// KindCounts the per-call_kind span tally (sessions.tasks /
	// sessions.kind_counts), both JSONB served verbatim on the composite
	// traces response. Nil until the session derives.
	Tasks         json.RawMessage
	KindCounts    json.RawMessage
	Preview       string // first user turn text, truncated; empty when unavailable
	PreviewIsJSON bool   // full prompt was valid JSON before preview truncation
	// AuthSubject is the gateway-stamped JWT subject (the WorkOS user id)
	// captured at ingest. Empty for rows captured before the edge began
	// stamping the x-paper-auth-subject header.
	AuthSubject string
	// SortVal is the canonical ::text form of this row's active sort column,
	// populated by ListSessionRecords so the API can mint an exact next cursor.
	// Empty on records returned by point lookups.
	SortVal string
}

SessionRecord is the flat sessions-table row surfaced by GET /v1/sessions. Fields absent in the DB (NULL) are represented as empty strings or nil/zero values so API callers never have to unwrap optional pgtype wrappers.

type SessionSortField added in v0.22.0

type SessionSortField string

SessionSortField is the validated column a sessions-list page is ordered by. The zero value sorts by last activity (the historical default).

const (
	SortLastActive    SessionSortField = "last_active"
	SortStartedAt     SessionSortField = "started_at"
	SortTurnCount     SessionSortField = "turn_count"
	SortTotalCost     SessionSortField = "total_cost_usd"
	SortTotalTokens   SessionSortField = "total_tokens"
	SortDurationNs    SessionSortField = "duration_ns"
	SortDerivedStatus SessionSortField = "derived_status"
	SortAuthSubject   SessionSortField = "auth_subject"
)

func ParseSessionSortField added in v0.22.0

func ParseSessionSortField(raw string) (SessionSortField, bool)

ParseSessionSortField validates a raw sort key. Empty string is the default (last_active). ok is false for any unrecognized value.

type SortColumn added in v0.22.0

type SortColumn struct {
	// contains filtered or unexported fields
}

SortColumn is an opaque, SQL-safe sort target: a physical column name and the Postgres type the keyset cursor value is cast back to. Its fields are unexported and there is no exported constructor, so the only SortColumns that exist are the entries in sessionSortColumn below. A SortColumn interpolated into an ORDER BY clause or a ::cast therefore cannot carry an attacker-controlled identifier by construction — the type system enforces the allowlist that a bare string could only enforce by convention.

func SessionSortColumn added in v0.22.0

func SessionSortColumn(f SessionSortField) (SortColumn, bool)

SessionSortColumn resolves a validated sort field to its opaque SortColumn (physical column + cursor cast type). ok is false for unknown fields — the injection guard: an unrecognized field never yields a SortColumn, so it can never reach SQL.

func (SortColumn) Cast added in v0.22.0

func (c SortColumn) Cast() string

Cast is the Postgres type the keyset cursor value is cast back to (bigint, numeric, timestamptz, text) — likewise allowlist-sourced and SQL-safe.

func (SortColumn) Col added in v0.22.0

func (c SortColumn) Col() string

Col is the physical column name, safe to interpolate into SQL because it can only have originated from the allowlist.

type SortDirection added in v0.22.0

type SortDirection string

SortDirection is the validated order direction.

const (
	SortAsc  SortDirection = "asc"
	SortDesc SortDirection = "desc"
)

func ParseSortDirection added in v0.22.0

func ParseSortDirection(raw string) (SortDirection, bool)

ParseSortDirection validates a raw direction. Empty string defaults to desc.

type SpanLinkRecord added in v0.16.0

type SpanLinkRecord struct {
	FromTraceID string
	FromSpanID  string
	FromIO      string
	ToTraceID   string
	ToSpanID    string
	ToIO        string
	Kind        string
}

SpanLinkRecord is a dataflow edge between spans, possibly across traces (compaction seams).

type SpanModelReader added in v0.16.0

type SpanModelReader interface {
	ListSessionSpanModel(ctx context.Context, sessionID string) ([]SpanTurnRecord, []SpanRecord, []SpanLinkRecord, error)
	ListTraceSummaries(ctx context.Context, sessionID string) ([]TraceSummaryRecord, error)
	// ListSessionLinks returns a session's dataflow links alone — the
	// payload-free half of ListSessionSpanModel. It backs the per-trace
	// streaming export, which loads the light turn headers and links whole
	// but reads the heavy spans one trace at a time.
	ListSessionLinks(ctx context.Context, sessionID string) ([]SpanLinkRecord, error)
	// ListTraceSpans returns one trace's spans in presentation order — the
	// same per-trace read GetTraceDetail performs, without the turn/link
	// round-trips. It backs the per-trace streaming export.
	ListTraceSpans(ctx context.Context, orgID, traceID string) ([]SpanRecord, error)
	GetTraceDetail(ctx context.Context, orgID, traceID string) (*SpanTurnRecord, []SpanRecord, []SpanLinkRecord, error)
	GetSpanRecord(ctx context.Context, orgID, traceID, spanID string) (*SpanRecord, error)
	ListRawTurnHeaders(ctx context.Context, orgID, harnessID, harnessSessionID string) ([]RawTurnHeader, error)
}

SpanModelReader serves the span projection for session UIs.

type SpanRecord added in v0.16.0

type SpanRecord struct {
	TraceID      string
	SpanID       string
	ParentSpanID string
	Kind         string
	Name         string
	Status       string
	CallKind     string
	ThreadID     string
	Model        string
	StopReason   string
	StartedAt    time.Time
	DurationNS   int64
	// Seq is the deriver's emit ordinal within the trace —
	// presentation order, since started_at ties inside one llm call.
	Seq       int64
	Input     json.RawMessage
	Output    json.RawMessage
	Usage     json.RawMessage
	RawTurnID int64
	NodeHash  string
	// Verdict is the deriver-written security-monitor disposition JSON
	// (null on non-permission-check spans). Served verbatim on the wire.
	Verdict json.RawMessage
}

SpanRecord is one observed unit of work within a trace. Input and Output hold delta-only content-block arrays; Usage is the llm.Usage JSON for llm spans.

type SpanStats added in v0.16.0

type SpanStats struct {
	TurnCount           int
	SessionCount        int
	CompletedCount      int
	InputTokens         int64
	OutputTokens        int64
	CacheCreationTokens int64
	CacheReadTokens     int64
	TotalDurationNS     int64
	TotalCostUSD        float64
	ToolCalls           int
}

SpanStats is the span-layer aggregate behind /v1/stats: trace-grain rollups summed over a time window, so the dashboard numbers agree with the session detail and trace views. TotalDurationNS is the sum of trace durations (agent time), not a wall-clock window.

type SpanStatsReader added in v0.16.0

type SpanStatsReader interface {
	AggregateSpanStats(ctx context.Context, orgID string, since, until *time.Time, authSubject string) (SpanStats, error)
}

SpanStatsReader is the capability interface for span-layer stats.

authSubject narrows every total in the returned SpanStats to sessions captured for one gateway-stamped JWT subject; empty means the whole org, the only behavior there used to be. It is a filter and not an authorization boundary — the caller has already been scoped to a tenant, and this only chooses which of that tenant's rows to add up, the same way the auth_subject filter on ListSessionRecords does.

type SpanTurnRecord added in v0.16.0

type SpanTurnRecord struct {
	TraceID    string
	SessionID  string
	UserPrompt string
	// ResponsePreview is the derive-time fold of the closing spine llm
	// call's text output — the turn card's answer line.
	ResponsePreview string
	Synthetic       string
	Status          string
	// Source is the capture origin of the turn's raw rows ("wire" |
	// "transcript"), promoted from raw_turns.source at derive time.
	Source            string
	StartedAt         time.Time
	EndedAt           *time.Time
	DurationNS        int64
	TotalInputTokens  int64
	TotalOutputTokens int64
	// Main* counts only conversation-spine llm calls; the difference
	// from Total* is shadow spend.
	MainInputTokens     int64
	MainOutputTokens    int64
	CacheReadTokens     int64
	CacheCreationTokens int64
	TotalCostUSD        float64
}

SpanTurnRecord is one user-visible turn (trace).

type TraceSummaryRecord added in v0.16.0

type TraceSummaryRecord struct {
	SpanTurnRecord
	SpanCount int
}

TraceSummaryRecord is a turn header with its span count — the lazy session-detail row (no payloads).

type TransientProbeError added in v0.40.0

type TransientProbeError struct {
	Err error
}

TransientProbeError marks a probe attempt the store never answered — a timeout, a refused connection, a closed pool. It is not a verdict about the view: the same probe may well succeed a moment later, so callers that cache probe outcomes retain their prior state rather than treating the blip as a refusal.

func (*TransientProbeError) Error added in v0.40.0

func (failure *TransientProbeError) Error() string

func (*TransientProbeError) Unwrap added in v0.40.0

func (failure *TransientProbeError) Unwrap() error

Unwrap exposes the underlying failure for errors.Is/As.

Directories

Path Synopsis
Package postgres
Package postgres

Jump to

Keyboard shortcuts

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