package driver import ( "fmt" "sync" "onehouse/protocol" ) const commandResultCapacity = 256 // Ledger keeps the first result of each of the most recent 256 commands for one // device, in insertion order. Its methods are safe for concurrent use. A single // queue consumer checks Lookup before acting and calls Remember before sending // the result, also after a failed send. The ledger does not reserve physical // work, persist records, or prevent replay after eviction. type Ledger struct { deviceID string mu sync.Mutex results map[string]ResultRecord order []string } // NewLedger creates an empty result ledger for one canonical device ID. func NewLedger(deviceID string) (*Ledger, error) { if !protocol.ValidID(deviceID) { return nil, fmt.Errorf("ledger device ID must be a canonical ULID") } return &Ledger{ deviceID: deviceID, results: make(map[string]ResultRecord), order: make([]string, 0, commandResultCapacity), }, nil } // Lookup returns an independent copy of a result without changing its age. func (l *Ledger) Lookup(commandID string) (ResultRecord, bool) { l.mu.Lock() defer l.mu.Unlock() result, exists := l.results[commandID] return result.clone(), exists } // Remember stores the first validated result before publication. An existing // result wins even when a duplicate proposes another status or reason. Its ID // and observation time remain stable after failed publication and on replay. func (l *Ledger) Remember(commandID string, payload protocol.ResultPayload) (ResultRecord, error) { l.mu.Lock() defer l.mu.Unlock() result, exists := l.results[commandID] if exists { return result.clone(), nil } metadata, err := newMetadata(protocol.Metadata{CorrelationID: commandID}) if err != nil { return ResultRecord{}, fmt.Errorf("create result metadata: %w", err) } _, err = protocol.BuildResult(l.deviceID, commandID, metadata, payload) if err != nil { return ResultRecord{}, fmt.Errorf("validate result: %w", err) } if len(l.order) == commandResultCapacity { delete(l.results, l.order[0]) l.order = l.order[1:] } result = ResultRecord{ Metadata: metadata, Payload: payload, } l.results[commandID] = result l.order = append(l.order, commandID) return result.clone(), nil }