store

package
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Jul 22, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Overview

Package store is the R2 observability dashboard's read-through data layer.

It reads directly from the on-disk iteration-log roots and the iter-N.score.yaml / session-<id>.score.yaml sidecars that R1 already writes (internal/scoring), and projects them into the stable presentation DTOs the dashboard HTTP handlers (t03) and frontend (t08) share. There is no new persistent store (spec D2.1): aggregates are computed on demand behind an in-process LRU (cache.go) keyed per iter-log root and invalidated when the root's newest file mtime changes.

The DTO field shapes here are the Go twin of the JSON Schemas under schemas/dashboard-*.schema.json and the endpoint contract in .agents/workflow/plans/r2-observability-dashboard/design/API.md — those two artifacts are authority for payload shape and endpoint behaviour respectively. The Store interface below is the one API.md pins as authoritative for this task (t02).

Anti-scope (pinned by the task): this layer never writes and never recomputes a score. Missing or stale sidecars degrade to scored:false / score:null — the synchronous recompute-on-miss path is t06's job. Consequently the detail-only integrity / objective / integrity_observation_count / transcript_turn_count fields, which are products of the scoring pipeline rather than the persisted sidecar, are left empty here; t06 populates them when it recomputes.

recompute.go implements the t06 score-recompute-on-miss half of plan task t06-recompute-on-miss-and-fswatch.

RecomputeStore decorates DiskStore's GetIteration: when the requested iteration's iter-N.score.yaml sidecar is missing, corrupt, or older than its iter-N.yaml record, the store calls scoring.ScoreIterationWithSignals synchronously — the same single-iteration entry point close-task's scoring.ScoreIteration wraps, in the variant that also returns the scored SignalSet so its integrity / objective outputs survive into the DTO — and returns the freshly-computed score. The sidecar write is best-effort in a background goroutine, so the cold-cache dashboard never says "no score" when the scorer could just answer, and never blocks the response on a disk write.

The recompute path is also what populates the DTO fields t02's raw read layer declares but leaves empty (integrity, objective, integrity_observation_count, transcript_turn_count): they are products of the scoring pipeline / raw iter-log entry, not of the persisted sidecar, so only this path can source them. A detail read served from a fresh sidecar still leaves them empty — that is the t02/t06 boundary, not a bug.

Any pipeline failure degrades to the raw t02 read (spec R10 resilience): GetIteration never trades a valid raw answer for a recompute error.

Index

Constants

This section is empty.

Variables

View Source
var ErrNotFound = errors.New("dashboard/store: not found")

ErrNotFound is returned by GetRun / GetIteration when the requested session or iteration is absent from every resolved iter-log root. Handlers (t03) map it to a 404 not_found envelope.

View Source
var ErrRootNotAllowed = errors.New("dashboard/store: iter_log_dir is not a configured root")

ErrRootNotAllowed is returned by GetIteration when the requested iter_log_dir is not one of the store's configured roots. It guards against a handler snapshotting an arbitrary local directory (path-traversal / info-leak): iter_log_dir may only disambiguate among the resolved roots, never widen them. Handlers (t03) map it to a 400 bad_request envelope.

Functions

This section is empty.

Types

type BreakdownRow

type BreakdownRow struct {
	Signal          string  `json:"signal"`
	Label           string  `json:"label"`
	Present         bool    `json:"present"`
	SubScore        float64 `json:"sub_score"`
	Detail          string  `json:"detail,omitempty"`
	NominalWeight   float64 `json:"nominal_weight"`
	EffectiveWeight float64 `json:"effective_weight"`
	Contribution    float64 `json:"contribution"`
}

BreakdownRow is one per-signal score-breakdown row (detail only), sourced from the persisted sidecar's breakdown in rubric order.

type CacheMetrics

type CacheMetrics struct {
	Hits      int64 `json:"hits"`
	Misses    int64 `json:"misses"`
	Evictions int64 `json:"evictions"`
	Size      int   `json:"size"`
	Capacity  int   `json:"capacity"`
}

CacheMetrics is the exposed read-cache instrumentation (task requirement: "exposed metrics"). Handlers / the health surface can surface it for operator triage; it is a snapshot, safe to copy.

type DiskStore

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

DiskStore is the read-through Store implementation over iter-log roots and their score sidecars. It is safe for concurrent use.

func New

func New(roots []string, opts ...Option) *DiskStore

New builds a DiskStore over the given iter-log roots. Per spec OQ1 the store discovers nothing beyond the roots it is handed: multi-root is supported so a future wave can add historical roots, but v1's default config passes only the active root.

func (*DiskStore) CacheMetrics

func (s *DiskStore) CacheMetrics() CacheMetrics

CacheMetrics exposes the read-cache instrumentation.

func (*DiskStore) Evict

func (s *DiskStore) Evict(root string)

Evict drops the cached snapshot for one root and the aggregate projection that folds it in (broker per-root push hook). Dropping the projection too is load-bearing: a same-mtime content rewrite the fingerprint cannot see leaves the combined key unchanged, so without this the memo would keep serving the pre-eviction view even though the root snapshot was force-dropped.

func (*DiskStore) EvictAll

func (s *DiskStore) EvictAll()

EvictAll drops every cached snapshot (broker whole-cache push hook).

func (*DiskStore) GetIteration

func (s *DiskStore) GetIteration(_ context.Context, iterLogDir string, n int) (IterationDetail, error)

GetIteration implements Store. iterLogDir defaults to the active (first) root and, when non-empty, must resolve to one of the configured roots (see resolveRoot) — an unlisted path is rejected with ErrRootNotAllowed.

func (*DiskStore) GetRun

func (s *DiskStore) GetRun(_ context.Context, sessionID string) (RunDetail, error)

GetRun implements Store.

func (*DiskStore) Health

func (s *DiskStore) Health(_ context.Context) (Health, error)

Health implements Store.

func (*DiskStore) ListIterations

func (s *DiskStore) ListIterations(_ context.Context, sessionID string) ([]IterationSummary, error)

ListIterations implements Store.

func (*DiskStore) ListRuns

func (s *DiskStore) ListRuns(_ context.Context, f RunFilter) ([]RunSummary, error)

ListRuns implements Store.

func (*DiskStore) Rubric

func (s *DiskStore) Rubric(_ context.Context) (RubricDoc, error)

Rubric implements Store.

type Health

type Health struct {
	Status           string   `json:"status"`
	RunCount         int      `json:"run_count"`
	IterationCount   int      `json:"iteration_count"`
	LastIterLogMtime *string  `json:"last_iter_log_mtime"`
	SubscriberCount  int      `json:"subscriber_count"`
	RubricVersion    string   `json:"rubric_version"`
	Roots            []string `json:"roots"`
}

Health is the inline liveness payload (API.md §3.6, spec R9).

type IntegrityRow

type IntegrityRow struct {
	Signal     string     `json:"signal"`
	Role       string     `json:"role"`
	Claimed    SignalSide `json:"claimed"`
	Observed   SignalSide `json:"observed"`
	Comparable bool       `json:"comparable"`
	Delta      *float64   `json:"delta"`
}

IntegrityRow is one claimed-vs-observed integrity observation (detail only). It is a product of the scoring pipeline, not the persisted sidecar, so the read-through store leaves it empty — t06's recompute path populates it.

type IterScoreRef

type IterScoreRef struct {
	Iteration int      `json:"iteration"`
	Scored    bool     `json:"scored"`
	Score     *float64 `json:"score"`
	Band      string   `json:"band"`
}

IterScoreRef is a lightweight per-iteration score ref inside RunDetail.

type Iteration

type Iteration struct {
	Iteration     int      `json:"iteration"`
	SessionID     string   `json:"session_id"`
	SchemaVersion int      `json:"schema_version"`
	Date          string   `json:"date"`
	Wave          string   `json:"wave"`
	TaskID        string   `json:"task_id"`
	Commit        string   `json:"commit"`
	RubricVersion string   `json:"rubric_version"`
	Scored        bool     `json:"scored"`
	Score         *float64 `json:"score"`
	Band          string   `json:"band"`
	FilesChanged  int      `json:"files_changed"`
	LinesAdded    int      `json:"lines_added"`
	LinesRemoved  int      `json:"lines_removed"`
	Retries       int      `json:"retries"`
	// IntegrityObservationCount is populated by t06 recompute-on-miss; empty (0) from the raw read layer.
	IntegrityObservationCount int `json:"integrity_observation_count"`
	// TranscriptTurnCount is populated by t06 recompute-on-miss; empty (null) from the raw read layer.
	TranscriptTurnCount *int           `json:"transcript_turn_count"`
	TokenUsage          *TokenUsage    `json:"token_usage"`
	Verifiers           []Verifier     `json:"verifiers,omitempty"`
	Breakdown           []BreakdownRow `json:"breakdown,omitempty"`
	// Integrity is populated by t06 recompute-on-miss; empty (null) from the raw read layer.
	Integrity []IntegrityRow `json:"integrity,omitempty"`
	// Objective is populated by t06 recompute-on-miss; empty (null) from the raw read layer.
	Objective *Objective `json:"objective,omitempty"`
}

Iteration is the presentation projection of one agent-run iteration (dashboard-iteration schema). IterationSummary omits the heavy detail-only fields (breakdown / integrity / objective / verifiers); IterationDetail includes those the store can source from disk.

type IterationDetail

type IterationDetail = Iteration

IterationDetail is the full object returned by get one iteration.

type IterationSummary

type IterationSummary = Iteration

IterationSummary is the row shape inside the iteration list.

type Objective

type Objective struct {
	RanCliCommand       SignalSide `json:"ran_cli_command"`
	CommittedAfterTests SignalSide `json:"committed_after_tests"`
	ReadLoopState       SignalSide `json:"read_loop_state"`
}

Objective carries transcript-derived process checks (detail only). Like IntegrityRow it is recompute-sourced, so the store leaves it nil.

type Option

type Option func(*DiskStore)

Option configures a DiskStore.

func WithCache

func WithCache(size int, ttl time.Duration) Option

WithCache overrides the default LRU (size 256, TTL 30s).

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger sets the structured logger used for skipped-file warnings.

func WithSubscriberCounter

func WithSubscriberCounter(fn func() int) Option

WithSubscriberCounter injects the live SSE subscriber count reported by Health. The broker (t04) owns the real count; when unset Health reports 0.

type RecomputeStore

type RecomputeStore struct {
	*DiskStore
	// contains filtered or unexported fields
}

RecomputeStore is the t06 decorator over DiskStore. It implements Store; every method except GetIteration is the embedded DiskStore's. Construct with NewRecompute; the zero value is not usable.

func NewRecompute

func NewRecompute(s *DiskStore, repoDir string, transcriptDirs ...string) *RecomputeStore

NewRecompute wraps s with the score-recompute-on-miss path. repoDir is the repository root the scoring pipeline runs git topology against (the same argument scoring.ScoreIteration takes); transcriptDirs are the optional agent session-log roots for token backfill and the transcript-derived objective checks.

func (*RecomputeStore) Flush

func (r *RecomputeStore) Flush()

Flush waits for every pending background sidecar write. t07's server calls it on graceful shutdown so a just-recomputed score is not lost with the process; tests use it to observe the write deterministically.

func (*RecomputeStore) GetIteration

func (r *RecomputeStore) GetIteration(_ context.Context, iterLogDir string, n int) (IterationDetail, error)

GetIteration implements Store with the recompute-on-miss overlay: a fresh sidecar serves the plain t02 read; a missing / corrupt / stale sidecar triggers a synchronous pipeline recompute whose result both fills the response and is persisted back (best-effort, in the background).

type RubricBand

type RubricBand struct {
	Name string  `json:"name"`
	Min  float64 `json:"min"`
}

RubricBand is one band row of the rubric doc.

type RubricDoc

type RubricDoc struct {
	Version     string         `json:"version"`
	Combination string         `json:"combination"`
	Signals     []RubricSignal `json:"signals"`
	Bands       []RubricBand   `json:"bands"`
}

RubricDoc is the active-rubric projection (dashboard-rubric schema).

type RubricSignal

type RubricSignal struct {
	ID          string  `json:"id"`
	Label       string  `json:"label"`
	Weight      float64 `json:"weight"`
	Description string  `json:"description"`
	TwoWay      bool    `json:"two_way"`
}

RubricSignal is one signal row of the rubric doc.

type Run

type Run struct {
	SessionID        string         `json:"session_id"`
	Harness          string         `json:"harness"`
	Model            string         `json:"model"`
	Wave             string         `json:"wave"`
	RubricVersion    string         `json:"rubric_version"`
	IterationCount   int            `json:"iteration_count"`
	Scored           bool           `json:"scored"`
	Score            *float64       `json:"score"`
	Band             string         `json:"band"`
	FirstIteration   *int           `json:"first_iteration"`
	LastIteration    *int           `json:"last_iteration"`
	LastUpdate       *string        `json:"last_update"`
	IterLogDir       string         `json:"iter_log_dir"`
	PerIteration     []IterScoreRef `json:"per_iteration,omitempty"`
	MeanCacheHitRate *float64       `json:"mean_cache_hit_rate"`
}

Run is the presentation projection of one agent-run session (dashboard-run schema). RunSummary omits per_iteration; RunDetail includes it — they are the same shape, distinguished only by which fields the store populates.

type RunDetail

type RunDetail = Run

RunDetail is the full object returned by get one run (per_iteration present).

type RunFilter

type RunFilter struct {
	Limit   int
	Offset  int
	Sort    string // last_update | score | iteration_count | session_id
	Order   string // asc | desc
	Band    string // optional exact-match band filter
	Harness string // optional exact-match harness filter
}

RunFilter is the query shape for ListRuns. Zero values are normalized to the contract defaults (limit 50, offset 0, sort last_update, order desc) — param validation / 400s are the handler's job (t03), so the store is forgiving.

type RunSummary

type RunSummary = Run

RunSummary is the row shape returned by list runs (per_iteration omitted).

type SignalSide

type SignalSide struct {
	Present  bool    `json:"present"`
	SubScore float64 `json:"sub_score,omitempty"`
	Detail   string  `json:"detail,omitempty"`
}

SignalSide mirrors internal/scoring.SignalValue for the integrity DTO.

type Store

type Store interface {
	// ListRuns returns one RunSummary per session discovered across the
	// resolved roots, filtered / sorted / paginated per f. Never errors on a
	// corrupt file — the bad file is skipped and the remainder returned.
	ListRuns(ctx context.Context, f RunFilter) ([]RunSummary, error)
	// GetRun returns the full RunDetail (including per_iteration) for one
	// session, or ErrNotFound.
	GetRun(ctx context.Context, sessionID string) (RunDetail, error)
	// ListIterations returns the iteration list for one session ascending by
	// iteration, or ErrNotFound if the session is unknown. A listed iteration
	// whose score sidecar is absent returns scored:false / score:null.
	ListIterations(ctx context.Context, sessionID string) ([]IterationSummary, error)
	// GetIteration returns the full IterationDetail for iteration n in
	// iterLogDir (defaulting to the active root when empty), or ErrNotFound.
	GetIteration(ctx context.Context, iterLogDir string, n int) (IterationDetail, error)
	// Rubric returns the active rubric projection (spec R4).
	Rubric(ctx context.Context) (RubricDoc, error)
	// Health returns liveness counts for cheap operator triage (spec R9).
	Health(ctx context.Context) (Health, error)
}

Store is the read-through surface the dashboard handlers depend on. It is the interface API.md pins as authoritative for t02; a future denormalized backend (spec D2.1 forward door) can satisfy the same contract.

type TokenUsage

type TokenUsage struct {
	InputTokens         int     `json:"input_tokens"`
	OutputTokens        int     `json:"output_tokens"`
	CacheReadTokens     int     `json:"cache_read_tokens"`
	CacheCreationTokens int     `json:"cache_creation_tokens"`
	CacheHitRate        float64 `json:"cache_hit_rate"`
}

TokenUsage mirrors internal/scoring.TokenUsage for the DTO.

type Verifier

type Verifier struct {
	Type       string `json:"type"`
	Status     string `json:"status"`
	GatePassed bool   `json:"gate_passed"`
	TestsAdded int    `json:"tests_added"`
	Retries    int    `json:"retries"`
}

Verifier is one per-verifier outcome row (detail only).

Jump to

Keyboard shortcuts

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