'refused','reason'=>'unsupported_operation_kind'); } $deny=array('status'=>'refused','reason'=>'invalid_contract'); foreach (array('generation','operation_id') as $key) { if (!isset($row[$key]) || !is_string($row[$key]) || !preg_match('/\A[a-f0-9]{32}\z/D',$row[$key])) { return $deny; } } if (!isset($row['state']) || !in_array($row['state'],self::STATES,true)) { return $deny; } foreach (array('revision','attempts','max_attempts','expires_at','claim_until') as $key) { if (!isset($row[$key]) || !is_int($row[$key]) || $row[$key]<0) { return $deny; } } if ($now<0 || $now>PHP_INT_MAX-30 || $row['max_attempts']!==1 || $row['attempts']>$row['max_attempts'] || !array_key_exists('claim_id',$row) || ($row['claim_id']!==null && (!is_string($row['claim_id']) || !preg_match('/\A[a-f0-9]{32}\z/D',$row['claim_id'])))) { return $deny; } if (($authority['generation']??null)!==$row['generation'] || ($authority['open']??null)!==true) { return array('status'=>'refused','reason'=>'generation_closed_or_changed'); } $unsent=in_array($row['state'],array('intent','claimed','cancelled_unsent','expired_unsent'),true); if (($unsent && $row['attempts']!==0) || (!$unsent && ($row['attempts']!==1 || $row['claim_id']===null)) || ($row['state']==='claimed' && $row['claim_id']===null) || ($row['state']==='intent' && $row['claim_id']!==null)) { return $deny; } $next=$row; $state=$row['state']; $mayCollect=($authority['suppression']??null)==='clear' && ($authority['staff']??null)==='not_allowed'; $mayAttempt=$mayCollect && $now<$row['expires_at'] && $row['attempts']<$row['max_attempts']; // Consistency only, NOT authorisation: both inputs come from the adapter. // Capability, current lifecycle context and bound receipt verification // must be established independently of this comparison. $matchingClaim=$row['claim_id']!==null && ($authority['claim_id']??null)===$row['claim_id']; if ($event==='claim') { // Only definitely unsent work can be leased again. A persisted // dispatch marker is ambiguous forever until evidence resolves it. if (!$mayAttempt || !($state==='intent' || ($state==='claimed' && $now>=$row['claim_until']))) { return array('status'=>'refused','reason'=>'claim_ineligible'); } $claim=$authority['new_claim_id']??null; if (!is_string($claim) || !preg_match('/\A[a-f0-9]{32}\z/D',$claim) || $claim===$row['claim_id']) { return $deny; } $next['state']='claimed'; $next['claim_id']=$claim; $next['claim_until']=min($now+30,$row['expires_at']); } elseif ($event==='mark_dispatch') { if ($state!=='claimed' || !$matchingClaim || !$mayAttempt || $now>=$row['claim_until']) { return array('status'=>'refused','reason'=>'dispatch_ineligible'); } $next['state']='dispatch_unknown'; ++$next['attempts']; } elseif ($event==='fence') { if (($authority['suppression']??null)!=='suppressed' && ($authority['staff']??null)!=='allowed') { return $deny; } if (in_array($state,array('intent','claimed'),true)) { $next['state']='cancelled_unsent'; } elseif ($state==='dispatch_unknown') { $next['state']='attention_unknown'; } // Known delivery is not retroactively made unknown or unsent. } elseif ($event==='expire') { if ($now<$row['expires_at']) { return $deny; } if (in_array($state,array('intent','claimed'),true)) { $next['state']='expired_unsent'; } elseif ($state==='dispatch_unknown') { $next['state']='attention_unknown'; } } elseif ($event==='unresolved') { if ($state!=='dispatch_unknown' || !$matchingClaim) { return $deny; } $next['state']='attention_unknown'; } elseif ($event==='receipt_delivered' || $event==='receipt_refused') { if (!in_array($state,array('dispatch_unknown','attention_unknown'),true) || !$matchingClaim || ($authority['receipt_bound']??null)!==true) { return $deny; } // Unknown suppression must not be presented as ordinary safe delivery. if (!in_array($authority['suppression']??null,array('clear','suppressed'),true)) { return array('status'=>'refused','reason'=>'suppression_unknown'); } $next['state']=$event==='receipt_refused'?'refused':($authority['suppression']==='suppressed'?'delivered_despite_suppression':'delivered'); } else { return $deny; } if ($next===$row) { return array('status'=>'unchanged','row'=>$row); } if ($row['revision']===PHP_INT_MAX) { return array('status'=>'refused','reason'=>'revision_exhausted'); } ++$next['revision']; return array('status'=>'proposed','expected'=>$row,'row'=>$next); } /** Never an erasure completion predicate: even known delivery needs erasure. */ public static function unresolved(string $state): bool { return !in_array($state,array('intent','claimed','cancelled_unsent','expired_unsent','refused'),true); } }