checkpoint: multi-repo Gitea ingestion, per-project repos, rotation anchor_sha fix
Pre-existing uncommitted work found at session start: rotation now emits anchor_sha on TaskReleased (previously silently dropped by store.Append validation), multi-repo Gitea provider support, per-project git worktree roots, and associated test coverage. Committing as a checkpoint before starting remediation work tracked in AUDIT.md.
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"orchestra/internal/authz"
|
||||
"orchestra/internal/domain"
|
||||
"orchestra/internal/registry"
|
||||
"orchestra/internal/store"
|
||||
@@ -17,7 +18,7 @@ func TestAssignPendingUsesProjectAffinityAndCapability(t *testing.T) {
|
||||
}
|
||||
add := func(id, project string, caps []string) {
|
||||
b, _ := json.Marshal(map[string]any{"source": "test", "external_id": id, "project": project, "capability": caps})
|
||||
if err := s.Append(domain.Event{Type: "TaskCreated", TaskID: id, Version: 1, Payload: b}); err != nil {
|
||||
if err := s.Append(domain.Event{Type: "TaskCreated", TaskID: id, Version: 1, Payload: b, Surface: string(authz.System)}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -45,7 +46,7 @@ func TestAssignPendingDoesNotPreemptRunningWork(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b, _ := json.Marshal(map[string]any{"source": "test", "external_id": "queued", "project": "p", "capability": []string{}})
|
||||
if err := s.Append(domain.Event{Type: "TaskCreated", TaskID: "queued", Version: 1, Payload: b}); err != nil {
|
||||
if err := s.Append(domain.Event{Type: "TaskCreated", TaskID: "queued", Version: 1, Payload: b, Surface: string(authz.System)}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r, err := registry.New(registry.Config{Projects: []registry.Project{{ID: "p", MachineAffinity: []string{"m"}}}, Machines: []registry.Machine{{ID: "m", Address: "m:1"}}, Herdrs: []registry.Herdr{{ID: "h", MachineID: "m", Concurrency: 1}}})
|
||||
|
||||
+51
-25
@@ -4,6 +4,7 @@ package router
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"orchestra/internal/authz"
|
||||
"orchestra/internal/domain"
|
||||
"orchestra/internal/registry"
|
||||
"orchestra/internal/store"
|
||||
@@ -17,46 +18,71 @@ type AlwaysAvailable struct{}
|
||||
|
||||
func (AlwaysAvailable) Available(registry.Herdr) bool { return true }
|
||||
|
||||
// QuotaAvailability applies the conservative 80% rule to summed native
|
||||
// receipts in the configured rolling window. Receipts are additive across
|
||||
// rotations; a cumulative report must not replace earlier rotations.
|
||||
// QuotaWindowLimits are the two independent caps the spec (§7.2) requires:
|
||||
// the subscription pool's 5-hour rolling window and its weekly window. They
|
||||
// are tracked and evaluated separately — a harness deep into its 5h window
|
||||
// but fine on the week, or vice versa, must still be excluded.
|
||||
type QuotaWindowLimits struct {
|
||||
FiveHour float64
|
||||
Weekly float64
|
||||
}
|
||||
|
||||
const (
|
||||
fiveHourWindow = 5 * time.Hour
|
||||
weeklyWindow = 7 * 24 * time.Hour
|
||||
// quotaConservativeFraction is the degrade-safe default from spec §7.2/§9
|
||||
// item 1: since no quota pool is authoritative, treat 80% reported as
|
||||
// full rather than trusting the exact number.
|
||||
quotaConservativeFraction = 0.8
|
||||
)
|
||||
|
||||
// QuotaAvailability applies the conservative 80% rule independently to the
|
||||
// 5-hour rolling window and the weekly window, per harness. Receipts are
|
||||
// additive across rotations; a cumulative session total must never replace
|
||||
// earlier rotations' receipts (spec §5.2.1) — summing native per-report
|
||||
// `consumed` deltas is what keeps this correct across rotation.
|
||||
type QuotaAvailability struct {
|
||||
Store *store.Store
|
||||
Limits map[string]float64
|
||||
Window time.Duration
|
||||
Limits map[string]QuotaWindowLimits
|
||||
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
|
||||
}
|
||||
func (q QuotaAvailability) sumSince(harnessID string, since time.Time) float64 {
|
||||
var consumed float64
|
||||
for _, e := range q.Store.Events(0) {
|
||||
if e.Type != "QuotaReported" || e.At.Before(now.Add(-window)) {
|
||||
if e.Type != "QuotaReported" || e.At.Before(since) {
|
||||
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 >= 0 {
|
||||
if json.Unmarshal(e.Payload, &p) == nil && p.HarnessID == harnessID && p.Consumed >= 0 {
|
||||
consumed += p.Consumed
|
||||
}
|
||||
}
|
||||
return consumed < limit*0.8
|
||||
return consumed
|
||||
}
|
||||
|
||||
func (q QuotaAvailability) Available(h registry.Herdr) bool {
|
||||
if q.Store == nil {
|
||||
return false
|
||||
}
|
||||
limits, bounded := q.Limits[h.ID]
|
||||
if !bounded || (limits.FiveHour <= 0 && limits.Weekly <= 0) {
|
||||
return true
|
||||
}
|
||||
now := time.Now()
|
||||
if q.Now != nil {
|
||||
now = q.Now()
|
||||
}
|
||||
if limits.FiveHour > 0 && q.sumSince(h.ID, now.Add(-fiveHourWindow)) >= limits.FiveHour*quotaConservativeFraction {
|
||||
return false
|
||||
}
|
||||
if limits.Weekly > 0 && q.sumSince(h.ID, now.Add(-weeklyWindow)) >= limits.Weekly*quotaConservativeFraction {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type RetryPolicy struct {
|
||||
@@ -185,6 +211,6 @@ func importance(t domain.Task, now time.Time) time.Time {
|
||||
}
|
||||
func (r *Router) fail(t domain.Task) (domain.Event, error) {
|
||||
b, _ := json.Marshal(map[string]any{"reason": "retry_limit", "attempts": r.attempts[t.ID]})
|
||||
e := domain.Event{ID: domain.NewID(), Type: "TaskFailed", TaskID: t.ID, Version: t.Version + 1, Payload: b}
|
||||
e := domain.Event{ID: domain.NewID(), Type: "TaskFailed", TaskID: t.ID, Version: t.Version + 1, Payload: b, Surface: string(authz.System)}
|
||||
return e, r.Store.Append(e)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package router
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"orchestra/internal/authz"
|
||||
"orchestra/internal/domain"
|
||||
"orchestra/internal/registry"
|
||||
"orchestra/internal/store"
|
||||
@@ -28,7 +29,7 @@ func TestAssignsByAffinityCapabilityAndConcurrency(t *testing.T) {
|
||||
}
|
||||
makeTask := func(id string) {
|
||||
b, _ := json.Marshal(map[string]any{"source": "test", "external_id": id, "project": "p", "capability": []string{"go"}})
|
||||
if err := s.Append(domain.Event{ID: id, TaskID: id, Type: "TaskCreated", Version: 1, Payload: b}); err != nil {
|
||||
if err := s.Append(domain.Event{ID: id, TaskID: id, Type: "TaskCreated", Version: 1, Payload: b, Surface: string(authz.System)}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -43,3 +44,45 @@ func TestAssignsByAffinityCapabilityAndConcurrency(t *testing.T) {
|
||||
t.Fatal("no task leased")
|
||||
}
|
||||
}
|
||||
|
||||
// TestQuotaWindowsAreIndependent proves the 5-hour rolling window and the
|
||||
// weekly window (spec §7.2, §9 item 1) are each conservative-80%-full gates
|
||||
// on their own — a harness can be fine on one window and excluded by the
|
||||
// other, and receipts outside a window must not count toward it.
|
||||
func TestQuotaWindowsAreIndependent(t *testing.T) {
|
||||
s, err := store.Open(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
report := func(at time.Time, consumed float64) {
|
||||
p, _ := json.Marshal(map[string]any{"harness_id": "h1", "consumed": consumed})
|
||||
if err := s.Append(domain.Event{ID: domain.NewID(), Type: "QuotaReported", TaskID: "quota", Version: 1, Surface: string(authz.System), Payload: p, At: at}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Case 1: only weekly limit configured. A receipt older than 5h but
|
||||
// within the week still counts toward the weekly gate.
|
||||
report(now.Add(-6*time.Hour), 85)
|
||||
weeklyOnly := QuotaAvailability{Store: s, Limits: map[string]QuotaWindowLimits{"h1": {Weekly: 100}}, Now: func() time.Time { return now }}
|
||||
if weeklyOnly.Available(registry.Herdr{ID: "h1"}) {
|
||||
t.Fatal("weekly window should be exhausted at 85/100 (>=80%)")
|
||||
}
|
||||
|
||||
// Case 2: only a 5h limit configured. The same 6h-old receipt is outside
|
||||
// the 5h window and must not count.
|
||||
fiveHourOnly := QuotaAvailability{Store: s, Limits: map[string]QuotaWindowLimits{"h1": {FiveHour: 100}}, Now: func() time.Time { return now }}
|
||||
if !fiveHourOnly.Available(registry.Herdr{ID: "h1"}) {
|
||||
t.Fatal("receipt outside the 5h window incorrectly counted against it")
|
||||
}
|
||||
|
||||
// Case 3: a fresh receipt inside the 5h window trips the 5h gate even
|
||||
// though the weekly gate (fed by both receipts) also trips — both are
|
||||
// independently enforced, and either failing excludes the harness.
|
||||
report(now.Add(-time.Minute), 90)
|
||||
both := QuotaAvailability{Store: s, Limits: map[string]QuotaWindowLimits{"h1": {FiveHour: 100, Weekly: 500}}, Now: func() time.Time { return now }}
|
||||
if both.Available(registry.Herdr{ID: "h1"}) {
|
||||
t.Fatal("5h window should be exhausted at 90/100 (>=80%) regardless of weekly headroom")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user