fix: harden task recovery and DR contract handling
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
#include "workflow_fixture.hpp"
|
||||
#include <fstream>
|
||||
int main(){
|
||||
Simulator driver;const auto path=Fixture::test_root()+"/dispatch-audit.journal";
|
||||
ActiveGoalRegistry registry(driver,path);GoalRequest q;q.robot_id="robot";q.trace={"task","pick","run",1,1,1,1};q.skill=Skill::PICK;
|
||||
bool observed=false;
|
||||
registry.set_dispatch_recorder([&](const GoalRequest& request){std::ifstream durable(path);std::string line;std::getline(durable,line);assert(line.find(request.goal_id)!=std::string::npos);assert(driver.sent.empty());observed=true;throw std::runtime_error("task audit journal unavailable");});
|
||||
auto id=registry.start(q,SteadyTime{});assert(id);assert(observed);assert(driver.sent.empty());assert(registry.robot_locked("robot"));
|
||||
q.trace.attempt=2;assert(!registry.start(q,SteadyTime{}));
|
||||
std::cout<<"dispatch manifest audit failure prevents transport and retains lock\n";
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#include "workflow_fixture.hpp"
|
||||
struct EvidenceDriver:Simulator {
|
||||
std::string sabotage;
|
||||
void send(const GoalRequest& q)override {
|
||||
if(!sabotage.empty()){std::filesystem::remove(sabotage);std::filesystem::create_directory(sabotage);}
|
||||
record_wire_request(q.goal_id,"bt_skill_interfaces/action/ExecuteManipulation_Goal","000102ff");
|
||||
sent.push_back(q);
|
||||
}
|
||||
};
|
||||
int main(){
|
||||
const auto path=Fixture::test_root()+"/evidence.journal"; EvidenceDriver driver; std::string id;
|
||||
GoalRequest q;q.robot_id="robot";q.trace={"task","pick","run",1,1,1,1};q.skill=Skill::PICK;
|
||||
{ActiveGoalRegistry registry(driver,path);id=*registry.start(q,SteadyTime{});
|
||||
assert(registry.find(id)->request.wire_request_snapshot=="000102ff");
|
||||
GoalEvent e;e.goal_id=id;e.trace=q.trace;e.kind=EventKind::ACCEPTED;driver.events.push_back(e);registry.pump(SteadyTime{});
|
||||
e.kind=EventKind::FEEDBACK;e.sequence=2;e.feedback_snapshot="{\"phase\":3,\"message\":\"executing\\nchunk\",\"progress\":0.25}";driver.events.push_back(e);registry.pump(SteadyTime{});
|
||||
e.sequence=1;e.feedback_snapshot="old feedback";driver.events.push_back(e);registry.pump(SteadyTime{});
|
||||
assert(registry.find(id)->feedback_snapshot.find("executing")!=std::string::npos);
|
||||
registry.request_cancel(id,SteadyTime{});e.sequence=3;e.feedback_snapshot="STOPPING";driver.events.push_back(e);registry.pump(SteadyTime{});assert(registry.find(id)->feedback_snapshot=="STOPPING");assert(registry.robot_locked("robot"));
|
||||
e.kind=EventKind::RESULT;e.native_status=NativeStatus::ABORTED;e.result.code=ResultCode::FAILED;e.result.stop=StopState::CONFIRMED;e.result.error_code="VLA_INFERENCE_TIMEOUT";e.result.execution_record_ref="records/run/pick.json";e.result.wire_result_type="bt_skill_interfaces/action/ExecuteManipulation_Result";e.result.wire_result_snapshot="000abbff";driver.events.push_back(e);registry.pump(SteadyTime{});
|
||||
}
|
||||
{ActiveGoalRegistry registry(driver,path);auto r=registry.find(id);assert(r);
|
||||
assert(r->request.wire_request_type=="bt_skill_interfaces/action/ExecuteManipulation_Goal");
|
||||
assert(r->request.wire_request_snapshot=="000102ff");assert(r->feedback_snapshot=="STOPPING");
|
||||
assert(r->result->wire_result_type=="bt_skill_interfaces/action/ExecuteManipulation_Result");assert(r->result->wire_result_snapshot=="000abbff");assert(r->result->error_code=="VLA_INFERENCE_TIMEOUT");assert(r->result->execution_record_ref=="records/run/pick.json");
|
||||
}
|
||||
const auto bad=Fixture::test_root()+"/wire-write-failure.journal";EvidenceDriver broken;broken.sabotage=bad;
|
||||
{ActiveGoalRegistry registry(broken,bad);try{registry.start(q,SteadyTime{});}catch(const std::exception&){}assert(broken.sent.empty());assert(registry.robot_locked("robot"));}
|
||||
std::filesystem::remove(bad);
|
||||
std::cout<<"wire request, ordered feedback and structured result survive restart; snapshot storage failure prevents transport\n";
|
||||
}
|
||||
@@ -1,2 +1,23 @@
|
||||
#include "workflow_fixture.hpp"
|
||||
int main(){Fixture f("refresh_readiness");auto r=f.runner();r.update_safety({true,true,Holding::EMPTY,1000000,9000000000});assert(r.tick(Stage::PREFLIGHT,SteadyTime{},1000000)==TickStatus::RUNNING);assert(r.tick(Stage::PREFLIGHT,SteadyTime{}+Milliseconds(1),2000000)==TickStatus::SUCCESS);f.driver.unavailable_skill=Skill::NAVIGATE;TickStatus status=TickStatus::RUNNING;for(int i=1;i<=60&&status==TickStatus::RUNNING;++i){f.driver.now=2000000+i*100000000LL;r.update_safety({true,true,Holding::EMPTY,f.driver.now,f.driver.now+1000000000});status=r.tick(Stage::NAVIGATE_OBSERVE,SteadyTime{}+Milliseconds(i*100),f.driver.now);}assert(status==TickStatus::FAILURE);assert(f.count(Skill::NAVIGATE)==0);std::cout<<"after 6000ms unavailable navigation: still_running="<<(status==TickStatus::RUNNING)<<" verify_empty_calls="<<f.count(Skill::VERIFY_EMPTY)<<" nav_calls="<<f.count(Skill::NAVIGATE)<<"\n";}
|
||||
void stopped_state_race(bool recover) {
|
||||
Fixture f(recover?"stopped_recovers":"stopped_expires"); auto r=f.runner();
|
||||
r.update_safety({true,true,Holding::EMPTY,1000000,9000000000});
|
||||
assert(r.tick(Stage::PREFLIGHT,SteadyTime{},1000000)==TickStatus::RUNNING);
|
||||
assert(r.tick(Stage::PREFLIGHT,SteadyTime{}+Milliseconds(1),2000000)==TickStatus::SUCCESS);
|
||||
assert(r.tick(Stage::NAVIGATE_OBSERVE,SteadyTime{}+Milliseconds(2),3000000)==TickStatus::RUNNING);
|
||||
r.update_safety({true,false,Holding::EMPTY,4000000,9000000000});
|
||||
assert(r.tick(Stage::NAVIGATE_OBSERVE,SteadyTime{}+Milliseconds(3),4000000)==TickStatus::SUCCESS);
|
||||
// Terminal result can arrive before the independent stopped-state subscription.
|
||||
assert(r.tick(Stage::LOCATE_SHELF_COLUMN,SteadyTime{}+Milliseconds(4),5000000)==TickStatus::RUNNING);
|
||||
assert(f.count(Skill::LOCATE_SHELF_COLUMN)==0);
|
||||
if(recover) {
|
||||
r.update_safety({true,true,Holding::EMPTY,6000000,9000000000});
|
||||
assert(r.tick(Stage::LOCATE_SHELF_COLUMN,SteadyTime{}+Milliseconds(5),6000000)==TickStatus::RUNNING);
|
||||
assert(f.count(Skill::LOCATE_SHELF_COLUMN)==1);
|
||||
assert(r.tick(Stage::LOCATE_SHELF_COLUMN,SteadyTime{}+Milliseconds(6),7000000)==TickStatus::SUCCESS);
|
||||
} else {
|
||||
assert(r.tick(Stage::LOCATE_SHELF_COLUMN,SteadyTime{}+Milliseconds(2004),2005000000)==TickStatus::FAILURE);
|
||||
assert(f.count(Skill::LOCATE_SHELF_COLUMN)==0);
|
||||
}
|
||||
}
|
||||
int main(){stopped_state_race(true);stopped_state_race(false);Fixture f("refresh_readiness");auto r=f.runner();r.update_safety({true,true,Holding::EMPTY,1000000,9000000000});assert(r.tick(Stage::PREFLIGHT,SteadyTime{},1000000)==TickStatus::RUNNING);assert(r.tick(Stage::PREFLIGHT,SteadyTime{}+Milliseconds(1),2000000)==TickStatus::SUCCESS);f.driver.unavailable_skill=Skill::NAVIGATE;TickStatus status=TickStatus::RUNNING;for(int i=1;i<=60&&status==TickStatus::RUNNING;++i){f.driver.now=2000000+i*100000000LL;r.update_safety({true,true,Holding::EMPTY,f.driver.now,f.driver.now+1000000000});status=r.tick(Stage::NAVIGATE_OBSERVE,SteadyTime{}+Milliseconds(i*100),f.driver.now);}assert(status==TickStatus::FAILURE);assert(f.count(Skill::NAVIGATE)==0);std::cout<<"stopped-state race bounded; unavailable readiness bounded\n";}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
#include "workflow_fixture.hpp"
|
||||
#include <set>
|
||||
int main(){Simulator d;const auto path=Fixture::test_root()+"/retention.journal";std::string first;Trace original;
|
||||
{ActiveGoalRegistry r(d,path);for(int i=0;i<280;++i){GoalRequest q;q.robot_id="r";q.trace={"task","step"+std::to_string(i),"run",1,1,1,1};auto id=r.start(q,SteadyTime{});assert(id);if(i==0){first=*id;original=q.trace;}r.pump(SteadyTime{});}assert(r.records().size()<=256);assert(!r.robot_locked("r"));auto old=r.history(first);assert(old&&old->state==GoalState::TERMINAL&&same_trace(old->request.trace,original));const auto history=r.task_records("run");assert(history.size()==280);std::set<std::string> identities;for(const auto& record:history){assert(!record.request.goal_id.empty());identities.insert(record.request.goal_id);}assert(identities.size()==280);}
|
||||
{ActiveGoalRegistry r(d,path);assert(r.records().size()<=256);GoalRequest q;q.robot_id="r";q.trace=original;assert(!r.start(q,SteadyTime{}));q.trace.subtask_id="pending";auto id=r.start(q,SteadyTime{});assert(id);}
|
||||
{ActiveGoalRegistry r(d,path);assert(r.robot_locked("r"));assert(r.records().size()<=257);}
|
||||
std::cout<<"terminal history bounded and archived attempts never replayed\n";
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#include "workflow_fixture.hpp"
|
||||
void semantic_failure(const std::string& code,bool ordinary) {
|
||||
Fixture f("semantic_"+code);auto r=f.runner();Workflow flow(r);unsigned i=0;
|
||||
for(;f.count(Skill::LOCATE_SHELF_COLUMN)==0&&i<40;++i){f.driver.now=1000000+i*1000000;r.update_safety({true,true,Holding::EMPTY,f.driver.now,f.driver.now+1000000000});assert(flow.tick(SteadyTime{}+Milliseconds(i),f.driver.now)==TickStatus::RUNNING);}
|
||||
assert(f.count(Skill::LOCATE_SHELF_COLUMN)==1);
|
||||
for(auto& e:f.driver.events)if(e.kind==EventKind::RESULT){e.result.response.valid=false;e.result.error_code=code;}
|
||||
f.driver.now+=1000000;r.update_safety({true,true,Holding::EMPTY,f.driver.now,f.driver.now+1000000000});
|
||||
assert(flow.tick(SteadyTime{}+Milliseconds(++i),f.driver.now)==(ordinary?TickStatus::FAILURE:TickStatus::INTERVENTION_REQUIRED));
|
||||
assert(r.error_code()==code);assert(f.count(Skill::PICK)==0);
|
||||
if(ordinary){r.halt(SteadyTime{}+Milliseconds(i));TickStatus status=TickStatus::RUNNING;for(unsigned n=0;n<5&&status==TickStatus::RUNNING;++n){f.driver.now+=1000000;r.update_safety({true,true,Holding::EMPTY,f.driver.now,f.driver.now+1000000000});status=r.settle(SteadyTime{}+Milliseconds(++i),f.driver.now);}assert(status==TickStatus::SUCCESS);assert(r.error_code()==code);assert(f.count(Skill::PICK)==0);}
|
||||
}
|
||||
int main(){semantic_failure("AMBIGUOUS",true);semantic_failure("NOT_FOUND",true);semantic_failure("INVALID_RESULT",false);std::cout<<"typed readonly ambiguity allows independently verified safe settlement\n";}
|
||||
@@ -2,7 +2,7 @@
|
||||
struct FailedPlaceDriver : Simulator {
|
||||
void send(const GoalRequest& q) override {
|
||||
Simulator::send(q);
|
||||
if(q.skill==Skill::PLACE){auto& event=events.back();event.native_status=NativeStatus::ABORTED;event.result.code=ResultCode::FAILED;}
|
||||
if(q.skill==Skill::PLACE){auto& event=events.back();event.native_status=NativeStatus::ABORTED;event.result.code=ResultCode::FAILED;event.result.error_code="VLA_EXECUTION_FAILED";}
|
||||
}
|
||||
};
|
||||
int main(){
|
||||
@@ -13,9 +13,11 @@ int main(){
|
||||
for(;i<200&&status==TickStatus::RUNNING;++i){driver.now=1000000+i*1000000;runner.update_safety({true,true,driver.sensor_holding,driver.now,driver.now+1000000000});status=flow.tick(SteadyTime{}+Milliseconds(i),driver.now);}
|
||||
assert(status==TickStatus::FAILURE);assert(deliveries==0);
|
||||
runner.halt(SteadyTime{}+Milliseconds(i));
|
||||
runner.update_safety({true,false,driver.sensor_holding,driver.now,driver.now+1000000000});
|
||||
assert(runner.settle(SteadyTime{}+Milliseconds(i),driver.now)==TickStatus::RUNNING);
|
||||
// Settlement is read-only except for the idempotent business receipt.
|
||||
for(status=TickStatus::RUNNING;i<300&&status==TickStatus::RUNNING;++i){driver.now=1000000+i*1000000;runner.update_safety({true,true,driver.sensor_holding,driver.now,driver.now+1000000000});status=runner.settle(SteadyTime{}+Milliseconds(i),driver.now);}
|
||||
assert(status==TickStatus::SUCCESS);assert(deliveries==1);
|
||||
assert(status==TickStatus::SUCCESS);assert(deliveries==1);assert(runner.error_code()=="VLA_EXECUTION_FAILED");
|
||||
unsigned place=0,verify=0;for(const auto&q:driver.sent){place+=q.skill==Skill::PLACE;verify+=q.skill==Skill::VERIFY_PLACE;}
|
||||
assert(place==1&&verify==1);assert(runner.empty_verified(driver.now));
|
||||
assert(runner.settle(SteadyTime{}+Milliseconds(i),driver.now)==TickStatus::SUCCESS);assert(deliveries==1);
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#include "workflow_fixture.hpp"
|
||||
void holding_alignment(bool contradictory){Fixture f(contradictory?"fresh_contradiction":"holding_lag");auto r=f.runner();Workflow flow(r);unsigned i=0;
|
||||
for(;flow.current_stage()!=Stage::TRANSPORT_POSTURE&&i<100;++i){f.driver.now=1000000+i*1000000;r.update_safety({true,true,f.driver.sensor_holding,f.driver.now,f.driver.now+1000000000});assert(flow.tick(SteadyTime{}+Milliseconds(i),f.driver.now)==TickStatus::RUNNING);}
|
||||
assert(flow.current_stage()==Stage::TRANSPORT_POSTURE);auto proof=f.registry.history(f.driver.sent.back().goal_id)->result->response.evidence->observed_at;
|
||||
f.driver.now+=1000000;r.update_safety({true,true,Holding::EMPTY,contradictory?f.driver.now:proof-1,f.driver.now+1000000000});
|
||||
auto status=flow.tick(SteadyTime{}+Milliseconds(++i),f.driver.now);
|
||||
if(contradictory){assert(status==TickStatus::INTERVENTION_REQUIRED);assert(f.count(Skill::TRANSPORT_POSTURE)==0);return;}
|
||||
assert(status==TickStatus::RUNNING);assert(f.count(Skill::TRANSPORT_POSTURE)==0);
|
||||
f.driver.now+=1000000;r.update_safety({true,true,Holding::HOLDING_TARGET,f.driver.now,f.driver.now+1000000000});
|
||||
assert(flow.tick(SteadyTime{}+Milliseconds(++i),f.driver.now)==TickStatus::RUNNING);assert(f.count(Skill::TRANSPORT_POSTURE)==1);
|
||||
}
|
||||
void empty_alignment(){Fixture f("empty_lag");auto r=f.runner();r.update_safety({true,true,Holding::EMPTY,1000000,9000000000});assert(r.tick(Stage::PREFLIGHT,SteadyTime{},1000000)==TickStatus::RUNNING);assert(r.tick(Stage::PREFLIGHT,SteadyTime{}+Milliseconds(1),2000000)==TickStatus::SUCCESS);
|
||||
r.update_safety({true,true,Holding::UNKNOWN,999999,9000000000});assert(r.tick(Stage::NAVIGATE_OBSERVE,SteadyTime{}+Milliseconds(2),3000000)==TickStatus::RUNNING);assert(f.count(Skill::NAVIGATE)==0);
|
||||
r.update_safety({true,true,Holding::EMPTY,4000000,9000000000});assert(r.tick(Stage::NAVIGATE_OBSERVE,SteadyTime{}+Milliseconds(3),4000000)==TickStatus::RUNNING);assert(f.count(Skill::NAVIGATE)==1);
|
||||
}
|
||||
int main(){holding_alignment(false);holding_alignment(true);empty_alignment();std::cout<<"older robot state waits for proof corroboration; fresh contradictions block motion\n";}
|
||||
Reference in New Issue
Block a user