Files
mesh-controller/internal/inventory/looks.go
T
jochen dcdafd37f0
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
Keep a condition's looks in the store, so a look that changes nothing writes no bucket (issue 420)
Every look rewrote the condition's value in the conditions bucket, because the
value carried the count of looks, the last look and the newest evidence: 131 647
writes in four days for twelve open conditions, against ADR 0289's rule that a
bucket is written only when what it describes changed. The looks now live in the
controller's store (migration 0095, condition_looks), the bucket holds what a
condition says and is written only when that changes, and the conditions verb and
the condition events put the looks back.
2026-10-12 12:11:13 +02:00

91 lines
3.0 KiB
Go

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
}