package inventory import ( "context" "encoding/json" "errors" "time" "github.com/jackc/pgx/v5" ) // The looks of the open conditions (novox/hq issue 420): see migration 0095. How many times each was // observed, when last, and the newest evidence change on every look, so they are kept here rather than in // the conditions bucket, which is written only when what a condition says changes (ADR 0289 ยง1). // ConditionLooks is one open condition's looks. Evidence is kept as the condition says it, newest first. type ConditionLooks struct { Key string Observations int LastObserved time.Time Evidence json.RawMessage } // KeepConditionLooks writes a condition's looks, replacing what its row held. Written under the lease: a // controller that may not act keeps no looks. func (i *Inventory) KeepConditionLooks(ctx context.Context, l ConditionLooks) error { if l.Key == "" { return errors.New("a condition's looks name its key") } epoch, err := i.actingEpoch(ctx) if err != nil { return err } evidence := l.Evidence if len(evidence) == 0 { evidence = json.RawMessage("[]") } _, err = i.store.Pool().Exec(ctx, `insert into condition_looks (key, observations, last_observed, evidence, epoch, kept_at) values ($1, $2, $3, $4, $5, now()) on conflict (key) do update set observations = excluded.observations, last_observed = excluded.last_observed, evidence = excluded.evidence, epoch = excluded.epoch, kept_at = excluded.kept_at`, l.Key, l.Observations, l.LastObserved, []byte(evidence), epoch) return err } // ConditionLooksOf is one condition's looks; false when none are kept. func (i *Inventory) ConditionLooksOf(ctx context.Context, key string) (ConditionLooks, bool, error) { l := ConditionLooks{Key: key} var evidence []byte err := i.store.Pool().QueryRow(ctx, `select observations, last_observed, evidence from condition_looks where key = $1`, key). Scan(&l.Observations, &l.LastObserved, &evidence) if errors.Is(err, pgx.ErrNoRows) { return ConditionLooks{}, false, nil } if err != nil { return ConditionLooks{}, false, err } l.Evidence = evidence return l, true, nil } // AllConditionLooks is every kept condition's looks, by key. func (i *Inventory) AllConditionLooks(ctx context.Context) (map[string]ConditionLooks, error) { rows, err := i.store.Pool().Query(ctx, `select key, observations, last_observed, evidence from condition_looks`) if err != nil { return nil, err } defer rows.Close() out := map[string]ConditionLooks{} for rows.Next() { var l ConditionLooks var evidence []byte if err := rows.Scan(&l.Key, &l.Observations, &l.LastObserved, &evidence); err != nil { return nil, err } l.Evidence = evidence out[l.Key] = l } return out, rows.Err() } // ForgetConditionLooks removes a condition's looks: it cleared. Whatever the lease says by now, as a // clearing is: what is not open has nothing to count. func (i *Inventory) ForgetConditionLooks(ctx context.Context, key string) error { _, err := i.store.Pool().Exec(ctx, `delete from condition_looks where key = $1`, key) return err }