Harden lease lifecycle durability

This commit is contained in:
kami
2026-07-30 14:34:29 +04:00
parent 1ff0af2e69
commit f6ee0e3060
40 changed files with 2108 additions and 590 deletions
+24 -8
View File
@@ -22,11 +22,11 @@ var ErrInvalid = errors.New("invalid event")
// task for this content; nothing was appended.
var ErrDuplicate = errors.New("duplicate task ingestion")
// CurrentEventSchema is 2: schema 2 requires every event to declare its
// authorizing Surface (see ValidateEvent), enforced at the store append
// boundary. Schema 1 events already on disk replay unchanged — tolerant
// reader, not upcast (spec open question #2).
const CurrentEventSchema = 2
// CurrentEventSchema is 3: schema 2 requires every event to declare its
// authorizing Surface; schema 3 adds lease fencing epochs. Older events stay
// readable so a deployment can recover its existing log before new writes
// are emitted (the store derives a non-renewable legacy epoch on replay).
const CurrentEventSchema = 3
type TaskState string
@@ -102,8 +102,13 @@ type SessionEvidence struct {
CheckedAt time.Time `json:"checked_at,omitempty"`
}
type Lease struct {
HarnessID string `json:"harness_id"`
Until time.Time `json:"until"`
HarnessID string `json:"harness_id"`
// Epoch is an opaque fencing token minted for every assignment. Versions
// change for ordinary lifecycle events; an epoch changes only when
// ownership changes, so an old pane can never become current again after
// a release/re-lease cycle.
Epoch string `json:"epoch"`
Until time.Time `json:"until"`
}
type Task struct {
ID string `json:"id"`
@@ -191,7 +196,18 @@ func ValidateEvent(e Event) error {
if p == nil {
return fmt.Errorf("%w: payload must be an object", ErrInvalid)
}
return ValidatePayload(e.Type, p)
if err := ValidatePayload(e.Type, p); err != nil {
return err
}
if e.SchemaVersion >= 3 {
switch e.Type {
case "TaskLeased", "TaskLeaseRenewed", "TaskPickupValidated":
if v, ok := p["lease_epoch"].(string); !ok || strings.TrimSpace(v) == "" {
return fmt.Errorf("%w: lease_epoch required", ErrInvalid)
}
}
}
return nil
}
func ValidateCreated(p map[string]any) error {
for _, k := range []string{"source", "external_id", "project"} {