harden versioned event substrate

This commit is contained in:
kami
2026-07-26 20:19:18 +04:00
parent 6bf6445d40
commit 4e05e4df7b
3 changed files with 37 additions and 11 deletions
+17 -8
View File
@@ -17,6 +17,8 @@ var ErrConflict = errors.New("task version conflict")
var ErrNotFound = errors.New("task not found")
var ErrInvalid = errors.New("invalid event")
const CurrentEventSchema = 1
type TaskState string
const (
@@ -53,13 +55,14 @@ type Task struct {
}
type Event struct {
Seq uint64 `json:"seq"`
ID string `json:"id"`
Type string `json:"type"`
TaskID string `json:"task_id"`
Version int `json:"version"`
At time.Time `json:"at"`
Payload json.RawMessage `json:"payload"`
SchemaVersion int `json:"schema_version,omitempty"`
Seq uint64 `json:"seq"`
ID string `json:"id"`
Type string `json:"type"`
TaskID string `json:"task_id"`
Version int `json:"version"`
At time.Time `json:"at"`
Payload json.RawMessage `json:"payload"`
}
func Hash(v []byte) string { h := sha256.Sum256(v); return hex.EncodeToString(h[:]) }
@@ -76,7 +79,7 @@ func NewID() string {
return ulidEncoding.EncodeToString(b)
}
func ValidateEvent(e Event) error {
if e.Type == "" || e.TaskID == "" || len(e.Payload) == 0 || len(e.Payload) > 64*1024 {
if e.SchemaVersion > CurrentEventSchema || e.Type == "" || e.TaskID == "" || len(e.Payload) == 0 || len(e.Payload) > 64*1024 {
return ErrInvalid
}
allowed := map[string]bool{"TaskCreated": true, "TaskLeased": true, "TaskReleased": true, "TaskCompleted": true, "TaskFailed": true, "TaskBlocked": true, "ApprovalRequested": true, "ApprovalGranted": true, "ApprovalDenied": true, "TaskAmended": true, "QuotaReported": true, "StandupAdvisory": true}
@@ -87,6 +90,9 @@ func ValidateEvent(e Event) error {
if err := json.Unmarshal(e.Payload, &p); err != nil {
return fmt.Errorf("%w: payload is not JSON", ErrInvalid)
}
if p == nil {
return fmt.Errorf("%w: payload must be an object", ErrInvalid)
}
return ValidatePayload(e.Type, p)
}
func ValidateCreated(p map[string]any) error {
@@ -116,6 +122,9 @@ func ValidatePayload(typ string, p map[string]any) error {
if _, ok := p["until_ns"].(float64); !ok {
return fmt.Errorf("%w: until_ns required", ErrInvalid)
}
if v, ok := p["expected_version"].(float64); ok && v < 0 {
return fmt.Errorf("%w: expected_version invalid", ErrInvalid)
}
case "TaskReleased":
if err := requiredString("handoff_ref"); err != nil && p["reason"] == nil {
return err