feat: add audited task review and linked manual retries
This commit is contained in:
@@ -137,20 +137,25 @@ func emitEvent(ctx context.Context, tx *sql.Tx, ns, resource core.ID, revision u
|
||||
return err
|
||||
}
|
||||
func appendAudit(ctx context.Context, tx *sql.Tx, token auth.Token, operation, resource core.ID, kind, action, reason string, before, after any) error {
|
||||
_, err := appendAuditWithEvidence(ctx, tx, token, operation, resource, kind, action, reason, "", before, after)
|
||||
return err
|
||||
}
|
||||
|
||||
func appendAuditWithEvidence(ctx context.Context, tx *sql.Tx, token auth.Token, operation, resource core.ID, kind, action, reason, evidence string, before, after any) (core.ID, error) {
|
||||
id, err := core.NewID()
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
old, err := json.Marshal(before)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
current, err := json.Marshal(after)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
_, err = tx.ExecContext(ctx, "INSERT INTO orbit_audit_events(namespace_id,id,operation_id,actor_id,action,resource_kind,resource_id,reason,`before`,`after`) VALUES (?,?,?,?,?,?,?,?,?,?)", token.NamespaceID, id, operation, string(token.CredentialID), action, kind, resource, reason, string(old), string(current))
|
||||
return err
|
||||
_, err = tx.ExecContext(ctx, "INSERT INTO orbit_audit_events(namespace_id,id,operation_id,actor_id,action,resource_kind,resource_id,reason,evidence_ref,`before`,`after`) VALUES (?,?,?,?,?,?,?,?,NULLIF(?,''),?,?)", token.NamespaceID, id, operation, string(token.CredentialID), action, kind, resource, reason, evidence, string(old), string(current))
|
||||
return id, err
|
||||
}
|
||||
func dbNow(ctx context.Context, tx *sql.Tx) (time.Time, error) {
|
||||
var now time.Time
|
||||
@@ -188,34 +193,7 @@ func (s *TaskStore) Submit(ctx context.Context, token auth.Token, key string, in
|
||||
if _, _, err := lockQueueAfterApplication(ctx, tx, token.NamespaceID, in.QueueID); err != nil {
|
||||
return err
|
||||
}
|
||||
now, err := dbNow(ctx, tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
available := now
|
||||
if in.AvailableAt != nil {
|
||||
available = *in.AvailableAt
|
||||
}
|
||||
deadline := now.Add(time.Duration(in.TaskTimeoutMS) * time.Millisecond)
|
||||
if available.Before(now.Add(-time.Second)) || !available.Before(deadline) {
|
||||
return fault.Invalid("available_at must be current/future and before the task deadline")
|
||||
}
|
||||
if in.StartDeadlineAt != nil && (!in.StartDeadlineAt.After(now) || in.StartDeadlineAt.Before(available) || in.StartDeadlineAt.After(deadline)) {
|
||||
return fault.Invalid("start_deadline_at must be after now, at or after available_at, and no later than the task deadline")
|
||||
}
|
||||
policy, err := json.Marshal(in.Retry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := reserveQueueTask(ctx, tx, token.NamespaceID, in.QueueID); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tx.ExecContext(ctx, `INSERT INTO pulse_tasks(namespace_id,id,queue_id,handler,handler_version,payload_version,payload,policy_snapshot,recovery_safety,business_key,concurrency_key,concurrency_key_digest,priority,available_at,start_deadline_at,deadline_at,attempt_timeout_ms,max_attempts,created_at,updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,NULLIF(?,''),?,?,?,?,?,?,?,?,?,?)`, token.NamespaceID, taskID, in.QueueID, in.Handler, in.HandlerVersion, in.PayloadVersion, string(in.Payload), string(policy), in.RecoverySafety, in.BusinessKey, concurrencyKeyValue(in.ConcurrencyKey), concurrencyDigest(in.ConcurrencyKey), in.Priority, available, in.StartDeadlineAt, deadline, in.Retry.AttemptTimeoutMS, in.Retry.MaxAttempts, now, now)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out.Task, err = readTask(ctx, tx, token.NamespaceID, taskID, false)
|
||||
out.Task, err = insertAcceptedTask(ctx, tx, token.NamespaceID, taskID, in, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -230,14 +208,46 @@ func (s *TaskStore) Submit(ctx context.Context, token auth.Token, key string, in
|
||||
return out, err
|
||||
}
|
||||
|
||||
const taskColumns = `id,namespace_id,queue_id,state,revision,handler,handler_version,payload_version,payload,result,COALESCE(business_key,''),COALESCE(concurrency_key,''),priority,available_at,start_deadline_at,deadline_at,recovery_safety,policy_snapshot,attempt_count,cancel_requested_at,completed_at,created_at,updated_at,effect_uncertain`
|
||||
// insertAcceptedTask 要求调用方已持有应用额度和队列锁,并将身份、事件及审计同事务提交。
|
||||
func insertAcceptedTask(ctx context.Context, tx *sql.Tx, ns, taskID core.ID, in service.SubmitInput, retryOf *core.ID) (service.TaskView, error) {
|
||||
now, err := dbNow(ctx, tx)
|
||||
if err != nil {
|
||||
return service.TaskView{}, err
|
||||
}
|
||||
available := now
|
||||
if in.AvailableAt != nil {
|
||||
available = *in.AvailableAt
|
||||
}
|
||||
deadline := now.Add(time.Duration(in.TaskTimeoutMS) * time.Millisecond)
|
||||
if available.Before(now.Add(-time.Second)) || !available.Before(deadline) {
|
||||
return service.TaskView{}, fault.Invalid("available_at must be current/future and before the task deadline")
|
||||
}
|
||||
if in.StartDeadlineAt != nil && (!in.StartDeadlineAt.After(now) || in.StartDeadlineAt.Before(available) || in.StartDeadlineAt.After(deadline)) {
|
||||
return service.TaskView{}, fault.Invalid("start_deadline_at must be after now, at or after available_at, and no later than the task deadline")
|
||||
}
|
||||
policy, err := json.Marshal(in.Retry)
|
||||
if err != nil {
|
||||
return service.TaskView{}, err
|
||||
}
|
||||
if err := reserveQueueTask(ctx, tx, ns, in.QueueID); err != nil {
|
||||
return service.TaskView{}, err
|
||||
}
|
||||
_, err = tx.ExecContext(ctx, `INSERT INTO pulse_tasks(namespace_id,id,queue_id,handler,handler_version,payload_version,payload,policy_snapshot,recovery_safety,business_key,concurrency_key,concurrency_key_digest,priority,available_at,start_deadline_at,deadline_at,attempt_timeout_ms,max_attempts,created_at,updated_at,retry_of)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,NULLIF(?,''),?,?,?,?,?,?,?,?,?,?,?)`, ns, taskID, in.QueueID, in.Handler, in.HandlerVersion, in.PayloadVersion, string(in.Payload), string(policy), in.RecoverySafety, in.BusinessKey, concurrencyKeyValue(in.ConcurrencyKey), concurrencyDigest(in.ConcurrencyKey), in.Priority, available, in.StartDeadlineAt, deadline, in.Retry.AttemptTimeoutMS, in.Retry.MaxAttempts, now, now, retryOf)
|
||||
if err != nil {
|
||||
return service.TaskView{}, err
|
||||
}
|
||||
return readTask(ctx, tx, ns, taskID, false)
|
||||
}
|
||||
|
||||
const taskColumns = `id,namespace_id,queue_id,state,revision,handler,handler_version,payload_version,payload,result,COALESCE(business_key,''),COALESCE(concurrency_key,''),priority,available_at,start_deadline_at,deadline_at,recovery_safety,policy_snapshot,attempt_count,cancel_requested_at,completed_at,created_at,updated_at,effect_uncertain,retry_of`
|
||||
|
||||
type scanner interface{ Scan(...any) error }
|
||||
|
||||
func scanTask(row scanner) (service.TaskView, error) {
|
||||
var v service.TaskView
|
||||
var policy, payload, result []byte
|
||||
err := row.Scan(&v.ID, &v.NamespaceID, &v.QueueID, &v.State, &v.Revision, &v.Handler, &v.HandlerVersion, &v.PayloadVersion, &payload, &result, &v.BusinessKey, &v.ConcurrencyKey, &v.Priority, &v.AvailableAt, &v.StartDeadlineAt, &v.DeadlineAt, &v.RecoverySafety, &policy, &v.AttemptCount, &v.CancelRequestedAt, &v.CompletedAt, &v.CreatedAt, &v.UpdatedAt, &v.EffectUncertain)
|
||||
err := row.Scan(&v.ID, &v.NamespaceID, &v.QueueID, &v.State, &v.Revision, &v.Handler, &v.HandlerVersion, &v.PayloadVersion, &payload, &result, &v.BusinessKey, &v.ConcurrencyKey, &v.Priority, &v.AvailableAt, &v.StartDeadlineAt, &v.DeadlineAt, &v.RecoverySafety, &policy, &v.AttemptCount, &v.CancelRequestedAt, &v.CompletedAt, &v.CreatedAt, &v.UpdatedAt, &v.EffectUncertain, &v.RetryOf)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user