add conservative quota availability filtering
This commit is contained in:
@@ -155,6 +155,17 @@ func ValidatePayload(typ string, p map[string]any) error {
|
||||
if err := requiredString("subject_ref"); err != nil {
|
||||
return err
|
||||
}
|
||||
case "QuotaReported":
|
||||
if err := requiredString("harness_id"); err != nil {
|
||||
return err
|
||||
}
|
||||
if v, ok := p["consumed"].(float64); !ok || v < 0 {
|
||||
return fmt.Errorf("%w: consumed required", ErrInvalid)
|
||||
}
|
||||
case "StandupAdvisory":
|
||||
if _, ok := p["items"]; !ok {
|
||||
return fmt.Errorf("%w: items required", ErrInvalid)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -17,6 +17,47 @@ type AlwaysAvailable struct{}
|
||||
|
||||
func (AlwaysAvailable) Available(registry.Herdr) bool { return true }
|
||||
|
||||
// QuotaAvailability applies the conservative 80% rule to the most recent
|
||||
// native quota report in the configured rolling window.
|
||||
type QuotaAvailability struct {
|
||||
Store *store.Store
|
||||
Limits map[string]float64
|
||||
Window time.Duration
|
||||
Now func() time.Time
|
||||
}
|
||||
|
||||
func (q QuotaAvailability) Available(h registry.Herdr) bool {
|
||||
if q.Store == nil {
|
||||
return false
|
||||
}
|
||||
limit, bounded := q.Limits[h.ID]
|
||||
if !bounded || limit <= 0 {
|
||||
return true
|
||||
}
|
||||
now := time.Now()
|
||||
if q.Now != nil {
|
||||
now = q.Now()
|
||||
}
|
||||
window := q.Window
|
||||
if window <= 0 {
|
||||
window = 7 * 24 * time.Hour
|
||||
}
|
||||
var consumed float64
|
||||
for _, e := range q.Store.Events(0) {
|
||||
if e.Type != "QuotaReported" || e.At.Before(now.Add(-window)) {
|
||||
continue
|
||||
}
|
||||
var p struct {
|
||||
HarnessID string `json:"harness_id"`
|
||||
Consumed float64 `json:"consumed"`
|
||||
}
|
||||
if json.Unmarshal(e.Payload, &p) == nil && p.HarnessID == h.ID && p.Consumed > consumed {
|
||||
consumed = p.Consumed
|
||||
}
|
||||
}
|
||||
return consumed < limit*0.8
|
||||
}
|
||||
|
||||
type RetryPolicy struct {
|
||||
MaxAttempts int
|
||||
Backoff time.Duration
|
||||
|
||||
@@ -63,6 +63,8 @@ The coordinator now persists active task→herdr session mappings in an atomic r
|
||||
|
||||
Provider supervision/reflection is now wired: Gitea webhook and polling use append-first task reflection, JSONL and Gitea loops restart with bounded backoff, and `/v1/providers/health` exposes running/error state. Provider lifecycle cancellation remains tied to process shutdown until the server gains a root cancellation context.
|
||||
|
||||
Quota reporting now has strict payload validation, and `router.QuotaAvailability` implements rolling-window conservative headroom filtering: a harness is considered full at 80% of its configured limit. Standup event payloads also require an items field; scheduled advisory production and approval application remain open.
|
||||
|
||||
Recommended order:
|
||||
|
||||
1. Add the orchestration coordinator: lease → worktree → harness session → bootstrap → lifecycle events.
|
||||
|
||||
Reference in New Issue
Block a user