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 ¶
- Variables
- type BreakdownRow
- type CacheMetrics
- type DiskStore
- func (s *DiskStore) CacheMetrics() CacheMetrics
- func (s *DiskStore) Evict(root string)
- func (s *DiskStore) EvictAll()
- func (s *DiskStore) GetIteration(_ context.Context, iterLogDir string, n int) (IterationDetail, error)
- func (s *DiskStore) GetRun(_ context.Context, sessionID string) (RunDetail, error)
- func (s *DiskStore) Health(_ context.Context) (Health, error)
- func (s *DiskStore) ListIterations(_ context.Context, sessionID string) ([]IterationSummary, error)
- func (s *DiskStore) ListRuns(_ context.Context, f RunFilter) ([]RunSummary, error)
- func (s *DiskStore) Rubric(_ context.Context) (RubricDoc, error)
- type Health
- type IntegrityRow
- type IterScoreRef
- type Iteration
- type IterationDetail
- type IterationSummary
- type Objective
- type Option
- type RecomputeStore
- type RubricBand
- type RubricDoc
- type RubricSignal
- type Run
- type RunDetail
- type RunFilter
- type RunSummary
- type SignalSide
- type Store
- type TokenUsage
- type Verifier
Constants ¶
This section is empty.
Variables ¶
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.
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 ¶
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 ¶
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) ListIterations ¶
ListIterations 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 WithLogger ¶
WithLogger sets the structured logger used for skipped-file warnings.
func WithSubscriberCounter ¶
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 ¶
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.