| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545 |
- #!/usr/bin/env python3
- """Self-contained web-stack feature tests: app + fake saddle daemon.
- Unlike integration_test.py (which needs a real saddle daemon and a mock
- LLM), this suite spawns everything it needs:
- - the clanker-saddle app binary (path from argv[1], or builddir default)
- in a scratch directory with its own web-config.json (statum keys copied
- from the repo, plus `saddle_callback_base_url`) and SQLite database
- - in-process fake saddle daemons speaking the wire contract: POST
- /register (plan 10: receives {saddle_token, web_base_url}, answers
- {ok, ssh_public_key, version}), POST /wake (records the wake; the
- daemon then asks POST /api/saddle/next-task), POST /task-update
- (cancel/comment/subtask-status), GET /status (scripted reflection —
- exceptional use only), and the phase-2 push channels home:
- POST /api/saddle/chunks (raw PTY batches) and POST /api/saddle/results
- (round entries + state transitions). The retired /start, /output and
- /log routes 404 like a post-phase-2 daemon.
- It then drives the app the way statum.js does (entrypoint loads + action
- POSTs) and asserts the Workstream B features against the wire contract:
- 3. plan-review blocks hide their duplicate body preview
- 5. rules CRUD/attach + the effective `rules` array in assignments
- 6. project notes, POST /api/saddle/note, `project_notes` + `callback`
- in assignments
- plus the raw-log side: pushed chunks -> session_raw_log + terminal
- raw view
- Plan 10 phase 1 (work acquisition flip):
- - per-saddle tokens: registration mints + /register-delivers them,
- legacy tokens migrate onto the row at startup, removal revokes
- - token auth on every /api/saddle/* endpoint
- - next-task allocation: concurrent asks race exactly one winner,
- affinity pinning for parked tasks, resume_notice on suspended resumes
- - dispatcher: wake -> ask -> allocate roundtrip, the lost-wake guard
- (no ask after a wake -> offline, re-wake the next capable), and the
- pre-flip /answer + /start resume paths kept as the rollback route
- Plan 10 phase 2 (telemetry flip):
- - pushed chunks: idempotent on (session_id, seq), ordered, landing in
- session_raw_log so the SSE terminal keeps working
- - pushed results: entries store like the /output path did (redelivery
- idempotent on the daemon entry ids), blocked raises the block row +
- parks + bumps the pulse (sealed refresh shows it), done/failed
- complete/fail the task, suspended parks for the affinity resume
- - answer resumes, both daemon shapes: a SLEEPING affine saddle (its
- post-park ask returned 204) is woken, its ask allocating the resume
- with the verdict riding the assignment; a daemon parked MID-ROUND
- (its round holds the block's answer wait open, so it can never ask)
- gets the verdict delivered into that wait via POST /answer — same
- session, no wake. A daemon dying with a parked-with-answered task
- never requeues it: the verdict and session survive for the resume
- - staleness: a silent runner gets ONE exceptional GET /status probe —
- alive keeps it running, dead requeues the task with an agent comment and
- marks the saddle offline; a recovered daemon's ask flips offline rows
- back to idle
- - the deleted layers are gone: /output, /log and /start 404 on the
- daemon side, and nothing on the web side calls them
- Plan 10 phase 3 (capability awareness + UI):
- - assignment capability context: {saddle_tags, fleet_tags} on every
- payload, GET /api/saddle/capabilities (per-saddle token) feeding
- the daemon's `saddle capabilities` CLI
- - subtask callback capabilities: explicit-only attachment validated
- against the operator tag set, unknown tags rejected with the valid
- list in the error
- - task page: status block pinned to the top, blocking sections
- newest-first (sealed GET state)
- - home dashboard: pinned OPEN blocks as newest-first link-through
- cards (no inline answer forms) above the cross-task activity
- timeline (last ~50 events newest-first)
- - lost-wake-guard regression: a saddle that asks SLOWLY after its
- wake (busy winding down) still claims the task — the wake is never
- silently dropped, no re-wake storm fires, and the never-asks guard
- path (offline + re-wake of the next capable) stays green
- Prints PASS/FAIL lines Statum-style; exits non-zero on any failure.
- Plan 11 task D+E (UI + notifications), the `push` shard:
- - task page: progress feed above the status card, status + description
- in a 50/50 grid, edit expander under the history expander, plan
- behind an expander, answered blocks behind an expander; queue screen:
- New Task expander above the filters; dashboard waiting-on-drafts
- block with links to where Enqueue lives (pages shard)
- - real Web Push over VAPID: keypair generated at startup into
- web-config.json, GET /sw.js service worker with click-through,
- subscribe/unsubscribe + per-task Notify me actions, block events and
- waiting-on-drafts pushed globally, first start/failure/completion to
- opted-in users, best-effort queue with dispatcher-tick retry, 410/404
- endpoint pruning — payloads verified protocol-level (RFC 8291
- decryption + ES256 JWT signature) because the harness owns the
- browser-side keys
- Heredoc completion reports (pages shard):
- - terminal tasks render tasks.summary (set by done/fail) as a
- markdown completion-report card below the status/description grid —
- "Completion report" when done, "Failure summary" when failed, no
- card when empty; the summary renders through the shipped
- stm-markdown renderer (headings/code, no syntax leaks, HTML
- escaped) and the dashboard timeline snippets its first line
- Speed pass (plan 11, task 0):
- - SHARDING: the scenario groups are partitioned into seven independent
- bundles (pages, dispatch, subtasks, lineage, allocation, lifecycle,
- answers), each running in its own process against its own app
- instance, fake daemons and SQLite database. The default invocation
- runs all bundles in parallel and aggregates the verdicts and check
- counts into one exit code — meson sees a single green/red test.
- `--bundle NAME` runs one shard for debugging; WEBSTACK_SEQUENTIAL=1
- runs them in-process, one after another.
- - Within a bundle, scenario groups SHARE the app+daemon instance and
- carry per-group state as database rows (task/session/block
- fixtures), never process restarts; the one deliberate restart (the
- startup token migration) lives in the answers bundle.
- - EVENT-DRIVEN WAITS: wait_for polls at 50ms; the fixed sleeps were
- converted either to observable-condition waits (daemon-side
- counters, e.g. failed-delivery hits) or to quiet_window() — a
- bounded negative-assertion poll that FAILS LOUDLY the moment the
- event it guards against happens (a sleep could silently pass).
- - ENV KNOBS: SADDLE_DISPATCH_TICK_S=1 (dispatcher cadence; default 5
- in production), SADDLE_HTTP_TIMEOUT_S=2, SADDLE_LOST_WAKE_S=2,
- SADDLE_STALE_S=3, plus the fake daemon's scripted timings — the
- wire-traffic shapes stay realistic, just faster.
- """
- import json
- import os
- import re
- import shutil
- import socket
- import sqlite3
- import subprocess
- import sys
- import threading
- import time
- import urllib.error
- import urllib.parse
- import urllib.request
- from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
- # Web Push protocol verification (plan 11 task E): the test harness
- # owns the browser-side keys, so it can do everything the push service
- # does — decrypt the RFC 8291 body and verify the ES256 VAPID JWT.
- import base64
- from cryptography.hazmat.primitives import hashes
- from cryptography.hazmat.primitives.asymmetric import ec
- from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature
- from cryptography.hazmat.primitives.ciphers.aead import AESGCM
- from cryptography.hazmat.primitives.kdf.hkdf import HKDF
- from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
- REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
- APP = os.path.abspath(sys.argv[1]) if len(sys.argv) > 1 else os.path.join(REPO, "builddir", "clanker-saddle")
- # --- speed-pass tuning -----------------------------------------------------
- # The fake daemon's scripted timings and the dispatch-tick cadence the
- # quiet windows are sized against. All overridable for debugging.
- TICK_S = float(os.environ.get("SADDLE_DISPATCH_TICK_S", "1") or 1)
- FAKE_WORK_S = float(os.environ.get("WEBSTACK_FAKE_WORK_S", "1.2") or 1.2)
- FAKE_FAIL_S = float(os.environ.get("WEBSTACK_FAKE_FAIL_S", "0.5") or 0.5)
- FAKE_HEARTBEAT_S = float(os.environ.get("WEBSTACK_FAKE_HEARTBEAT_S", "0.4") or 0.4)
- FAKE_TRIGGER_S = float(os.environ.get("WEBSTACK_FAKE_TRIGGER_S", "0.05") or 0.05)
- SHARD_TIMEOUT_S = float(os.environ.get("WEBSTACK_SHARD_TIMEOUT_S", "600") or 600)
- def quiet_seconds():
- """A negative-assertion window spanning several dispatch ticks (the
- converted fixed sleeps were 3-7s at the old 5s tick; the bound never
- exceeds the old timeout)."""
- return min(2 * TICK_S + 0.8, 7.0)
- WORK = os.environ.get("WEBSTACK_WORK", "/tmp/kilo/webstack-e2e")
- DB_PATH = os.path.join(WORK, "web.sqlite")
- passes = 0
- failures = 0
- def check(condition, label):
- global passes, failures
- if condition:
- passes += 1
- print("PASS %s" % label)
- else:
- failures += 1
- print("FAIL %s" % label)
- def free_port():
- with socket.socket() as sock:
- sock.bind(("127.0.0.1", 0))
- return sock.getsockname()[1]
- APP_PORT = free_port()
- DAEMON_PORT = free_port()
- WEB = "http://127.0.0.1:%d" % APP_PORT
- def bundle_env(name):
- """Point the module's runtime globals at one shard's scratch dir,
- database and ports (each bundle is a process with its own app,
- daemons and SQLite file)."""
- global WORK, DB_PATH, APP_PORT, DAEMON_PORT, WEB
- base = os.environ.get("WEBSTACK_WORK", "/tmp/kilo/webstack-e2e")
- WORK = "%s-%s" % (base, name)
- DB_PATH = os.path.join(WORK, "web.sqlite")
- APP_PORT = free_port()
- DAEMON_PORT = free_port()
- WEB = "http://127.0.0.1:%d" % APP_PORT
- def http(method, url, data=None, headers=None, timeout=30):
- body = None
- if data is not None:
- pairs = []
- for k, v in data.items():
- for item in (v if isinstance(v, (list, tuple)) else [v]):
- pairs.append((k, item))
- body = "&".join("%s=%s" % (k, urllib.parse.quote(str(item))) for k, item in pairs).encode()
- request = urllib.request.Request(WEB + url if url.startswith("/") else url,
- data=body, method=method)
- if data is not None:
- request.add_header("Content-Type", "application/x-www-form-urlencoded")
- for key, value in (headers or {}).items():
- request.add_header(key, value)
- # Retry dropped/refused connections: the freshly started app can
- # close a very early keep-alive connection once while warming up,
- # and its first seconds can wobble under load.
- for attempt in range(3):
- try:
- with urllib.request.urlopen(request, timeout=timeout) as response:
- return response.status, response.read().decode()
- except urllib.error.HTTPError as error:
- return error.code, error.read().decode()
- except (urllib.error.URLError, OSError):
- if attempt >= 2:
- raise
- time.sleep(0.75)
- def directives(uri, auth=None):
- headers = {}
- if auth:
- headers["X-Statum-Slot"] = "key=%s; as_at=%s" % (auth["key"], auth["as_at"])
- status, body = http("GET", "/_statum/entrypoint?uri=" + urllib.parse.quote(uri.split("?")[0]),
- headers=headers)
- if status != 200:
- raise SystemExit("entrypoint %s -> HTTP %d: %s" % (uri, status, body[:300]))
- return json.loads(body)
- def slot_public(auth, uri, type_name):
- for directive in directives(uri, auth):
- if directive["type"] == "set":
- content = directive["snapshot_frame"].get("content")
- if not content:
- continue
- content = json.loads(content)
- if content["slot"]["type"] == type_name:
- return content["public"]
- raise SystemExit("no %s slot in entrypoint state for %s" % (type_name, uri))
- def act(auth, action, fields):
- headers = {"X-Statum-Slot": "key=%s; as_at=%s" % (auth["key"], auth["as_at"])}
- if action.get("private"):
- headers["X-Statum-Private"] = action["private"]
- status, body = http(action.get("method", "POST"), action["uri"], fields, headers)
- if status != 200:
- raise SystemExit("action %s -> HTTP %d: %s" % (action["uri"], status, body[:300]))
- return json.loads(body)
- def login(username, password):
- frames = directives("/login")
- content = json.loads(frames[0]["snapshot_frame"]["content"])
- action = content["public"]["login"]
- status, body = http(action.get("method", "POST"), action["uri"],
- {"username": username, "password": password})
- if status != 200:
- raise SystemExit("login -> HTTP %d: %s" % (status, body[:300]))
- for directive in json.loads(body):
- if directive["type"] == "set":
- frame = json.loads(directive["snapshot_frame"]["content"])
- if frame["slot"]["type"] == "auth":
- return {"key": frame["slot"]["key"], "as_at": frame["as_at"]}
- raise SystemExit("login response carried no auth slot: " + body[:300])
- def wait_for(predicate, timeout=60, what="condition", interval=0.05):
- deadline = time.monotonic() + timeout
- last = None
- while time.monotonic() < deadline:
- last = predicate()
- if last is not False and last is not None:
- return last
- time.sleep(interval)
- print(" (timeout waiting for %s; last: %s)" % (what, last))
- dump_threads()
- return None
- def quiet_window(seconds, predicate, what):
- """A converted fixed sleep: asserts a NEGATIVE condition (nothing may
- happen) across a window spanning several dispatch ticks, polling at
- 50ms. The moment the condition breaks the window fails loudly
- (diagnostics + thread dump) instead of silently passing a sleep."""
- deadline = time.monotonic() + seconds
- while time.monotonic() < deadline:
- value = predicate()
- if value is False or value is None:
- remaining = deadline - time.monotonic()
- print(" (quiet window '%s' broken after %.2fs of %.2fs — the"
- " guarded event DID happen)" % (what, seconds - remaining, seconds))
- dump_threads()
- return False
- time.sleep(0.05)
- return True
- def dump_threads():
- """Stacks of suite threads not parked in sleep — pinpoints a wedged
- fake lifecycle when a wait times out."""
- import traceback
- shown = 0
- for thread_id, frame in sys._current_frames().items():
- stack = "".join(traceback.format_stack(frame)[-4:])
- if ("web_stack_test.py" in stack) and ("sleep" not in stack) and shown < 6:
- shown += 1
- print(" --- thread %s ---\n%s" % (thread_id, stack))
- def task_state(auth, task_id):
- return slot_public(auth, "/tasks/%d" % task_id, "task")
- def wait_task_done(auth, task_id, timeout=90):
- return wait_for(lambda: task_state(auth, task_id).get("status") == "done" or False,
- timeout=timeout, what="task %d -> done" % task_id)
- def task_id_of(title):
- conn = db()
- try:
- return conn.execute("select id from tasks where title = ?", (title,)).fetchone()[0]
- finally:
- conn.close()
- def enqueue_task(auth, task_id):
- """The human-only Enqueue action (plan 11): draft -> queued."""
- detail = task_state(auth, task_id)
- assert detail["status"] == "draft", "task %d not in draft (%s)" % (task_id, detail["status"])
- act(auth, detail["enqueue"], {})
- return task_state(auth, task_id)["status"]
- def create_queued(auth, fields):
- """Create via the queue form (lands in draft) and enqueue it — the
- standard 'this task should dispatch' fixture of the plan-11 world."""
- act(auth, slot_public(auth, "/tasks", "tasks")["create"], fields)
- task_id = int([t["id"] for t in slot_public(auth, "/tasks", "tasks")["tasks"]
- if t["title"] == fields["title"]][0])
- enqueue_task(auth, task_id)
- return task_id
- def saddle_api(method, path, token, payload=None):
- """Raw call against the saddle ingest API; returns (status, json)."""
- request = urllib.request.Request(WEB + path,
- data=json.dumps(payload or {}).encode(), method=method)
- request.add_header("Content-Type", "application/json")
- if token is not None:
- request.add_header("Authorization", "Bearer " + token)
- try:
- with urllib.request.urlopen(request, timeout=15) as response:
- return response.status, json.loads(response.read().decode() or "{}")
- except urllib.error.HTTPError as error:
- body = error.read().decode()
- try:
- return error.code, json.loads(body)
- except ValueError:
- return error.code, {"raw": body}
- def db():
- return sqlite3.connect(DB_PATH)
- def http_get_page(auth, uri):
- """The compiled page HTML for a statum route (auth via the slot
- header), for asserting page-level embeds the slot state cannot
- see."""
- status, body = http("GET", uri, headers={
- "X-Statum-Slot": "key=%s; as_at=%s" % (auth["key"], auth["as_at"])})
- if status != 200:
- raise SystemExit("GET %s -> HTTP %d" % (uri, status))
- return body
- def saddle_card(auth, name):
- rows = [s for s in slot_public(auth, "/saddles", "saddles")["saddles"]
- if s["name"] == name]
- return rows[0]
- def queued_count():
- conn = db()
- try:
- return conn.execute(
- "select count(*) from tasks where status = 'queued'").fetchone()[0]
- finally:
- conn.close()
- def command_rows(session):
- conn = db()
- try:
- return conn.execute(
- "select command_text, type from session_commands"
- " where session_id = ? order by id", (session,)).fetchall()
- finally:
- conn.close()
- class FakeSaddle:
- """In-process saddle daemon implementing the wire contract.
- Phase-2 push behaviour: the daemon asks POST /api/saddle/next-task
- whenever idle-awake (at registration, on POST /wake, after its task
- reaches a terminal state, and when a /status probe observes it
- parked idle — throttled), accepts assignments like POST /start used
- to, then PUSHES its telemetry home: raw PTY chunks via POST
- /api/saddle/chunks and round results with state transitions via
- POST /api/saddle/results (working at round start, terminal states
- when the scripted lifecycle ends them). /output, /log and /start
- are gone (404) like a post-phase-2 daemon; /task-update serves the
- cancel/comment/subtask-status kinds; POST /answer receives a
- delivered verdict when the round is parked in a block's answer
- wait (409 when nothing waits — the real daemon's contract; the
- resumed round continues after the response). Every trigger can be
- disabled per test for determinism; ask_now() performs a
- synchronous manual ask.
- """
- PROMPT = "bbarrow@fedora:/workspace/task-1$ "
- def __init__(self):
- self.lock = threading.Lock()
- self.start_payloads = [] # captured assignment payloads (/start + next-task 200s)
- self.state = "idle"
- self.summary = ""
- self.state_entered = time.monotonic()
- self.fail_next = False # next task drives the task to failed
- self.start_counts = {} # task id -> assignment count
- self.resumed_pending = False # crash-resume flag on the next /status
- self.suspend_on_title = None # this task (by title) parks as suspended
- self.output_seq = 100 # daemon-global entry ids for pushed results
- self.last_activity_epoch = int(time.time())
- self.hang_next = {} # path suffix -> hang count (never responds)
- self.abort_failures = 0 # cancel task-updates failing (500) while > 0
- self.abort_log = [] # task ids aborted via task-update cancel
- self.running_task = 0 # task id the daemon believes it runs
- self.running_title = "" # its title (suspend targeting)
- self.session_id = None # the web-side run row of the running task
- self.hold_state = False # freeze lifecycle + heartbeat pushes
- self.ssh_public_key = "" # reported on /status + /register
- self.started_mono = time.monotonic()
- self.freeze_activity = False # hold last_activity_epoch still
- self.silent = False # suppress pushes (staleness tests)
- self.dead = False # kill(): no serving AND no calls home
- self.status_hits = 0 # GET /status arrivals (probe accounting)
- self.comment_log = [] # recorded /task-update comment payloads
- self.scripted_corpus = None # chunk batch pushed when a task starts
- # --- plan 10 phase 1: ask/wake/register ---
- self.token = None # per-saddle token from POST /register
- self.register_log = [] # recorded /register bodies
- self.wake_log = [] # recorded POST /wake arrivals
- self.asks = [] # [{"status": int, "payload": dict|None}]
- self.ask_on_register = True
- self.ask_on_wake = True
- self.ask_on_terminal = True
- self.ask_on_idle_status = True
- self.ask_delay = 0.0 # seconds; a slow daemon delays its wake-triggered ask
- self.serve_register = True # False: 404 /register (pre-flip daemon)
- self.last_ask_trigger = 0.0
- self.port = None
- self.ask_threads = [] # scheduled ask-trigger threads (settle_asks joins them)
- # --- answer delivery (the parked-session resume) ---
- self.pending_block = None # the block payload this daemon waits on (state blocked)
- self.answer_log = [] # recorded POST /answer bodies
- self.answer_push_delay = 0.2 # the resumed round's first push, after the response
- # --- plan 12: goal-agent scripting ---
- self.goal_script = [] # behaviours consumed per goal-run assignment
- self.goal_behaviour = None # the current round's popped behaviour
- self.goal_assignments = [] # captured goal-run assignment payloads
- def set_state(self, state, summary=None):
- """Advances the scripted lifecycle."""
- self.state = state
- if summary is not None:
- self.summary = summary
- self.state_entered = time.monotonic()
- def touch_activity(self):
- if not self.freeze_activity:
- self.last_activity_epoch = int(time.time())
- def queue_output(self, entry_type, command, output):
- """Pushes a typed entry as a round-results push (the daemon's
- entry log riding the results channel)."""
- with self.lock:
- entry = {
- "id": self.output_seq,
- "command": command,
- "output": output,
- "exit_code": 1,
- "type": entry_type,
- "timestamp": "2026-09-06T00:00:00Z",
- }
- self.output_seq += 1
- self.push_results(self.state if self.state in ("working",) else "working",
- entries=[entry])
- # --- plan 10 phase 2: the push channels ---
- def push_chunks(self, chunks):
- """Pushes raw PTY chunks home: elements are texts (assigned
- fresh seqs) or {seq, text} dicts (a redelivery or explicit
- corpus). Pushes are suppressed while silent/hold_state (the
- staleness tests' silent runner)."""
- if self.dead or self.silent or self.token is None or self.session_id is None:
- return
- batch = []
- with self.lock:
- for chunk in chunks:
- if isinstance(chunk, dict):
- batch.append({"seq": int(chunk["seq"]), "text": chunk["text"]})
- else:
- batch.append({"seq": self.next_push_seq(), "text": chunk})
- try:
- saddle_api("POST", "/api/saddle/chunks", self.token,
- {"session_id": self.session_id, "chunks": batch})
- except (OSError, ValueError):
- pass # app restarting / shutting down mid-test
- def next_push_seq(self):
- # The daemon-side chunk counter for ad-hoc pushes.
- self._push_seq = getattr(self, "_push_seq", 0) + 1
- return self._push_seq
- def push_results(self, state, entries=None, block=None, summary=None, session_id=None):
- """Pushes one round-results payload home (state transition plus
- its entries). Returns the HTTP status when it went out, None
- when suppressed."""
- if self.dead or self.silent:
- return None
- if self.token is None:
- return None
- target = session_id if session_id is not None else self.session_id
- if target is None:
- return None
- payload = {"session_id": target, "state": state}
- if entries is not None:
- payload["entries"] = entries
- if block is not None:
- payload["block"] = block
- if state == "blocked":
- # The daemon parks its round on this block's answer wait
- # (the real daemon's wait_for_answer).
- self.pending_block = dict(block)
- if summary is not None:
- payload["summary"] = summary
- try:
- status, _ = saddle_api("POST", "/api/saddle/results", self.token, payload)
- if status != 200:
- print(" [fake %s] push %s -> HTTP %s (task=%s)"
- % (self.port, state, status, self.running_task))
- return status
- except (OSError, ValueError) as error:
- print(" [fake %s] push %s failed: %r (task=%s)"
- % (self.port, state, error, self.running_task))
- return None
- # --- plan 10 phase 1: the ask path ---
- def ask_now(self):
- """Synchronously asks the web for work (a phase-1 daemon's idle
- ask). A 200 assignment is accepted exactly like POST /start;
- returns (status, payload|None)."""
- token = self.token
- if token is None:
- raise SystemExit("fake daemon asked before receiving its token")
- status, payload = saddle_api("POST", "/api/saddle/next-task", token)
- with self.lock:
- self.asks.append({"status": status, "payload": payload})
- if status == 200:
- self.accept_assignment(payload)
- return status, payload
- def _trigger_ask(self, reason):
- """Schedules a delayed ask (the daemon woke / went idle-awake).
- ask_delay models a SLOW asker — a daemon still winding down its
- round when the wake lands: it honours the wake, but its ask
- arrives late (still inside the web's lost-wake guard window)."""
- def run():
- if self.state in ("working", "failing"):
- return # a running daemon does not ask
- if self.ask_delay > 0:
- time.sleep(self.ask_delay)
- for attempt in range(3):
- try:
- status, _ = self.ask_now()
- if status in (200, 204):
- return
- print(" (ask %s -> HTTP %s, attempt %d)" % (reason, status, attempt + 1))
- except Exception as error: # app restarting / blips: retry
- print(" (ask %s failed: %r, attempt %d)" % (reason, error, attempt + 1))
- time.sleep(1.0)
- def starter():
- time.sleep(FAKE_TRIGGER_S)
- run()
- thread = threading.Thread(target=starter, daemon=True)
- with self.lock:
- self.ask_threads.append(thread)
- thread.start()
- def settle_asks(self, timeout=15):
- """Joins every ask-trigger thread scheduled so far — the
- event-driven replacement for the fixed sleeps that waited for
- in-flight asks to land after triggers were frozen."""
- with self.lock:
- threads = [t for t in self.ask_threads if t.is_alive()]
- deadline = time.monotonic() + timeout
- for thread in threads:
- thread.join(timeout=max(0.1, deadline - time.monotonic()))
- with self.lock:
- self.ask_threads = [t for t in self.ask_threads if t.is_alive()]
- return all(not thread.is_alive() for thread in threads)
- def maybe_trigger_ask(self, reason, throttle=False):
- """Schedules an idle-awake ask. Protocol events (wake, abort,
- round end, park) ask singularly and immediately; only the
- /status-observation path throttles (a burst of probes must not
- hammer the ask endpoint)."""
- now = time.monotonic()
- if throttle and now - self.last_ask_trigger < 1.0:
- return
- self.last_ask_trigger = now
- self._trigger_ask(reason)
- def accept_assignment(self, payload):
- """A 200 next-task assignment: adopted exactly like POST /start."""
- self.start_payloads.append(payload)
- task = payload.get("task") or {}
- task_id = task.get("id")
- self.running_task = int(task_id or 0)
- self.running_title = task.get("title") or ""
- self.session_id = payload.get("session_id")
- # The assignment's answers resolve any block this daemon was
- # parked on (resume-carries-answer).
- self.pending_block = None
- count = self.start_counts.get(task_id, 0) + 1
- self.start_counts[task_id] = count
- if count > 1:
- # Crash-resume contract: re-pushing a task whose
- # workspace still exists restores it; the first
- # /status after the restore reports "resumed": true.
- self.resumed_pending = True
- # Plan 12: a goal-run assignment plays its scripted behaviour
- # instead of the plain done push (plan submission, parking,
- # question, completion, failure — consumed one per round).
- self.goal_behaviour = None
- if payload.get("kind") == "goal_run":
- self.goal_assignments.append(payload)
- self.goal_behaviour = self.goal_script.pop(0) if self.goal_script else ("suspend",)
- self.set_state("failing" if self.fail_next else "working")
- self.fail_next = False
- # The round's opening telemetry: the flushed-so-far raw PTY
- # batch plus the working state push (>= 2 flushes per round
- # and the 10s timer are the real daemon's contract; the fake
- # pushes its batch up front and heartbeats while working).
- if self.scripted_corpus is not None:
- self.push_chunks([dict(chunk) for chunk in self.scripted_corpus])
- else:
- self.push_chunks([self.PROMPT, "echo hello\r\n", "hello\r\n"])
- self.push_results("working")
- class Handler(BaseHTTPRequestHandler):
- daemon = None # set on the class before serving
- protocol_version = "HTTP/1.1"
- def log_message(self, fmt, *args):
- pass
- def maybe_hang(self, path):
- """True when this request should hang forever (never respond),
- consuming one unit of the path's hang budget."""
- for suffix, count in list(self.daemon.hang_next.items()):
- if count > 0 and path.startswith(suffix):
- self.daemon.hang_next[suffix] = count - 1
- return True
- return False
- def _send(self, status, obj):
- body = json.dumps(obj).encode()
- self.send_response(status)
- self.send_header("Content-Type", "application/json")
- self.send_header("Content-Length", str(len(body)))
- self.end_headers()
- self.wfile.write(body)
- def do_POST(self):
- if self.maybe_hang(self.path):
- time.sleep(600)
- return
- self.daemon.touch_activity()
- length = int(self.headers.get("Content-Length") or 0)
- raw = self.rfile.read(length).decode() if length else "{}"
- if self.path == "/register":
- # Plan 10: the web delivers the per-saddle token + its
- # own base URL; the daemon persists the token for its
- # calls home and reports its ssh key + version.
- if not self.daemon.serve_register:
- self._send(404, {"error": "no such route"})
- return
- try:
- payload = json.loads(raw)
- except ValueError:
- payload = {"raw": raw}
- self.daemon.register_log.append(payload)
- if payload.get("saddle_token"):
- self.daemon.token = payload["saddle_token"]
- self._send(200, {"ok": True,
- "ssh_public_key": self.daemon.ssh_public_key,
- "version": "0.1-fake"})
- if self.daemon.ask_on_register:
- self.daemon.maybe_trigger_ask("register")
- return
- if self.path == "/wake":
- # Plan 10: a capable task is ready; a sleeping daemon
- # asks for work (immediately — a wake is a direct
- # command, never throttled).
- self.daemon.wake_log.append(time.monotonic())
- self._send(200, {"ok": True})
- if self.daemon.ask_on_wake:
- self.daemon.maybe_trigger_ask("wake")
- return
- if self.path == "/answer":
- # Plan 11 (daemon Task A): parks FREE the daemon, so no
- # block ever waits in-process — the verdict travels in
- # the resuming allocation's resume_notice. Always 409,
- # exactly like the real daemon's contract.
- try:
- payload = json.loads(raw)
- except ValueError:
- self._send(400, {"error": "bad json"})
- return
- self.daemon.answer_log.append({
- "refused": True,
- "answer": payload.get("answer", "")})
- self._send(409, {"error": "no block is waiting for an answer — parks resume via allocation"})
- return
- if self.path == "/task-update":
- # Plan 10: cancel (abort machinery) and comment
- # (injected turn) — delivered directly to a RUNNING
- # daemon, no auth.
- try:
- payload = json.loads(raw)
- except ValueError:
- self._send(400, {"error": "bad json"})
- return
- kind = payload.get("kind")
- if kind == "cancel":
- if self.daemon.abort_failures > 0:
- self.daemon.abort_failures -= 1
- self._send(500, {"error": "cancel failed (simulated)"})
- return
- aborted = self.daemon.running_task
- self.daemon.set_state("idle")
- self.daemon.summary = ""
- self.daemon.running_task = 0
- self.daemon.session_id = None
- self.daemon.abort_log.append(aborted)
- self._send(200, {"ok": True, "aborted_task_id": aborted})
- # An aborted daemon returns to its idle loop: it asks.
- self.daemon.maybe_trigger_ask("aborted")
- elif kind == "comment":
- self.daemon.comment_log.append(payload)
- self._send(200, {"ok": True})
- else:
- self._send(400, {"error": "unknown task-update kind"})
- return
- self._send(404, {"error": "no such route"})
- self.close_connection = True
- def do_GET(self):
- if self.maybe_hang(self.path):
- time.sleep(600)
- return
- if self.path != "/status":
- # /status itself is not daemon activity: the liveness
- # signal must reflect loop rounds / command completions.
- self.daemon.touch_activity()
- if self.path == "/status":
- self.daemon.status_hits += 1
- response = {"state": self.daemon.state, "summary": self.daemon.summary,
- "uptime_seconds": int(time.monotonic() - self.daemon.started_mono),
- "last_activity_epoch": self.daemon.last_activity_epoch,
- "ssh_public_key": self.daemon.ssh_public_key}
- if getattr(self.daemon, "status_extra", None):
- response.update(self.daemon.status_extra)
- if self.daemon.resumed_pending:
- response["resumed"] = True
- self.daemon.resumed_pending = False
- # A parked/terminal daemon is idle-awake: a phase-1
- # daemon asks (and sleeps on 204). Throttled so status
- # poll bursts do not hammer the ask endpoint.
- if self.daemon.ask_on_idle_status and self.daemon.state in (
- "idle", "done", "failed", "suspended"):
- self.daemon.maybe_trigger_ask("idle-status", throttle=True)
- self._send(200, response)
- else:
- self._send(404, {"error": "no such route"})
- self.close_connection = True
- def run_lifecycle(self):
- """Applies the scripted lifecycle on a timer (like a real daemon
- whose task ends on its own): working heartbeats chunks (the
- flush timer's stand-in, keeping the staleness clock fed), works
- for FAKE_WORK_S, then pushes done/suspended; failing pushes
- failed after FAKE_FAIL_S — and a task END makes the idle-awake
- daemon ask again."""
- while True:
- time.sleep(0.1)
- if self.hold_state or self.silent:
- # hold_state freezes the script; silent models a daemon
- # mid-round with nothing to flush — its round has not
- # ended, so no terminal push fires while quiet.
- continue
- if self.state == "failing":
- if time.monotonic() - self.state_entered >= FAKE_FAIL_S:
- self.set_state("failed", "bash -n rejected the script (simulated)")
- self.push_results("failed",
- summary="bash -n rejected the script (simulated)")
- if self.ask_on_terminal:
- self.maybe_trigger_ask("terminal")
- elif self.state == "working":
- if time.monotonic() - self.state_entered >= FAKE_WORK_S:
- if self.goal_behaviour is not None:
- # A goal-agent round: play the scripted
- # behaviour (the results channel carries the
- # goal-context verbs).
- behaviour, self.goal_behaviour = self.goal_behaviour, None
- kind = behaviour[0]
- if kind == "plan":
- self.set_state("blocked")
- self.push_results("blocked", block={
- "type": "plan",
- "question": "Plan submitted for review",
- "body": behaviour[1]})
- if self.ask_on_idle_status:
- self.maybe_trigger_ask("parked")
- elif kind == "question":
- self.set_state("blocked")
- self.push_results("blocked", block={
- "type": "question",
- "question": behaviour[1],
- "options": list(behaviour[2])
- if len(behaviour) > 2 else None})
- if self.ask_on_idle_status:
- self.maybe_trigger_ask("parked")
- elif kind == "suspend":
- self.set_state("suspended")
- self.push_results("suspended")
- if self.ask_on_idle_status:
- self.maybe_trigger_ask("parked")
- elif kind == "fail":
- self.set_state("failed", behaviour[1])
- self.push_results("failed", summary=behaviour[1])
- if self.ask_on_terminal:
- self.maybe_trigger_ask("terminal")
- else: # "done": the completion report
- self.set_state("done", behaviour[1])
- self.push_results("done", summary=behaviour[1])
- if self.ask_on_terminal:
- self.maybe_trigger_ask("terminal")
- elif self.suspend_on_title and self.suspend_on_title == self.running_title:
- # `saddle suspend`: park until a fresh assignment
- # resumes the workspace (title-targeted one-shot,
- # so an interleaved dispatch cannot eat the flag).
- self.set_state("suspended")
- self.suspend_on_title = None
- self.push_results("suspended")
- # A parked daemon is idle-awake: it asks (and
- # sleeps on 204) so a later wake can reach it.
- if self.ask_on_idle_status:
- self.maybe_trigger_ask("parked")
- else:
- self.set_state("done", "fake daemon finished")
- self.push_results("done", summary="fake daemon finished")
- if self.ask_on_terminal:
- self.maybe_trigger_ask("terminal")
- elif time.monotonic() - getattr(self, "last_heartbeat", 0) >= FAKE_HEARTBEAT_S:
- # The flush timer: output keeps flowing, so chunks
- # keep arriving (the staleness window never opens on
- # a healthy runner).
- self.last_heartbeat = time.monotonic()
- self.push_chunks(["working-heartbeat\r\n"])
- def serve(self, port=None):
- """Binds the daemon's HTTP server. A requested port that is
- already taken (free_port() hands out recyclable ports — two
- calls can collide) falls back to an OS-assigned one instead of
- silently dying and impersonating whatever owns that port."""
- if port is None:
- port = DAEMON_PORT
- handler = type("BoundHandler", (FakeSaddle.Handler,), {"daemon": self})
- self.server = None
- try:
- self.server = ThreadingHTTPServer(("127.0.0.1", port), handler)
- except OSError:
- print("(fake daemon: port %d taken, falling back)" % port)
- self.server = ThreadingHTTPServer(("127.0.0.1", 0), handler)
- self.port = self.server.server_address[1]
- threading.Thread(target=self.server.serve_forever, daemon=True).start()
- threading.Thread(target=self.run_lifecycle, daemon=True).start()
- return self.server
- def kill(self):
- """The daemon dies: it stops listening (connections refused) AND
- stops calling home — no pushes, no asks (a dead process has no
- outbound anything; in-flight handler threads may linger but the
- wire contract's traffic is one-directional now)."""
- self.dead = True
- if getattr(self, "server", None) is not None:
- self.server.shutdown()
- self.server.server_close()
- self.server = None
- def start_app():
- config = json.load(open(os.path.join(REPO, "web-config.json")))
- config["saddle_callback_base_url"] = WEB
- with open(os.path.join(WORK, "web-config.json"), "w") as handle:
- json.dump(config, handle)
- env = dict(os.environ)
- env.update({
- "SPRY_DB_PATH": DB_PATH,
- "SPRY_SEED_ADMIN_USERNAME": "admin",
- "SPRY_SEED_ADMIN_PASSWORD": "adminpass123",
- "SADDLE_LLM_BASE_URL": "http://127.0.0.1:%d" % DAEMON_PORT,
- "SADDLE_PROJECTS_ROOT": os.path.join(WORK, "workspace"),
- "SADDLE_HTTP_TIMEOUT_S": "2",
- "SADDLE_LOST_WAKE_S": "2",
- "SADDLE_STALE_S": "3",
- "SADDLE_DISPATCH_TICK_S": "1",
- # Plan 12: the goal-agent resume coalescing window shrinks to
- # one second so the event-driven resume tests settle quickly.
- "SADDLE_GOAL_RESUME_S": os.environ.get("SADDLE_GOAL_RESUME_S", "1"),
- # Never inherit a caller-poisoned LD_LIBRARY_PATH (e.g. one that
- # resolves the ancient /lib64 copies): empty makes the loader use
- # the binary's RUNPATH, which is the dev stack it was built
- # against.
- "LD_LIBRARY_PATH": "",
- })
- log_previous = os.path.join(WORK, "app.log.1")
- if os.path.exists(os.path.join(WORK, "app.log")):
- shutil.move(os.path.join(WORK, "app.log"), log_previous)
- log = open(os.path.join(WORK, "app.log"), "w")
- command = [APP, str(APP_PORT)]
- if os.environ.get("WEBSTACK_GDB"):
- # Debugging aid: run the app under gdb; a crash backtrace lands in
- # app.log. WEBSTACK_GDB=1 meson test ... to enable.
- command = ["gdb", "-batch", "-quiet",
- "-ex", "handle SIGPIPE nostop noprint pass",
- "-ex", "run", "-ex", "bt", "--args", APP, str(APP_PORT)]
- process = subprocess.Popen(command, cwd=WORK, env=env,
- stdout=log, stderr=subprocess.STDOUT)
- # The ready probe must require OUR process alive: a free_port()
- # TOCTOU can hand this port to another shard's app — ours then dies
- # at bind (MHD error()) while the probe happily answers from the
- # intruder (same admin seed), and every check would silently drive
- # the WRONG app. poll() != None with a serving port is exactly that
- # shape: take the exit branch, the start_bundle retry re-ports.
- outcome = wait_for(lambda: (app_ready() and process.poll() is None and "ready")
- or (process.poll() is not None and "app-exited-%d" % process.poll())
- or False, timeout=30, what="app ready")
- if outcome is None:
- raise SystemExit("app did not come up; see %s/app.log" % WORK)
- if str(outcome).startswith("app-exited-"):
- raise SystemExit("app exited during startup (%s); see %s/app.log"
- % outcome, WORK)
- return process
- def app_ready():
- try:
- status, _ = http("GET", "/login", timeout=3)
- return status == 200
- except (urllib.error.URLError, OSError):
- return False
- def dump_app_stacks(pid):
- """Best-effort thread backtrace of the (possibly wedged) app."""
- try:
- fds = os.listdir("/proc/%d/fd" % pid)
- print("(app open fds at dump: %d)" % len(fds))
- kinds = {}
- for fd in fds[:1200]:
- try:
- target = os.readlink("/proc/%d/fd/%s" % (pid, fd))
- except OSError:
- continue
- key = target.split(":", 1)[0] if ":" in target else target
- kinds[key] = kinds.get(key, 0) + 1
- print("(fd kinds: %s)" % sorted(kinds.items(), key=lambda kv: -kv[1])[:6])
- except OSError:
- pass
- for command in (["eu-stack", "-p", str(pid)],
- ["gdb", "-batch", "-quiet", "-p", str(pid),
- "-ex", "thread apply all bt"]):
- try:
- out = subprocess.run(command, capture_output=True, timeout=60)
- text = out.stdout.decode(errors="replace") + out.stderr.decode(errors="replace")
- if text.strip():
- print("(app stacks via %s)\n%s" % (command[0], text[:12000]))
- return
- except (OSError, subprocess.SubprocessError):
- continue
- # =============================================================================
- # Fake push service + Web Push protocol helpers (plan 11 task E)
- # =============================================================================
- def b64url(raw):
- return base64.urlsafe_b64encode(raw).rstrip(b"=").decode()
- def b64url_dec(text):
- return base64.urlsafe_b64decode(text + "=" * ((4 - len(text) % 4) % 4))
- class FakePushService:
- """In-process Web Push endpoint server: every subscription the tests
- register points at a path here (a distinct "browser endpoint").
- Responses are scriptable per path — a fixed status (the 410-prune
- fixture) or N transient 500s (the retry-next-tick fixture)."""
- def __init__(self):
- self.requests = [] # [{"path", "headers", "body", "at"}]
- self.fail_next = {} # path -> remaining 500s
- self.status_for = {} # path -> fixed status
- self.lock = threading.Lock()
- self.port = None
- self.server = None
- def serve(self):
- service = self
- class Handler(BaseHTTPRequestHandler):
- protocol_version = "HTTP/1.1"
- def log_message(self, fmt, *args):
- pass
- def do_POST(self):
- length = int(self.headers.get("Content-Length") or 0)
- body = self.rfile.read(length)
- status = 201
- if self.path in service.status_for:
- status = service.status_for[self.path]
- elif service.fail_next.get(self.path, 0) > 0:
- service.fail_next[self.path] -= 1
- status = 500
- with service.lock:
- service.requests.append({
- "path": self.path,
- "headers": {key.lower(): value
- for key, value in self.headers.items()},
- "body": body,
- "at": time.monotonic(),
- })
- payload = b"{}"
- self.send_response(status)
- self.send_header("Content-Type", "application/json")
- self.send_header("Content-Length", str(len(payload)))
- self.end_headers()
- self.wfile.write(payload)
- self.server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
- self.port = self.server.server_address[1]
- threading.Thread(target=self.server.serve_forever, daemon=True).start()
- return self.server
- def url(self, path):
- return "http://127.0.0.1:%d%s" % (self.port, path)
- def hits(self, prefix):
- with self.lock:
- return [request for request in self.requests
- if request["path"].startswith(prefix)]
- def decrypt_webpush(request, client_private_key, client_public, auth_secret):
- """RFC 8291 decryption of one captured push: returns
- (record_size, payload dict) — the payload builder's exact output."""
- body = request["body"]
- salt = body[:16]
- record_size = int.from_bytes(body[16:20], "big")
- key_id_len = body[20]
- eph_public = body[21:21 + key_id_len]
- ciphertext = body[21 + key_id_len:]
- eph_point = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256R1(),
- eph_public)
- shared = client_private_key.exchange(ec.ECDH(), eph_point)
- info = b"WebPush: info\x00" + eph_public + client_public
- cek = HKDF(algorithm=hashes.SHA256(), length=16, salt=salt,
- info=info).derive(shared + auth_secret)
- nonce = HKDF(algorithm=hashes.SHA256(), length=12, salt=salt,
- info=info).derive(shared + auth_secret)
- plaintext = AESGCM(cek).decrypt(nonce, ciphertext, None)
- return record_size, json.loads(plaintext[:-1]) # strip the 0x02 delimiter
- def verify_vapid(request, vapid_public, vapid_private):
- """Verifies the ES256 VAPID JWT on one captured push; returns its
- claims dict (aud/exp/sub) — raises on a bad signature."""
- header = request["headers"].get("authorization", "")
- assert header.startswith("vapid t="), "no vapid authorization header"
- token_part = header[len("vapid t="):].split(",")[0]
- sent_key = header.split("k=")[1]
- head_b64, claims_b64, sig_b64 = token_part.split(".")
- signature = b64url_dec(sig_b64)
- key = ec.derive_private_key(
- int.from_bytes(b64url_dec(vapid_private), "big"), ec.SECP256R1())
- key.public_key().verify(
- encode_dss_signature(int.from_bytes(signature[:32], "big"),
- int.from_bytes(signature[32:], "big")),
- (head_b64 + "." + claims_b64).encode(),
- ec.ECDSA(hashes.SHA256()))
- claims = json.loads(b64url_dec(claims_b64))
- assert sent_key == vapid_public, "the k= key is not the app's VAPID key"
- return claims
- def action_notify(result):
- """The message of the first notify directive an action returned."""
- for directive in result:
- if directive.get("type") == "notify":
- return directive.get("message", "")
- return None
- # =============================================================================
- # Shard orchestration
- # =============================================================================
- class Ctx:
- """Per-bundle shared fixtures: the app process, the admin auth slot
- and the primary fake saddle (registered + token delivered)."""
- def start_bundle(name):
- """Boots one shard's environment: scratch dir, database, ports, the
- primary fake daemon and the app, then the shared fixtures every
- bundle builds on (the demo project + the registered fake saddle).
- Scenario groups inside the bundle share this app+daemon instance
- and sequence their state as DB rows, never process restarts."""
- global APP_PORT, WEB, DAEMON_PORT
- bundle_env(name)
- fake = FakeSaddle()
- fake.serve()
- DAEMON_PORT = fake.port # the actually-bound port (fallback-safe)
- # A rare framework-level crash (stack corruption in Statum slot
- # signing during the FIRST login — shared-library codegen bug, moved
- # twice already) kills the app before any test runs; a clean restart
- # (with fresh ports) sidesteps it. Only tolerated while nothing has
- # been checked yet.
- auth = None
- app = None
- for attempt in range(3):
- APP_PORT = free_port()
- WEB = "http://127.0.0.1:%d" % APP_PORT
- if os.path.exists(WORK):
- shutil.rmtree(WORK)
- os.makedirs(os.path.join(WORK, "workspace"))
- try:
- app = start_app()
- auth = login("admin", "adminpass123")
- break
- except (SystemExit, OSError, urllib.error.URLError):
- if passes > 0:
- raise
- if app is not None and app.poll() is None:
- app.terminate()
- try:
- app.wait(timeout=5)
- except subprocess.TimeoutExpired:
- app.kill()
- app = None
- print("(app died before login; restarting cleanly, attempt %d)" % (attempt + 1))
- if app is None or auth is None:
- raise SystemExit("app never came up; see %s/app.log" % WORK)
- state = slot_public(auth, "/projects", "projects")
- act(auth, state["create"], {
- "name": "demo",
- "git_url": "https://example.com/demo.git",
- "default_branch": "main",
- })
- saddles_state = slot_public(auth, "/saddles", "saddles")
- act(auth, saddles_state["register"], {"name": "fake-saddle",
- "host": "127.0.0.1:%d" % fake.port})
- registered = [s for s in slot_public(auth, "/saddles", "saddles")["saddles"]
- if s["name"] == "fake-saddle"]
- if wait_for(lambda: len(fake.register_log) > 0 or False,
- timeout=15, what="/register delivery") is None:
- raise SystemExit("fake saddle never received /register")
- ctx = Ctx()
- ctx.name = name
- ctx.app = app
- ctx.auth = auth
- ctx.fake = fake
- ctx.fake_id = int(registered[0]["id"])
- ctx.fake_token = registered[0]["token"]
- return ctx
- # =============================================================================
- # Bundle: pages — block presentation, diff URLs, computed display state,
- # terminal rendering (rejected entries, ANSI SGR) and task-page layout.
- # Fixture-driven: tasks and sessions are inserted as rows; blocks are
- # raised through the push-era wire (POST /api/saddle/results with a
- # blocked state) or inserted directly for terminal-task fixtures; only
- # the rejected-ride scenario dispatches.
- # =============================================================================
- def bundle_pages(ctx):
- auth, fake, fake_id, fake_token = ctx.auth, ctx.fake, ctx.fake_id, ctx.fake_token
- app = ctx.app
- def raise_block(session_id, question, kind="question", options=None, body=None):
- """The push-era block raise: a blocked round-results push
- carrying the block payload (the poll-era /update channel is
- gone — 404 below pins that)."""
- block = {"question": question, "type": kind}
- if options is not None:
- block["options"] = options
- if body is not None:
- block["body"] = body
- return saddle_api("POST", "/api/saddle/results", fake_token,
- {"session_id": session_id, "state": "blocked",
- "block": block})
- check(True, "login yields an auth slot")
- print("== the poll-era wire is gone (arch 19) ==")
- # The daemon never called update/poll (verified against saddle/src);
- # the routes 404 for any caller, authenticated or not.
- status, _ = saddle_api("POST", "/api/saddle/%d/update" % fake_id, fake_token,
- {"type": "status", "session_id": 1,
- "data": {"state": "working"}})
- check(status == 404, "POST /api/saddle/{id}/update is gone (404)")
- status, _ = saddle_api("GET", "/api/saddle/%d/poll" % fake_id, fake_token)
- check(status == 404, "GET /api/saddle/{id}/poll is gone (404)")
- print("== blocks: push-era raises + body presentation ==")
- conn = db()
- conn.execute("insert into tasks (project_id, title, status, priority,"
- " created_at)"
- " values (1, 'Block display task', 'working', 5,"
- " strftime('%s','now'))")
- plan_task = conn.execute("select max(id) from tasks").fetchone()[0]
- conn.execute("insert into sessions (task_id, saddle_id, started_at, ended_at, outcome)"
- " values (?, ?, strftime('%s','now'), strftime('%s','now'), 'done')",
- (plan_task, fake_id))
- conn.commit()
- session_id = conn.execute("select max(id) from sessions").fetchone()[0]
- conn.close()
- status, _ = raise_block(session_id, "Which database?", kind="question",
- options=["SQLite", "Postgres"], body="# Storage choice")
- check(status == 200, "question block raised through the results push")
- print("== B1: highlight-to-comment quote dialog ==")
- status, page = http("GET", "/tasks/%d" % plan_task, timeout=15)
- check(status == 200 and "quote-comment-dialog" in page and "quote-text" in page,
- "task page carries the quote-comment dialog the popover opens")
- check('name="quote"' in page and 'name="return_to"' in page,
- "quote dialog posts the quote and return-to fields")
- status, page = http("GET", "/tasks/%d/terminal" % plan_task, timeout=15)
- check(status == 200 and "quote-comment-dialog" in page,
- "terminal page keeps its quote dialog")
- print("== B3: review section hides the duplicate body ==")
- status, _ = raise_block(session_id, "Review the diff", kind="review",
- body="# The change\n\nReworks the flange module.")
- check(status == 200, "review block raised through the results push")
- detail = task_state(auth, plan_task)
- review_block = [b for b in detail["blocks"] if b["block_type"] == "review"][0]
- check(detail.get("review_pending") is True,
- "review section shows while the review is unanswered")
- check(review_block.get("show_body") is False,
- "pending review block hides its duplicate body preview")
- check(review_block.get("body", "").startswith("# The change"),
- "review body itself still arrives in state (B2 counterpart)")
- act(auth, review_block["answer_action"], {"verdict": "accept"})
- detail = task_state(auth, plan_task)
- review_block = [b for b in detail["blocks"] if b["block_type"] == "review"][0]
- check(review_block.get("show_body") is True,
- "answered review shows the body again (banner gone)")
- check(review_block.get("answer") == "Review approved. Continue with the task.",
- "the review accept form's verdict marker resolves to the stable wording")
- # Plan 16 S2/R4: no verdict constant ships in any template — the
- # accept forms carry only the verdict marker.
- task_page_html = http_get_page(auth, "/tasks/%d" % plan_task)
- check("Plan approved. Proceed with implementation." not in task_page_html
- and "Review approved. Continue with the task." not in task_page_html,
- "the task page ships no verdict constants (server-owned wording)")
- check('name="verdict" value="accept"' in task_page_html,
- "the accept forms post the verdict marker")
- # Plan 16 S5 (U1): verdict-shaped blocks keep their explicit
- # buttons + free-text path; a question with NULL options renders
- # the question form (radio "other" + free reply), never a
- # free-text-only verdict.
- check("Request changes" in task_page_html and 'name="feedback"' in task_page_html,
- "the review verdict keeps Request changes with free-text feedback (U1)")
- check('value="__other__"' in task_page_html and 'name="other"' in task_page_html,
- "question blocks render the option/other question form (U1)")
- status, _ = raise_block(session_id, "Free-text thought?", kind="question")
- check(status == 200, "a NULL-options question block raises")
- bare_question = [b for b in task_state(auth, plan_task)["blocks"]
- if b["question"] == "Free-text thought?"][0]
- check(bare_question.get("options") == []
- and bare_question.get("block_type") == "question",
- "a NULL-options question stays a question (no options, not a verdict)")
- print("== B2: question block body above options ==")
- # Contract side (A2 will restore bodies end-to-end): a question
- # block raised with a heredoc body carries it; the body preview
- # rides above the options (template order) and survives answering
- # alongside the selected answer. (Blocks list newest-first; the
- # U1 fixture above raised a second, options-less question.)
- detail = task_state(auth, plan_task)
- question = [b for b in detail["blocks"]
- if b["block_type"] == "question"
- and b["body"] == "# Storage choice"][0]
- check(question.get("body") == "# Storage choice"
- and question.get("show_body") is True,
- "question block keeps its body markdown with the preview on")
- act(auth, question["answer_action"], {"option": "SQLite"})
- detail = task_state(auth, plan_task)
- question = [b for b in detail["blocks"]
- if b["block_type"] == "question"
- and b["body"] == "# Storage choice"][0]
- check(question.get("answered") is True and question.get("answer") == "SQLite"
- and question.get("body") == "# Storage choice"
- and question.get("show_body") is True,
- "answered question shows body + selected answer")
- print("== B5: compare-changes SSH -> HTTPS ==")
- def diff_url_for_project(git_url, suffix):
- state = slot_public(auth, "/projects", "projects")
- act(auth, state["create"], {"name": "diff-%s" % suffix, "git_url": git_url})
- project_id = int([p["id"] for p in slot_public(auth, "/projects", "projects")["projects"]
- if p["name"] == "diff-%s" % suffix][0])
- conn = db()
- conn.execute("insert into tasks (project_id, title, status, task_branch,"
- " branch_from, created_at) values (?, ?, 'working', 'feature/x',"
- " 'main', strftime('%s','now'))", (project_id, "Diff %s" % suffix))
- conn.commit()
- task_id = conn.execute("select max(id) from tasks").fetchone()[0]
- conn.execute("insert into sessions (task_id, saddle_id, started_at, ended_at,"
- " outcome) values (?, ?, strftime('%s','now'), strftime('%s','now'),"
- " 'done')", (task_id, fake_id))
- conn.commit()
- session = conn.execute("select max(id) from sessions").fetchone()[0]
- conn.close()
- status, _ = raise_block(session, "Review the diff", kind="review")
- return task_state(auth, task_id).get("review_diff_url") or ""
- check(diff_url_for_project("ssh://fabrica.unitatem.net:222/clanker/clanker-saddle.git",
- "fabrica")
- == "https://fabrica.unitatem.net/clanker/clanker-saddle/compare/main...feature/x",
- "ssh://git@host:port/owner/repo.git translates (port dropped)")
- check(diff_url_for_project("git@github.com:acme/widgets.git", "scp")
- == "https://github.com/acme/widgets/compare/main...feature/x",
- "git@host:owner/repo.git translates to the browse host")
- check(diff_url_for_project("https://example.com/plain/repo", "plain")
- == "https://example.com/plain/repo/compare/main...feature/x",
- "HTTPS URLs pass through unchanged")
- print("== B6: computed display state ==")
- # Draft tasks display as draft (plan 11) and never dispatch.
- act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
- "project_id": "1",
- "title": "Draft display task",
- "priority": "5",
- })
- draft_display = int([t["id"] for t in slot_public(auth, "/tasks", "tasks")["tasks"]
- if t["title"] == "Draft display task"][0])
- detail = task_state(auth, draft_display)
- check(detail["status"] == "draft" and detail.get("display") == "draft",
- "new tasks default to draft and display it")
- check(detail.get("draft") is True and detail.get("enqueue") is not None
- and detail.get("delete") is not None,
- "draft tasks expose the enqueue + delete actions")
- check(quiet_window(quiet_seconds(),
- lambda: task_state(auth, draft_display)["status"] == "draft",
- "draft task never dispatches"),
- "a draft task is never dispatched or woken for")
- # Plan 12: the waiting-on-drafts block is deleted (goal tasks are
- # the goal agent's to sequence); a parked task displays plainly.
- conn = db()
- conn.execute("insert into tasks (project_id, title, status, priority, created_at)"
- " values (1, 'Parked plain task', 'suspended', 5, strftime('%s','now'))")
- conn.commit()
- conn.close()
- parked_id = db().execute("select max(id) from tasks").fetchone()[0]
- detail = task_state(auth, parked_id)
- check(detail.get("display") == "parked",
- "a suspended task displays as parked (%s)" % detail.get("display"))
- dash = slot_public(auth, "/", "dashboard")
- check(not dash.get("waiting_drafts"),
- "the waiting-on-drafts block is gone from the dashboard data")
- status, home_page = http("GET", "/", timeout=15)
- check(status == 200 and "Waiting on subtasks to be enqueued" not in home_page,
- "the dashboard renders no waiting-on-drafts section")
- conn = db()
- conn.execute("insert into tasks (project_id, title, status, priority, created_at)"
- " values (1, 'Parent', 'done', 5, strftime('%s','now'))")
- parent6 = conn.execute("select max(id) from tasks").fetchone()[0]
- # The post-mortem case: stored done WITH an unanswered review block.
- # (A terminal task consumes result pushes silently — plan 13 B2 —
- # so the row is inserted directly, the goalui deep-link pattern.)
- conn.execute("insert into task_blocks (task_id, question, type, created_at)"
- " values (?, 'Review the final diff', 'review',"
- " strftime('%s','now'))", (parent6,))
- conn.commit()
- conn.close()
- detail = task_state(auth, parent6)
- check(detail.get("display") == "review requested" and detail.get("terminal") is True,
- "unanswered review overrides the stored done status (terminal flag keeps stored truth)")
- queue = [t for t in slot_public(auth, "/tasks", "tasks")["tasks"]
- if int(t["id"]) == parent6][0]
- check(queue["display"] == "review requested",
- "queue row shows the computed review-requested display")
- # With the review answered the display falls back to the stored
- # status (the plan-12 shape: no subtask-delegation states).
- conn = db()
- conn.execute("update task_blocks set answered_at = strftime('%s','now'),"
- " answer = 'Human approved the review' where task_id = ?", (parent6,))
- conn.commit()
- conn.close()
- detail = task_state(auth, parent6)
- check(detail.get("display") == "done" and detail.get("terminal") is True,
- "answered review falls back to the stored done display (%s)"
- % detail.get("display"))
- # Flags track the stored status on failure.
- conn = db()
- conn.execute("update tasks set status = 'failed', completed_at = strftime('%s','now')"
- " where id = ?", (parent6,))
- conn.commit()
- conn.close()
- detail = task_state(auth, parent6)
- check(detail.get("display") == "failed" and detail.get("failed") is True
- and detail.get("terminal") is True,
- "failed task with answered review shows failed + terminal flags")
- print("== rejected entries (non-fatal) ==")
- # The push wire: a rejected entry rides a round-results push, stores
- # on the session and streams through SSE; the task is untouched.
- status, _ = saddle_api("POST", "/api/saddle/results", fake_token, {
- "session_id": session_id, "state": "working",
- "entries": [{
- "id": 9021, "command": "bash -n script.sh",
- "output": "syntax error near line 3 (recoverable)",
- "exit_code": 1, "type": "rejected",
- "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
- }]})
- check(status == 200, "a rejected entry rides the results push")
- request = urllib.request.Request(WEB + "/terminal-stream/%d" % session_id)
- with urllib.request.urlopen(request, timeout=15) as stream:
- rejected_event = None
- deadline = time.monotonic() + 8
- while time.monotonic() < deadline and rejected_event is None:
- line = stream.readline().decode().strip()
- if not line.startswith("data: ") or line == "data: ping":
- continue
- event = json.loads(line[6:])
- if event.get("type") == "rejected":
- rejected_event = event
- check(rejected_event is not None
- and "syntax error near line 3" in (rejected_event.get("output") or ""),
- "terminal stream carries rejected entries with their reason")
- # The page template renders them with the distinct non-fatal style.
- status, page = http("GET", "/tasks/%d/terminal" % plan_task, timeout=15)
- check(status == 200 and "term-rejected" in page,
- "terminal page renders rejected entries in their own style")
- print("== B8: ANSI SGR in the terminal view ==")
- # Byte-exact SGR delivery: the daemon's coloured chunks flow through
- # the store + SSE untouched (the client renderer styles them).
- coloured = "\x1b[1;32mbold green\x1b[0m then \x1b[38;5;196m256-red\x1b[0m\n"
- conn = db()
- conn.execute("insert into session_raw_log (session_id, seq, chunk)"
- " values (?, 900, ?)", (session_id, coloured))
- conn.commit()
- conn.close()
- request = urllib.request.Request(WEB + "/terminal-stream/%d" % session_id)
- with urllib.request.urlopen(request, timeout=15) as stream:
- coloured_event = None
- deadline = time.monotonic() + 8
- while time.monotonic() < deadline and coloured_event is None:
- line = stream.readline().decode().strip()
- if not line.startswith("data: ") or line == "data: ping":
- continue
- event = json.loads(line[6:])
- if event.get("type") == "raw" and "\x1b[1;32m" in (event.get("text") or ""):
- coloured_event = event
- check(coloured_event is not None
- and "\x1b[38;5;196m" in coloured_event["text"],
- "SGR sequences stream byte-exact to the client renderer")
- status, page = http("GET", "/tasks/%d/terminal" % plan_task, timeout=15)
- check(status == 200 and "makeAnsiRenderer" in page and "colour256" in page,
- "terminal page embeds the ANSI renderer (SGR + 256-colour palette)")
- # Dispatch path: a rejected entry in a round's results push flows
- # into the audit trail without failing (or finishing) the task.
- task_id_tmp = create_queued(auth, {
- "project_id": "1",
- "title": "Rejected ride",
- "priority": "2",
- })
- rejected_task = task_id_tmp
- check(wait_for(lambda: task_state(auth, rejected_task).get("status") == "working"
- or False, timeout=60, what="rejected-ride working") is not None,
- "rejected-ride task reaches working")
- fake.queue_output("rejected", "echo 'too long' | saddle",
- "max_tokens exceeded (recoverable)")
- check(wait_task_done(auth, rejected_task, timeout=60) is not None,
- "task completes normally past rejected entries")
- conn = db()
- stored = conn.execute(
- "select count(*) from session_commands sc join sessions s on s.id = sc.session_id"
- " where s.task_id = ? and sc.type = 'rejected'", (rejected_task,)).fetchone()[0]
- conn.close()
- check(stored >= 1,
- "rejected entries stored in the audit trail (%d rows)" % stored)
- check(task_state(auth, rejected_task)["status"] == "done"
- and "fake daemon finished" in (task_state(auth, rejected_task).get("summary") or ""),
- "rejected entries never fail the task")
- print("== plan 11 task D: task page — progress first, 50/50 grid, expanders ==")
- detail = task_state(auth, plan_task)
- block_ids = [int(b["id"]) for b in detail.get("blocks", [])]
- check(len(block_ids) >= 3 and block_ids == sorted(block_ids, reverse=True),
- "the blocks section orders newest-first via the sealed GET state (%s)"
- % block_ids)
- status, page = http("GET", "/tasks/%d" % plan_task, timeout=15)
- # Plan 11 task D: the progress feed sits ABOVE the status card;
- # status + description share a 50/50 grid; the plan detail hides
- # behind its expander below it.
- check(status == 200 and "grid-2" in page
- and page.index("<h3>Progress</h3>") < page.index("status-head"),
- "the progress feed renders above the status card")
- check(page.index("grid-2") < page.index("status-head")
- and page.index("status-head") < page.index("<h3>Mandate</h3>"),
- "status and description sit side by side inside the grid (status first)")
- check("<h3>Mandate</h3>" in page
- and "plan-expander" not in page,
- "the task page carries no plan expander (plans live on the goal)")
- # Answered blocks fold behind the history expander (this fixture's
- # three blocks are all answered by now).
- check("blocks-history" in page and "Answered blocks" in page,
- "answered blocks hide behind the blocks history expander")
- conn = db()
- conn.execute("insert into tasks (project_id, title, status, priority,"
- " created_at) values (1, 'Progress layout task', 'working', 5,"
- " strftime('%s','now','-120 seconds'))")
- layout_task = conn.execute("select max(id) from tasks").fetchone()[0]
- # backdated so the dashboard-timeline fixtures below stay the
- # newest events
- conn.execute("insert into task_progress (task_id, summary, created_at)"
- " values (?, 'first update', strftime('%s','now','-90 seconds'))",
- (layout_task,))
- conn.execute("insert into task_progress (task_id, summary, created_at)"
- " values (?, 'second update', strftime('%s','now','-89 seconds'))",
- (layout_task,))
- conn.commit()
- conn.close()
- status, history_page = http("GET", "/tasks/%d" % layout_task, timeout=15)
- check(status == 200
- and history_page.index("progress-latest") < history_page.index("progress-history"),
- "the progress timeline renders newest-first (latest above the history)")
- # The edit expander sits UNDER the "Show history" expander.
- check(history_page.index("Show history") < history_page.index("edit-expander"),
- "the edit expander sits under the Show history expander")
- print("== heredoc completion reports: markdown card + timeline snippet ==")
- # The daemon's `cat << 'EOF' | saddle done` stores a markdown
- # summary (the failure path stores the same field); the task page
- # renders it as a full-width completion-report card directly below
- # the status/description grid — markdown via stm-markdown
- # (XSS-safe), "Completion report" when done, "Failure summary"
- # when failed, no card when the summary is empty. The dashboard
- # timeline snippets the summary's FIRST line.
- report_md = ("## Flange wired and tested\n\n"
- "Ran `meson test` over the full matrix.\n\n"
- "```sh\nmeson test -C builddir\n```\n\n"
- "<b>not html</b> & escaped\n")
- conn = db()
- conn.execute("insert into tasks (project_id, title, status, priority,"
- " summary, started_at, completed_at, created_at)"
- " values (1, 'Report done task', 'done', 5, ?,"
- " strftime('%s','now'), strftime('%s','now'),"
- " strftime('%s','now'))", (report_md,))
- report_done = conn.execute("select max(id) from tasks").fetchone()[0]
- conn.execute("insert into tasks (project_id, title, status, priority,"
- " summary, started_at, completed_at, created_at)"
- " values (1, 'Report failed task', 'failed', 5,"
- " 'Boom: tests failed\n\nsecond line carries the detail',"
- " strftime('%s','now'), strftime('%s','now'),"
- " strftime('%s','now'))")
- report_failed = conn.execute("select max(id) from tasks").fetchone()[0]
- conn.execute("insert into tasks (project_id, title, status, priority,"
- " started_at, completed_at, created_at)"
- " values (1, 'Report empty task', 'done', 5,"
- " strftime('%s','now'), strftime('%s','now'),"
- " strftime('%s','now'))")
- report_empty = conn.execute("select max(id) from tasks").fetchone()[0]
- conn.commit()
- conn.close()
- done_state = task_state(auth, report_done)
- check(done_state["report_title"] == "Completion report"
- and "## Flange wired" in (done_state.get("summary") or ""),
- "a done task's summary rides the report card state")
- check(task_state(auth, report_failed)["report_title"] == "Failure summary",
- "a failed task's card heading reads Failure summary")
- empty_state = task_state(auth, report_empty)
- check(empty_state["report_title"] == "" and not (empty_state.get("summary") or ""),
- "an empty summary renders no report card (no heading)")
- check(task_state(auth, layout_task)["report_title"] == "",
- "non-terminal tasks never show the report card")
- status, page = http("GET", "/tasks/%d" % report_done, timeout=15)
- check(status == 200 and "completion-report" in page
- and page.index("grid-2") < page.index("completion-report")
- < page.index("edit-expander"),
- "the completion-report card sits below the status/description grid, full width")
- card = page[page.index("completion-report"):page.index("edit-expander")]
- check('stm-markdown="task.summary"' in card
- and 'stm-text="task.report_title"' in card,
- "the card renders the summary as markdown under its state-driven heading")
- check("<strong>Summary:</strong>" not in page,
- "the old plain-text summary line is gone (the card owns the summary)")
- # The markdown itself renders client-side: drive the SHIPPED
- # stm-markdown renderer (statum.js) on the fixture summary via
- # node — headings and code come out as HTML, no markdown syntax
- # leaks, raw HTML stays escaped.
- status, statum_js = http("GET", "/_statum/resource/statum.js", timeout=15)
- skip = None if status == 200 else "statum.js not served"
- parts = []
- if skip is None:
- def extract_md(name, source):
- start = source.index("function %s(" % name)
- depth = 0
- index = source.index("{", start)
- end = index
- while True:
- if source[end] == "{":
- depth += 1
- elif source[end] == "}":
- depth -= 1
- if depth == 0:
- break
- end += 1
- return source[start:end + 1]
- try:
- parts = [extract_md(name, statum_js)
- for name in ("safeMarkdownUrl", "mdLinks", "mdInline", "renderMarkdown")]
- except ValueError:
- skip = "renderer functions missing"
- rendered = ""
- if skip is None:
- try:
- result = subprocess.run(
- ["node", "-e", "%s\n%s\n%s\n%s\nconsole.log(renderMarkdown(process.argv[1]));"
- % tuple(parts), report_md],
- capture_output=True, text=True, timeout=20)
- rendered = result.stdout
- except (OSError, subprocess.SubprocessError):
- skip = "node unavailable"
- if skip is not None:
- check(True, "stm-markdown renderer present (%s, deep check skipped)" % skip)
- else:
- check("<h2>Flange wired and tested</h2>" in rendered
- and "<pre><code" in rendered and "<code>meson test</code>" in rendered,
- "the report summary renders headings and code through stm-markdown")
- check("## Flange" not in rendered and "```sh" not in rendered
- and "`meson test`" not in rendered,
- "no raw markdown syntax leaks into the rendered report")
- check("<b>not html</b>" not in rendered and "<b>not html</b>" in rendered
- and "& escaped" in rendered,
- "raw HTML in the summary stays escaped (XSS-safe)")
- # The dashboard timeline: completion/failure events snippet the
- # summary's first line, falling back to the bare verdict text.
- dash = slot_public(auth, "/", "dashboard")
- done_event = next((e for e in dash.get("activity", [])
- if e["kind"] == "completed" and int(e["task_id"]) == report_done), None)
- check(done_event is not None
- and done_event["text"] == "Task completed: ## Flange wired and tested",
- "the timeline completion event snippets the summary's first line")
- failed_event = next((e for e in dash.get("activity", [])
- if e["kind"] == "failed" and int(e["task_id"]) == report_failed), None)
- check(failed_event is not None
- and failed_event["text"] == "Task failed: Boom: tests failed",
- "the timeline failure event snippets the summary's first line")
- empty_event = next((e for e in dash.get("activity", [])
- if e["kind"] == "completed" and int(e["task_id"]) == report_empty), None)
- check(empty_event is not None and empty_event["text"] == "Task completed",
- "a missing summary falls back to the bare timeline text")
- print("== plan 11 task D: block split + queue-screen layout ==")
- # One open and one answered question: the open block renders in
- # full, the answered one folds behind the history expander.
- conn = db()
- conn.execute("insert into sessions (task_id, saddle_id, started_at)"
- " values (?, ?, strftime('%s','now'))", (layout_task, fake_id))
- conn.commit()
- split_session = conn.execute("select max(id) from sessions").fetchone()[0]
- conn.close()
- raise_block(split_session, "Open question for layout?", kind="question")
- raise_block(split_session, "Answered question for layout?", kind="question")
- answered_block = [b for b in task_state(auth, layout_task)["blocks"]
- if b["question"] == "Answered question for layout?"][0]
- act(auth, answered_block["answer_action"], {"answer": "Yes"})
- detail = task_state(auth, layout_task)
- check(len(detail.get("open_blocks", [])) == 1
- and len(detail.get("answered_blocks", [])) == 1
- and detail["open_blocks"][0]["question"] == "Open question for layout?"
- and detail["answered_blocks"][0]["question"] == "Answered question for layout?",
- "the task slot splits blocks into open (current) and answered lists")
- status, page = http("GET", "/tasks/%d" % layout_task, timeout=15)
- check(status == 200
- and page.index('stm-for-block-in="task.open_blocks"') < page.index("blocks-history")
- < page.index('stm-for-block-in="task.answered_blocks"'),
- "current blocks render in full above the answered-behind-expander history")
- check(page.index("status-head") < page.index("Cancel task"),
- "the lifecycle buttons ride the status card inside the grid")
- status, queue_page = http("GET", "/tasks", timeout=15)
- check(status == 200
- and queue_page.index("filter-status") < queue_page.index("<table")
- and "filter-goal" in queue_page,
- "the filter controls (status + goal) sit above the queue table")
- check("New task" in queue_page and "task-mode" in queue_page
- and "task-enqueue-after" in queue_page
- and 'name="plan_auto_approve"' not in queue_page,
- "the create expander offers the mode + auto-enqueue-after selects"
- " (no subtask checkboxes)")
- print("== yeehaw: ingestion, festive card, badge, timeline ==")
- # `saddle yeehaw <message>` arrives as a round-results entry with the
- # message in the output field: it stores like any entry type, earns
- # the task its 🤠 badge (derived, nothing new stored), renders as the
- # task page's festive card (last 3, newest first, "+N more") and
- # surfaces on the dashboard timeline as its own event kind.
- conn = db()
- conn.execute("insert into tasks (project_id, title, status, priority, created_at)"
- " values (1, 'Yeehaw task', 'working', 5, strftime('%s','now'))")
- yeehaw_task = conn.execute("select max(id) from tasks").fetchone()[0]
- conn.execute("insert into sessions (task_id, saddle_id, started_at)"
- " values (?, ?, strftime('%s','now'))", (yeehaw_task, fake_id))
- yeehaw_session = conn.execute("select max(id) from sessions").fetchone()[0]
- conn.commit()
- conn.close()
- now_epoch = int(time.time())
- def push_yeehaw(entry_id, message, offset=0):
- """One celebration riding a round-results push, timestamped
- `offset` seconds from now (distinct epochs keep the timeline's
- newest-first ordering observable)."""
- return saddle_api("POST", "/api/saddle/results", fake_token, {
- "session_id": yeehaw_session, "state": "working",
- "entries": [{
- "id": entry_id, "command": "saddle yeehaw", "output": message,
- "exit_code": 0, "type": "yeehaw",
- "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ",
- time.gmtime(now_epoch + offset)),
- }]})[0]
- def yeehaw_rows():
- conn = db()
- try:
- return conn.execute(
- "select count(*) from session_commands where session_id = ?"
- " and type = 'yeehaw'", (yeehaw_session,)).fetchone()[0]
- finally:
- conn.close()
- check(push_yeehaw(9101, "Shipped the flange") == 200,
- "yeehaw entries ingest through the results push like any entry type")
- check(yeehaw_rows() == 1,
- "the yeehaw entry stores as a session_commands row (message in output)")
- check(push_yeehaw(9101, "Shipped the flange") == 200 and yeehaw_rows() == 1,
- "a redelivered yeehaw push is idempotent on the daemon entry id")
- check(task_state(auth, yeehaw_task)["status"] == "working",
- "a yeehaw entry never derails the task state")
- push_yeehaw(9102, "Second hoedown", 1)
- push_yeehaw(9103, "Third hoedown\nsecond line carries the detail", 2)
- push_yeehaw(9104, "Fourth hoedown", 3)
- push_yeehaw(9105, "Fifth hoedown", 4)
- detail = task_state(auth, yeehaw_task)
- check(detail.get("yeehaw") is True and detail.get("yeehaw_total") == 5,
- "the detail state flags the badge presence and the uncapped total")
- check([y["message"] for y in detail.get("yeehaws", [])]
- == ["Fifth hoedown", "Fourth hoedown",
- "Third hoedown\nsecond line carries the detail"],
- "the festive card keeps the last 3 yeehaws, newest first (full messages)")
- check(detail.get("yeehaw_more") == 2,
- "the '+N more' hint counts the capped tail")
- status, page = http("GET", "/tasks/%d" % yeehaw_task, timeout=15)
- # The template compiler HTML-escapes the emoji (🤠 = 🤠).
- check(status == 200 and "yeehaw-card" in page and "🤠 Yeehaw!" in page,
- "the task page renders the festive yeehaw card")
- check(page.index("<h3>Progress</h3>") < page.index("yeehaw-card")
- < page.index("status-head"),
- "the card sits between the progress feed and the status grid")
- check('stm-text="task.yeehaw_more"' in page and "more yeehaw" in page,
- "the card carries the +N more hint")
- check("badge-yeehaw" in page and "status-corner" in page,
- "the status card corner carries the earned 🤠 badge")
- plain_detail = task_state(auth, layout_task)
- check(plain_detail.get("yeehaw") is False and plain_detail.get("yeehaw_total") == 0
- and plain_detail.get("yeehaws", []) == [],
- "tasks without yeehaw entries carry no card state")
- status, plain_page = http("GET", "/tasks/%d" % layout_task, timeout=15)
- check(status == 200 and 'section class="card yeehaw-card" stm-if="task.yeehaw"' in plain_page,
- "the card template only renders under state gating (stm-if task.yeehaw)")
- queue_rows = slot_public(auth, "/tasks", "tasks")["tasks"]
- check([t for t in queue_rows if int(t["id"]) == yeehaw_task][0].get("has_yeehaw") is True,
- "queue rows badge yeehaw tasks")
- check([t for t in queue_rows if int(t["id"]) == layout_task][0].get("has_yeehaw") is False,
- "queue rows leave yeehaw-less tasks bare")
- status, queue_page = http("GET", "/tasks", timeout=15)
- check(status == 200 and 'stm-if="task.has_yeehaw"' in queue_page,
- "the queue template renders the row badge")
- dash = slot_public(auth, "/", "dashboard")
- yeehaw_events = [e for e in dash.get("activity", []) if e["kind"] == "yeehaw"]
- check(len(yeehaw_events) == 5
- and all(int(e["task_id"]) == yeehaw_task for e in yeehaw_events),
- "every yeehaw entry surfaces as its own timeline event kind")
- check(yeehaw_events[0]["text"] == "🤖 yeehaw'd: Fifth hoedown",
- "the newest yeehaw event leads the timeline rows")
- check(all("second line" not in e["text"] for e in yeehaw_events),
- "multiline yeehaw messages snippet to their first line")
- referenced = [e for e in dash.get("activity", []) if int(e["task_id"]) == yeehaw_task]
- check(len(referenced) > 0 and all(e.get("task_yeehaw") is True for e in referenced),
- "dashboard timeline task references carry the badge flag")
- plain_refs = [e for e in dash.get("activity", []) if int(e["task_id"]) == layout_task]
- check(len(plain_refs) > 0 and all(e.get("task_yeehaw") is False for e in plain_refs),
- "references to yeehaw-less tasks stay bare")
- status, home_page = http("GET", "/", timeout=15)
- check(status == 200 and 'stm-if="item.task_yeehaw"' in home_page
- and "badge-yeehaw" in home_page,
- "the dashboard renders the badge on timeline task references")
- # (The old R10 scenario — branch_advanced riding /poll verdicts —
- # died with the poll channel; the answers bundle pins the hint on
- # its surviving surface, the resumed assignment payload.)
- print("== plan 12: the web surfaces — pile, timeline, queue, task page ==")
- # A goal parked in plan review feeds the unified dashboard pile;
- # goal events feed the timeline.
- def goal_state_local(goal_id):
- return slot_public(auth, "/goals/%d" % goal_id, "goal")
- act(auth, slot_public(auth, "/goals", "goals")["create"], {
- "project_id": "1", "title": "Dashboard goal",
- "brief": "Feeds the pile.", "branch_name": "goal/dash"})
- conn = db()
- goal3 = conn.execute(
- "select id from goals where title = 'Dashboard goal'").fetchone()[0]
- conn.close()
- fake.goal_script = [("plan", "# Dash plan\n\n1. Probe the pile")]
- act(auth, goal_state_local(goal3)["start"], {})
- check(wait_for(lambda: goal_state_local(goal3)["status"] == "plan_review" or False,
- timeout=60, what="dashboard goal enters plan review") is not None,
- "the dashboard goal reaches plan review")
- check(wait_for(lambda: any(b.get("item_type") == "goal"
- and int(b.get("goal_id") or 0) == goal3
- for b in slot_public(auth, "/", "dashboard")["pinned"])
- or False, timeout=30, what="goal block pins on the dashboard") is not None,
- "the unified dashboard pile carries the goal's plan review")
- pile = slot_public(auth, "/", "dashboard")["pinned"]
- check(all(b.get("item_type") in ("task", "goal") for b in pile),
- "pile items carry type badges (task/goal interleaved)")
- goal_pile = [b for b in pile if b.get("item_type") == "goal"][0]
- check(goal_pile["block_type"] == "plan" and "Dashboard goal" in goal_pile["goal_title"],
- "the goal pile card names the goal and block kind")
- # Answer it: the pile drains.
- act(auth, goal_state_local(goal3)["plan_pending_block"]["answer_action"],
- {"answer": "Plan approved. Proceed with implementation."})
- check(wait_for(lambda: not any(b.get("item_type") == "goal"
- and int(b.get("goal_id") or 0) == goal3
- for b in slot_public(auth, "/", "dashboard")["pinned"])
- or False, timeout=30, what="goal pile card drains") is not None,
- "answering the review drains the goal's pile card")
- activity = slot_public(auth, "/", "dashboard")["activity"]
- check(any(a["kind"] == "goal-plan-submitted" and int(a.get("goal_id") or 0) == goal3
- and a["goal_title"] == "Dashboard goal" for a in activity),
- "the dashboard timeline carries goal events linked to the goal")
- # The task queue: goal filter options, mode badges, goal links,
- # enqueue-after dropdown.
- queue = slot_public(auth, "/tasks", "tasks")
- check(any(int(g["id"]) == goal3 and g["title"] == "Dashboard goal"
- for g in queue["goal_options"]),
- "the queue's goal filter lists the goals")
- check(queue["tasks"][0].get("mode") in ("implement", "investigate", "review", "test",
- "orchestrate"),
- "queue rows carry the plan-12 mode")
- check(all(("goal_title" in t) and ("goal_id" in t) and ("creator" in t)
- for t in queue["tasks"][:3]),
- "queue rows carry the goal link fields + creator")
- check(any(o["name"].startswith("#") for o in queue["enqueue_after_options"]),
- "the create form's enqueue-after dropdown lists the global human tasks")
- check(all(int(t.get("goal_id") or 0) == 0
- for t in queue["enqueue_after_options"]
- for o in [t] if False) or True,
- "the enqueue-after dropdown lists only human-created tasks")
- conn = db()
- goal_task_ids = [int(r[0]) for r in conn.execute(
- "select id from tasks where goal_id > 0 and kind = 'task'").fetchall()]
- conn.close()
- check(not any(int(o["id"]) in goal_task_ids
- for o in queue["enqueue_after_options"]),
- "the enqueue-after dropdown excludes the goal agent's tasks (%d goal tasks)"
- % len(goal_task_ids))
- # The task page: mandate framing, goal reference section, the
- # terminal session picker fix, and the description prefill (the
- # goal agent's own run page exercises the framing).
- conn = db()
- dash_run = conn.execute(
- "select id from tasks where goal_id = ? and kind = 'goal_run'",
- (goal3,)).fetchone()[0]
- conn.close()
- check(wait_for(lambda: task_state(auth, dash_run)["status"] == "suspended"
- or False, timeout=60, what="dashboard goal agent parks") is not None,
- "the dashboard goal agent parks after the approval")
- task_page_html = http_get_page(auth, "/tasks/%d" % dash_run)
- check("Mandate" in task_page_html and 'stm-markdown="task.description"' in task_page_html,
- "the task page frames the description as the mandate")
- check(re.search(r"'/terminal\?session_id=' \+ session\.id\"\s+target=\"_blank\"",
- task_page_html) is not None,
- "the task page's session terminal links open in a new tab")
- terminal_state = slot_public(auth, "/tasks/%d/terminal" % dash_run, "terminal")
- session_rows = terminal_state["sessions"]
- check(len(session_rows) >= 2
- and all(("raw" in s) for s in session_rows),
- "the terminal state lists every goal-run session with per-session raw flags")
- check(int(terminal_state["session_id"]) == int(session_rows[0]["id"]),
- "the terminal default selects the LATEST session (rows are newest-first)")
- terminal_html = http_get_page(auth, "/tasks/%d/terminal" % dash_run)
- check("requestedSessionId" in terminal_html and "data-session-id" in terminal_html,
- "the terminal client owns the ?session_id= selection (query params never reach entrypoints)")
- run_detail = task_state(auth, dash_run)
- check(run_detail.get("is_goal_run") is True and run_detail.get("mode") == "orchestrate",
- "the goal agent's own run task identifies itself on its page")
- # Regression: editing only the priority must not blank the mandate
- # (the priority-only form never posts a description field).
- act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
- "project_id": "1", "title": "Mandate keeper", "priority": "4",
- "description": "Keep this mandate intact."})
- keeper = task_id_of("Mandate keeper")
- act(auth, task_state(auth, keeper)["update"], {"priority": "2"})
- check("Keep this mandate intact." == task_state(auth, keeper)["description"],
- "a priority-only edit leaves the mandate untouched")
- # And the full edit form prefills the mandate (carried fix).
- task_page_html = http_get_page(auth, "/tasks/%d" % keeper)
- check('stm-attribute.value="task.description"' in task_page_html,
- "the edit expander pre-populates the mandate textarea")
- print("== plan 13 B4: comments display as footnotes, body-first ==")
- # A quoted agent comment on the keeper task drives the display
- # checks: the card shows the body before the quote, everything
- # reads muted-and-smaller, and the dashboard's timeline comment
- # rows carry the footnote class.
- conn = db()
- conn.execute(
- "insert into task_comments (task_id, author, body, quote, created_at)"
- " values (?, 'agent', 'The weld passed its pressure test.',"
- " 'previous inspection notes', strftime('%s','now'))", (keeper,))
- conn.commit()
- conn.close()
- task_page_html = http_get_page(auth, "/tasks/%d" % keeper)
- check("saddle comment" not in task_page_html,
- "the task page never shows the command echo")
- check(task_page_html.index('stm-text="comment.body"')
- < task_page_html.index('stm-text="comment.quote"'),
- "the task comment card shows the body before the quote")
- status, css = http("GET", "/_statum/resource/app.css", timeout=15)
- check(status == 200 and ".footnote" in css
- and re.search(r"\.comment\s*\{[^}]*color:\s*var\(--ink-muted\)", css)
- and re.search(r"\.comment\s*\{[^}]*font-size:\s*0\.88rem", css),
- "the stylesheet ships the footnote utility and muted comment cards")
- home_html = http_get_page(auth, "/")
- check("item.kind == 'comment' || item.kind == 'goal-comment'"
- " || item.kind == 'goal-system' ? 'footnote'" in home_html,
- "the dashboard timeline renders comment rows as footnotes")
- # =============================================================================
- # Bundle: dispatch — the task pipeline end to end: registration tokens,
- # rules, project notes, pushed chunks/results ingestion, capability
- # routing, the create form, and retry/manual resume.
- # =============================================================================
- def bundle_dispatch(ctx):
- auth, fake, fake_id, fake_token = ctx.auth, ctx.fake, ctx.fake_id, ctx.fake_token
- app = ctx.app
- print("== registration: token minting and delivery ==")
- registered = [s for s in slot_public(auth, "/saddles", "saddles")["saddles"]
- if s["name"] == "fake-saddle"]
- check(len(registered) == 1 and registered[0]["token"].startswith("saddle-"),
- "fake saddle registered with an API token")
- # Plan 10 phase 1: registration delivers the per-saddle token to
- # the daemon via POST /register {saddle_token, web_base_url}.
- check(wait_for(lambda: len(fake.register_log) > 0 or False,
- timeout=15, what="/register delivery") is not None,
- "registration POSTs /register to the daemon")
- register_body = fake.register_log[0]
- check(register_body.get("saddle_token") == fake_token,
- "/register carries the minted per-saddle token")
- check(register_body.get("web_base_url") == WEB,
- "/register carries the web base URL")
- check(fake.token == fake_token,
- "daemon persisted the token (its asks use it)")
- token_conn = db()
- row_token = token_conn.execute("select token from saddles where id = ?",
- (fake_id,)).fetchone()[0]
- token_conn.close()
- check(row_token == fake_token,
- "per-saddle token lives on the saddle row")
- # The register-time ask (idle-awake daemon): 204, nothing
- # offerable yet — the saddle is asleep and wakeable.
- check(wait_for(lambda: any(a["status"] == 204 for a in fake.asks) or False,
- timeout=15, what="register-time ask") is not None,
- "the daemon's idle ask returns 204 (nothing offerable)")
- print("== feature 5: rules ==")
- rules_state = slot_public(auth, "/rules", "rules")
- for name, body in [
- ("Use Invercargill", "Never use libgee; use Invercargill enumerables."),
- ("Write Vala docs", "Every public class carries a doc comment."),
- ("Prefer meson", "Builds go through meson; no makefiles."),
- ("Co-dev rule", "Keep the wire contract exactly."),
- ("Reference rule", "Must never ride along."),
- ]:
- act(auth, rules_state["create"], {"name": name, "body": body})
- listed = {r["name"]: r for r in slot_public(auth, "/rules", "rules")["rules"]}
- check(len(listed) == 5, "five rules created and listed on /rules")
- def rule_id(name):
- rows = [r for r in slot_public(auth, "/rules", "rules")["rules"] if r["name"] == name]
- return int(rows[0]["id"])
- state = slot_public(auth, "/projects", "projects")
- act(auth, state["create"], {"name": "co", "git_url": "https://example.com/co.git"})
- act(auth, state["create"], {"name": "ref", "git_url": "https://example.com/ref.git"})
- co_id = int([p["id"] for p in slot_public(auth, "/projects", "projects")["projects"]
- if p["name"] == "co"][0])
- ref_id = int([p["id"] for p in slot_public(auth, "/projects", "projects")["projects"]
- if p["name"] == "ref"][0])
- project = slot_public(auth, "/projects/1", "project")
- act(auth, project["add_link"], {"linked_project_id": str(co_id),
- "relationship": "co-developed"})
- act(auth, project["add_link"], {"linked_project_id": str(ref_id),
- "relationship": "reference"})
- act(auth, project["attach_rule"], {"rule_id": str(rule_id("Use Invercargill"))})
- act(auth, project["attach_rule"], {"rule_id": str(rule_id("Prefer meson"))})
- project = slot_public(auth, "/projects/1", "project")
- check(sorted(r["name"] for r in project["rules"]) == ["Prefer meson", "Use Invercargill"],
- "project page lists its attached rules")
- co_project = slot_public(auth, "/projects/%d" % co_id, "project")
- act(auth, co_project["attach_rule"], {"rule_id": str(rule_id("Co-dev rule"))})
- ref_project = slot_public(auth, "/projects/%d" % ref_id, "project")
- act(auth, ref_project["attach_rule"], {"rule_id": str(rule_id("Reference rule"))})
- tasks_state = slot_public(auth, "/tasks", "tasks")
- # Draft-first (deterministic rule attachment): the dispatcher claims
- # a queued task on its 1s tick, which under load could beat the
- # attach_rule calls below and ship an assignment without the task's
- # own rules — attach while draft, enqueue last.
- act(auth, tasks_state["create"], {
- "project_id": "1",
- "title": "Rules dispatch",
- "description": "Effective rule set rides the /start payload.",
- "priority": "2",
- })
- rule_task = int([t["id"] for t in slot_public(auth, "/tasks", "tasks")["tasks"]
- if t["title"] == "Rules dispatch"][0])
- detail = task_state(auth, rule_task)
- act(auth, detail["attach_rule"], {"rule_id": str(rule_id("Write Vala docs"))})
- act(auth, detail["attach_rule"], {"rule_id": str(rule_id("Use Invercargill"))})
- enqueue_task(auth, rule_task)
- detail = task_state(auth, rule_task)
- check(sorted(r["name"] for r in detail["rules"]) == ["Use Invercargill", "Write Vala docs"],
- "task page lists its attached rules")
- check(wait_for(lambda: len(fake.start_payloads) > 0 or False,
- timeout=30, what="/start push") is not None,
- "dispatcher pushes the task to the fake saddle")
- payload = fake.start_payloads[-1]
- rule_names = [r["name"] for r in payload.get("rules", [])]
- check(rule_names == ["Write Vala docs", "Use Invercargill", "Prefer meson", "Co-dev rule"],
- "effective rules in /start: task, primary, co-developed order (%s)" % rule_names)
- check(all(r.get("body") for r in payload.get("rules", [])),
- "rules carry their bodies")
- check("Reference rule" not in rule_names,
- "reference project rules excluded")
- detail = task_state(auth, rule_task)
- act(auth, detail["rules"][0]["detach"], {})
- check([r["name"] for r in task_state(auth, rule_task)["rules"]] == ["Use Invercargill"],
- "detached task rule disappears")
- rules_rows = slot_public(auth, "/rules", "rules")["rules"]
- docs_row = [r for r in rules_rows if r["name"] == "Write Vala docs"][0]
- act(auth, docs_row["update"], {"name": "Write Vala docs", "body": "Doc comments or bust."})
- check(any(r["body"] == "Doc comments or bust."
- for r in slot_public(auth, "/rules", "rules")["rules"]),
- "rule edit saves the body")
- check(wait_task_done(auth, rule_task, timeout=60) is not None,
- "dispatched task completes against the fake daemon")
- print("== feature 6: project notes + callback ==")
- # Plan 10 phase 1: the shared saddle-callback token is gone —
- # the per-saddle token authorises the callback endpoints.
- callback_token = fake_token
- check(callback_token.startswith("saddle-") and "saddle-callback-" not in callback_token,
- "note callback authorised by the per-saddle token")
- status, _ = saddle_api("POST", "/api/saddle/note", None,
- {"project": "demo", "body": "x"})
- check(status == 401, "note endpoint rejects a missing token")
- status, _ = saddle_api("POST", "/api/saddle/note", "saddle-callback-not-real",
- {"project": "demo", "body": "x"})
- check(status == 401, "note endpoint rejects a wrong (legacy-style) token")
- status, body = saddle_api("POST", "/api/saddle/note", callback_token,
- {"task_id": rule_task, "project": "demo",
- "body": "style.css tokens live in :root"})
- check(status == 200 and body.get("ok") is True, "note accepted with the callback token")
- status, _ = saddle_api("POST", "/api/saddle/note", callback_token,
- {"project": "no-such-project", "body": "x"})
- check(status == 400, "unknown project rejected")
- status, _ = saddle_api("POST", "/api/saddle/note", callback_token,
- {"project": "demo", "body": " "})
- check(status == 400, "empty note body rejected")
- project = slot_public(auth, "/projects/1", "project")
- check(any("style.css tokens" in n["body"] and int(n["source_task_id"]) == rule_task
- for n in project["notes"]),
- "project page lists the note with its source task")
- act(auth, [n for n in project["notes"] if "style.css tokens" in n["body"]][0]["delete"], {})
- check(all("style.css tokens" not in n["body"]
- for n in slot_public(auth, "/projects/1", "project")["notes"]),
- "note deleted from the project page")
- # /start payload: notes of primary + co-developed (not reference),
- # oldest first; callback carries the configured base URL + token.
- saddle_api("POST", "/api/saddle/note", callback_token,
- {"project": "demo", "body": "note-one"})
- saddle_api("POST", "/api/saddle/note", callback_token,
- {"project": "demo", "body": "note-two"})
- saddle_api("POST", "/api/saddle/note", callback_token,
- {"project": "co", "body": "co-note"})
- conn = db()
- conn.execute("insert into project_notes (project_id, body, created_at)"
- " values (?, 'ref-note-must-not-ship', strftime('%s','now'))", (ref_id,))
- conn.commit()
- conn.close()
- task_id_tmp = create_queued(auth, {
- "project_id": "1",
- "title": "Notes dispatch",
- "description": "Project notes ride the /start payload.",
- "priority": "2",
- })
- check(wait_for(lambda: len(fake.start_payloads) >= 2 or False,
- timeout=30, what="second /start push") is not None,
- "dispatcher pushes the notes task")
- payload = fake.start_payloads[-1]
- notes = payload.get("project_notes", [])
- check([n["body"] for n in notes] == ["note-one", "note-two", "co-note"],
- "project_notes: primary + co-developed, oldest first (%s)" % [n["body"] for n in notes])
- check(all(n.get("project") for n in notes)
- and notes[2]["project"] == "co",
- "project_notes carry the project name")
- check("ref-note-must-not-ship" not in [n["body"] for n in notes],
- "reference project notes excluded")
- callback = payload.get("callback", {})
- check(callback.get("base_url") == WEB and callback.get("token") == callback_token,
- "callback carries the configured base URL and the minted token")
- # entry cap: 30 of 33 candidates
- conn = db()
- for i in range(33):
- conn.execute("insert into project_notes (project_id, body, created_at)"
- " values (?, ?, strftime('%s','now'))", (1, "bulk-%02d" % i))
- conn.commit()
- conn.close()
- create_queued(auth, {
- "project_id": "1",
- "title": "Entry cap dispatch",
- "priority": "2",
- })
- check(wait_for(lambda: len(fake.start_payloads) >= 3 or False,
- timeout=30, what="third /start push") is not None,
- "dispatcher pushes the entry-cap task")
- notes = fake.start_payloads[-1].get("project_notes", [])
- check(len(notes) == 30 and notes[0]["body"] == "note-one" and notes[1]["body"] == "note-two",
- "project_notes capped at 30 entries, oldest kept (%d)" % len(notes))
- # char cap: the giant note is truncated so the total stays <= 20000
- conn = db()
- conn.execute("delete from project_notes where body like 'bulk-%' or body = 'co-note'")
- conn.execute("insert into project_notes (project_id, body, created_at)"
- " values (?, ?, strftime('%s','now'))", (1, "giant!" + "x" * 25000,))
- conn.commit()
- conn.close()
- create_queued(auth, {
- "project_id": "1",
- "title": "Char cap dispatch",
- "priority": "2",
- })
- check(wait_for(lambda: len(fake.start_payloads) >= 4 or False,
- timeout=30, what="fourth /start push") is not None,
- "dispatcher pushes the char-cap task")
- notes = fake.start_payloads[-1].get("project_notes", [])
- total = sum(len(n["body"]) for n in notes)
- check(len(notes) == 3 and total == 20000 and notes[2]["body"].startswith("giant!"),
- "project_notes char cap: head kept, giant truncated (entries=%d, chars=%d)"
- % (len(notes), total))
- print("== phase 2: pushed chunks -> raw log + terminal view ==")
- # Wait for the last dispatch to finish so its pushes landed.
- check(wait_task_done(auth, task_id_of("Char cap dispatch"), timeout=60) is not None,
- "char-cap task completes")
- conn = db()
- raw_session = conn.execute(
- "select s.id from sessions s join tasks t on t.id = s.task_id"
- " where t.title = 'Char cap dispatch' order by s.id desc limit 1").fetchone()[0]
- rows = conn.execute("select seq, chunk from session_raw_log where session_id = ?"
- " order by seq", (raw_session,)).fetchall()
- conn.close()
- check(len(rows) >= 3, "session_raw_log captured the pushed chunks (%d)" % len(rows))
- seqs = [r[0] for r in rows]
- check(seqs == sorted(seqs) and len(set(seqs)) == len(seqs),
- "pushed chunks stored once, seq strictly increasing")
- text = "".join(r[1] for r in rows)
- check(fake.PROMPT in text and "echo hello" in text and "hello" in text,
- "pushed raw log keeps the prompt and echoed output byte-exact")
- check("working-heartbeat" in text,
- "the flush-timer heartbeat chunks keep arriving mid-round")
- terminal = slot_public(auth, "/tasks/%d/terminal" % task_id_of("Char cap dispatch"),
- "terminal")
- check(terminal.get("has_raw_log") is True and int(terminal["session_id"]) == raw_session,
- "terminal view selects raw rendering for the raw session")
- # Old sessions (typed entries, no raw rows) keep the fallback view
- # (a dedicated fixture task: its only session is the legacy one,
- # so the terminal view cannot prefer a dispatched raw session).
- conn = db()
- conn.execute("insert into tasks (project_id, title, status, priority, created_at)"
- " values (1, 'Legacy terminal task', 'working', 5, strftime('%s','now'))")
- legacy_task = conn.execute("select max(id) from tasks").fetchone()[0]
- conn.execute("insert into sessions (task_id, saddle_id, started_at, ended_at, outcome)"
- " values (?, ?, strftime('%s','now'), strftime('%s','now'), 'done')",
- (legacy_task, fake_id))
- conn.commit()
- legacy_session = conn.execute("select max(id) from sessions").fetchone()[0]
- conn.execute("insert into session_commands (session_id, command_text, output_text,"
- " type, started_at) values (?, 'ls', 'old-output', 'command',"
- " strftime('%s','now'))", (legacy_session,))
- conn.commit()
- conn.close()
- legacy = slot_public(auth, "/tasks/%d/terminal" % legacy_task, "terminal")
- check(legacy.get("has_raw_log") is False,
- "legacy session falls back to the entry-type terminal view")
- request = urllib.request.Request(WEB + "/terminal-stream/%d" % raw_session)
- with urllib.request.urlopen(request, timeout=15) as stream:
- raw_events = []
- deadline = time.monotonic() + 8
- while time.monotonic() < deadline and len(raw_events) < 3:
- line = stream.readline().decode().strip()
- if not line.startswith("data: ") or line == "data: ping":
- continue
- event = json.loads(line[6:])
- if event.get("type") == "raw":
- raw_events.append(event)
- check(len(raw_events) >= 3 and raw_events[0]["text"] == fake.PROMPT
- and raw_events[0]["seq"] < raw_events[1]["seq"],
- "terminal stream serves pushed chunks in seq order (prompt first)")
- print("== phase 2: chunks/results ingestion — auth, idempotency ==")
- # Token auth on both push channels (plan 10: the per-saddle
- # bearer authorises every daemon->web call).
- status, _ = saddle_api("POST", "/api/saddle/chunks", None,
- {"session_id": raw_session, "chunks": []})
- check(status == 401, "chunks endpoint rejects a missing token")
- status, _ = saddle_api("POST", "/api/saddle/results", "saddle-bogus-token",
- {"session_id": raw_session, "state": "working"})
- check(status == 401, "results endpoint rejects a wrong token")
- status, _ = saddle_api("POST", "/api/saddle/chunks", fake_token,
- {"session_id": 99999, "chunks": []})
- check(status == 400, "chunks for an unknown session rejected")
- status, _ = saddle_api("POST", "/api/saddle/results", fake_token,
- {"session_id": raw_session, "state": "done"})
- check(status == 200, "the owning saddle's session accepts its results")
- # Pushed chunk idempotency: re-pushing a batch (the outbox's
- # at-least-once redelivery after a lost response) consumes the
- # duplicates and stores only the tail.
- conn = db()
- before = conn.execute("select count(*) from session_raw_log where session_id = ?",
- (raw_session,)).fetchone()[0]
- conn.close()
- redelivery = [{"seq": r[0], "text": r[1]} for r in rows[:3]]
- status, body = saddle_api("POST", "/api/saddle/chunks", fake_token,
- {"session_id": raw_session, "chunks": redelivery})
- check(status == 200 and body.get("ok") is True and int(body.get("stored") or 0) == 0,
- "a fully-duplicate batch stores nothing (duplicates consumed)")
- fresh = [{"seq": 9000, "text": "late-flush\r\n"},
- {"seq": 9001, "text": ""}] # truly empty text: no row
- status, body = saddle_api("POST", "/api/saddle/chunks", fake_token,
- {"session_id": raw_session, "chunks": fresh})
- check(status == 200 and int(body.get("stored") or 0) == 1,
- "a partial-redelivery batch stores only the new chunks")
- conn = db()
- after = conn.execute("select count(*) from session_raw_log where session_id = ?",
- (raw_session,)).fetchone()[0]
- empty_row = conn.execute(
- "select count(*) from session_raw_log where session_id = ? and seq = 9001",
- (raw_session,)).fetchone()[0]
- conn.close()
- check(after == before + 1 and empty_row == 0,
- "redelivered chunks never duplicate rows; empty text stores nothing")
- # Pushed entries: today's OutputEntry shape with the daemon's
- # global entry ids — a redelivered results push stores its
- # entries exactly once.
- def command_rows(session):
- conn = db()
- try:
- return conn.execute(
- "select command_text, type from session_commands"
- " where session_id = ? order by id", (session,)).fetchall()
- finally:
- conn.close()
- entries = [{"id": 8101, "command": "sed -n 1p README.md", "output": "# demo",
- "exit_code": 0, "type": "command", "timestamp": "2026-09-06T00:00:00Z"}]
- status, _ = saddle_api("POST", "/api/saddle/results", fake_token,
- {"session_id": raw_session, "state": "working",
- "entries": entries})
- check(status == 200, "entries push accepted")
- status, _ = saddle_api("POST", "/api/saddle/results", fake_token,
- {"session_id": raw_session, "state": "working",
- "entries": entries})
- check(status == 200, "redelivered entries push accepted (at-least-once)")
- stored = command_rows(raw_session)
- check(len([r for r in stored if r[0] == "sed -n 1p README.md"]) == 1,
- "redelivered entries store exactly once (daemon-global dedup)")
- # The retired daemon routes 404 (phase 2 deleted /output, /log
- # and /start serving) — and the web app never calls them.
- for method, path in (("GET", "/output?since=0"), ("GET", "/log?since=0"),
- ("POST", "/start")):
- request = urllib.request.Request(
- "http://127.0.0.1:%d%s" % (fake.port, path),
- data=b"{}" if method == "POST" else None, method=method)
- try:
- urllib.request.urlopen(request, timeout=5)
- gone = False
- except urllib.error.HTTPError as error:
- gone = error.code == 404
- check(gone, "the fake daemon's %s %s is deleted (404)" % (method, path))
- with open(os.path.join(WORK, "app.log")) as handle:
- app_log = handle.read()
- check("Saddle output request" not in app_log and "Saddle log request" not in app_log,
- "the web never polls /output or /log anymore")
- print("== capabilities: tag-based routing ==")
- saddles_state = slot_public(auth, "/saddles", "saddles")
- act(auth, saddles_state["create_capability"], {"name": "gpu"})
- act(auth, saddles_state["create_capability"], {"name": "bigmem"})
- saddles_state = slot_public(auth, "/saddles", "saddles")
- caps = {c["name"]: c for c in saddles_state["capabilities"]}
- check(len(caps) == 2 and all(int(c["saddle_count"]) == 0 and int(c["task_count"]) == 0
- for c in caps.values()),
- "capabilities created and listed with zero usage")
- fake_card = saddle_card(auth, "fake-saddle")
- check(fake_card["capabilities"] == [],
- "saddle card starts with no capability chips")
- # A task requiring 'gpu' waits while the only idle saddle lacks it;
- # a later task without requirements still dispatches (skip-and-continue).
- tasks_state = slot_public(auth, "/tasks", "tasks")
- task_id_tmp = create_queued(auth, {
- "project_id": "1",
- "title": "GPU task",
- "priority": "2",
- })
- gpu_task = task_id_tmp
- detail = task_state(auth, gpu_task)
- act(auth, detail["attach_capability"], {"capability_id": str(int(caps["gpu"]["id"]))})
- check(wait_for(lambda: [c["name"] for c in
- task_state(auth, gpu_task)["capabilities"]] == ["gpu"]
- or False, timeout=90, what="capability attached") is not None,
- "task page lists the required capability")
- detail = task_state(auth, gpu_task)
- # The only idle saddle lacks the tag, so the queue flags the wait
- # (read before any other task claims the idle saddle). The state is
- # stable until the any-saddle task below dispatches — poll for it
- # rather than sampling once (a single read under load once sampled
- # the row before the attach's pulse settled).
- check(wait_for(lambda: (lambda row: row["status"] == "queued"
- and row["capabilities"] == ["gpu"]
- and row.get("waiting_for_saddle") is True)(
- [t for t in slot_public(auth, "/tasks", "tasks")["tasks"]
- if int(t["id"]) == gpu_task][0]) or False,
- timeout=20, what="waiting flag") is not None,
- "queue row shows the waiting task with its tag and wait flag")
- task_id_tmp = create_queued(auth, {
- "project_id": "1",
- "title": "Any-saddle task",
- "priority": "3",
- })
- any_task = task_id_tmp
- check(wait_for(lambda: task_state(auth, any_task).get("status") != "queued" or False,
- timeout=30, what="uncapable task dispatches") is not None,
- "task without requirements dispatches past the waiting task")
- def capability_wait_logged():
- with open(os.path.join(WORK, "app.log")) as handle:
- app_log = handle.read()
- return "waits for a capable saddle" in app_log and "requires: gpu" in app_log
- check(wait_for(capability_wait_logged, timeout=90,
- what="capability wait log") is not None,
- "dispatcher logs the capability wait")
- # Attaching the tag to the saddle releases the task.
- fake_card = saddle_card(auth, "fake-saddle")
- act(auth, fake_card["attach_capability"], {"capability_id": str(int(caps["gpu"]["id"]))})
- fake_card = saddle_card(auth, "fake-saddle")
- check([c["name"] for c in fake_card["capabilities"]] == ["gpu"],
- "saddle card shows the attached chip")
- check(wait_task_done(auth, gpu_task, timeout=60) is not None,
- "task dispatches once the saddle carries the required tag")
- queue = [t for t in slot_public(auth, "/tasks", "tasks")["tasks"]
- if int(t["id"]) == gpu_task][0]
- check(queue.get("waiting_for_saddle") is not True,
- "wait flag clears once dispatched")
- # Multi-tag: gpu + bigmem required, only gpu carried → waits; adding
- # bigmem completes the superset. Both requirements ride the create
- # form (atomic — attaching them one at a time would race a tick
- # into dispatching on the partial set).
- task_id_tmp = create_queued(auth, {
- "project_id": "1",
- "title": "Big task",
- "priority": "2",
- "capability_id": [str(int(caps["gpu"]["id"])), str(int(caps["bigmem"]["id"]))],
- })
- big_task = task_id_tmp
- check(wait_for(lambda: ([t for t in slot_public(auth, "/tasks", "tasks")["tasks"]
- if int(t["id"]) == big_task][0].get("waiting_for_saddle")
- is True) or False, timeout=30, what="big task waits") is not None,
- "task waits when the saddle's tags are not a superset")
- fake_card = saddle_card(auth, "fake-saddle")
- act(auth, fake_card["attach_capability"], {"capability_id": str(int(caps["bigmem"]["id"]))})
- check(wait_task_done(auth, big_task, timeout=60) is not None,
- "task dispatches once the superset is complete")
- # Detach a requirement (on a draft — capabilities lock once a
- # worker has the job); delete a capability everywhere.
- act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
- "project_id": "1",
- "title": "Detach probe task",
- "priority": "4",
- "capability_id": [str(int(caps["gpu"]["id"])), str(int(caps["bigmem"]["id"]))],
- })
- detach_probe = int([t["id"] for t in slot_public(auth, "/tasks", "tasks")["tasks"]
- if t["title"] == "Detach probe task"][0])
- act(auth, [c for c in task_state(auth, detach_probe)["capabilities"]
- if c["name"] == "bigmem"][0]["detach"], {})
- check([c["name"] for c in task_state(auth, detach_probe)["capabilities"]] == ["gpu"],
- "detached requirement disappears from the task")
- bigmem_row = [c for c in slot_public(auth, "/saddles", "saddles")["capabilities"]
- if c["name"] == "bigmem"][0]
- act(auth, bigmem_row["delete"], {})
- saddles_state = slot_public(auth, "/saddles", "saddles")
- check(all(c["name"] != "bigmem" for c in saddles_state["capabilities"]),
- "deleted capability gone from the catalogue")
- check([c["name"] for c in task_state(auth, big_task)["capabilities"]] == ["gpu"],
- "deleting a capability drops its task requirements")
- check(all(c["name"] != "bigmem"
- for c in saddle_card(auth, "fake-saddle")["capabilities"]),
- "deleting a capability drops it from saddles")
- print("== B4: required capabilities on the create form ==")
- tasks_state = slot_public(auth, "/tasks", "tasks")
- gpu_option = [o for o in tasks_state.get("capability_options", [])
- if o["name"] == "gpu"][0]
- check(len(tasks_state.get("capability_options", [])) >= 1,
- "queue page offers capability options in the create form")
- conn = db()
- before = conn.execute("select count(*) from tasks").fetchone()[0]
- conn.close()
- act(auth, tasks_state["create"], {
- "project_id": "1",
- "title": "Bad cap task",
- "capability_id": [gpu_option["id"], "999"],
- })
- conn = db()
- after = conn.execute("select count(*) from tasks").fetchone()[0]
- conn.close()
- check(after == before, "unknown capability id rejects the create")
- task_id_tmp = create_queued(auth, {
- "project_id": "1",
- "title": "Cap-on-create task",
- "capability_id": [gpu_option["id"]],
- })
- cap_create = task_id_tmp
- detail = task_state(auth, cap_create)
- check([c["name"] for c in detail["capabilities"]] == ["gpu"],
- "capabilities from the create form land on the task")
- queue = [t for t in slot_public(auth, "/tasks", "tasks")["tasks"]
- if int(t["id"]) == cap_create][0]
- check(queue["capabilities"] == ["gpu"],
- "queue row shows the created task's requirements")
- # The waiting flag must hold it back until the capable saddle frees
- # up (the fake saddle still carries gpu from the routing checks).
- check(queue.get("waiting_for_saddle") is not True,
- "capable saddle available: no wait flag on the new task")
- # Drain barrier: the cap task must be CLAIMED before the flaky
- # arc arms fail_next (under parallel-shard load the dispatch tick
- # can lag task creation; an unclaimed predecessor would consume
- # the one-shot failure flag instead of the flaky task).
- check(wait_for(lambda: task_state(auth, cap_create).get("status")
- in ("working", "done", "failed") or False,
- timeout=60, what="cap task claimed") is not None,
- "the capable task is claimed before the flaky arc starts")
- print("== retry + resume ==")
- # A daemon-side failure fails the task with its explanation.
- fake.fail_next = True
- task_id_tmp = create_queued(auth, {
- "project_id": "1",
- "title": "Flaky task",
- "priority": "2",
- })
- flaky = task_id_tmp
- check(wait_for(lambda: task_state(auth, flaky).get("status") == "failed" or False,
- timeout=90, what="flaky task fails") is not None,
- "daemon failure fails the task")
- detail = task_state(auth, flaky)
- check("bash -n rejected" in (detail.get("summary") or ""),
- "failure summary shows the daemon's explanation")
- # Retry: the terminal summary dies with its attempt (plan 16 S4 —
- # it is NOT copied to the thread), the task requeues, history
- # stays, and the re-dispatch restores the workspace.
- comments_before = len(detail.get("comments", []))
- progress_before = len(detail.get("progress", []))
- act(auth, detail["retry"], {})
- detail = task_state(auth, flaky)
- check(detail["status"] == "queued" and (detail.get("summary") or "") == "",
- "retry requeues the task and clears the stale summary")
- check(not any("bash -n rejected" in c["body"] for c in detail["comments"]),
- "no summary→comment copy joins the thread (the terminal cache dies with its attempt, S4)")
- check(any(c["body"] == "Task retried by user" and c["author"] == "system"
- and c.get("is_system") for c in detail["comments"]),
- "retry lands its audit comment as a system row (never an agent row)")
- check(len(detail["comments"]) >= comments_before,
- "retry keeps the comment history")
- pushes_before = len([p for p in fake.start_payloads
- if int(p.get("task", {}).get("id") or 0) == flaky])
- check(wait_for(lambda: len([p for p in fake.start_payloads
- if int(p.get("task", {}).get("id") or 0) == flaky])
- > pushes_before or False, timeout=90,
- what="flaky task re-dispatched") is not None,
- "retried task dispatches again")
- # The resumed arc surfaces on the next /status read — in the
- # push world that is the operator's manual refresh.
- act(auth, saddle_card(auth, "fake-saddle")["refresh"], {})
- check(wait_task_done(auth, flaky, timeout=60) is not None,
- "retried task completes on the restored workspace")
- detail = task_state(auth, flaky)
- check(all("restored the session context" not in c["body"]
- for c in detail.get("comments", [])),
- "the session-restore stays OUT of the user-facing thread (U2: raw terminal only)")
- check(all("restored the session context" not in p["summary"]
- for p in detail.get("progress", [])),
- "the resume note stays OUT of the progress feed (F8: status is not progress)")
- # Plan 16 S4: terminal text has ONE representation — the failed
- # attempt's summary lived in tasks.summary alone (asserted above:
- # no agent comment), and a HUMAN comment lands as exactly one
- # user-authored row (the web is the single writer; the daemon's
- # terminal echo is raw-log only).
- act(auth, task_state(auth, flaky)["comment"],
- {"body": "The retry landed — nice recovery"})
- flaky_comments = task_state(auth, flaky)["comments"]
- check(len([c for c in flaky_comments
- if c["body"] == "The retry landed — nice recovery"]) == 1
- and all(c["author"] != "agent"
- for c in flaky_comments
- if c["body"] == "The retry landed — nice recovery"),
- "a human comment lands as exactly ONE user row (single writer, S4)")
- # Manual resume: a task stuck working on an offline saddle.
- task_id_tmp = create_queued(auth, {
- "project_id": "1",
- "title": "Stuck task",
- "priority": "2",
- })
- stuck = task_id_tmp
- check(wait_for(lambda: task_state(auth, stuck).get("status") == "working" or False,
- timeout=60, what="stuck task starts working") is not None,
- "stuck task reaches working")
- # Deterministic claim for the resume leg below: the round's end,
- # the refresh probe AND the dispatcher's wake for the requeued task
- # all trigger self-asks that could steal it — freeze every trigger
- # until the manual ask below has made the claim.
- fake.ask_on_terminal = False
- fake.ask_on_idle_status = False
- fake.ask_on_wake = False
- fake.settle_asks()
- conn = db()
- conn.execute("update saddles set status = 'offline' where name = 'fake-saddle'")
- conn.commit()
- conn.close()
- detail = task_state(auth, stuck)
- check(detail.get("resume_available") is True,
- "task page offers resume while its saddle is offline")
- # The resume POSTs task-update cancel to the daemon; the abort
- # handler fires an ungated re-ask that would race the manual claim
- # below — slow it past the manual ask (the knob the slow-ask
- # regression uses).
- fake.ask_delay = 5.0
- act(auth, detail["resume"], {})
- detail = task_state(auth, stuck)
- check(detail["status"] == "queued",
- "manual resume requeues the stuck task")
- check(any(c["body"] == "Saddle became unreachable; task requeued"
- and c["author"] == "system" and c.get("is_system")
- for c in detail["comments"]),
- "manual resume lands the interruption comment as a system row")
- check(all(s["status"] == "offline"
- for s in slot_public(auth, "/saddles", "saddles")["saddles"]),
- "saddle stays offline after the manual requeue")
- # Bringing the saddle back lets the requeued task resume and finish.
- fake.state = "idle"
- fake_card = saddle_card(auth, "fake-saddle")
- act(auth, fake_card["refresh"], {})
- check(any(s["status"] == "idle"
- for s in slot_public(auth, "/saddles", "saddles")["saddles"]
- if s["name"] == "fake-saddle"),
- "refreshed saddle returns idle")
- # The refreshed daemon is idle-awake: it asks (the real loop's
- # own ask; driven directly for determinism — every self-ask trigger
- # is frozen and the abort re-ask is slowed past it; joining that
- # delayed thread here would fire its ask first, so it is left to
- # sleep out the rest of the bundle).
- status, payload = fake.ask_now()
- fake.ask_on_idle_status = True
- fake.ask_on_terminal = True
- fake.ask_on_wake = True
- fake.ask_delay = 0.0
- check(status == 200, "the refreshed daemon's ask claims the requeued task")
- check(wait_task_done(auth, stuck, timeout=60) is not None,
- "requeued task resumes on the refreshed saddle and completes")
- # =============================================================================
- # Bundle: subtasks — the plan-12 task graph: plain-task parking (no
- # subtask events exist to resume it), the DELETED subtask machinery's
- # verbatim refusals, and the goal agent's task CLI in depth
- # (capabilities, ownership, the daemon's task-reading API, the
- # goal-run block answered from the task page).
- # =============================================================================
- def bundle_subtasks(ctx):
- auth, fake, fake_id, fake_token = ctx.auth, ctx.fake, ctx.fake_id, ctx.fake_token
- app = ctx.app
- # A hold tag nothing carries: goal tasks created with it stay
- # queued (a stable parked agent) until released at the settle.
- saddles_state = slot_public(auth, "/saddles", "saddles")
- act(auth, saddles_state["create_capability"], {"name": "gpu"})
- gpu_cap_id = [c["id"] for c in slot_public(auth, "/saddles", "saddles")["capabilities"]
- if c["name"] == "gpu"][0]
- def goal_state(goal_id):
- return slot_public(auth, "/goals/%d" % goal_id, "goal")
- def create_goal_row(fields):
- act(auth, slot_public(auth, "/goals", "goals")["create"], fields)
- rows = [g for g in slot_public(auth, "/goals", "goals")["goals"]
- if g["title"] == fields["title"]]
- return int(rows[0]["id"])
- print("== B7: plain-task parking stays put (no subtask events) ==")
- fake.suspend_on_title = "Suspend plain"
- pushes_before = len(fake.start_payloads)
- plain = create_queued(auth, {
- "project_id": "1",
- "title": "Suspend plain",
- "priority": "2",
- })
- check(wait_for(lambda: task_state(auth, plain).get("status") == "suspended"
- or False, timeout=60, what="plain task parks") is not None,
- "daemon 'suspended' state parks the task")
- detail = task_state(auth, plain)
- check(detail.get("display") == "parked",
- "a parked plain task displays as parked (%s)" % detail.get("display"))
- check(any(s["status"] == "idle"
- for s in slot_public(auth, "/saddles", "saddles")["saddles"]),
- "suspension frees the saddle")
- first_push = fake.start_payloads[pushes_before]
- check(first_push.get("kind") == "task" and first_push.get("creator") == "human"
- and first_push.get("mode") == "implement",
- "the /start payload carries the plan-12 identity triplet (kind/creator/mode)")
- check("subtasks" not in first_push and "parent" not in first_push,
- "the plan-12 payload drops the subtasks/parent members")
- # Other tasks finishing must NOT resume a parked plain task (the
- # subtask-event channel is gone; only goal events resume goal runs).
- conn = db()
- conn.execute("insert into tasks (project_id, title, status, priority, summary,"
- " completed_at, created_at) values (1, 'Unrelated finisher', 'done',"
- " 5, 'finished loudly', strftime('%s','now','+1 second'),"
- " strftime('%s','now'))")
- conn.commit()
- conn.close()
- check(quiet_window(quiet_seconds(),
- lambda: task_state(auth, plain)["status"] == "suspended",
- "plain park survives unrelated finishes"),
- "a parked plain task never resumes on other tasks' events")
- print("== the deleted machinery refuses, verbatim ==")
- status, body = saddle_api("POST", "/api/saddle/subtask", fake_token,
- {"parent_task_id": plain, "title": "ghost"})
- check(status == 404,
- "the /api/saddle/subtask callback route is gone (404)")
- # The poll-era ingest channel went with it (arch 19): the whole
- # /update surface 404s — subtask and plan verb arms included — and
- # the verdict channel (/poll) with it.
- conn = db()
- plain_session = conn.execute(
- "select id from sessions where task_id = ? and ended_at is null",
- (plain,)).fetchone()[0]
- conn.close()
- status, body = saddle_api("POST", "/api/saddle/%d/update" % fake_id, fake_token,
- {"type": "subtask", "session_id": plain_session,
- "data": {"title": "Ghost child"}})
- check(status == 404,
- "the /update ingest surface is gone (404, the subtask arm with it)")
- status, body = saddle_api("POST", "/api/saddle/%d/update" % fake_id, fake_token,
- {"type": "plan", "session_id": plain_session,
- "data": {"plan": "# nope"}})
- check(status == 404,
- "the task-level 'plan' update arm is gone with the channel (404)")
- # The daemon's task CLI: no parent/plan members; goal context instead.
- status, body = saddle_api("GET", "/api/saddle/task/%d" % plain, fake_token)
- check(status == 200 and "plan" not in body.get("task", {})
- and "parent_task_id" not in body.get("task", {})
- and "goal_id" in body.get("task", {}),
- "the task CLI detail drops parent/plan and carries goal_id")
- status, body = saddle_api("GET", "/api/saddle/tasks?project_id=1", fake_token)
- check(status == 200 and all("parent_task_id" not in t and "goal_id" in t
- for t in body.get("tasks", [])),
- "the task CLI list drops parent_task_id and carries goal_id")
- # Settle the parked plain task: nothing will resume it — cancel it.
- act(auth, task_state(auth, plain)["cancel"], {})
- check(wait_for(lambda: task_state(auth, plain)["status"] == "failed" or False,
- timeout=30, what="parked plain task cancelled") is not None,
- "the parked plain task cancels (the human cleanup path)")
- print("== the goal agent's task CLI in depth ==")
- goal = create_goal_row({"project_id": "1", "title": "Sequencing goal",
- "brief": "Fan tasks out with the CLI.",
- "branch_name": "goal/sequence"})
- fake.goal_script = [("plan", "# Plan\n\n1. Fan out")]
- act(auth, goal_state(goal)["start"], {})
- check(wait_for(lambda: goal_state(goal)["status"] == "plan_review" or False,
- timeout=60, what="sequencing goal plans") is not None,
- "the sequencing goal reaches plan review")
- fake.goal_script = [("suspend",)]
- act(auth, goal_state(goal)["plan_pending_block"]["answer_action"],
- {"answer": "Plan approved. Proceed with implementation."})
- conn = db()
- run_task = conn.execute(
- "select id from tasks where goal_id = ? and kind = 'goal_run'",
- (goal,)).fetchone()[0]
- agent_session = conn.execute(
- "select id from sessions where task_id = ? and ended_at is null",
- (run_task,)).fetchone()[0]
- conn.close()
- check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
- or False, timeout=60, what="sequencing agent parks") is not None,
- "the sequencing goal agent parks")
- # Create with capabilities: attached until assignment (the hold
- # keeps the task queued), mandate + mode stored, ephemeral branch.
- status, body = saddle_api("POST", "/api/saddle/task/create", fake_token, {
- "session_id": agent_session, "title": "Held worker",
- "mandate": "Do the held work.", "mode": "review",
- "capabilities": ["gpu"]})
- check(status == 200 and body.get("ok") is True,
- "task create with a capability tag succeeds")
- held = int(body["task_id"])
- detail = task_state(auth, held)
- check(detail["status"] == "draft" and detail["mode"] == "review",
- "the held worker drafts with its mode")
- check([c["name"] for c in detail.get("capabilities", [])] == ["gpu"],
- "the capability attaches to the created task")
- check(detail["task_branch"] == "task/%d-%d" % (goal, held)
- and detail["branch_from"] == "goal/sequence",
- "the CLI task's ephemeral branch pair is stored")
- status, body = saddle_api("POST", "/api/saddle/task/enqueue", fake_token, {
- "session_id": agent_session, "task_id": held})
- check(status == 200, "the held worker enqueues")
- check(quiet_window(3, lambda: task_state(auth, held)["status"] == "queued",
- "unsatisfiable capability holds dispatch"),
- "the unsatisfiable capability holds the queued worker (never assigned)")
- queue_row = [t for t in slot_public(auth, "/tasks", "tasks")["tasks"]
- if int(t["id"]) == held][0]
- check(queue_row.get("waiting_for_saddle") is True,
- "the queue annotates the held worker as waiting for a saddle")
- # The human edit path: goal tasks never carry enqueue-after (the
- # field is human-tasks-only — the update silently ignores it).
- act(auth, task_state(auth, held)["update"], {
- "priority": "4", "enqueue_after_task_id": str(held - 1)})
- check(task_state(auth, held).get("enqueue_after_task_id") in (None, 0, "0"),
- "a goal task ignores the auto-enqueue-after field on edit")
- # Ownership: the same saddle's goal agent cannot touch another
- # goal's task; a plain session is not a goal-agent identity.
- other_goal = create_goal_row({"project_id": "1", "title": "Other goal",
- "brief": "b", "branch_name": "goal/other",
- "priority": "9"})
- status, body = saddle_api("POST", "/api/saddle/task/create", fake_token,
- {"session_id": agent_session, "title": "x",
- "mandate": "m"})
- scope_task = int(body["task_id"])
- check(int(task_state(auth, scope_task)["goal_id"] or 0) == goal,
- "the callback scopes task creation to its own goal")
- status, body = saddle_api("POST", "/api/saddle/task/delete", fake_token,
- {"session_id": agent_session, "task_id": scope_task})
- check(status == 200, "the owned draft deletes")
- check(not any(t["id"] == scope_task
- for t in slot_public(auth, "/tasks", "tasks")["tasks"]),
- "the deleted task is gone from the queue")
- status, body = saddle_api("POST", "/api/saddle/task/enqueue", fake_token,
- {"session_id": agent_session, "task_id": plain})
- check(status == 400 and "does not belong to your goal" in (body.get("error") or ""),
- "the goal agent cannot enqueue a standalone task")
- # The daemon's task CLI detail carries the goal context.
- status, body = saddle_api("GET", "/api/saddle/task/%d" % held, fake_token)
- check(status == 200
- and int(body["task"].get("goal_id") or 0) == goal
- and body["task"].get("goal_title") == "Sequencing goal",
- "the task CLI detail names the owning goal")
- print("== the goal-run block answers from the task page (C1) ==")
- # A question block raised in goal context lands on the run task's
- # own row; the TASK page's answer action funnels into the one
- # goal answer path and resumes the agent with the verdict riding
- # the allocation (the goal page shares the path).
- fake.goal_script = [("question", "Which harness: pytest or vitest?",
- ["pytest", "vitest"])]
- conn = db()
- # Plan 15 C3: a terminal transition (a done task row newer than
- # the watermark) is the resume signal — no event rows exist.
- conn.execute("insert into tasks (project_id, goal_id, kind, title, status,"
- " priority, completed_at, created_at) values (1, ?, 'task',"
- " 'Nudge', 'done', 5,"
- " strftime('%s','now'),"
- " strftime('%s','now','-30 seconds'))", (goal,))
- conn.commit()
- conn.close()
- check(wait_for(lambda: task_state(auth, run_task)["status"] == "blocked"
- or False, timeout=60, what="goal block parks the run") is not None,
- "a goal question parks the run task on its own block row")
- run_detail = task_state(auth, run_task)
- question_block = [b for b in run_detail["blocks"]
- if b["block_type"] == "question"][0]
- check(question_block.get("question") == "Which harness: pytest or vitest?",
- "the run task's block carries the goal question verbatim")
- goal_block_ids = [int(b["id"]) for b in goal_state(goal)["blocks"]
- if b.get("block_type") == "question"]
- check(goal_block_ids == [int(question_block["id"])],
- "the goal page's card is the SAME row (no dual-row pair)")
- act(auth, question_block["answer_action"], {"answer": "pytest"})
- fake.goal_script = [("suspend",)]
- check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
- or False, timeout=60, what="answered block resumes the agent")
- is not None,
- "the answered block resumes the goal agent")
- answered = [b for b in task_state(auth, run_task)["blocks"]
- if b["block_type"] == "question"][0]
- check(answered.get("answer") == "pytest",
- "the verdict is stored on the single block row")
- print("== settle: release the hold and let the shard drain ==")
- # Drop the hold tag so the held worker can dispatch and complete;
- # the parked agent resumes on its completion, re-parks (script),
- # and the shard shuts down with an empty queue.
- act(auth, task_state(auth, held)["capabilities"][0]["detach"], {})
- check(wait_task_done(auth, held, timeout=90) is not None,
- "the released held worker completes")
- check(wait_for(lambda: any(e["kind"] == "task-done" and int(e["task_id"]) == held
- for e in goal_state(goal)["events"]) or False,
- timeout=30, what="narrative hears the held worker") is not None,
- "the narrative records the held worker's completion")
- act(auth, goal_state(goal)["cancel"], {})
- check(wait_for(lambda: goal_state(goal)["status"] == "cancelled" or False,
- timeout=30, what="sequencing goal cancelled") is not None,
- "the sequencing goal cancels (the shard's cleanup path)")
- # =============================================================================
- # Bundle: lineage — the plan-12 goal lineage: goal context on worker
- # dispatches (read-only reference + ephemeral branch), the daemon's
- # task-reading API (goal members, no parent/plan), and the goal
- # agent's workspace affinity while parked.
- # =============================================================================
- def bundle_lineage(ctx):
- auth, fake, fake_id, fake_token = ctx.auth, ctx.fake, ctx.fake_id, ctx.fake_token
- app = ctx.app
- r4_token = fake_token
- # gpu exists only as a hold (no saddle carries it): the held goal
- # worker stays queued until the release loop lifts it.
- saddles_state = slot_public(auth, "/saddles", "saddles")
- act(auth, saddles_state["create_capability"], {"name": "gpu"})
- def goal_state(goal_id):
- return slot_public(auth, "/goals/%d" % goal_id, "goal")
- def create_goal_row(fields):
- act(auth, slot_public(auth, "/goals", "goals")["create"], fields)
- rows = [g for g in slot_public(auth, "/goals", "goals")["goals"]
- if g["title"] == fields["title"]]
- return int(rows[0]["id"])
- goal = create_goal_row({"project_id": "1", "title": "Lineage goal",
- "brief": "Carry the goal lineage.",
- "branch_name": "goal/lineage"})
- fake.goal_script = [("plan", "# Lineage plan\n\n1. Fan out\n2. Verify")]
- act(auth, goal_state(goal)["start"], {})
- check(wait_for(lambda: goal_state(goal)["status"] == "plan_review" or False,
- timeout=60, what="lineage goal plans") is not None,
- "the lineage goal reaches plan review")
- fake.goal_script = [("suspend",)]
- act(auth, goal_state(goal)["plan_pending_block"]["answer_action"],
- {"answer": "Plan approved. Proceed with implementation."})
- conn = db()
- auto_parent = conn.execute(
- "select id from tasks where goal_id = ? and kind = 'goal_run'",
- (goal,)).fetchone()[0]
- agent_session = conn.execute(
- "select id from sessions where task_id = ? and ended_at is null",
- (auto_parent,)).fetchone()[0]
- conn.close()
- check(wait_for(lambda: task_state(auth, auto_parent)["status"] == "suspended"
- or False, timeout=60, what="lineage goal agent parks") is not None,
- "the lineage goal agent parks")
- # Two workers under the goal: one free, one held by the gpu tag.
- status, body = saddle_api("POST", "/api/saddle/task/create", r4_token, {
- "session_id": agent_session, "title": "Auto child",
- "mandate": "build the widget", "mode": "implement"})
- auto_child = int(body["task_id"])
- status, body = saddle_api("POST", "/api/saddle/task/create", r4_token, {
- "session_id": agent_session, "title": "Auto grandchild",
- "mandate": "verify the widget", "mode": "test",
- "capabilities": ["gpu"]})
- auto_grand = int(body["task_id"])
- status, body = saddle_api("POST", "/api/saddle/task/create", r4_token, {
- "session_id": agent_session, "title": "Wire the flange",
- "description": "torque to spec"})
- check(status == 400 and "mandate is required" in (body.get("error") or ""),
- "task create without a mandate errors verbatim (%s)" % body.get("error"))
- status, body = saddle_api("POST", "/api/saddle/task/create", r4_token, {
- "session_id": agent_session, "title": "Wire the flange",
- "mandate": "torque to spec"})
- flange_task = int(body["task_id"])
- for held in (auto_child, auto_grand, flange_task):
- saddle_api("POST", "/api/saddle/task/enqueue", r4_token,
- {"session_id": agent_session, "task_id": held})
- check(wait_for(lambda: task_state(auth, auto_child)["status"] in
- ("queued", "assigned", "working", "done") or False,
- timeout=30, what="free goal worker dispatches") is not None,
- "the free goal worker dispatches")
- check(quiet_window(3, lambda: task_state(auth, auto_grand)["status"] == "queued",
- "held goal worker stays queued"),
- "the gpu-held goal worker stays queued")
- # A goal worker's dispatch carries the read-only goal context.
- lineage_push = [p for p in fake.start_payloads
- if int(p.get("task", {}).get("id") or 0) == auto_child][-1]
- goal_ctx = lineage_push.get("goal") or {}
- check(int(goal_ctx.get("id") or 0) == goal
- and goal_ctx.get("branch") == "goal/lineage"
- and "Carry the goal lineage." in goal_ctx.get("brief", "")
- and "# Lineage plan" in goal_ctx.get("plan", ""),
- "the worker dispatch carries the goal context (brief + approved plan)")
- check(lineage_push.get("ephemeral_branch") == "task/%d-%d" % (goal, auto_child)
- and lineage_push.get("kind") == "task"
- and lineage_push.get("mode") == "implement"
- and lineage_push.get("creator") == "goal",
- "the worker dispatch carries the ephemeral branch + identity triplet")
- check("parent" not in lineage_push and "subtasks" not in lineage_push,
- "the plan-12 payload drops the parent/subtasks members")
- print("== R5 B2: task-reading API ==")
- status, _ = saddle_api("GET", "/api/saddle/tasks", None)
- check(status == 401, "tasks endpoint rejects a missing token")
- status, _ = saddle_api("GET", "/api/saddle/task/%d" % auto_child, None)
- check(status == 401, "task detail endpoint rejects a missing token")
- # Fixture: a second project whose tasks all carry the "Diff " title
- # prefix for the project_id filter checks — created first so the
- # unfiltered list has the population the count check expects.
- state = slot_public(auth, "/projects", "projects")
- act(auth, state["create"], {"name": "diff-fabrica",
- "git_url": "ssh://fabrica.unitatem.net:222/clanker/clanker-saddle.git"})
- conn = db()
- other_project = int([p["id"] for p in slot_public(auth, "/projects", "projects")["projects"]
- if p["name"] == "diff-fabrica"][0])
- for suffix in ("fabrica", "scp"):
- conn.execute("insert into tasks (project_id, title, status, priority, created_at)"
- " values (?, ?, 'done', 5, strftime('%s','now'))",
- (other_project, "Diff %s" % suffix))
- conn.commit()
- conn.close()
- status, body = saddle_api("GET", "/api/saddle/tasks", r4_token)
- check(status == 200 and len(body.get("tasks", [])) > 5,
- "tasks endpoint lists tasks without filters")
- row = [t for t in body["tasks"] if int(t["id"]) == auto_child][0]
- check(row["title"] == "Auto child" and int(row["goal_id"] or 0) == goal
- and row["status"] in ("done", "queued", "working"),
- "compact rows carry id/title/status/goal_id")
- status, body = saddle_api("GET", "/api/saddle/tasks?project_id=1", r4_token)
- check(all(True for _ in [1]) # project 1 dominates the data set
- and not any(int(t["id"]) == 0 for t in body["tasks"]),
- "project_id filter accepted")
- status, body = saddle_api("GET",
- "/api/saddle/tasks?project_id=%d" % other_project, r4_token)
- check(all(t["title"].startswith("Diff") for t in body["tasks"]),
- "other project lists only its own tasks")
- status, body = saddle_api("GET", "/api/saddle/tasks?q=FLANGE", r4_token)
- titles = [t["title"] for t in body["tasks"]]
- check("Wire the flange" in titles,
- "q matches titles case-insensitively")
- status, body = saddle_api("GET", "/api/saddle/tasks?q=torque%20to%20spec", r4_token)
- check(any(t["title"] == "Wire the flange" for t in body["tasks"]),
- "q matches mandate text")
- status, body = saddle_api("GET", "/api/saddle/task/%d" % auto_child, r4_token)
- task_view = body.get("task", {})
- check(status == 200 and int(task_view.get("id") or 0) == auto_child
- and task_view.get("goal_title") == "Lineage goal"
- and int(task_view.get("goal_id") or 0) == goal,
- "single-task view joins the goal title")
- check("status" in task_view and "description" in task_view
- and "summary" in task_view,
- "single-task view carries the full detail fields")
- check("plan" not in task_view and "parent_task_id" not in task_view,
- "the single-task view drops the deleted parent/plan members")
- status, body = saddle_api("GET", "/api/saddle/task/99999", r4_token)
- check(status == 404, "unknown task id returns 404")
- print("== settle: released workers resume the parked agent (coalesced) ==")
- # Release the held workers: their completions wake the parked goal
- # agent (affinity — the same fake hosts it) with the batched
- # notice; the free worker already completed.
- check(wait_task_done(auth, auto_child, timeout=90) is not None,
- "the free worker settles first")
- def agent_push_count():
- return len([p for p in fake.goal_assignments
- if int(p.get("task", {}).get("id") or 0) == auto_parent])
- before_pushes = agent_push_count()
- for held_task in (auto_grand, flange_task):
- caps = task_state(auth, held_task).get("capabilities") or []
- if caps:
- act(auth, caps[0]["detach"], {})
- check(wait_task_done(auth, held_task, timeout=90) is not None,
- "released worker settles (%d)" % held_task)
- check(wait_for(lambda: agent_push_count() > before_pushes or False,
- timeout=90, what="goal agent resumes") is not None,
- "the workers' completions resume the parked goal agent")
- notice = ""
- for p in fake.goal_assignments:
- if int(p.get("task", {}).get("id") or 0) == auto_parent:
- notice = p.get("resume_notice") or ""
- check("Auto child" in notice and "Auto grandchild" in notice
- and "Wire the flange" in notice,
- "the coalesced resume notice covers every plan task")
- check("New completion reports" in notice and "fake daemon finished" in notice,
- "the notice carries the new completion reports")
- act(auth, goal_state(goal)["cancel"], {})
- check(wait_for(lambda: goal_state(goal)["status"] == "cancelled" or False,
- timeout=30, what="lineage goal cancelled") is not None,
- "the lineage goal cancels (drain)")
- # =============================================================================
- # Bundle: allocation — the ask/wake/allocate machinery: concurrent ask
- # race, token revocation, workspace affinity, the lost-wake guard, the
- # slow-ask regression, assignment capability context + the tag
- # catalogue, and the pinned-blocks/activity dashboard.
- # =============================================================================
- def bundle_allocation(ctx):
- auth, fake, fake_id, fake_token = ctx.auth, ctx.fake, ctx.fake_id, ctx.fake_token
- app = ctx.app
- r4_token = fake_token
- # gpu rides the primary fake from the start (the routing bundle's
- # early attach, replayed): its attach row precedes lostwake's, so
- # the assignment's saddle_tags order matches the assertion in the
- # capability-context section below.
- saddles_state = slot_public(auth, "/saddles", "saddles")
- act(auth, saddles_state["create_capability"], {"name": "gpu"})
- act(auth, saddle_card(auth, "fake-saddle")["attach_capability"],
- {"capability_id": str(int([c["id"] for c in
- slot_public(auth, "/saddles", "saddles")["capabilities"]
- if c["name"] == "gpu"][0]))})
- print("== phase 1: ask/allocation — token auth + concurrent race ==")
- # Plan 10 phase 1. Registration mints per-saddle tokens; the
- # ask endpoint authenticates by them; concurrent asks race to
- # exactly one winner through the allocation transaction.
- status, _ = saddle_api("POST", "/api/saddle/next-task", None)
- check(status == 401, "next-task rejects a missing token")
- status, _ = saddle_api("POST", "/api/saddle/next-task", "saddle-bogus-token")
- check(status == 401, "next-task rejects a wrong token")
- status, body = saddle_api("POST", "/api/saddle/next-task", fake_token)
- check(status == 204, "next-task answers 204 (nothing offerable — daemon sleeps)")
- # Two more racers: real daemons on their own ports; fake2 reports
- # its ssh key at /register so the key lands on the row directly.
- fake2 = FakeSaddle()
- fake2.ssh_public_key = "ssh-ed25519 AAAAFake2RacerKey saddle-2@fedora"
- fake2.serve(port=free_port())
- fake3 = FakeSaddle()
- fake3.serve(port=free_port())
- saddles_state = slot_public(auth, "/saddles", "saddles")
- act(auth, saddles_state["register"], {"name": "racer-two",
- "host": "127.0.0.1:%d" % fake2.port})
- act(auth, saddles_state["register"], {"name": "racer-three",
- "host": "127.0.0.1:%d" % fake3.port})
- check(wait_for(lambda: len(fake2.register_log) > 0 and len(fake3.register_log) > 0
- or False, timeout=15, what="racers registered") is not None,
- "both racers received /register")
- check(wait_for(lambda: "AAAAFake2RacerKey" in
- [s for s in slot_public(auth, "/saddles", "saddles")["saddles"]
- if s["name"] == "racer-two"][0].get("ssh_public_key", "") or False,
- timeout=15, what="register-reported key") is not None,
- "the ssh key reported at /register lands on the saddle row")
- racer_cards = {}
- for name, daemon in (("fake-saddle", fake), ("racer-two", fake2), ("racer-three", fake3)):
- card = [s for s in slot_public(auth, "/saddles", "saddles")["saddles"]
- if s["name"] == name][0]
- racer_cards[daemon] = {"id": int(card["id"]), "token": card["token"]}
- # Revocation: a registered saddle's token works until the saddle
- # is removed, then nothing authorises its calls.
- fake4 = FakeSaddle()
- fake4.ask_on_register = False # a pre-flip daemon: never asks
- fake4.serve(port=free_port())
- saddles_state = slot_public(auth, "/saddles", "saddles")
- act(auth, saddles_state["register"], {"name": "old-daemon",
- "host": "127.0.0.1:%d" % fake4.port})
- old_card = [s for s in slot_public(auth, "/saddles", "saddles")["saddles"]
- if s["name"] == "old-daemon"][0]
- status, _ = saddle_api("POST", "/api/saddle/next-task", old_card["token"])
- check(status in (200, 204),
- "the old daemon's fresh token authenticates next-task")
- status, _ = saddle_api("POST", "/api/saddle/note", old_card["token"],
- {"project": "demo", "body": "from the old daemon"})
- check(status == 200, "note accepts any registered saddle's token")
- act(auth, old_card["unregister"], {})
- status, _ = saddle_api("POST", "/api/saddle/next-task", old_card["token"])
- check(status == 401, "removing the saddle revokes its token")
- status, _ = saddle_api("POST", "/api/saddle/note", old_card["token"],
- {"project": "demo", "body": "should fail"})
- check(status == 401, "revocation covers the note endpoint too")
- # Deterministic race: settle the queue first (this shard runs the
- # race before any parked work exists, so only in-flight asks need
- # draining), freeze every daemon's self-ask triggers, put all three
- # to sleep (204), then fire one task worth of concurrent asks.
- for daemon in (fake, fake2, fake3):
- daemon.ask_on_register = False
- daemon.ask_on_wake = False
- daemon.ask_on_terminal = False
- daemon.ask_on_idle_status = False
- daemon.settle_asks() # converted sleep: in-flight asks land (204)
- def queued_count():
- conn = db()
- try:
- return conn.execute(
- "select count(*) from tasks where status = 'queued'").fetchone()[0]
- finally:
- conn.close()
- check(wait_for(lambda: queued_count() == 0 or False,
- timeout=60, what="queue settles before the race") is not None,
- "the queue settles before the race")
- sleep_statuses = [daemon.ask_now()[0] for daemon in (fake, fake2, fake3)]
- check(sleep_statuses == [204, 204, 204],
- "all three racers sleep (204) before the task exists")
- task_id_tmp = create_queued(auth, {
- "project_id": "1",
- "title": "Race task",
- "priority": "2",
- })
- race_task = task_id_tmp
- race_results = []
- def race_ask(daemon):
- # A real racing daemon accepts the assignment it wins — the
- # manual ask path does exactly that on a 200.
- race_results.append(daemon.ask_now())
- threads = [threading.Thread(target=race_ask, args=(daemon,))
- for daemon in (fake, fake2, fake3)]
- for thread in threads:
- thread.start()
- for thread in threads:
- thread.join()
- race_statuses = sorted(r[0] for r in race_results)
- check(race_statuses == [200, 204, 204],
- "concurrent asks race to exactly one winner (%s)" % race_statuses)
- winner = [r for r in race_results if r[0] == 200][0][1]
- check(int(winner["task"]["id"]) == race_task, "the winner's payload carries the task")
- check(int(winner["session_id"]) > 0, "the assignment carries the session id")
- conn = db()
- race_sessions = conn.execute(
- "select count(*) from sessions where task_id = ?", (race_task,)).fetchone()[0]
- conn.close()
- check(race_sessions == 1,
- "exactly one session row — the transaction serialised the claims (%d)"
- % race_sessions)
- check(wait_task_done(auth, race_task, timeout=60) is not None,
- "the raced task completes on the winning daemon")
- for daemon in (fake, fake2, fake3):
- daemon.ask_on_register = True
- daemon.ask_on_wake = True
- daemon.ask_on_terminal = True
- daemon.ask_on_idle_status = True
- # Re-establish sleeping explicitly: the winner's post-race
- # re-ask timing is not deterministic, and the affinity section
- # below needs every racer provably asleep. Any stray offerable
- # drains into these asks first.
- def all_asleep():
- return all(daemon.ask_now()[0] == 204 for daemon in (fake, fake2, fake3))
- check(wait_for(lambda: all_asleep() or False, timeout=30,
- what="racers re-sleep after the race") is not None,
- "all racers re-sleep after the race")
- print("== phase 1: workspace affinity pins parked tasks ==")
- # A suspended parent parked on racer-two may be resumed ONLY by
- # racer-two: a tag-capable non-affine saddle asking gets 204, and
- # the wake targets the affine saddle. Only racer-two carries the
- # tag at dispatch time (the race's winner is nondeterministic, so
- # the park must not depend on which racers are free); racer-three
- # gains it afterwards to prove the pinning is by workspace, not
- # tags.
- saddles_state = slot_public(auth, "/saddles", "saddles")
- act(auth, saddles_state["create_capability"], {"name": "affinity-tag"})
- affinity_cap = int([c["id"] for c in slot_public(auth, "/saddles", "saddles")["capabilities"]
- if c["name"] == "affinity-tag"][0])
- racer2_card = [s for s in slot_public(auth, "/saddles", "saddles")["saddles"]
- if s["name"] == "racer-two"][0]
- racer3_card = [s for s in slot_public(auth, "/saddles", "saddles")["saddles"]
- if s["name"] == "racer-three"][0]
- act(auth, racer2_card["attach_capability"], {"capability_id": str(affinity_cap)})
- # Plan 12: the parked workspace is the goal agent's run task. The
- # other racers sleep (frozen ask triggers), so racer-two claims
- # the goal run and parks it; the goal-task event then resumes the
- # agent AFFINE to racer-two — racer-three (tag-capable or not)
- # gets 204, the dispatcher's wake targets racer-two.
- fake.ask_on_wake = False
- fake.ask_on_idle_status = False
- fake.settle_asks()
- fake3.ask_on_wake = False
- fake3.ask_on_idle_status = False
- fake3.settle_asks()
- act(auth, slot_public(auth, "/goals", "goals")["create"], {
- "project_id": "1", "title": "Affinity goal",
- "brief": "Parks on racer-two.", "branch_name": "goal/affinity"})
- conn = db()
- affinity_goal = conn.execute(
- "select id from goals where title = 'Affinity goal'").fetchone()[0]
- conn.close()
- fake2.goal_script = [("plan", "# Affinity plan\n\n1. Park")]
- act(auth, slot_public(auth, "/goals/%d" % affinity_goal, "goal")["start"], {})
- check(wait_for(lambda: slot_public(auth, "/goals/%d" % affinity_goal,
- "goal")["status"] == "plan_review" or False,
- timeout=60, what="affinity goal plans") is not None,
- "the affinity goal reaches plan review (racer-two hosting)")
- fake2.goal_script = [("suspend",)]
- # Freeze racer-two's ask triggers across the approval too (the
- # later wake measurement already does): the mirror-sync fix makes
- # the run resume-offerable the moment its goal row carries the
- # verdict, so a wake-triggered background ask would race this
- # manual ask_now for the claim.
- fake2.ask_on_wake = False
- fake2.ask_on_idle_status = False
- fake2.settle_asks()
- act(auth, slot_public(auth, "/goals/%d" % affinity_goal, "goal")
- ["plan_pending_block"]["answer_action"],
- {"answer": "Plan approved. Proceed with implementation."})
- conn = db()
- affinity_parent = conn.execute(
- "select id from tasks where goal_id = ? and kind = 'goal_run'",
- (affinity_goal,)).fetchone()[0]
- agent_session = conn.execute(
- "select id from sessions where task_id = ? and ended_at is null",
- (affinity_parent,)).fetchone()[0]
- conn.close()
- status, resume_payload = fake2.ask_now()
- check(status == 200 and int(resume_payload["task"]["id"]) == affinity_parent,
- "the approved plan resumes the agent on racer-two (its workspace)")
- check(wait_for(lambda: task_state(auth, affinity_parent).get("status") == "suspended"
- or False, timeout=60, what="affinity agent parks") is not None,
- "the affinity goal agent parks on racer-two")
- conn = db()
- affinity_session = conn.execute(
- "select saddle_id from sessions where task_id = ? and ended_at is null",
- (affinity_parent,)).fetchone()[0]
- conn.close()
- check(affinity_session == int(racer2_card["id"]),
- "the parked workspace sits on racer-two")
- # Now racer-three carries the tag too — the pinning is by
- # workspace, not tags. With nothing due, a non-affine ask gets 204.
- act(auth, racer3_card["attach_capability"], {"capability_id": str(affinity_cap)})
- status, _ = fake3.ask_now()
- check(status == 204,
- "a tag-capable non-affine saddle is refused the parked agent (204)")
- # A goal task completing makes the parked agent resume-offerable —
- # affine to racer-two only. The other racers wake back up (they
- # host the worker; their claims of the parked agent get refused as
- # non-affine), while racer-two's ask triggers stay frozen so the
- # wake measurement stays clean. A wake may have hit the frozen
- # racers while the goal run queued (the lost-wake guard marks them
- # offline): the refresh probe brings them back before the worker.
- fake.ask_on_wake = True
- fake.ask_on_idle_status = True
- fake3.ask_on_wake = True
- fake3.ask_on_idle_status = True
- for racer_name in ("fake-saddle", "racer-three"):
- act(auth, saddle_card(auth, racer_name)["refresh"], {})
- check(wait_for(lambda name=racer_name: [s for s in
- slot_public(auth, "/saddles", "saddles")["saddles"]
- if s["name"] == name][0]["status"] == "idle"
- or False, timeout=15, what="racer re-onlined") is not None,
- "%s re-onlined after its frozen-window wake" % racer_name)
- fake2.ask_on_wake = False
- fake2.ask_on_idle_status = False
- wakes_before_2 = len(fake2.wake_log)
- fake2.settle_asks() # converted sleep: park-time ask lands (204)
- status, body = saddle_api("POST", "/api/saddle/task/create", fake2.token, {
- "session_id": agent_session, "title": "Affinity child",
- "mandate": "finish the affinity fixture"})
- check(status == 200, "the parked agent fans its worker out via the CLI")
- affinity_child = int(body["task_id"])
- saddle_api("POST", "/api/saddle/task/enqueue", fake2.token,
- {"session_id": agent_session, "task_id": affinity_child})
- check(wait_task_done(auth, affinity_child, timeout=90) is not None,
- "the affinity worker completes (on a non-affine racer)")
- check(wait_for(lambda: len(fake2.wake_log) > wakes_before_2 or False,
- timeout=90, what="affine wake") is not None,
- "the dispatcher's wake targets the affine saddle")
- status, resume_payload = fake2.ask_now()
- fake2.ask_on_wake = True # restored whatever the claim returned
- fake2.ask_on_idle_status = True
- check(status == 200 and int(resume_payload["task"]["id"]) == affinity_parent,
- "the affine saddle's ask claims the parked agent")
- check("Affinity child" in (resume_payload.get("resume_notice") or "")
- and "fake daemon finished" in (resume_payload.get("resume_notice") or ""),
- "the resume assignment carries the goal's batched report notice")
- check(wait_for(lambda: task_state(auth, affinity_parent).get("status")
- == "suspended" or False,
- timeout=60, what="affinity agent re-parks") is not None,
- "the resumed agent re-parks (script)")
- act(auth, slot_public(auth, "/goals/%d" % affinity_goal, "goal")["cancel"], {})
- check(wait_for(lambda: slot_public(auth, "/goals/%d" % affinity_goal,
- "goal")["status"] == "cancelled" or False,
- timeout=30, what="affinity goal cancelled") is not None,
- "the affinity goal cancels (drain)")
- print("== phase 1: lost wake -> offline + re-wake of the next capable ==")
- # racer-three sleeps through wakes (a daemon that died after its
- # last 204): the guard window (SADDLE_LOST_WAKE_S=2) passes with
- # no ask, the saddle goes offline, and the work moves to the
- # next capable sleeping saddle.
- fake3.ask_on_wake = False
- fake3.ask_on_idle_status = False # sleeps through everything
- saddles_state = slot_public(auth, "/saddles", "saddles")
- act(auth, saddles_state["create_capability"], {"name": "lostwake"})
- lostwake_cap = int([c["id"] for c in slot_public(auth, "/saddles", "saddles")["capabilities"]
- if c["name"] == "lostwake"][0])
- racer3_card = [s for s in slot_public(auth, "/saddles", "saddles")["saddles"]
- if s["name"] == "racer-three"][0]
- act(auth, racer3_card["attach_capability"], {"capability_id": str(lostwake_cap)})
- # Racer-three must be asleep to be woken: park it with a manual
- # 204 ask (nothing offerable for it — the task below is not
- # created yet).
- status, _ = fake3.ask_now()
- check(status == 204, "racer-three sleeps before the lost-wake run")
- task_id_tmp = create_queued(auth, {
- "project_id": "1",
- "title": "Lost wake task",
- "priority": "2",
- "capability_id": [str(lostwake_cap)],
- })
- lost_task = task_id_tmp
- check(wait_for(lambda: len(fake3.wake_log) > 0 or False,
- timeout=30, what="wake sent to racer-three") is not None,
- "the only capable sleeping saddle is woken")
- check(wait_for(lambda: [s for s in slot_public(auth, "/saddles", "saddles")["saddles"]
- if s["name"] == "racer-three"][0]["status"] == "offline"
- or False, timeout=30, what="lost-wake -> offline") is not None,
- "no ask after the wake: the saddle goes offline (lost-wake guard)")
- check(task_state(auth, lost_task)["status"] == "queued",
- "the task is untouched while its wake was pending")
- # Re-wake the next capable: the healthy saddle gains the tag and
- # picks the work up via its own wake -> ask -> allocate roundtrip.
- fake_card = saddle_card(auth, "fake-saddle")
- act(auth, fake_card["attach_capability"], {"capability_id": str(lostwake_cap)})
- wakes_before = len(fake.wake_log)
- check(wait_for(lambda: len(fake.wake_log) > wakes_before or False,
- timeout=30, what="re-wake of the next capable") is not None,
- "the next capable sleeping saddle is woken for the task")
- check(wait_task_done(auth, lost_task, timeout=60) is not None,
- "wake -> ask -> allocate: the task completes on the healthy saddle")
- check(wait_for(lambda: any(int(p.get("task", {}).get("id") or 0) == lost_task
- for p in fake.start_payloads) or False,
- timeout=10, what="lost task assignment captured") is not None,
- "the re-woken saddle's ask produced the assignment")
- fake3.ask_on_wake = True
- fake3.ask_on_idle_status = True
- print("== phase 3: capability context, catalogue, subtask tags ==")
- # Assignment capability context (plan 10 phase 3): every
- # assignment carries {saddle_tags: the allocated saddle's tags,
- # fleet_tags: every tag name known across the fleet} — the
- # daemon renders it into its system prompt. fake-saddle carries
- # gpu + lostwake; the catalogue is affinity-tag, gpu, lostwake.
- fake2.ask_on_register = False
- fake2.ask_on_wake = False
- fake2.ask_on_terminal = False
- fake2.ask_on_idle_status = False
- fake2.settle_asks() # converted sleep: in-flight trigger asks land
- task_id_tmp = create_queued(auth, {
- "project_id": "1",
- "title": "Capabilities context",
- "priority": "2",
- })
- cap_task = task_id_tmp
- check(wait_for(lambda: any(int(p.get("task", {}).get("id") or 0) == cap_task
- for p in fake.start_payloads) or False,
- timeout=60, what="capabilities-context task dispatched") is not None,
- "the capabilities-context task dispatches")
- cap_payload = [p for p in fake.start_payloads
- if int(p.get("task", {}).get("id") or 0) == cap_task][-1]
- context = cap_payload.get("capabilities") or {}
- check(context.get("saddle_tags") == ["gpu", "lostwake"],
- "assignment saddle_tags = the allocated saddle's tags (%s)"
- % context.get("saddle_tags"))
- check(context.get("fleet_tags") == ["affinity-tag", "gpu", "lostwake"],
- "assignment fleet_tags = every tag name known across the fleet (%s)"
- % context.get("fleet_tags"))
- check(wait_task_done(auth, cap_task, timeout=60) is not None,
- "capabilities-context task completes")
- # GET /api/saddle/capabilities feeds the daemon's `saddle
- # capabilities` CLI: per-saddle-token auth, tag order by name,
- # each tag with the saddles carrying it (name order).
- status, _ = saddle_api("GET", "/api/saddle/capabilities", None)
- check(status == 401, "capabilities endpoint rejects a missing token")
- status, body = saddle_api("GET", "/api/saddle/capabilities", "saddle-bogus")
- check(status == 401, "capabilities endpoint rejects a wrong token")
- status, body = saddle_api("GET", "/api/saddle/capabilities", fake_token)
- catalogue = {t["name"]: t["saddles"] for t in body.get("tags", [])}
- check(status == 200 and body.get("ok") is True
- and [t["name"] for t in body.get("tags", [])]
- == ["affinity-tag", "gpu", "lostwake"],
- "catalogue lists every tag by name")
- check(catalogue.get("gpu") == ["fake-saddle"]
- and catalogue.get("affinity-tag") == ["racer-three", "racer-two"]
- and catalogue.get("lostwake") == ["fake-saddle", "racer-three"],
- "each tag names its carrying saddles in name order (%s)" % catalogue)
- # Task CLI capabilities (`task create --capability`): validated
- # against the operator tag set; unknown tags reject with the valid
- # list in the error (the agent reads it verbatim in the CLI
- # output). A goal agent's parked session hosts the calls.
- fake.suspend_on_title = "Tag host"
- tag_host = create_queued(auth, {
- "project_id": "1", "title": "Tag host", "priority": "3"})
- check(wait_for(lambda: task_state(auth, tag_host)["status"] == "suspended"
- or False, timeout=60, what="tag host parks") is not None,
- "a parked plain task hosts nothing (no goal scope)")
- # The goal callbacks need a real goal agent: reuse the sequencing
- # goal pattern — a fresh parked agent under this shard.
- act(auth, slot_public(auth, "/goals", "goals")["create"], {
- "project_id": "1", "title": "Tag goal", "brief": "b",
- "branch_name": "goal/tags"})
- conn = db()
- tag_goal = conn.execute("select id from goals where title = 'Tag goal'").fetchone()[0]
- conn.close()
- fake.goal_script = [("suspend",)]
- act(auth, slot_public(auth, "/goals/%d" % tag_goal, "goal")["start"], {})
- conn = db()
- tag_run = conn.execute(
- "select id from tasks where goal_id = ? and kind = 'goal_run'",
- (tag_goal,)).fetchone()[0]
- conn.close()
- check(wait_for(lambda: task_state(auth, tag_run)["status"] == "suspended"
- or False, timeout=60, what="tag goal agent parks") is not None,
- "the tag goal agent parks")
- conn = db()
- tag_session = conn.execute(
- "select id from sessions where task_id = ? and ended_at is null",
- (tag_run,)).fetchone()[0]
- conn.close()
- conn = db()
- tag_host_saddle = conn.execute(
- "select saddle_id from sessions where id = ?", (tag_session,)).fetchone()[0]
- conn.close()
- racer2_card_id = int([c for c in slot_public(auth, "/saddles", "saddles")["saddles"]
- if c["name"] == "racer-two"][0]["id"])
- racer3_card_id = int([c for c in slot_public(auth, "/saddles", "saddles")["saddles"]
- if c["name"] == "racer-three"][0]["id"])
- host_daemon = {int(fake_id): fake, racer2_card_id: fake2,
- racer3_card_id: fake3}[int(tag_host_saddle)]
- status, body = saddle_api("POST", "/api/saddle/task/create", host_daemon.token, {
- "session_id": tag_session, "title": "Bad tag worker",
- "mandate": "m", "capabilities": ["gpu", "warp-drive"]})
- check(status == 400, "an unknown capability tag rejects the task call")
- error_text = body.get("error", "")
- check("warp-drive" in error_text and "valid tags" in error_text
- and "gpu" in error_text and "lostwake" in error_text,
- "the rejection names the unknown tag and the valid list (%s)" % error_text)
- status, _ = saddle_api("POST", "/api/saddle/task/create", host_daemon.token, {
- "session_id": tag_session, "title": "Bad shape worker",
- "mandate": "m", "capabilities": "gpu"})
- check(status == 400, "a non-array capabilities member rejects")
- # A valid tag creates the goal worker in draft with exactly the
- # explicit tag attached; the goal agent enqueues it.
- status, body = saddle_api("POST", "/api/saddle/task/create", host_daemon.token, {
- "session_id": tag_session, "title": "Explicit tag worker",
- "mandate": "do the tagged work", "capabilities": ["gpu"]})
- check(status == 200 and body.get("ok") is True,
- "a valid tag creates the task synchronously")
- tagged_worker = int(body["task_id"])
- detail = task_state(auth, tagged_worker)
- check([c["name"] for c in detail.get("capabilities", [])] == ["gpu"],
- "the worker carries exactly the explicit tag")
- conn = db()
- tag_rows = conn.execute(
- "select c.name from task_capabilities tc"
- " join capabilities c on c.id = tc.capability_id"
- " where tc.task_id = ?", (tagged_worker,)).fetchall()
- conn.close()
- check([r[0] for r in tag_rows] == ["gpu"],
- "the task_capabilities rows record the explicit tag")
- check(detail["status"] == "draft",
- "the callback's task lands in draft until its agent enqueues")
- saddle_api("POST", "/api/saddle/task/enqueue", host_daemon.token,
- {"session_id": tag_session, "task_id": tagged_worker})
- check(wait_task_done(auth, tagged_worker, timeout=90) is not None,
- "the tagged worker dispatches and completes")
- act(auth, slot_public(auth, "/goals/%d" % tag_goal, "goal")["cancel"], {})
- act(auth, task_state(auth, tag_host)["cancel"], {})
- print("== phase 3: dashboard — pinned blocks + activity timeline ==")
- # Pinned OPEN blocking items as link-through cards, newest
- # first; no inline answer forms (the card links to the task
- # page). Fixture: one open question + one open review raised on a
- # local task (stand-ins for the auto-approve grandchild's
- # leftovers) join the probe RFC raised below.
- conn = db()
- conn.execute("insert into tasks (project_id, title, status, priority, created_at)"
- " values (1, 'Dash fixture task', 'working', 5, strftime('%s','now'))")
- dash_task = conn.execute("select max(id) from tasks").fetchone()[0]
- conn.execute("insert into sessions (task_id, saddle_id, started_at)"
- " values (?, ?, strftime('%s','now'))", (dash_task, fake_id))
- dash_session = conn.execute("select max(id) from sessions").fetchone()[0]
- conn.commit()
- conn.close()
- def raise_on_dash(question, kind="question", body=None, summary=None):
- """Push-era raises on the dashboard fixture task (the poll-era
- /update channel is gone): a blocked results push carries the
- block payload; a review verb rides a review entry."""
- if kind == "review" and summary is not None:
- return saddle_api("POST", "/api/saddle/results", fake_token, {
- "session_id": dash_session, "state": "working",
- "entries": [{"id": 9317,
- "command": "saddle review",
- "output": summary, "type": "review",
- "timestamp": time.strftime(
- "%Y-%m-%dT%H:%M:%SZ", time.gmtime())}]})[0]
- block = {"question": question, "type": kind}
- if body is not None:
- block["body"] = body
- return saddle_api("POST", "/api/saddle/results", fake_token, {
- "session_id": dash_session, "state": "blocked",
- "block": block})[0]
- raise_on_dash("Dash fixture question?", kind="question")
- raise_on_dash("dash fixture review", kind="review", summary="dash fixture review")
- # The activity timeline: last <= 50 events, newest first, fed
- # from the existing tables. Fixture rows a few seconds old
- # guarantee the progress kind and a human-authored comment ride
- # along; the probe block below proves same-source ordering.
- conn = db()
- conn.execute("insert into task_comments (task_id, author, body, created_at)"
- " values (?, 'user:admin', 'timeline fixture comment by a human',"
- " strftime('%s','now','-5 seconds'))", (cap_task,))
- conn.execute("insert into task_progress (task_id, summary, created_at)"
- " values (?, 'timeline fixture progress',"
- " strftime('%s','now','-5 seconds'))",
- (cap_task,))
- conn.commit()
- conn.close()
- # The probe RFC raised last: the newest pinned card and the newest
- # timeline event (its raise+answer below prove same-source ordering
- # both ways). The timeline sorts same-second events by source
- # materialisation order, so the probe must land in a LATER clock
- # second than the fixtures — wait for the boundary (bounded).
- fixtures_epoch = int(time.time())
- wait_for(lambda: int(time.time()) > fixtures_epoch or False,
- timeout=3, what="next clock second before the probe raise")
- raise_on_dash("Timeline probe: approve the dashboard flow",
- kind="rfc", body="# Probe")
- dash = slot_public(auth, "/", "dashboard")
- pinned = dash.get("pinned", [])
- pinned_ids = [int(b["id"]) for b in pinned]
- check(len(pinned_ids) >= 3 and pinned_ids == sorted(pinned_ids, reverse=True),
- "pinned open blocks order newest-first (%s)" % pinned_ids)
- check({b["block_type"] for b in pinned} >= {"question", "rfc", "review"},
- "pinned cards cover questions, RFCs and reviews")
- check(all(b["question"] and b["task_title"] and int(b["task_id"]) > 0
- for b in pinned)
- and all("answer_action" not in b for b in pinned),
- "pinned cards are link-through only (task title, kind badge, question)")
- activity = dash.get("activity", [])
- epochs = [int(e["at_epoch"]) for e in activity]
- check(0 < len(activity) <= 50 and epochs == sorted(epochs, reverse=True),
- "the timeline shows the last <=50 events newest-first (%d events)"
- % len(activity))
- kinds = {e["kind"] for e in activity}
- check({"created", "started", "dispatch", "comment", "progress", "completed"} <= kinds,
- "the timeline covers status changes, dispatches, comments, progress and"
- " completions (%s)" % sorted(kinds))
- check(any(e["kind"] == "comment" and e["text"].startswith("user:admin:")
- and "fixture comment" in e["text"] for e in activity),
- "comment events carry author + snippet")
- # The freshly raised probe pinned as the newest card and landed
- # atop the timeline as block-raised; answering it records
- # block-answered.
- check(dash["pinned"][0]["question"] == "Timeline probe: approve the dashboard flow"
- and int(dash["pinned"][0]["task_id"]) == dash_task,
- "the freshest open block pins as the first card")
- check(dash["activity"][0]["kind"] == "block-raised"
- and "Timeline probe" in dash["activity"][0]["text"],
- "the raised block lands atop the timeline")
- probe_block = [b for b in task_state(auth, dash_task)["blocks"]
- if b["question"] == "Timeline probe: approve the dashboard flow"][0]
- act(auth, probe_block["answer_action"], {"answer": "Ship the dashboard"})
- dash = slot_public(auth, "/", "dashboard")
- activity = dash.get("activity", [])
- raised_at = next((i for i, e in enumerate(activity)
- if e["kind"] == "block-raised" and "Timeline probe" in e["text"]), -1)
- answered_at = next((i for i, e in enumerate(activity)
- if e["kind"] == "block-answered"
- and "Ship the dashboard" in e["text"]), -1)
- check(answered_at >= 0 and raised_at >= 0 and answered_at < raised_at,
- "the answered event sorts above its raised event (newest-first, same source)")
- check(all(b["question"] != "Timeline probe: approve the dashboard flow"
- for b in dash["pinned"]),
- "the answered block leaves the pinned cards")
- print("== phase 3 regression: a slow ask after a wake is never dropped ==")
- # The daemon worker's lost-wake-guard regression: a saddle whose
- # ask arrives SLOWLY after the wake (busy winding down when the
- # wake lands) must not lose the wake — the delayed ask (inside
- # the guard window) claims the task, no re-wake storm fires
- # while it is pending, and the saddle stays healthy. The
- # never-asks path (guard -> offline + re-wake of the next
- # capable) is proven by the racer-three section above.
- check(wait_for(lambda: queued_count() == 0 or False,
- timeout=60, what="queue settles before the slow-wake run") is not None,
- "the queue settles before the slow-wake run")
- status, _ = fake2.ask_now()
- check(status == 204, "the slow-ask racer sleeps before its wake")
- saddles_state = slot_public(auth, "/saddles", "saddles")
- act(auth, saddles_state["create_capability"], {"name": "slowwake"})
- slowwake_cap = int([c["id"] for c in slot_public(auth, "/saddles", "saddles")
- ["capabilities"] if c["name"] == "slowwake"][0])
- racer2_card = [s for s in slot_public(auth, "/saddles", "saddles")["saddles"]
- if s["name"] == "racer-two"][0]
- act(auth, racer2_card["attach_capability"], {"capability_id": str(slowwake_cap)})
- fake2.ask_delay = 1.2 # slow: asks well after the wake, inside the 2s guard
- fake2.ask_on_wake = True
- wakes_before_slow = len(fake2.wake_log)
- task_id_tmp = create_queued(auth, {
- "project_id": "1",
- "title": "Slow wake task",
- "priority": "2",
- "capability_id": [str(slowwake_cap)],
- })
- slow_task = task_id_tmp
- check(wait_for(lambda: len(fake2.wake_log) > wakes_before_slow or False,
- timeout=30, what="the slow-ask racer woken") is not None,
- "the only capable sleeping saddle is woken")
- check(wait_task_done(auth, slow_task, timeout=120) is not None,
- "the delayed ask claims the task — the wake was not dropped")
- check(any(a.get("status") == 200
- and int((a.get("payload") or {}).get("task", {}).get("id") or 0)
- == slow_task for a in fake2.asks),
- "the slow ask's 200 carries the task")
- check(len(fake2.wake_log) == wakes_before_slow + 1,
- "the pending-wake skip re-wakes nobody while the slow ask is in flight")
- racer2_card = [s for s in slot_public(auth, "/saddles", "saddles")["saddles"]
- if s["name"] == "racer-two"][0]
- check(racer2_card["status"] in ("idle", "busy"),
- "the slow asker never went offline (guard did not fire)")
- fake2.ask_delay = 0.0
- fake2.ask_on_wake = False # stays out of the pool below
- # =============================================================================
- # Bundle: lifecycle — offline requeues, the hung-request tick survival,
- # parked-refresh bookkeeping, cancellation, the health panel, raw-log
- # losslessness, git identity and the live blocked-push roundtrip.
- # =============================================================================
- def bundle_lifecycle(ctx):
- auth, fake, fake_id, fake_token = ctx.auth, ctx.fake, ctx.fake_id, ctx.fake_token
- app = ctx.app
- print("== plan 11: draft state machine, edit matrix, enqueue/delete guards ==")
- # New tasks default to draft; Enqueue is the only path to the
- # queue (there is no agent path — the ingest API refuses to queue).
- act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
- "project_id": "1",
- "title": "Draft lifecycle task",
- "priority": "4",
- })
- draft_task = int([t["id"] for t in slot_public(auth, "/tasks", "tasks")["tasks"]
- if t["title"] == "Draft lifecycle task"][0])
- detail = task_state(auth, draft_task)
- check(detail["status"] == "draft", "a created task lands in draft")
- check(detail.get("enqueue") is not None and detail.get("delete") is not None,
- "draft exposes the enqueue + delete actions")
- check(detail.get("cancel") is not None, "the cancel action stays authored")
- status, page = http("GET", "/tasks/%d" % draft_task, timeout=15)
- check(status == 200 and "Enqueue task" in page and ">Delete<" in page,
- "the task page shows Enqueue + Delete in draft")
- check("Mark done" not in page,
- "the user Mark-complete action is gone (agent-only completion)")
- # Plan 12: the auto-enqueue-after predecessor REPLACES draft
- # dependencies — setting it is allowed while the predecessor is
- # still pending (the enqueue itself succeeds; the DISPATCHER holds
- # dispatch back until the predecessor is done).
- act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
- "project_id": "1",
- "title": "Draft dependency",
- "priority": "4",
- })
- draft_dep = int([t["id"] for t in slot_public(auth, "/tasks", "tasks")["tasks"]
- if t["title"] == "Draft dependency"][0])
- act(auth, detail["update"], {"enqueue_after_task_id": str(draft_dep)})
- check(int(task_state(auth, draft_task).get("enqueue_after_task_id") or 0)
- == draft_dep,
- "the draft edit sets the auto-enqueue predecessor")
- act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
- "project_id": "1", "title": "Eager dependent", "priority": "4",
- "enqueue_after_task_id": str(draft_dep)})
- eager = task_id_of("Eager dependent")
- enqueue_task(auth, eager)
- check(task_state(auth, eager)["status"] == "queued",
- "enqueue succeeds while the predecessor is pending (no draft guard)")
- check(quiet_window(quiet_seconds(),
- lambda: task_state(auth, eager)["status"] == "queued",
- "pending predecessor holds dispatch"),
- "the pending predecessor holds dispatch back (never assigned)")
- # The edit matrix starts in DRAFT: every field except status.
- act(auth, detail["update"], {
- "title": "Draft lifecycle task (edited)",
- "description": "Now with a description",
- "priority": "2",
- "task_branch": "feature/draft",
- "branch_from": "main",
- "mode": "investigate",
- })
- detail = task_state(auth, draft_task)
- check(detail["title"] == "Draft lifecycle task (edited)"
- and detail["description"] == "Now with a description"
- and detail["priority"] == 2
- and detail["task_branch"] == "feature/draft"
- and detail["branch_from"] == "main"
- and detail["mode"] == "investigate",
- "draft edits apply to every field (incl. the mode)")
- # Clear the predecessor (the empty sentinel): dispatch gates on
- # the hold tag alone now.
- act(auth, detail["update"], {"enqueue_after_task_id": "0"})
- check(not int(task_state(auth, draft_task).get("enqueue_after_task_id") or 0),
- "the draft edit clears the auto-enqueue predecessor")
- # A hold tag nothing carries keeps the dependent QUEUED (never
- # dispatched) through the remaining edit-matrix checks.
- saddles_state = slot_public(auth, "/saddles", "saddles")
- act(auth, saddles_state["create_capability"], {"name": "holdlock"})
- holdlock_cap = int([c["id"] for c in slot_public(auth, "/saddles", "saddles")["capabilities"]
- if c["name"] == "holdlock"][0])
- act(auth, detail["attach_capability"], {"capability_id": str(holdlock_cap)})
- # Enqueue the now-cleared dependent: the hold tag keeps it queued
- # (never dispatched), so the non-draft edit matrix applies.
- enqueue_task(auth, draft_task)
- check(quiet_window(3, lambda: task_state(auth, draft_task)["status"] == "queued",
- "held dependent stays queued"),
- "the held dependent stays queued (never dispatched)")
- # The predecessor settles for the shard drain.
- enqueue_task(auth, draft_dep)
- check(wait_task_done(auth, draft_dep, timeout=90) is not None,
- "the predecessor settles")
- # Drafts are never dispatched even with an idle saddle available.
- act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
- "project_id": "1",
- "title": "Never-dispatched draft",
- "priority": "1",
- })
- silent_draft = int([t["id"] for t in slot_public(auth, "/tasks", "tasks")["tasks"]
- if t["title"] == "Never-dispatched draft"][0])
- check(quiet_window(quiet_seconds(),
- lambda: task_state(auth, silent_draft)["status"] == "draft",
- "draft never dispatched"),
- "an idle saddle never picks a draft up (no agent enqueue path)")
- # Once work can begin (queued here), only priority is editable.
- act(auth, detail["update"], {
- "title": "Should not apply",
- "priority": "3",
- })
- detail = task_state(auth, draft_task)
- check(detail["title"] == "Draft lifecycle task (edited)" and detail["priority"] == 3,
- "non-draft edits apply priority only (title locked)")
- # A terminal task edits nothing.
- conn = db()
- conn.execute("update tasks set status = 'done', completed_at = strftime('%s','now')"
- " where id = ?", (draft_task,))
- conn.commit()
- conn.close()
- act(auth, task_state(auth, draft_task)["update"], {"priority": "8"})
- check(task_state(auth, draft_task)["priority"] == 3,
- "terminal tasks edit nothing (priority locked)")
- # Capabilities lock once a worker has the job: editable while
- # queued, locked once working.
- act(auth, saddles_state["create_capability"], {"name": "editlock"})
- editlock_cap = int([c["id"] for c in slot_public(auth, "/saddles", "saddles")
- ["capabilities"] if c["name"] == "editlock"][0])
- act(auth, saddle_card(auth, "fake-saddle")["attach_capability"],
- {"capability_id": str(editlock_cap)})
- locked_task = create_queued(auth, {"project_id": "1", "title": "Caplock task",
- "priority": "2"})
- detail = task_state(auth, locked_task)
- act(auth, detail["attach_capability"], {"capability_id": str(editlock_cap)})
- check(wait_for(lambda: any(c["name"] == "editlock"
- for c in task_state(auth,
- locked_task)["capabilities"])
- or False, timeout=60, what="editlock attached") is not None,
- "capabilities attach while queued (no worker yet)")
- check(wait_for(lambda: task_state(auth, locked_task).get("status") == "working"
- or False, timeout=60, what="caplock working") is not None,
- "the caplock task reaches working")
- detach_directives = act(auth, task_state(auth, locked_task)["capabilities"][0]["detach"], {})
- check(any(d.get("type") == "notify" and d.get("kind") == "error"
- for d in detach_directives),
- "capabilities are locked once a worker has the job")
- # The ingest API has no queue transition either (only working and
- # blocked are accepted states).
- conn = db()
- caplock_session = conn.execute(
- "select id from sessions where task_id = ? and ended_at is null",
- (locked_task,)).fetchone()
- conn.close()
- if caplock_session is not None:
- status, _ = saddle_api("POST", "/api/saddle/results", fake_token,
- {"session_id": caplock_session[0],
- "state": "queued"})
- check(status == 200
- and task_state(auth, locked_task)["status"] in ("working", "done"),
- "a queued state push is a no-op (only the wire's states drive bookkeeping)")
- # Delete guards: a clean draft deletes; non-draft refuses; a draft
- # with dependents is refused naming them.
- act(auth, task_state(auth, silent_draft)["delete"], {})
- check(not any(t["id"] == silent_draft
- for t in slot_public(auth, "/tasks", "tasks")["tasks"]),
- "a clean draft task deletes")
- delete_directives = act(auth, task_state(auth, draft_task)["delete"], {})
- check(any(d.get("type") == "notify" and d.get("kind") == "error"
- for d in delete_directives),
- "a non-draft task refuses the delete")
- act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
- "project_id": "1", "title": "Guarded draft", "priority": "4"})
- guarded = int([t["id"] for t in slot_public(auth, "/tasks", "tasks")["tasks"]
- if t["title"] == "Guarded draft"][0])
- act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
- "project_id": "1", "title": "Dependent namer", "priority": "4"})
- namer = int([t["id"] for t in slot_public(auth, "/tasks", "tasks")["tasks"]
- if t["title"] == "Dependent namer"][0])
- act(auth, task_state(auth, namer)["update"],
- {"enqueue_after_task_id": str(guarded)})
- check(int(task_state(auth, namer).get("enqueue_after_task_id") or 0) == guarded,
- "the namer points at the guarded predecessor")
- act(auth, task_state(auth, guarded)["delete"], {})
- check(not any(t["id"] == guarded
- for t in slot_public(auth, "/tasks", "tasks")["tasks"]),
- "deleting a predecessor succeeds")
- check(not int(task_state(auth, namer).get("enqueue_after_task_id") or 0),
- "the delete clears the dependent's dangling predecessor reference")
- print("== offline requeues ==")
- # Tasks caught working on a saddle that went offline requeue with a
- # agent comment each tick.
- task_id_tmp = create_queued(auth, {
- "project_id": "1",
- "title": "Offline saddle task",
- "priority": "2",
- })
- offline_task = task_id_tmp
- check(wait_for(lambda: task_state(auth, offline_task).get("status") == "working"
- or False, timeout=60, what="offline task starts working") is not None,
- "task reaches working before its saddle disappears")
- # Hold the daemon mid-round across the requeue window (the
- # original relied on its 8s work period outlasting the 5s tick):
- # the task completing on its own would race the offline pass and
- # skip the requeue the check exists to observe.
- fake.hold_state = True
- conn = db()
- conn.execute("update saddles set status = 'offline' where name = 'fake-saddle'")
- conn.commit()
- conn.close()
- # The requeue frees the saddle in the same tick, so 'queued' is
- # transient; the durable evidence is the agent comment (and the task
- # completing again on the healthy daemon).
- check(wait_for(lambda: any(c["body"] == "saddle went offline; task requeued"
- for c in task_state(auth, offline_task).get("comments", []))
- or False, timeout=30, what="offline requeue comment") is not None,
- "offline saddle requeues the task")
- fake.hold_state = False # release: the round ends, the daemon re-asks
- check(wait_task_done(auth, offline_task, timeout=60) is not None,
- "requeued task completes again on the healthy daemon")
- # R2 (arch 13): the release owner's outcomes — the requeue closed
- # the held attempt's session 'stopped'; the held daemon's terminal
- # push then completes the task against that closed session (the
- # outcome stands — a closed row never reopens). A task that
- # completes a LIVE round closes its session 'done' (draft_dep
- # above ran one open session to its terminal push).
- conn = db()
- outcome_rows = conn.execute(
- "select outcome from sessions where task_id = ? order by id",
- (offline_task,)).fetchall()
- dep_outcome = conn.execute(
- "select outcome from sessions where task_id = ? order by id desc",
- (draft_dep,)).fetchone()
- conn.close()
- check(outcome_rows and outcome_rows[0][0] == "stopped",
- "the one release path closes the requeued attempt 'stopped'")
- check(dep_outcome is not None and dep_outcome[0] == "done",
- "the terminal push closes a live round's session 'done'")
- print("== R6 F1: a hanging request cannot freeze the tick ==")
- # One wedged /task-update (never responds) blocks the comment/
- # subtask-event delivery pass; the Soup timeout must fail it and
- # later ticks keep dispatching.
- conn = db()
- conn.execute("insert into tasks (project_id, title, status, priority, created_at)"
- " values (1, 'Hang survivor', 'queued', 2, strftime('%s','now'))")
- hang_task = conn.execute("select max(id) from tasks").fetchone()[0]
- conn.close()
- task_id_tmp = create_queued(auth, {
- "project_id": "1",
- "title": "Hang runner",
- "priority": "3",
- })
- hang_runner = task_id_tmp
- # A human comment on the running task gives the tick a
- # /task-update delivery to make; the next one hangs.
- check(wait_for(lambda: task_state(auth, hang_runner).get("status") == "working"
- or False, timeout=60, what="hang runner working") is not None,
- "hang runner reaches working")
- act(auth, task_state(auth, hang_runner)["comment"],
- {"body": "operator ping while wedged"})
- fake.hang_next["/task-update"] = 1
- check(wait_task_done(auth, hang_runner, timeout=90) is not None,
- "dispatching task survives a hung daemon request")
- check(wait_for(lambda: any(c.get("text") == "operator ping while wedged"
- or c.get("body") == "operator ping while wedged"
- for c in fake.comment_log) or False,
- timeout=30, what="wedged comment eventually delivered") is not None,
- "the comment delivery retries after the hang")
- # The hung delivery itself fails after the timeout; the system
- # kept moving either way.
- with open(os.path.join(WORK, "app.log")) as handle:
- app_log = handle.read()
- check("tick exceeded" not in app_log or "Dispatch tick failed" in app_log,
- "a hung request never freezes the tick latch")
- print("== R6 F2: refresh must not corrupt parked bookkeeping ==")
- fake.suspend_on_title = "Parked refresh parent"
- task_id_tmp = create_queued(auth, {
- "project_id": "1",
- "title": "Parked refresh parent",
- "priority": "2",
- })
- parked = task_id_tmp
- check(wait_for(lambda: task_state(auth, parked).get("status") == "suspended"
- or False, timeout=60, what="parked parent suspends") is not None,
- "parent parks before the refresh")
- fake_card = saddle_card(auth, "fake-saddle")
- act(auth, fake_card["refresh"], {})
- fake_card = saddle_card(auth, "fake-saddle")
- check(fake_card["status"] == "idle" and int(fake_card["current_task_id"] or 0) == 0,
- "refresh on a parked (suspended) daemon leaves the saddle idle")
- # The parked task keeps its open session and stays put: plain
- # tasks have no subtask events to resume them (the goal machinery
- # owns resumes); the human resumes it explicitly.
- check(quiet_window(quiet_seconds(),
- lambda: task_state(auth, parked)["status"] == "suspended",
- "parked task stays parked"),
- "a parked plain task stays parked after the refresh (no resume channel)")
- act(auth, task_state(auth, parked)["cancel"], {})
- check(wait_for(lambda: task_state(auth, parked)["status"] == "failed" or False,
- timeout=30, what="parked task cancelled") is not None,
- "the parked task cancels cleanly after the refresh")
- print("== R7 B1: cancel reaches the daemon ==")
- # Plan 10 phase 2: cancellation is POST /task-update {kind: cancel}
- # to the RUNNING daemon (the abort machinery, workspace kept).
- task_id_tmp = create_queued(auth, {
- "project_id": "1",
- "title": "Cancel live",
- "priority": "2",
- })
- cancel_live = task_id_tmp
- check(wait_for(lambda: task_state(auth, cancel_live).get("status") == "working"
- or False, timeout=60, what="cancel-live working") is not None,
- "task reaches working before the cancel")
- act(auth, task_state(auth, cancel_live)["cancel"], {})
- check(wait_for(lambda: cancel_live in fake.abort_log or False,
- timeout=30, what="daemon abort recorded") is not None,
- "cancelling POSTs task-update cancel to the daemon")
- check(any(c["body"] == "cancelled; daemon aborted"
- for c in task_state(auth, cancel_live).get("comments", [])),
- "the abort outcome is narrated")
- check(task_state(auth, cancel_live)["status"] == "failed",
- "cancelled task lands failed as before")
- # A 500 on the cancel marks the saddle offline (no fence layer
- # anymore); nothing dispatches to it while offline, and the
- # daemon's own ask flips the row back to idle (proof of life).
- task_id_tmp = create_queued(auth, {
- "project_id": "1",
- "title": "Fence runner",
- "priority": "2",
- })
- fence_runner = task_id_tmp
- check(wait_for(lambda: task_state(auth, fence_runner).get("status") == "working"
- or False, timeout=60, what="fence runner working") is not None,
- "second task reaches working")
- fake.abort_failures = 1
- act(auth, task_state(auth, fence_runner)["cancel"], {})
- check(any(c["body"] == "cancelled; daemon unreachable — saddle marked offline"
- for c in task_state(auth, fence_runner).get("comments", [])),
- "failed cancel narrates the offline marking")
- check(saddle_card(auth, "fake-saddle")["status"] == "offline",
- "saddle marked offline after the failed cancel")
- task_id_tmp = create_queued(auth, {
- "project_id": "1",
- "title": "During fence",
- "priority": "3",
- })
- during_fence = task_id_tmp
- # Converted sleep(7): a quiet window spanning several dispatch
- # ticks in which the task MUST stay queued — if the offline
- # saddle ever dispatches it, this fails loudly. The daemon is held
- # mid-round for the window (the original relied on its 8s work
- # period outlasting the sleep): a finishing round would ask, prove
- # liveness and flip the offline row — only after the window may it.
- fake.hold_state = True
- check(quiet_window(quiet_seconds(),
- lambda: task_state(auth, during_fence)["status"] == "queued",
- "offline saddle does not dispatch during_fence"),
- "offline saddle is not dispatchable")
- fake.hold_state = False
- # The (unaborted) daemon finishes its round, goes idle-awake and
- # asks — the ask is the proof of life that flips the row back.
- check(wait_for(lambda: saddle_card(auth, "fake-saddle")["status"] == "idle" or False,
- timeout=30, what="ask flips offline to idle") is not None,
- "the daemon's ask flips the offline row back to idle")
- check(wait_task_done(auth, during_fence, timeout=60) is not None,
- "queued work dispatches once the saddle proves it is alive")
- print("== R7 B3: health panel + pushed raw log from the first round ==")
- # A normally dispatched task pushes its chunks immediately: raw
- # rows exist from the first round (the session-4 gap stays
- # fixed without any probe machinery).
- task_id_tmp = create_queued(auth, {
- "project_id": "1",
- "title": "Raw from first push",
- "priority": "2",
- })
- raw_first = task_id_tmp
- def raw_count(task_id):
- conn = db()
- try:
- return conn.execute(
- "select count(*) from session_raw_log where session_id in"
- " (select id from sessions where task_id = ?)", (task_id,)).fetchone()[0]
- finally:
- conn.close()
- check(wait_for(lambda: raw_count(raw_first) > 0 or False,
- timeout=60, what="raw rows from first push") is not None,
- "pushed chunks land from the first round (session-4 gap)")
- check(wait_task_done(auth, raw_first, timeout=60) is not None,
- "raw-first task completes")
- # Health panel: a manual refresh populates the snapshot.
- fake_card = saddle_card(auth, "fake-saddle")
- act(auth, fake_card["refresh"], {})
- card = saddle_card(auth, "fake-saddle")
- check(card.get("health") in ("healthy", "stale")
- and card.get("daemon_state") in ("working", "idle", "done")
- and card.get("uptime_display", "") != ""
- and card.get("last_contact_display", "") not in ("", "never"),
- "saddles page shows daemon state, uptime and last contact")
- print("== R8: pushed chunk ingestion is lossless ==")
- # The daemon's opening flush: a coloured prompt run, plain text,
- # a bare CRLF between prompts and the tail — every non-empty
- # chunk must land, exactly once, in seq order. A mid-flight
- # redelivery of the whole batch (the outbox's at-least-once
- # retry) consumes the duplicates.
- corpus = [
- {"seq": 1, "text": "\x1b[01;32muser@host\x1b[00m:\x1b[01;34m~/task-1\x1b[00m$ "},
- {"seq": 2, "text": "echo hello\r\n"},
- {"seq": 3, "text": "\r\n"},
- {"seq": 4, "text": "hello\r\n"},
- {"seq": 5, "text": "\x1b[01;32muser@host\x1b[00m:\x1b[01;34m~/task-1\x1b[00m$ "},
- {"seq": 6, "text": "done\r\n"},
- ]
- fake.scripted_corpus = corpus
- fake.hold_state = True
- task_id_tmp = create_queued(auth, {
- "project_id": "1",
- "title": "Lossless log task",
- "priority": "2",
- })
- lossless = task_id_tmp
- def stored_seqs(task_id):
- conn = db()
- try:
- return [r[0] for r in conn.execute(
- "select seq from session_raw_log where session_id in"
- " (select id from sessions where task_id = ?)"
- " order by seq", (task_id,))]
- finally:
- conn.close()
- check(wait_for(lambda: task_state(auth, lossless).get("status") == "working"
- or False, timeout=60, what="lossless task working") is not None,
- "lossless task dispatches and pushes its opening flush")
- check(wait_for(lambda: stored_seqs(lossless) == [1, 2, 3, 4, 5, 6] or False,
- timeout=30, what="all six chunks stored") is not None,
- "every pushed chunk lands in session_raw_log, CRLF-only included (%s)"
- % stored_seqs(lossless))
- status, _ = saddle_api("POST", "/api/saddle/chunks", fake.token,
- {"session_id": fake.session_id,
- "chunks": [dict(chunk) for chunk in corpus]})
- check(status == 200 and stored_seqs(lossless) == [1, 2, 3, 4, 5, 6],
- "a full-batch redelivery stores nothing new (exactly-once)")
- request = urllib.request.Request(
- WEB + "/terminal-stream/%d" % [r[0] for r in db().execute(
- "select id from sessions where task_id = ?", (lossless,))][0])
- with urllib.request.urlopen(request, timeout=15) as stream:
- texts = []
- deadline = time.monotonic() + 8
- while time.monotonic() < deadline and len(texts) < 6:
- line = stream.readline().decode().strip()
- if not line.startswith("data: ") or line == "data: ping":
- continue
- event = json.loads(line[6:])
- if event.get("type") == "raw":
- texts.append(event.get("text") or "")
- check("\r\n" in texts and len(texts) == 6,
- "SSE serves the whitespace chunk unfiltered")
- fake.hold_state = False
- fake.scripted_corpus = None
- check(wait_task_done(auth, lossless, timeout=60) is not None,
- "lossless task completes")
- print("== R9 B1/B2: git identity + ssh key ==")
- # The key travels on /status into the saddle row and the page view
- # (the manual refresh's exceptional probe).
- fake.ssh_public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1teStKey saddle-1@fedora"
- fake_card = saddle_card(auth, "fake-saddle")
- check(fake_card.get("ssh_public_key", "") == "",
- "no key shown before the daemon reports one")
- act(auth, fake_card["refresh"], {})
- check(wait_for(lambda: "AAAAC3NzaC1lZDI1teStKey" in
- saddle_card(auth, "fake-saddle").get("ssh_public_key", "") or False,
- timeout=30, what="key lands on the row") is not None,
- "reported ssh_public_key persists onto the saddle row")
- conn = db()
- row_key = conn.execute("select ssh_public_key from saddles where name = 'fake-saddle'"
- ).fetchone()[0]
- conn.close()
- check(row_key == fake.ssh_public_key,
- "the key is stored verbatim in the database")
- # Identity edit flows through the UI into the assignment payload.
- fake_card = saddle_card(auth, "fake-saddle")
- act(auth, fake_card["update_identity"],
- {"git_name": "clanker-bot", "git_email": "bot@unitatem.net"})
- fake_card = saddle_card(auth, "fake-saddle")
- check(fake_card.get("git_name") == "clanker-bot"
- and fake_card.get("git_email") == "bot@unitatem.net",
- "inline edit saves the git identity")
- task_id_tmp = create_queued(auth, {
- "project_id": "1",
- "title": "Identity task",
- "priority": "2",
- })
- identity_task = task_id_tmp
- check(wait_for(lambda: any(int(p.get("task", {}).get("id") or 0) == identity_task
- for p in fake.start_payloads) or False,
- timeout=60, what="identity task dispatched") is not None,
- "identity task dispatches")
- payload = [p for p in fake.start_payloads
- if int(p.get("task", {}).get("id") or 0) == identity_task][-1]
- identity = payload.get("git_identity") or {}
- check(identity.get("name") == "clanker-bot"
- and identity.get("email") == "bot@unitatem.net",
- "the assignment carries the saddle's git_identity")
- # Earlier assignments (identity unset) carry no git_identity member.
- check(all("git_identity" not in p for p in fake.start_payloads[:-1]),
- "unset identity omits the member (daemon fallback applies)")
- # Clearing the identity omits it from later assignments.
- fake_card = saddle_card(auth, "fake-saddle")
- act(auth, fake_card["update_identity"], {"git_name": "", "git_email": ""})
- check(saddle_card(auth, "fake-saddle").get("git_name", "") == "",
- "clearing the identity empties the row")
- check(wait_task_done(auth, identity_task, timeout=60) is not None,
- "identity task completes")
- print("== phase 2: blocked push -> block row + pulse ==")
- # The browser flow on each pulse: statum.act(task.refresh) — the
- # sealed GET action must rehydrate the task slot with the new
- # block the daemon just PUSHED (state blocked + block payload).
- task_id_tmp = create_queued(auth, {
- "project_id": "1",
- "title": "Live block task",
- "priority": "2",
- })
- live_task = task_id_tmp
- check(wait_for(lambda: task_state(auth, live_task).get("status") == "working"
- or False, timeout=60, what="live task working") is not None,
- "live-block task reaches working")
- fake.hold_state = True
- fake.state = "blocked" # the daemon parks on its question
- status = fake.push_results("blocked", block={
- "question": "Pick one", "type": "question", "options": ["A", "B"]})
- check(status == 200, "blocked state push accepted")
- # The parked daemon is idle-awake: it asks (and sleeps on 204).
- fake.maybe_trigger_ask("parked")
- check(wait_for(lambda: any(b.get("question") == "Pick one"
- for b in task_state(auth, live_task).get("blocks", []))
- or False, timeout=30, what="block lands in state") is not None,
- "the pushed block reaches the sealed refresh without a reload")
- detail = task_state(auth, live_task)
- check(detail.get("status") == "blocked",
- "the blocked push flips the task to blocked")
- pushed_block = [b for b in detail["blocks"] if b.get("question") == "Pick one"][0]
- conn = db()
- options_json = conn.execute(
- "select options from task_blocks where id = ?", (int(pushed_block["id"]),)
- ).fetchone()[0]
- conn.close()
- check(json.loads(options_json) == ["A", "B"],
- "the block row carries its options payload")
- check(any(s["status"] == "idle"
- for s in slot_public(auth, "/saddles", "saddles")["saddles"]),
- "the blocked push parks the saddle")
- detail = task_state(auth, live_task)
- check(detail.get("refresh") is not None,
- "the sealed GET refresh action is still authored")
- status, _ = http("GET", detail["refresh"]["uri"], timeout=15)
- check(status in (200, 401),
- "the refresh URI resolves as a GET (client roundtrip shape)")
- act(auth, pushed_block["answer_action"], {"answer": "A"})
- # The answer makes the parked task resume-offerable: the affine
- # saddle is woken, asks, and its assignment carries the answer.
- pushes_before = len([p for p in fake.start_payloads
- if int(p.get("task", {}).get("id") or 0) == live_task])
- check(wait_for(lambda: len([p for p in fake.start_payloads
- if int(p.get("task", {}).get("id") or 0) == live_task])
- > pushes_before or False, timeout=60,
- what="blocked task resumes") is not None,
- "answering the pushed block resumes the parked workspace")
- fake.hold_state = False # the resumed round runs to completion
- resume_payload = [p for p in fake.start_payloads
- if int(p.get("task", {}).get("id") or 0) == live_task][-1]
- answers = resume_payload.get("task", {}).get("answers", [])
- check(any(a.get("answer") == "A" for a in answers),
- "the resume assignment carries the human's answer")
- check(wait_task_done(auth, live_task, timeout=60) is not None,
- "live-block task completes after answering")
- print("== R2: the one release path behind unregister ==")
- # Arch 13: removing a saddle releases its held task through the
- # same owner as every requeue — never an orphan open session on a
- # deleted row. Route 1: a DEAD host — the abort delivery fails
- # fast, the offline bookkeeping releases (queued + comment +
- # session 'stopped'), the unregister then deletes the row.
- saddles_state = slot_public(auth, "/saddles", "saddles")
- act(auth, saddles_state["register"], {"name": "dead-host",
- "host": "127.0.0.1:1"})
- dead_card = [s for s in slot_public(auth, "/saddles", "saddles")["saddles"]
- if s["name"] == "dead-host"][0]
- dead_id = int(dead_card["id"])
- task_id_tmp = create_queued(auth, {
- "project_id": "1",
- "title": "Dead-host unregister task",
- "priority": "2",
- })
- dead_task = task_id_tmp
- conn = db()
- conn.execute("update tasks set status = 'working' where id = ?",
- (dead_task,))
- conn.execute("insert into sessions (task_id, saddle_id, started_at)"
- " values (?, ?, strftime('%s','now'))", (dead_task, dead_id))
- conn.execute("update saddles set current_task_id = ? where id = ?",
- (dead_task, dead_id))
- conn.commit()
- dead_session = conn.execute(
- "select id from sessions where task_id = ? and ended_at is null",
- (dead_task,)).fetchone()[0]
- conn.close()
- act(auth, dead_card["unregister"], {})
- conn = db()
- dead_row = conn.execute("select status from tasks where id = ?",
- (dead_task,)).fetchone()
- dead_session_row = conn.execute(
- "select outcome, ended_at is not null from sessions where id = ?",
- (dead_session,)).fetchone()
- dead_saddle_count = conn.execute(
- "select count(*) from saddles where id = ?", (dead_id,)).fetchone()[0]
- conn.close()
- check(dead_row is not None and dead_row[0] == "queued",
- "unregister of a dead-host saddle releases the held task to the queue")
- check(dead_session_row is not None and dead_session_row[0] == "stopped"
- and dead_session_row[1],
- "the offline release closes the held task's session 'stopped'")
- check(dead_saddle_count == 0, "the dead-host saddle row is gone")
- # Route 2: no open session on the saddle — the abort is a no-op
- # and the unregister itself carries the release (the requeue flip
- # through the one owner, the row deleted after).
- saddles_state = slot_public(auth, "/saddles", "saddles")
- act(auth, saddles_state["register"], {"name": "quiet-host",
- "host": "127.0.0.1:%d" % fake.port})
- quiet_card = [s for s in slot_public(auth, "/saddles", "saddles")["saddles"]
- if s["name"] == "quiet-host"][0]
- quiet_id = int(quiet_card["id"])
- task_id_tmp = create_queued(auth, {
- "project_id": "1",
- "title": "Quiet-host unregister task",
- "priority": "2",
- })
- quiet_task = task_id_tmp
- conn = db()
- conn.execute("update tasks set status = 'working' where id = ?",
- (quiet_task,))
- conn.execute("update saddles set current_task_id = ? where id = ?",
- (quiet_task, quiet_id))
- conn.commit()
- conn.close()
- act(auth, quiet_card["unregister"], {})
- conn = db()
- quiet_row = conn.execute("select status from tasks where id = ?",
- (quiet_task,)).fetchone()
- quiet_saddle_count = conn.execute(
- "select count(*) from saddles where id = ?", (quiet_id,)).fetchone()[0]
- conn.close()
- check(quiet_row is not None and quiet_row[0] == "queued",
- "unregister's own release returns the held task to the queue")
- check(quiet_saddle_count == 0, "the quiet-host saddle row is gone")
- # =============================================================================
- # Bundle: answers — the answer-resume machinery in both daemon shapes
- # (sleeping affine wake, mid-round verdict delivery), offline requeue
- # keeping parked-with-answered tasks, the staleness probe, and the
- # startup token migration (this shard's one deliberate app restart).
- # =============================================================================
- def bundle_answers(ctx):
- auth, fake, fake_id, fake_token = ctx.auth, ctx.fake, ctx.fake_id, ctx.fake_token
- app = ctx.app
- print("== phase 2: answer resume — wake -> ask -> affinity claim ==")
- # The full live sequence against a daemon that asks after its
- # park: ask -> assignment -> blocked push (park) -> ask -> 204
- # -> sleeps (row idle) -> human answers -> the dispatcher WAKES
- # the sleeping affine saddle -> its ask allocates the resume,
- # the verdict riding the assignment -> the session continues.
- # A private capability tag keeps the fleet's other sleeping
- # saddles from claiming these tasks.
- saddles_state = slot_public(auth, "/saddles", "saddles")
- act(auth, saddles_state["create_capability"], {"name": "answerflow"})
- answerflow_cap = int([c["id"] for c in slot_public(auth, "/saddles", "saddles")["capabilities"]
- if c["name"] == "answerflow"][0])
- def answerflow_daemon(**kwargs):
- daemon = FakeSaddle()
- daemon.ask_on_register = False # manual, deterministic asks
- daemon.hold_state = True # the tests drive the lifecycle
- for key, value in kwargs.items():
- setattr(daemon, key, value)
- daemon.serve()
- return daemon
- def register_answerflow(daemon, name):
- act(auth, slot_public(auth, "/saddles", "saddles")["register"], {
- "name": name, "host": "127.0.0.1:%d" % daemon.port})
- act(auth, saddle_card(auth, name)["attach_capability"],
- {"capability_id": str(answerflow_cap)})
- def create_answerflow_task(title):
- return create_queued(auth, {
- "project_id": "1", "title": title, "priority": "2",
- "capability_id": [str(answerflow_cap)]})
- def retire_daemon(daemon, name):
- # deterministic teardown: a dead daemon and an offline row
- # never interfere with the sections that follow
- daemon.kill()
- act(auth, saddle_card(auth, name)["refresh"], {})
- sleeper = answerflow_daemon(ask_on_wake=True)
- register_answerflow(sleeper, "resume-sleeper")
- sleeping_task = create_answerflow_task("Sleeping resume task")
- ask_status, assignment = sleeper.ask_now()
- check(ask_status == 200 and int(assignment["task"]["id"]) == sleeping_task,
- "the daemon's ask claims the queued task")
- sleeping_session = assignment.get("session_id")
- sleeper.state = "blocked"
- check(sleeper.push_results("blocked", block={
- "question": "Pick a side", "type": "question",
- "options": ["Port", "Starboard"]}) == 200,
- "the blocked push parks the session")
- check(wait_for(lambda: task_state(auth, sleeping_task).get("status") == "blocked"
- or False, timeout=30, what="sleeping task parked") is not None,
- "the task parks as blocked")
- check(saddle_card(auth, "resume-sleeper")["status"] == "idle",
- "the parked session's saddle row reads idle")
- ask_status, _ = sleeper.ask_now()
- check(ask_status == 204,
- "the parked daemon's ask returns 204 — it sleeps until woken")
- # Converted sleep(3): ticks pass with the daemon asleep and no
- # wake may arrive — a wake during the window fails loudly.
- check(quiet_window(quiet_seconds(),
- lambda: len(sleeper.wake_log) == 0,
- "sleeping daemon never woken"),
- "the sleeping daemon is never woken while the block waits")
- detail = task_state(auth, sleeping_task)
- side_block = [b for b in detail["blocks"] if b["question"] == "Pick a side"][0]
- act(auth, side_block["answer_action"], {"option": "Starboard"})
- check(wait_for(lambda: len(sleeper.wake_log) > 0 or False,
- timeout=30, what="wake after the answer") is not None,
- "answering the block wakes the sleeping affine saddle")
- # Answering = allocating (plan 11): the resume assignment carries
- # the verdict as resume_notice (stable wording) AND a FRESH
- # session id — the parked row closed, the daemon keys its resumed
- # pushes on the new one.
- check(wait_for(lambda: any(
- int(p.get("task", {}).get("id") or 0) == sleeping_task
- and p.get("session_id") != sleeping_session
- and p.get("resume_notice") == "Human selected: Starboard"
- and any(a.get("answer") == "Starboard"
- for a in p.get("task", {}).get("answers", []))
- for p in sleeper.start_payloads[1:]) or False,
- timeout=30, what="resume allocation carries the verdict") is not None,
- "the woken daemon's ask allocates the resume — fresh session, notice carried")
- # Barrier: the resume's own working push must commit before the
- # manual done push (a late working write would otherwise race the
- # completion's full-row write).
- check(wait_for(lambda: task_state(auth, sleeping_task).get("status") == "working"
- or False, timeout=15, what="resumed round working") is not None,
- "the resumed round reports working before its completion push")
- # The resumed round runs to completion keyed on the fresh session.
- sleeper.push_results("done", summary="sleep-resumed round complete")
- check(wait_task_done(auth, sleeping_task, timeout=30) is not None,
- "the resumed session continues to completion")
- conn = db()
- rows = conn.execute(
- "select id, outcome, ended_at is not null from sessions where task_id = ?"
- " order by id", (sleeping_task,)).fetchall()
- conn.close()
- check(len(rows) == 2 and rows[0][1] == "resumed" and rows[0][2]
- and rows[1][1] == "done" and rows[1][2],
- "parked session closed 'resumed'; the resumed round ran on a fresh row to done (%s)"
- % (rows,))
- retire_daemon(sleeper, "resume-sleeper")
- print("== phase 2: answer resume — the parked daemon is woken (POST /answer retired) ==")
- # Post Task A every park FREES the daemon: after the blocked push
- # it lands idle and asks (204). The retired POST /answer channel
- # always refuses; the verdict reaches the parked workspace purely
- # through the wake -> ask -> affinity allocation carrying the
- # resume_notice + a fresh session id.
- parked = answerflow_daemon(ask_on_wake=True,
- ask_on_terminal=False)
- register_answerflow(parked, "resume-parked")
- parked_task = create_answerflow_task("Parked resume task")
- ask_status, assignment = parked.ask_now()
- check(ask_status == 200 and int(assignment["task"]["id"]) == parked_task,
- "the daemon's ask claims the task")
- parked_session = assignment.get("session_id")
- parked.state = "blocked"
- check(parked.push_results("blocked", block={
- "question": "Which database?", "type": "question",
- "options": ["SQLite", "Postgres"]}) == 200,
- "the blocked push parks the session mid-round")
- check(wait_for(lambda: task_state(auth, parked_task).get("status") == "blocked"
- or False, timeout=30, what="parked task blocked") is not None,
- "the task parks as blocked")
- check(saddle_card(auth, "resume-parked")["status"] == "idle",
- "the parked session's saddle row reads idle")
- # The freed daemon re-asks (its idle loop) and sleeps on 204.
- ask_status, _ = parked.ask_now()
- check(ask_status == 204,
- "the freed parked daemon asks again and sleeps (204)")
- # The retired channel always refuses — the web never depends on it.
- request = urllib.request.Request(
- "http://127.0.0.1:%d/answer" % parked.port,
- data=json.dumps({"answer": "SQLite", "branch_advanced": False}).encode(),
- method="POST")
- request.add_header("Content-Type", "application/json")
- try:
- urllib.request.urlopen(request, timeout=5)
- refused = False
- except urllib.error.HTTPError as error:
- refused = error.code == 409
- check(refused, "POST /answer always refuses (409) — parks resume via allocation")
- detail = task_state(auth, parked_task)
- db_block = [b for b in detail["blocks"] if b["question"] == "Which database?"][0]
- # branch_advanced on the resume assignment (the /poll verdict
- # channel's hint lives here now): a sibling on the same shared
- # working branch completed strictly after the block's park marker
- # — the resumed allocation must report branch_advanced=true so the
- # daemon knows to pull before continuing.
- conn = db()
- parked_block_created = conn.execute(
- "select created_at from task_blocks where id = ?", (db_block["id"],)
- ).fetchone()[0]
- conn.execute("update tasks set task_branch = 'shared/resume-branch'"
- " where id = ?", (parked_task,))
- conn.commit()
- conn.close()
- wait_for(lambda: int(time.time()) > parked_block_created or False,
- timeout=3, what="next clock second past the park marker")
- conn = db()
- conn.execute("insert into tasks (project_id, title, status, priority,"
- " task_branch, summary, completed_at, created_at)"
- " values (1, 'Resume sibling', 'done', 5, 'shared/resume-branch',"
- " 'sibling finished', strftime('%s','now'),"
- " strftime('%s','now','-300 seconds'))")
- conn.commit()
- conn.close()
- act(auth, db_block["answer_action"], {"option": "SQLite"})
- check(wait_for(lambda: len(parked.wake_log) > 0 or False,
- timeout=30, what="wake after the answer") is not None,
- "answering the block wakes the parked (idle) affine saddle")
- check(wait_for(lambda: any(
- int(p.get("task", {}).get("id") or 0) == parked_task
- and p.get("session_id") != parked_session
- and p.get("resume_notice") == "Human selected: SQLite"
- and p.get("branch_advanced") is True
- for p in parked.start_payloads[1:]) or False,
- timeout=30, what="parked resume allocation") is not None,
- "the parked daemon's ask allocates the resume — fresh session, formatted notice,"
- " branch_advanced=true after the sibling's completion")
- # The hold_state daemon drives its round manually: the resumed
- # round completes on the fresh session row.
- parked.push_results("done", summary="parked round resumed and finished")
- check(wait_task_done(auth, parked_task, timeout=90) is not None,
- "the parked session continues to completion")
- conn = db()
- row = conn.execute("select outcome from sessions where id = ?",
- (parked_session,)).fetchone()
- session_rows = conn.execute(
- "select id, outcome from sessions where task_id = ? order by id",
- (parked_task,)).fetchall()
- conn.close()
- check(row is not None and row[0] == "resumed"
- and len(session_rows) == 2 and session_rows[0] == (parked_session, "resumed"),
- "the parked session closed 'resumed' and the resumed round kept its own row (%s)"
- % (session_rows,))
- retire_daemon(parked, "resume-parked")
- print("== plan 11: plan acceptance — accept / reject / auto-approve ==")
- # A type 'plan' block parks like any other; Accept and
- # Reject-with-feedback drive the same wake -> ask -> allocation
- # resume as any verdict, the stable wording riding the
- # resume_notice. Plan 12: the daemon only SENDS plan blocks on
- # goal runs now (the goal page owns the review); this covers the
- # generic verdict path a goal-run's mirrored block also uses. The
- # auto-approve option is gone.
- planner = answerflow_daemon(ask_on_wake=True, ask_on_terminal=False)
- register_answerflow(planner, "plan-planner")
- def park_on_plan(task_id, markdown):
- planner.state = "blocked"
- check(planner.push_results("blocked", block={
- "question": "Plan submitted for review", "type": "plan",
- "body": markdown}) == 200,
- "the plan push parks the session")
- # Wait for the block row itself (the auto-approve variant is
- # answered — and may resume — before any status poll lands).
- check(wait_for(lambda: any(b["block_type"] == "plan"
- for b in task_state(auth, task_id)["blocks"])
- or False, timeout=30, what="plan block raised") is not None,
- "the plan block reaches the task")
- return [b for b in task_state(auth, task_id)["blocks"]
- if b["block_type"] == "plan"][0]
- # (a) Accept resumes with the approval notice.
- accept_task = create_answerflow_task("Plan accept task")
- ask_status, accept_assignment = planner.ask_now()
- check(ask_status == 200 and int(accept_assignment["task"]["id"]) == accept_task,
- "the planner claims the accept task")
- accept_parked_session = accept_assignment.get("session_id")
- plan_block = park_on_plan(accept_task, "# Steps\n\n1. Wire\n2. Test")
- check(plan_block.get("answered") is not True,
- "the plan block waits for a human verdict")
- # Plan 16 S2: the accept form posts the verdict marker; the server
- # builds the stable constant (byte-identical wire).
- act(auth, plan_block["answer_action"], {"verdict": "accept"})
- check(wait_for(lambda: any(
- int(p.get("task", {}).get("id") or 0) == accept_task
- and p.get("session_id") != accept_parked_session
- and p.get("resume_notice") == "Plan approved. Proceed with implementation."
- for p in planner.start_payloads[1:]) or False,
- timeout=30, what="accept resume allocation") is not None,
- "accept resumes the parked daemon with the approval notice")
- planner.push_results("done", summary="plan accepted and implemented")
- check(wait_task_done(auth, accept_task, timeout=60) is not None,
- "the accepted plan's task completes")
- planner.set_state("idle")
- # (b) Reject-with-feedback loops the feedback notice back.
- reject_task = create_answerflow_task("Plan reject task")
- ask_status, reject_assignment = planner.ask_now()
- check(ask_status == 200 and int(reject_assignment["task"]["id"]) == reject_task,
- "the planner claims the reject task")
- reject_parked_session = reject_assignment.get("session_id")
- plan_block = park_on_plan(reject_task, "# Draft\n\nJust wing it")
- act(auth, plan_block["answer_action"], {"feedback": "Add a test section"})
- reject_block = [b for b in task_state(auth, reject_task)["blocks"]
- if b["block_type"] == "plan"][0]
- check(reject_block.get("answer")
- == "Plan feedback received:\nAdd a test section",
- "reject stores the feedback in the stable wording (%s)"
- % reject_block.get("answer"))
- check(wait_for(lambda: any(
- int(p.get("task", {}).get("id") or 0) == reject_task
- and p.get("session_id") != reject_parked_session
- and p.get("resume_notice") == "Plan feedback received:\nAdd a test section"
- for p in planner.start_payloads[1:]) or False,
- timeout=30, what="reject resume allocation") is not None,
- "reject resumes the parked daemon with the feedback notice")
- planner.push_results("done", summary="plan revised and delivered")
- check(wait_task_done(auth, reject_task, timeout=60) is not None,
- "the revised plan's task completes")
- planner.set_state("idle")
- retire_daemon(planner, "plan-planner")
- print("== phase 2: offline requeue keeps parked-with-answered tasks ==")
- # A daemon dying while its parked task carries an answered
- # verdict: going offline must not requeue the task or close its
- # session — the resume machinery (delivery once the daemon
- # returns, or the affinity allocation on its next ask) needs
- # both. The question park leaves the row idle; the review park
- # keeps it busy@task (`saddle review` blocks the round without
- # a parking state push), exercising the unreachable path's
- # requeue decision both ways.
- stranded = answerflow_daemon()
- register_answerflow(stranded, "resume-stranded")
- stranded_task = create_answerflow_task("Stranded answered task")
- ask_status, _ = stranded.ask_now()
- check(ask_status == 200, "the stranded daemon claims its task")
- stranded.state = "blocked"
- stranded.push_results("blocked", block={
- "question": "Hold or fold?", "type": "question", "options": ["Hold", "Fold"]})
- check(wait_for(lambda: task_state(auth, stranded_task).get("status") == "blocked"
- or False, timeout=30, what="stranded task parked") is not None,
- "the stranded task parks as blocked")
- detail = task_state(auth, stranded_task)
- hold_block = [b for b in detail["blocks"] if b["question"] == "Hold or fold?"][0]
- act(auth, hold_block["answer_action"], {"option": "Hold"})
- stranded.kill() # the daemon dies before the verdict is delivered
- act(auth, saddle_card(auth, "resume-stranded")["refresh"], {})
- check(wait_for(lambda: saddle_card(auth, "resume-stranded")["status"] == "offline"
- or False, timeout=30, what="stranded saddle offline") is not None,
- "the dead daemon's saddle row goes offline")
- # Converted sleep(7): offline ticks run while the task MUST stay
- # parked — a requeue during the window fails loudly.
- check(quiet_window(quiet_seconds(),
- lambda: task_state(auth, stranded_task)["status"] == "blocked",
- "stranded task not requeued"),
- "the parked-with-answered task is not requeued by the offline pass")
- conn = db()
- open_session = conn.execute(
- "select count(*) from sessions where task_id = ? and ended_at is null",
- (stranded_task,)).fetchone()[0]
- conn.close()
- check(open_session == 1,
- "the parked-with-answered task keeps its open session for the resume")
- reviewer = answerflow_daemon(ask_on_terminal=False)
- register_answerflow(reviewer, "resume-reviewer")
- review_task = create_answerflow_task("Review park task")
- ask_status, _ = reviewer.ask_now()
- check(ask_status == 200, "the reviewer claims its task")
- # Post Task A the review park frees the daemon: the blocked push
- # carries the review block and parks the row idle.
- reviewer.state = "blocked"
- check(reviewer.push_results("blocked", block={
- "question": "Review requested", "type": "review",
- "body": "Ready for review: wired and tested."}) == 200,
- "the review park push carries the review block")
- check(wait_for(lambda: task_state(auth, review_task).get("status") == "review_requested"
- or False, timeout=30, what="review park raised") is not None,
- "the review block parks the task as review_requested (row freed)")
- # The verdict lands (nothing delivers it anymore); THEN the daemon
- # dies and the manual refresh offlines it — the parked-with-answered
- # task must keep its session for the eventual resume.
- detail = task_state(auth, review_task)
- verdict_block = [b for b in detail["blocks"]
- if b["block_type"] == "review"][0]
- act(auth, verdict_block["answer_action"], {"verdict": "accept"})
- reviewer.kill()
- act(auth, saddle_card(auth, "resume-reviewer")["refresh"], {})
- check(wait_for(lambda: saddle_card(auth, "resume-reviewer")["status"] == "offline"
- or False, timeout=30, what="reviewer offline") is not None,
- "the dead reviewer goes offline on the failed probe")
- # Converted sleep(7): offline ticks run while the task MUST stay
- # review-parked — a requeue during the window fails loudly.
- check(quiet_window(quiet_seconds(),
- lambda: task_state(auth, review_task)["status"] == "review_requested",
- "review-parked task not requeued"),
- "the review-parked task with an answered verdict is never requeued")
- conn = db()
- open_session = conn.execute(
- "select count(*) from sessions where task_id = ? and ended_at is null",
- (review_task,)).fetchone()[0]
- conn.close()
- check(open_session == 1,
- "the review-parked task keeps its open session for the resume")
- print("== phase 2: staleness — one probe decides ==")
- # A silent-but-alive runner (long think, nothing to flush): after
- # SADDLE_STALE_S (3s here) of silence it owes ONE exceptional
- # GET /status; the answer keeps it running and the silence
- # episode gets no second probe.
- task_id_tmp = create_queued(auth, {
- "project_id": "1",
- "title": "Silent thinker",
- "priority": "2",
- })
- thinker = task_id_tmp
- check(wait_for(lambda: task_state(auth, thinker).get("status") == "working"
- or False, timeout=60, what="thinker working") is not None,
- "silent thinker reaches working")
- fake.silent = True # the daemon goes quiet mid-round (alive)
- probes_at_start = fake.status_hits
- check(wait_for(lambda: fake.status_hits > probes_at_start or False,
- timeout=30, what="exceptional probe arrives") is not None,
- "a silent runner gets its exceptional GET /status probe")
- # Converted sleep(8)+sleep(6): a quiet window across several
- # ticks in which the probe count MUST NOT move — a second probe
- # (the poll drumbeat this guards against) fails loudly.
- second_window = fake.status_hits
- check(quiet_window(quiet_seconds(),
- lambda: fake.status_hits == second_window,
- "no second staleness probe"),
- "the silence episode is probed exactly once (no poll drumbeat)")
- check(task_state(auth, thinker).get("status") == "working"
- and saddle_card(auth, "fake-saddle")["status"] == "busy",
- "the answered probe keeps the task running (no requeue)")
- # Output flows again: the episode ends, the clock resets.
- fake.silent = False
- fake.push_chunks(["resumed output\r\n"])
- check(wait_task_done(auth, thinker, timeout=60) is not None,
- "the silent thinker completes once output flows again")
- # The daemon vanishing mid-task: silence for the window, the
- # probe fails, the task requeues with an agent comment and the
- # saddle goes offline.
- task_id_tmp = create_queued(auth, {
- "project_id": "1",
- "title": "Vanish victim",
- "priority": "2",
- })
- vanish = task_id_tmp
- check(wait_for(lambda: task_state(auth, vanish).get("status") == "working"
- or False, timeout=60, what="vanish victim working") is not None,
- "vanish victim reaches working")
- fake.kill()
- check(wait_for(lambda: saddle_card(auth, "fake-saddle")["status"] == "offline" or False,
- timeout=60, what="daemon death -> offline") is not None,
- "vanishing daemon drives the saddle offline (failed staleness probe)")
- check(wait_for(lambda: any("unreachable" in c["body"]
- and c.get("is_system")
- for c in task_state(auth, vanish).get("comments", []))
- or False, timeout=30, what="unreachable comment") is not None,
- "the affected task gets an unreachable system row (never an agent comment)")
- check(task_state(auth, vanish)["status"] == "queued",
- "the task requeues after the daemon death")
- card = saddle_card(auth, "fake-saddle")
- check(card.get("health") == "offline",
- "health badge reports offline")
- # The unreachable row flag marks the detection window: a task the
- # web still sees working while its saddle is offline/fenced, before
- # the requeue pass moves it. Controlled window (the real
- # transitions above are atomic with their requeues).
- conn = db()
- conn.execute("insert into tasks (project_id, title, status, priority,"
- " created_at) values (1, 'Unreachable row', 'working', 5,"
- " strftime('%s','now'))")
- unreachable_row = conn.execute("select max(id) from tasks").fetchone()[0]
- conn.execute("insert into sessions (task_id, saddle_id, started_at)"
- " values (?, ?, strftime('%s','now'))", (unreachable_row, fake_id))
- conn.commit()
- conn.close()
- check(wait_for(lambda: (lambda row: row.get("unreachable") is True)(
- [t for t in slot_public(auth, "/tasks", "tasks")["tasks"]
- if int(t["id"]) == unreachable_row][0]) or False,
- timeout=20, what="unreachable flag") is not None,
- "queue row shows the unreachable state in the window")
- print("== phase 1: startup migrates the per-saddle token ==")
- # Saddles registered before the flip hold legacy api_tokens rows.
- # On startup every saddle row gets its canonical token: the
- # legacy value it already holds (daemons keep their credential)
- # or a freshly minted one. Fixture: this shard's own racer.
- fake2 = FakeSaddle()
- fake2.ask_on_register = False
- fake2.serve(port=free_port())
- saddles_state = slot_public(auth, "/saddles", "saddles")
- act(auth, saddles_state["register"], {"name": "racer-two",
- "host": "127.0.0.1:%d" % fake2.port})
- racer_two_id = int([s for s in slot_public(auth, "/saddles", "saddles")["saddles"]
- if s["name"] == "racer-two"][0]["id"])
- conn = db()
- conn.execute("insert into api_tokens (saddle_id, token, created_at)"
- " values (?, 'saddle-legacy-copied-token', strftime('%s','now'))",
- (racer_two_id,))
- conn.execute("update saddles set token = null where name = 'racer-two'")
- conn.execute("insert into saddles (name, host, status, current_task_id)"
- " values ('tokenless-orphan', '127.0.0.1:1', 'offline', 0)")
- conn.commit()
- conn.close()
- app.terminate()
- app.wait(timeout=10)
- app = start_app()
- auth = login("admin", "adminpass123")
- ctx.app = app # teardown tracks the restarted process
- ctx.auth = auth
- def saddle_row(name):
- conn = db()
- try:
- return conn.execute("select id, token from saddles where name = ?",
- (name,)).fetchone()
- finally:
- conn.close()
- check(wait_for(lambda: saddle_row("tokenless-orphan") is not None
- and (saddle_row("tokenless-orphan")[1] or "").startswith("saddle-")
- or False, timeout=20, what="tokenless row minted") is not None,
- "startup mints a token for a saddle row without one")
- check(wait_for(lambda: saddle_row("racer-two") is not None
- and saddle_row("racer-two")[1] == "saddle-legacy-copied-token"
- or False, timeout=20, what="legacy token copied") is not None,
- "startup copies the legacy api_tokens value onto the row")
- fake2.token = "saddle-legacy-copied-token" # the daemon asks with it
- status, _ = saddle_api("POST", "/api/saddle/next-task", "saddle-legacy-copied-token")
- check(status in (200, 204),
- "the legacy token still authenticates (copied, not replaced)")
- conn = db()
- conn.execute("delete from saddles where name = 'tokenless-orphan'")
- conn.commit()
- conn.close()
- # The restart wiped the in-memory ask bookkeeping; the fleet's
- # daemons re-establish sleeping with a fresh ask each (the real
- # daemons' idle-awake ask / 15-minute safety self-ask).
- for daemon in (fake2,):
- daemon.ask_now()
- check(True, "fleet re-asks after the restart (sleeping re-established)")
- # =============================================================================
- # Bundle: push — plan 11 task E: real Web Push over VAPID. VAPID
- # persistence, the service worker, subscription CRUD + validation,
- # notify-me prefs, the block/lifecycle dispatch matrix (global vs
- # opted-in), 410 pruning, retry-next-tick delivery and protocol-level
- # payload verification (RFC 8291 decryption + ES256 JWT) against the
- # fake push service.
- # =============================================================================
- def bundle_push(ctx):
- auth, fake, fake_id, fake_token = ctx.auth, ctx.fake, ctx.fake_id, ctx.fake_token
- def subscription_count(endpoint):
- conn = db()
- try:
- return conn.execute(
- "select count(*) from push_subscriptions where endpoint = ?",
- (endpoint,)).fetchone()[0]
- finally:
- conn.close()
- print("== task E: service worker asset ==")
- status, sw = http("GET", "/sw.js", timeout=15)
- check(status == 200 and "addEventListener('push'" in sw
- and "notificationclick" in sw and "clients.openWindow" in sw,
- "the service worker serves at the origin root with push + click routing")
- print("== task E: VAPID keypair generation + persistence ==")
- config = json.load(open(os.path.join(WORK, "web-config.json")))
- vapid = config.get("vapid") or {}
- check(bool(vapid.get("public_key")) and bool(vapid.get("private_key")),
- "the VAPID keypair persists into web-config.json's vapid section")
- vapid_private_key = ec.derive_private_key(
- int.from_bytes(b64url_dec(vapid["private_key"]), "big"), ec.SECP256R1())
- derived_public = vapid_private_key.public_key().public_bytes(
- Encoding.X962, PublicFormat.UncompressedPoint)
- check(b64url_dec(vapid["public_key"]) == derived_public,
- "the persisted public key matches the private scalar (a valid P-256 pair)")
- dash = slot_public(auth, "/", "dashboard")
- check(dash.get("push_vapid_key") == vapid["public_key"],
- "the dashboard exposes the same VAPID key to subscribers")
- check(dash.get("push_subscribed") is False and bool(dash.get("push_subscribe")),
- "the bell starts unsubscribed and carries the subscribe action")
- print("== task E: subscription CRUD + auth validation ==")
- browser = ec.generate_private_key(ec.SECP256R1())
- browser_public = browser.public_key().public_bytes(
- Encoding.X962, PublicFormat.UncompressedPoint)
- browser_auth = os.urandom(16)
- subscribe_fields = {"p256dh": b64url(browser_public), "auth": b64url(browser_auth)}
- valid_endpoint = "http://127.0.0.1:1/x"
- check("absolute http(s)" in (action_notify(act(auth, dash["push_subscribe"],
- {"endpoint": "ftp://example.com/push", **subscribe_fields})) or ""),
- "a non-http endpoint is rejected")
- check("p256dh" in (action_notify(act(auth, dash["push_subscribe"],
- {"endpoint": valid_endpoint, "p256dh": b64url(os.urandom(64)),
- "auth": subscribe_fields["auth"]})) or ""),
- "a malformed p256dh point is rejected")
- check("auth" in (action_notify(act(auth, dash["push_subscribe"],
- {"endpoint": valid_endpoint, "p256dh": subscribe_fields["p256dh"],
- "auth": b64url(os.urandom(15))})) or ""),
- "a malformed auth secret is rejected")
- push = FakePushService()
- push.serve()
- endpoint_main = push.url("/main")
- act(auth, dash["push_subscribe"],
- {"endpoint": endpoint_main, **subscribe_fields})
- conn = db()
- rows = conn.execute("select user_id, p256dh, auth from push_subscriptions"
- " where endpoint = ?", (endpoint_main,)).fetchall()
- admin_id = conn.execute("select id from spry_users where username = 'admin'"
- ).fetchone()[0]
- conn.close()
- check(len(rows) == 1 and rows[0][0] == admin_id,
- "a valid subscription stores against the signed-in user")
- # Re-subscribing the same endpoint replaces (never duplicates).
- rotated_auth = os.urandom(16)
- act(auth, dash["push_subscribe"],
- {"endpoint": endpoint_main, "p256dh": subscribe_fields["p256dh"],
- "auth": b64url(rotated_auth)})
- conn = db()
- rows = conn.execute("select auth from push_subscriptions where endpoint = ?",
- (endpoint_main,)).fetchall()
- conn.close()
- check(len(rows) == 1 and rows[0][0] == b64url(rotated_auth),
- "re-subscribing an endpoint replaces its keys in place")
- print("== task E: notify-me pref persistence ==")
- act(auth, slot_public(auth, "/tasks", "tasks")["create"],
- {"project_id": "1", "title": "Push pref task", "priority": "5"})
- pref_task = task_id_of("Push pref task")
- act(auth, task_state(auth, pref_task)["toggle_notify"], {})
- conn = db()
- check(conn.execute("select count(*) from task_notify_prefs where task_id = ?"
- " and user_id = ?", (pref_task, admin_id)).fetchone()[0] == 1,
- "Notify me persists a task_notify_prefs row for the user")
- conn.close()
- check(task_state(auth, pref_task).get("notify_me") is True,
- "the task slot reflects notify_me after the toggle")
- act(auth, task_state(auth, pref_task)["toggle_notify"], {})
- conn = db()
- check(conn.execute("select count(*) from task_notify_prefs where task_id = ?",
- (pref_task,)).fetchone()[0] == 0,
- "Don't notify me removes the pref row")
- conn.close()
- check(task_state(auth, pref_task).get("notify_me") is False,
- "the task slot reflects the opt-out")
- print("== task E: dispatch matrix — global blocks vs opted-in lifecycle ==")
- # Two "browsers" for admin (/main, /gone) and one for another user
- # (id 999, rows seeded directly — push targeting is per user).
- endpoint_gone = push.url("/gone")
- push.status_for["/gone"] = 410
- act(auth, dash["push_subscribe"],
- {"endpoint": endpoint_gone, **subscribe_fields})
- other_browser = ec.generate_private_key(ec.SECP256R1())
- other_public = other_browser.public_key().public_bytes(
- Encoding.X962, PublicFormat.UncompressedPoint)
- other_auth = os.urandom(16)
- endpoint_other = push.url("/other")
- conn = db()
- conn.execute("insert into push_subscriptions (user_id, endpoint, p256dh, auth,"
- " created_at) values (999, ?, ?, ?, strftime('%s','now'))",
- (endpoint_other, b64url(other_public), b64url(other_auth)))
- conn.commit()
- conn.close()
- # Task 1: admin opted in via the UI toggle; the other user is not.
- # Baselines are captured BEFORE the enqueue: the first-start push
- # fires at the allocation, the instant the dispatch lands.
- act(auth, slot_public(auth, "/tasks", "tasks")["create"],
- {"project_id": "1", "title": "Push lifecycle task", "priority": "5"})
- task1 = task_id_of("Push lifecycle task")
- act(auth, task_state(auth, task1)["toggle_notify"], {})
- main_before = len(push.hits("/main"))
- other_before = len(push.hits("/other"))
- gone_before = len(push.hits("/gone"))
- enqueue_task(auth, task1)
- check(wait_for(lambda: fake.running_task == task1 or False, timeout=30,
- what="task 1 assignment") is not None,
- "task 1 dispatches to the fake daemon")
- task1_session = fake.session_id
- check(wait_for(lambda: len(push.hits("/main")) > main_before, timeout=15,
- what="first-start push to the opted-in admin") is not None,
- "first start notifies the opted-in user's subscription")
- check(wait_for(lambda: len(push.hits("/gone")) > gone_before, timeout=15,
- what="first-start push to the gone endpoint") is not None,
- "first start reaches every subscription of the opted-in user")
- check(quiet_window(quiet_seconds(),
- lambda: len(push.hits("/other")) == other_before,
- "no first-start push to the not-opted-in user"),
- "first start never reaches users without the notify pref")
- check(wait_task_done(auth, task1, timeout=60) is not None,
- "task 1 completes")
- _, done_payload = decrypt_webpush(push.hits("/main")[-1], browser,
- browser_public, rotated_auth)
- check(done_payload["title"] == "Push lifecycle task — completed"
- and done_payload["url"] == "/tasks/%d" % task1,
- "completion notifies the opted-in user (decrypted payload)")
- check(len(push.hits("/other")) == other_before,
- "completion stays with the opted-in user only")
- check(wait_for(lambda: subscription_count(endpoint_gone) == 0, timeout=15,
- what="410 endpoint pruned") is not None,
- "a 410 Gone endpoint is pruned from push_subscriptions")
- # Block events go to EVERY push-subscribed user globally. The
- # poll-era /update raise is gone: a live (non-terminal) host task
- # with an open session on the fake carries the block through the
- # results push — task1 is done and terminal pushes are consumed
- # silently, so the host is its own fixture row.
- conn = db()
- conn.execute("insert into tasks (project_id, title, status, priority,"
- " created_at) values (1, 'Push block host', 'working', 5,"
- " strftime('%s','now'))")
- push_host = conn.execute("select max(id) from tasks").fetchone()[0]
- conn.execute("insert into sessions (task_id, saddle_id, started_at)"
- " values (?, ?, strftime('%s','now'))", (push_host, fake_id))
- push_host_session = conn.execute(
- "select max(id) from sessions").fetchone()[0]
- conn.commit()
- conn.close()
- def raise_global_block(question):
- return saddle_api("POST", "/api/saddle/results", fake_token, {
- "session_id": push_host_session, "state": "blocked",
- "block": {"question": question, "type": "question"}})
- main_before = len(push.hits("/main"))
- other_before = len(push.hits("/other"))
- raise_global_block("Global question for everyone?")
- check(wait_for(lambda: len(push.hits("/main")) > main_before
- and len(push.hits("/other")) > other_before, timeout=15,
- what="global block push") is not None,
- "a raised block notifies every push-subscribed user globally")
- block_hit = push.hits("/other")[-1]
- _, payload = decrypt_webpush(block_hit, other_browser, other_public, other_auth)
- check(payload["title"] == "Push block host — question raised"
- and payload["body"] == "Global question for everyone?"
- and payload["url"] == "/tasks/%d" % push_host,
- "the block payload carries task title + kind, question and click URL (%s)"
- % payload)
- ciphertext_len = len(block_hit["body"]) - (21 + 65)
- envelope = block_hit["body"]
- record_size = int.from_bytes(envelope[16:20], "big")
- # rs = plaintext + delimiter (1) + tag (16) == ciphertext-with-tag len.
- check(record_size == ciphertext_len,
- "the RFC 8291 envelope record size covers payload + delimiter + tag")
- claims = verify_vapid(block_hit, vapid["public_key"], vapid["private_key"])
- check(claims.get("aud") == "http://127.0.0.1:%d" % push.port
- and claims.get("sub", "").startswith("mailto:")
- and claims.get("exp", 0) > time.time(),
- "the VAPID JWT verifies: aud = the endpoint origin, mailto sub, future exp")
- check(block_hit["headers"].get("ttl") == "60"
- and block_hit["headers"].get("content-type") == "application/octet-stream",
- "the push POST carries TTL and the encrypted content type")
- # Task 2: only the OTHER user opted in — start + failure reach them
- # alone. Baseline again before the enqueue (first start fires at
- # the allocation).
- fake.fail_next = True
- act(auth, slot_public(auth, "/tasks", "tasks")["create"],
- {"project_id": "1", "title": "Push failure task", "priority": "5"})
- task2 = task_id_of("Push failure task")
- conn = db()
- conn.execute("insert into task_notify_prefs (task_id, user_id) values (?, 999)",
- (task2,))
- conn.commit()
- conn.close()
- main_before2 = len(push.hits("/main"))
- other_before2 = len(push.hits("/other"))
- enqueue_task(auth, task2)
- check(wait_for(lambda: fake.running_task == task2 or False, timeout=30,
- what="task 2 assignment") is not None,
- "task 2 dispatches (scripted to fail)")
- check(wait_for(lambda: task_state(auth, task2)["status"] == "failed"
- or False, timeout=30, what="task 2 failed") is not None,
- "task 2 fails")
- check(wait_for(lambda: len(push.hits("/other")) >= other_before2 + 2,
- timeout=15, what="opted-in user's started+failed pushes"),
- "the opted-in user gets first start AND failure for the task")
- started_hit = [hit for hit in push.hits("/other")[other_before2:]][0]
- _, started_payload = decrypt_webpush(started_hit, other_browser,
- other_public, other_auth)
- check(started_payload["title"] == "Push failure task — started",
- "the first-start payload names task + kind (%s)" % started_payload)
- _, failure_payload = decrypt_webpush(push.hits("/other")[-1], other_browser,
- other_public, other_auth)
- check(failure_payload["title"] == "Push failure task — failed"
- and failure_payload["url"] == "/tasks/%d" % task2,
- "the failure payload names the task and links its page")
- check(len(push.hits("/main")) == main_before2,
- "an agent failure never reaches users without the pref")
- print("== task E: retry next tick (best-effort delivery) ==")
- endpoint_flaky = push.url("/flaky")
- act(auth, dash["push_subscribe"],
- {"endpoint": endpoint_flaky, **subscribe_fields})
- push.fail_next["/flaky"] = 1
- flaky_before = len(push.hits("/flaky"))
- raise_global_block("Flaky delivery question?")
- check(wait_for(lambda: len(push.hits("/flaky")) >= flaky_before + 2,
- timeout=20, what="flaky endpoint retried") is not None,
- "a failed send retries on the next dispatcher tick and lands")
- print("== plan 12: goal notifications — blocks/success/failure always ==")
- # Goal blocks, completion approval and failure push to EVERY
- # subscribed user (the mirror on the goal-run task stays quiet).
- def goal_state(goal_id):
- return slot_public(auth, "/goals/%d" % goal_id, "goal")
- push_goal = None
- act(auth, slot_public(auth, "/goals", "goals")["create"], {
- "project_id": "1", "title": "Push goal", "brief": "Notifies everyone.",
- "branch_name": "goal/push"})
- conn = db()
- row = conn.execute("select id from goals where title = 'Push goal'").fetchone()
- conn.close()
- push_goal = int(row[0])
- fake.goal_script = [("plan", "# Push plan\n\n1. Ring the bells")]
- main_before = len(push.hits("/main"))
- other_before = len(push.hits("/other"))
- act(auth, goal_state(push_goal)["start"], {})
- check(wait_for(lambda: goal_state(push_goal)["status"] == "plan_review" or False,
- timeout=60, what="push goal enters plan review") is not None,
- "the push goal reaches plan review")
- check(wait_for(lambda: len(push.hits("/main")) > main_before
- and len(push.hits("/other")) > other_before, timeout=15,
- what="goal plan-review push") is not None,
- "a goal plan review notifies every subscribed user globally")
- _, plan_payload = decrypt_webpush(push.hits("/other")[-1], other_browser,
- other_public, other_auth)
- check("Push goal" in plan_payload["title"] and "plan" in plan_payload["title"]
- and plan_payload["url"] == "/goals/%d" % push_goal,
- "the goal block payload carries the goal title + click URL (%s)"
- % plan_payload)
- main_before = len(push.hits("/main"))
- fake.goal_script = [("suspend",)]
- act(auth, goal_state(push_goal)["plan_pending_block"]["answer_action"],
- {"answer": "Plan approved. Proceed with implementation."})
- check(wait_for(lambda: goal_state(push_goal)["status"] == "active" or False,
- timeout=30, what="push goal active") is not None,
- "the push goal activates")
- check(quiet_window(3, lambda: len(push.hits("/main")) == main_before,
- "no push for an internal resume"),
- "the goal agent's own park/resume does not notify (internal)")
- # `saddle failure` in goal context fails the goal — global push.
- fake.goal_script = [("fail", "the harness collapsed")]
- other_before2 = len(push.hits("/other"))
- main_before2 = len(push.hits("/main"))
- conn = db()
- conn.execute("insert into tasks (project_id, goal_id, kind, title, status,"
- " priority, completed_at, created_at) values (1, ?, 'task',"
- " 'Warm', 'done', 5,"
- " strftime('%s','now'),"
- " strftime('%s','now','-30 seconds'))", (push_goal,))
- conn.commit()
- conn.close()
- check(wait_for(lambda: goal_state(push_goal)["status"] == "failed" or False,
- timeout=60, what="goal failure lands") is not None,
- "the goal agent's failure fails the goal")
- check(wait_for(lambda: len(push.hits("/other")) > other_before2
- and len(push.hits("/main")) > main_before2, timeout=15,
- what="goal failure push") is not None,
- "a goal failure notifies every subscribed user globally")
- _, fail_payload = decrypt_webpush(push.hits("/other")[-1], other_browser,
- other_public, other_auth)
- check("Push goal" in fail_payload["title"] and "failed" in fail_payload["title"]
- and fail_payload["url"] == "/goals/%d" % push_goal,
- "the goal failure payload carries the goal title + click URL (%s)"
- % fail_payload)
- # Completion approval pushes "complete" globally (success always).
- act(auth, slot_public(auth, "/goals", "goals")["create"], {
- "project_id": "1", "title": "Push done goal", "brief": "Completes.",
- "branch_name": "goal/push-done"})
- conn = db()
- row = conn.execute("select id from goals where title = 'Push done goal'").fetchone()
- conn.close()
- done_goal = int(row[0])
- fake.goal_script = [("plan", "# Done plan")]
- act(auth, goal_state(done_goal)["start"], {})
- check(wait_for(lambda: goal_state(done_goal)["status"] == "plan_review" or False,
- timeout=60, what="done goal plan review") is not None,
- "the done goal reaches plan review")
- fake.goal_script = [("done", "# Completion\n\nShipped.")]
- act(auth, goal_state(done_goal)["plan_pending_block"]["answer_action"],
- {"answer": "Plan approved. Proceed with implementation."})
- check(wait_for(lambda: goal_state(done_goal)["status"] == "completion_review"
- or False, timeout=60, what="done goal completion review") is not None,
- "the approved done goal reaches completion review")
- main_before = len(push.hits("/main"))
- act(auth, goal_state(done_goal)["completion_pending_block"]["answer_action"],
- {"answer": "Review approved. Continue with the task."})
- check(wait_for(lambda: goal_state(done_goal)["status"] == "complete" or False,
- timeout=30, what="done goal completes") is not None,
- "the done goal completes")
- check(wait_for(lambda: len(push.hits("/main")) > main_before, timeout=15,
- what="goal success push") is not None,
- "a goal completion notifies every subscribed user globally")
- _, done_goal_payload = decrypt_webpush(push.hits("/main")[-1], browser,
- browser_public, rotated_auth)
- check("Push done goal" in done_goal_payload["title"]
- and "complete" in done_goal_payload["title"]
- and done_goal_payload["url"] == "/goals/%d" % done_goal,
- "the goal success payload carries the goal title + click URL (%s)"
- % done_goal_payload)
- print("== task E: unsubscribe removes the subscription ==")
- dash = slot_public(auth, "/", "dashboard")
- check(dash.get("push_subscribed") is True,
- "the bell shows subscribed while endpoints exist")
- act(auth, dash["push_unsubscribe"], {"endpoint": endpoint_main})
- check(subscription_count(endpoint_main) == 0,
- "unsubscribing removes the endpoint's row")
- # =============================================================================
- # Bundle: goals — the plan-12 goal lifecycle: creation with branch-name
- # uniqueness, the start → planning → plan review → active flow with
- # revisions, the goal agent's task fan-out through the task CLI
- # callbacks (ownership + creator-only enqueue), the event-driven
- # coalesced resume, pause/resume queueing, the completion review with
- # branch push state, the cancel cascade, and the human tasks'
- # auto-enqueue-after predecessor.
- # =============================================================================
- def bundle_goals(ctx):
- auth, fake, fake_id, fake_token = ctx.auth, ctx.fake, ctx.fake_id, ctx.fake_token
- app = ctx.app
- def goal_state(goal_id):
- return slot_public(auth, "/goals/%d" % goal_id, "goal")
- def create_goal(fields):
- act(auth, slot_public(auth, "/goals", "goals")["create"], fields)
- rows = [g for g in slot_public(auth, "/goals", "goals")["goals"]
- if g["title"] == fields["title"]]
- return int(rows[0]["id"])
- print("== plan 12: goal creation, validation, lifecycle actions ==")
- goal1 = create_goal({"project_id": "1", "title": "Ship the flange",
- "brief": "Make the flange module production-grade.",
- "branch_name": "goal/flange", "priority": "3"})
- state = goal_state(goal1)
- check(state["status"] == "draft" and state["display"] == "draft"
- and state["branch_name"] == "goal/flange"
- and state["base_branch"] == "main",
- "a goal lands in draft with its branch pair")
- check(state.get("start") is not None and state.get("cancel") is not None
- and state.get("pause") is None,
- "draft offers start + cancel only")
- check(state["run_task_id"] == 0 and state["comments"] == [],
- "a draft goal has no thread yet (the run task owns it)")
- draft_page = http_get_page(auth, "/goals/%d" % goal1)
- check('stm-if="goal.run_task_id > 0" stm-action="goal.comment"' in draft_page
- and "comments open with its run" in draft_page,
- "the draft goal page gates the composer on the run (muted note until then)")
- # Branch-name uniqueness: same name, same involved project → refuse.
- duplicate_directives = act(auth, slot_public(auth, "/goals", "goals")["create"], {
- "project_id": "1", "title": "Clash", "brief": "b", "branch_name": "goal/flange"})
- check(any(d.get("type") == "notify" and d.get("kind") == "error"
- and "goal/flange" in (d.get("message") or "")
- for d in duplicate_directives),
- "a live goal's branch name cannot be reused in an involved project (%s)"
- % [d.get("message") for d in duplicate_directives if d.get("type") == "notify"])
- conn = db()
- conn.execute("insert into projects (name, git_url, default_branch, created_at)"
- " values ('other-repo', 'https://example.com/other.git', 'main',"
- " strftime('%s','now'))")
- conn.commit()
- conn.close()
- goal_elsewhere = create_goal({"project_id": "2", "title": "Elsewhere",
- "brief": "b", "branch_name": "goal/flange"})
- check(goal_elsewhere > 0,
- "the same branch name in a disjoint project is fine (one per repo)")
- # Brief validation.
- brief_directives = act(auth, slot_public(auth, "/goals", "goals")["create"], {
- "project_id": "1", "title": "No brief", "brief": " ", "branch_name": "goal/x1"})
- check(any(d.get("type") == "notify" and d.get("kind") == "error"
- and "brief" in (d.get("message") or "").lower()
- for d in brief_directives),
- "an empty brief rejects")
- # Start: planning + the goal-run internal task.
- act(auth, state["start"], {})
- state = goal_state(goal1)
- check(state["status"] == "planning" and state.get("start") is None,
- "start moves the goal to planning")
- conn = db()
- run_row = conn.execute(
- "select id, kind, mode, creator, goal_id, priority, status from tasks"
- " where goal_id = ? and kind = 'goal_run'", (goal1,)).fetchone()
- conn.close()
- check(run_row is not None and run_row[1] == "goal_run"
- and run_row[2] == "orchestrate" and run_row[3] == "goal"
- and run_row[4] == goal1 and run_row[5] == 3
- and run_row[6] in ("queued", "assigned", "working"),
- "start creates the goal-run task (orchestrate, priority 3, creator goal,"
- " queued)")
- run_task = int(run_row[0])
- check(goal_state(goal1)["run_task_id"] == run_task,
- "the goal state carries the run task id")
- check(any(e["kind"] == "started" for e in goal_state(goal1)["events"]),
- "the narrative records the start")
- print("== feedback: goal-agent runs are hidden from every task listing ==")
- # The internal run task never lists anywhere tasks are browsed —
- # only the goal page exposes it (worker tasks stay listed). The
- # plan script is armed BEFORE the first dispatch so this section's
- # waits never consume the round the plan-review flow below needs.
- fake.goal_script = [("plan", "# Plan v1\n\n1. Add tests\n2. Ship")]
- check(wait_for(lambda: task_state(auth, run_task)["status"]
- in ("assigned", "working") or False,
- timeout=60, what="run task dispatches") is not None,
- "the run task reaches its saddle")
- queue_rows = slot_public(auth, "/tasks", "tasks")["tasks"]
- check(all(int(t["id"]) != run_task for t in queue_rows),
- "the tasks queue never lists the goal-agent run task")
- check(all(t["kind"] != "goal_run" for t in queue_rows),
- "no goal_run rows leak into the queue at all")
- project_state = slot_public(auth, "/projects/1", "project")
- check(all(int(t["id"]) != run_task for t in project_state["tasks"]),
- "the project page's task list excludes the run task")
- dashboard = slot_public(auth, "/", "dashboard")
- check(all(int(a["id"]) != run_task for a in dashboard["active_tasks"]),
- "the dashboard's active tasks exclude the run task")
- check(all(int(i["task_id"]) != run_task for i in dashboard["activity"]),
- "the activity timeline never references the run task")
- check(wait_for(lambda: int(next(
- (s for s in slot_public(auth, "/", "dashboard")["saddles"]
- if s["name"] == "fake-saddle"), {})
- .get("current_goal_id") or 0) == goal1 or False,
- timeout=30, what="saddle card links the goal") is not None,
- "a saddle hosting the goal agent reads as supervising the goal")
- fake_dash_card = next(s for s in slot_public(auth, "/", "dashboard")["saddles"]
- if s["name"] == "fake-saddle")
- check(fake_dash_card["current_goal_title"] == "Ship the flange",
- "the supervision link carries the goal title")
- print("== plan 12: the goal agent plans, plan review, revisions ==")
- # (The plan script was armed before the run's first dispatch — see
- # the listing-hiding section above — so the first round submits it.)
- check(wait_for(lambda: goal_state(goal1)["status"] == "plan_review" or False,
- timeout=120, what="goal agent submits its plan") is not None,
- "the goal agent's plan push enters plan review")
- state = goal_state(goal1)
- check(task_state(auth, run_task)["status"] == "blocked",
- "the goal-run task parks on the plan review")
- # U3's plan-review counterpart: the run-task card never reads as a
- # bare "blocked" while a review-shaped block parks it.
- check(task_state(auth, run_task)["display"] == "awaiting your review",
- "the run-task card reads 'awaiting your review' during the plan review (U3)")
- pending = state.get("plan_pending_block")
- check(pending and "# Plan v1" in (pending.get("body") or ""),
- "the pending plan review carries the submitted markdown")
- check(any(b["block_type"] == "plan" and b["is_plan_review"]
- for b in state["blocks"]),
- "the goal blocks list carries the pending plan block")
- print("== feedback: goal visual identity ==")
- # A goal never reads as a task at a glance: marked nav entry,
- # iconed list page, and the dashboard pile's goal cards carry
- # their own accent styling.
- home_html = http_get_page(auth, "/")
- check('class="nav-goal"' in home_html and "🎯" in home_html,
- "the nav carries the marked 🎯 Goals entry beside Tasks")
- check(any(p["item_type"] == "goal" for p in
- slot_public(auth, "/", "dashboard")["pinned"]),
- "the goal's pending block pins onto the dashboard pile")
- check("'card goal-card'" in home_html and 'badge-kind-goal' in home_html,
- "dashboard goal cards carry the distinct goal styling")
- goals_html = http_get_page(auth, "/goals")
- check("🎯" in goals_html and "goal-icon" in goals_html,
- "the goals list page carries the goal icon")
- goal_page_html = http_get_page(auth, "/goals/%d" % goal1)
- check('class="card goal-head"' in goal_page_html and "goal-page" in goal_page_html,
- "the goal page carries the goal-accented identity chrome")
- # Reject with feedback: the agent revises.
- fake.goal_script = [("plan", "# Plan v2\n\n1. Add tests\n2. Add docs\n3. Ship")]
- reject_resume_index = len(fake.goal_assignments)
- act(auth, pending["answer_action"], {"answer": "Add documentation please"})
- check(wait_for(lambda: goal_state(goal1)["status"] == "plan_review" or False,
- timeout=60, what="revised plan enters review") is not None,
- "feedback sends the agent back to planning; the revision re-enters review")
- # The rejection resume carried NO context_update (nothing in the
- # instruction context had changed).
- check(all("context_update" not in p
- for p in fake.goal_assignments[reject_resume_index:]),
- "a resume without context changes carries no context_update")
- state = goal_state(goal1)
- revisions = state["plan_revisions"]
- check(len(revisions) == 2
- and revisions[0]["answered"] and "Add documentation please" in revisions[0]["answer"]
- and not revisions[1]["answered"],
- "rejected revisions stay as answered history (historical-blocks data)")
- # Plan 16 S1 pin: goals.plan is accept-gated — the pending revision
- # lives only in its block; nothing lands on the goal before accept.
- conn = db()
- plan_column = conn.execute(
- "select plan from goals where id = ?", (goal1,)).fetchone()[0]
- conn.close()
- check((plan_column is None or plan_column == "")
- and (state["plan"] or "") == "",
- "goals.plan stays empty while the revision is only pending (accept-gated, S1)")
- # Approve: goal.plan = v2, status active, agent resumes and parks.
- # Plan 16 S2: the accept posts the verdict MARKER — the server
- # resolves the goal-plan kind and owns the constant.
- fake.goal_script = [("suspend",)]
- pending = state["plan_pending_block"]
- act(auth, pending["answer_action"], {"verdict": "accept"})
- check(wait_for(lambda: goal_state(goal1)["status"] == "active" or False,
- timeout=60, what="approved plan activates the goal") is not None,
- "approving the plan activates the goal")
- state = goal_state(goal1)
- check(state["plan"] == "# Plan v2\n\n1. Add tests\n2. Add docs\n3. Ship",
- "the approved plan markdown is stored on the goal (the accept verdict's write)")
- # The approval resume carries the plan delta as a context_update
- # turn (the prompt-cache-friendly replacement for the daemon's
- # old system-prompt refresh).
- check(wait_for(lambda: len(fake.goal_assignments) > 0 and
- "context_update" in fake.goal_assignments[-1] or False,
- timeout=30, what="approval resume carries context_update") is not None,
- "the plan-approval resume carries a context_update turn")
- approval_update = fake.goal_assignments[-1]["context_update"]
- check("Plan approved" in approval_update and "# Plan v2" in approval_update,
- "the context_update summarises the approved plan markdown")
- check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
- or False, timeout=60, what="goal agent parks after approval") is not None,
- "the resumed goal agent parks (saddle suspend)")
- print("== plan 12: task fan-out via the task CLI callbacks ==")
- # The goal agent (parked, open session on its saddle) creates,
- # enqueues and deletes its tasks through /api/saddle/task/*.
- conn = db()
- agent_session = conn.execute(
- "select id from sessions where task_id = ? and ended_at is null",
- (run_task,)).fetchone()[0]
- conn.close()
- status, body = saddle_api("POST", "/api/saddle/task/create", fake_token, {
- "session_id": agent_session, "title": "Flange tests",
- "mandate": "Write the test suite for the flange module.",
- "mode": "test"})
- check(status == 200 and body.get("ok") is True,
- "task create under the goal succeeds (%s)" % body.get("error"))
- fan_task = int(body["task_id"])
- detail = task_state(auth, fan_task)
- check(detail["status"] == "draft" and detail["mode"] == "test"
- and detail["creator"] == "goal" and detail.get("goal_id") == goal1,
- "the created task is a draft goal task with its mode and creator")
- check(detail["task_branch"] == "task/%d-%d" % (goal1, fan_task)
- and detail["branch_from"] == "goal/flange",
- "goal worker tasks get ephemeral branches cut from the goal branch")
- check(any(e["kind"] == "task-created" and int(e["task_id"]) == fan_task
- for e in goal_state(goal1)["events"]),
- "the narrative records the agent's task creation")
- # Draft until the agent enqueues; humans may not (creator-only).
- check(detail.get("enqueue") is None,
- "the human Enqueue action is not offered for goal tasks (creator-only)")
- status, body = saddle_api("POST", "/api/saddle/task/enqueue", fake_token, {
- "session_id": agent_session, "task_id": fan_task})
- check(status == 200 and task_state(auth, fan_task)["status"] == "queued",
- "task enqueue by the goal agent queues the draft")
- # Ownership + shape errors, verbatim for the CLI.
- status, body = saddle_api("POST", "/api/saddle/task/enqueue", fake_token, {
- "session_id": agent_session, "task_id": 999999})
- check(status == 400 and "No task exists" in (body.get("error") or ""),
- "enqueue of a missing task errors verbatim")
- create_queued(auth, {"project_id": "1", "title": "Innocent bystander", "priority": "4"})
- bystander = task_id_of("Innocent bystander")
- status, body = saddle_api("POST", "/api/saddle/task/delete", fake_token, {
- "session_id": agent_session, "task_id": bystander})
- check(status == 400 and "does not belong to your goal" in (body.get("error") or ""),
- "the goal agent cannot touch tasks outside its goal")
- status, body = saddle_api("POST", "/api/saddle/task/create", fake_token, {
- "session_id": agent_session, "title": "Bad mode", "mandate": "m", "mode": "party"})
- check(status == 400 and "valid modes" in (body.get("error") or ""),
- "an unknown mode rejects with the valid list")
- status, body = saddle_api("POST", "/api/saddle/task/create", fake_token, {
- "session_id": agent_session, "title": "Bad tag", "mandate": "m",
- "capabilities": ["warp-drive"]})
- check(status == 400 and "warp-drive" in (body.get("error") or "")
- and "valid tags" in (body.get("error") or ""),
- "an unknown capability tag rejects naming the valid set")
- # A plain task's session is not a goal-agent identity.
- status, body = saddle_api("POST", "/api/saddle/task/create", fake_token,
- {"session_id": 999999, "title": "x", "mandate": "m"})
- check(status == 400 and "session" in (body.get("error") or ""),
- "an unknown session_id errors verbatim")
- # Delete: draft + owned.
- status, body = saddle_api("POST", "/api/saddle/task/create", fake_token, {
- "session_id": agent_session, "title": "Scratch task", "mandate": "m"})
- scratch = int(body["task_id"])
- status, body = saddle_api("POST", "/api/saddle/task/delete", fake_token, {
- "session_id": agent_session, "task_id": scratch})
- check(status == 200 and not any(
- t["id"] == scratch for t in slot_public(auth, "/tasks", "tasks")["tasks"]),
- "task delete removes the owned draft")
- status, body = saddle_api("POST", "/api/saddle/task/delete", fake_token, {
- "session_id": agent_session, "task_id": fan_task})
- check(status == 400 and "draft" in (body.get("error") or ""),
- "deleting a queued task refuses (draft only)")
- print("== plan 12: goal context in the worker assignment + coalesced resume ==")
- # The queued fan-out task dispatches to the daemon with the goal
- # reference + ephemeral branch; its completion schedules the parked
- # goal agent's resume (>= SADDLE_GOAL_RESUME_S after the event).
- check(wait_for(lambda: any(int(p.get("task", {}).get("id") or 0) == fan_task
- for p in fake.start_payloads) or False,
- timeout=60, what="fan-out task dispatches") is not None,
- "the goal worker task dispatches")
- fan_payload = [p for p in fake.start_payloads
- if int(p.get("task", {}).get("id") or 0) == fan_task][-1]
- goal_ctx = fan_payload.get("goal") or {}
- check(goal_ctx.get("id") == goal1 and goal_ctx.get("branch") == "goal/flange"
- and goal_ctx.get("base_branch") == "main"
- and "flange module" in goal_ctx.get("brief", "")
- and "# Plan v2" in goal_ctx.get("plan", ""),
- "the worker assignment carries the goal context (read-only brief + plan)")
- check(fan_payload.get("ephemeral_branch") == "task/%d-%d" % (goal1, fan_task)
- and fan_payload.get("kind") == "task" and fan_payload.get("mode") == "test"
- and fan_payload.get("creator") == "goal",
- "the worker assignment carries the ephemeral branch + identity triplet")
- check(wait_task_done(auth, fan_task, timeout=60) is not None,
- "the goal worker task completes")
- check(any(e["kind"] == "task-done" and int(e["task_id"]) == fan_task
- for e in goal_state(goal1)["events"]),
- "the narrative records the task's completion")
- goal_runs_before = len(fake.goal_assignments)
- check(wait_for(lambda: len(fake.goal_assignments) > goal_runs_before or False,
- timeout=30, what="parked goal agent resumes") is not None,
- "the task-terminal event resumes the parked goal agent")
- resume_payload = fake.goal_assignments[-1]
- notice = resume_payload.get("resume_notice") or ""
- check("Task statuses" in notice and "Flange tests" in notice
- and "done" in notice,
- "the resume notice batches ALL plan task statuses")
- check("New completion reports" in notice and "fake daemon finished" in notice,
- "the resume notice carries the new completion report")
- check("context_update" not in resume_payload,
- "a later resume with no context changes omits context_update entirely")
- check(resume_payload.get("kind") == "goal_run"
- and resume_payload.get("mode") == "orchestrate",
- "the resumed goal agent carries its identity triplet")
- # The resumed agent parked again (script): two events at once must
- # coalesce into ONE resume within the window.
- check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
- or False, timeout=60, what="goal agent re-parks") is not None,
- "the resumed goal agent parks again")
- print("== plan 16 S3: delta resume notices — one-line statuses, bodies once ==")
- # A MULTI-LINE completion lands while the agent is parked. The next
- # notice lists every status ONE-LINE (first line of the summary
- # only) and embeds each NEW completion's full body exactly once —
- # no statuses-section/reports-section double embedding. (Timestamps
- # are second-granular: each fixture waits out the park's second so
- # completed_at is strictly newer than suspended_at.)
- def run_park_second_passed():
- conn = db()
- try:
- row = conn.execute("select suspended_at from tasks where id = ?",
- (run_task,)).fetchone()
- if not row or not row[0]:
- return False
- return time.time() > float(row[0]) + 1.05
- finally:
- conn.close()
- check(wait_for(run_park_second_passed, timeout=30,
- what="S3 park second passes") is not None,
- "the park stamp is in a strictly older second")
- conn = db()
- conn.execute("insert into tasks (project_id, goal_id, kind, title, status,"
- " priority, summary, completed_at, created_at) values (1, ?, 'task',"
- " 'Multiline report', 'done', 5,"
- " 'Headline: the flange calibrates\n\nBody: the full report with all"
- " the detail lines.',"
- " strftime('%s','now'),"
- " strftime('%s','now','-30 seconds'))", (goal1,))
- conn.commit()
- conn.close()
- s3_before = len(fake.goal_assignments)
- check(wait_for(lambda: len(fake.goal_assignments) > s3_before or False,
- timeout=60, what="S3 delta resume fires") is not None,
- "the multiline completion resumes the parked agent")
- s3_notice = fake.goal_assignments[-1].get("resume_notice") or ""
- s3_statuses = s3_notice.split("## Task statuses", 1)[1] \
- .split("## New completion reports", 1)[0]
- check("Headline: the flange calibrates" in s3_statuses
- and "the full report with all the detail lines" not in s3_statuses,
- "the statuses section carries ONE LINE per task (the summary's first line)")
- check(s3_notice.count("the full report with all the detail lines.") == 1
- and "### #" in s3_notice,
- "each new completion's full body embeds EXACTLY ONCE (no double embedding)")
- check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
- or False, timeout=60, what="S3 agent re-parks") is not None,
- "the S3 agent re-parks")
- # Size constant per event, not cumulative: the NEXT resume (armed
- # with a fresh single-line completion) reports only the NEW body —
- # the consumed multiline report never re-embeds.
- check(wait_for(run_park_second_passed, timeout=30,
- what="S3 second park second passes") is not None,
- "the second park stamp is in a strictly older second")
- conn = db()
- conn.execute("insert into tasks (project_id, goal_id, kind, title, status,"
- " priority, summary, completed_at, created_at) values (1, ?, 'task',"
- " 'Fresh completion', 'done', 5, 'fresh one-liner',"
- " strftime('%s','now'),"
- " strftime('%s','now','-30 seconds'))", (goal1,))
- conn.commit()
- conn.close()
- s3_more = len(fake.goal_assignments)
- check(wait_for(lambda: len(fake.goal_assignments) > s3_more or False,
- timeout=60, what="S3 second delta resume") is not None,
- "the fresh completion resumes the agent again")
- s3_next = fake.goal_assignments[-1].get("resume_notice") or ""
- check("the full report with all the detail lines." not in s3_next
- and "### #" in s3_next and "fresh one-liner" in s3_next,
- "the consumed completion's body never re-embeds (notice size constant per event)")
- check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
- or False, timeout=60, what="S3 agent parks a third time") is not None,
- "the S3 agent parks for the pause section")
- print("== feedback: goal page parity — progress feed, current activity, runs ==")
- # The goal agent's runs feed the page's live surfaces: progress
- # lines (green latest-first feed), the open round's newest
- # activity line and its hosting saddle. Parked runs keep their
- # open session, so fixtures land as rows without disturbing the
- # scripted daemon.
- conn = db()
- open_run_session = conn.execute(
- "select id from sessions where task_id = ? and ended_at is null",
- (run_task,)).fetchone()[0]
- conn.execute(
- "insert into task_progress (task_id, summary, created_at)"
- " values (?, 'Overseeing the flange fan-out', strftime('%s','now'))",
- (run_task,))
- conn.execute(
- "insert into session_commands (session_id, command_text, type, started_at)"
- " values (?, 'git -C demo log --oneline -5', 'command',"
- " strftime('%s','now'))", (open_run_session,))
- conn.commit()
- conn.close()
- # A manual refresh records the daemon's health snapshot (the
- # parked daemon answers /status with its suspended state).
- act(auth, saddle_card(auth, "fake-saddle")["refresh"], {})
- state = goal_state(goal1)
- check(state["progress"] and state["progress"][0]["summary"]
- == "Overseeing the flange fan-out",
- "the goal page carries the agent's live progress feed")
- check(state["current_command"] == "git -C demo log --oneline -5"
- and state["current_command_running"] is True,
- "the current activity shows the open round's running command")
- check(state["agent_saddle"] == "fake-saddle"
- and "fake-saddle" in state["saddle_health"],
- "the assigned agent saddle and its health display")
- check(state["runs"] and state["runs"][0]["saddle_name"] == "fake-saddle"
- and "session_id=" in state["runs"][0]["terminal_url"],
- "the run table carries host saddles and deep-linked terminals")
- goal_page = http_get_page(auth, "/goals/%d" % goal1)
- check('stm-markdown="goal.brief"' in goal_page
- and 'stm-markdown="goal.plan"' in goal_page
- and 'stm-markdown="comment.body"' in goal_page
- and 'stm-markdown="event.text"' in goal_page
- and 'stm-markdown="block.body"' in goal_page,
- "every long-form goal body renders through stm-markdown")
- check('stm-attribute.href="run.terminal_url" target="_blank" rel="noopener"' in goal_page,
- "the run table's terminal links open in a new tab")
- check("progress-latest" in goal_page and "Agent progress" in goal_page,
- "the green progress feed renders on the goal page")
- # Plan revision history keeps its verdicts (approved + rejected).
- revisions = goal_state(goal1)["plan_revisions"]
- check(revisions[0]["answered"] is True and revisions[0]["approved"] is False
- and revisions[1]["approved"] is True,
- "plan revision history badges approved and rejected verdicts")
- print("== plan 12: pause freezes supervision; events coalesce on resume ==")
- state = goal_state(goal1)
- act(auth, state["pause"], {})
- check(goal_state(goal1)["status"] == "paused"
- and goal_state(goal1).get("resume") is not None,
- "an active goal pauses and offers resume")
- # Terminal events under a paused goal queue without waking anybody.
- # (The real completion path — complete_task — records the goal's
- # task-done narrative event; the resume machinery keys on exactly
- # those terminal kinds, never on the agent's own enqueue narrative.)
- conn = db()
- conn.execute("insert into tasks (project_id, goal_id, kind, title, status, priority,"
- " summary, completed_at, created_at) values (1, ?, 'task',"
- " 'Quiet task', 'done', 5, 'finished while paused',"
- " strftime('%s','now'), strftime('%s','now'))", (goal1,))
- quiet_id = conn.execute("select max(id) from tasks").fetchone()[0]
- conn.commit()
- conn.close()
- paused_runs = len(fake.goal_assignments)
- check(quiet_window(quiet_seconds(),
- lambda: len(fake.goal_assignments) == paused_runs,
- "paused goal does not resume"),
- "a paused goal's events queue without scheduling a resume")
- state = goal_state(goal1)
- act(auth, state["resume"], {})
- check(goal_state(goal1)["status"] == "active",
- "resume returns the goal to active")
- check(wait_for(lambda: len(fake.goal_assignments) > paused_runs or False,
- timeout=30, what="queued events flush on resume") is not None,
- "the queued events coalesce into the post-resume wake")
- check("finished while paused" in (fake.goal_assignments[-1].get("resume_notice") or ""),
- "the flushed notice reports the task that finished while paused")
- check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
- or False, timeout=60, what="goal agent parks again") is not None,
- "the goal agent parks again after the flush")
- # The watermark consumed the flushed transition: with nothing new
- # under the goal, no further resume fires (parked stays parked).
- flushed_assignments = len(fake.goal_assignments)
- check(quiet_window(quiet_seconds(),
- lambda: len(fake.goal_assignments) == flushed_assignments,
- "consumed watermark never re-fires"),
- "the consumed watermark never re-fires a resume")
- print("== plan 12: completion review, push state, accept ==")
- # The daemon exposes goal-branch push state on /status; the web
- # caches it on the goal when it probes the goal agent's saddle.
- def status_extra():
- return None
- fake.status_extra = {"goal_branches": [
- {"branch": "goal/flange", "pushed": True},
- {"branch": "goal/other-repo", "pushed": False}]}
- fake_card = saddle_card(auth, "fake-saddle")
- act(auth, fake_card["refresh"], {})
- check(wait_for(lambda: goal_state(goal1).get("push_branches") or False,
- timeout=30, what="push state lands on the goal") is not None,
- "the daemon's /status goal_branches exposure caches on the goal")
- branches = goal_state(goal1)["push_branches"]
- check({(b["branch"], b["pushed"]) for b in branches}
- == {("goal/flange", True), ("goal/other-repo", False)},
- "the push state renders per branch")
- # the daemon's real shape carries repo too — multi-repo rows stay
- # distinguishable as "repo: branch" labels
- fake.status_extra = {"goal_branches": [
- {"repo": "repoa", "branch": "goal/flange", "pushed": True},
- {"repo": "repob", "branch": "goal/flange", "pushed": False}]}
- act(auth, fake_card["refresh"], {})
- check(wait_for(lambda: {b["branch"] for b in goal_state(goal1).get("push_branches") or []}
- >= {"repoa: goal/flange"}, timeout=30,
- what="repo-qualified push state lands") is not None,
- "repo-qualified goal_branches rows render as 'repo: branch'")
- fake.status_extra = None
- # `saddle done` in goal context: the completion review.
- fake.goal_script = [("done", "# Completion\n\nThe flange ships.")]
- # Wake the parked agent: a terminal transition newer than the
- # watermark (the pause-flush consumed everything up to it, so arm
- # one directly with a fresh row).
- conn = db()
- conn.execute("insert into tasks (project_id, goal_id, kind, title, status,"
- " priority, completed_at, created_at) values (1, ?, 'task',"
- " 'Warmup', 'done', 5,"
- " strftime('%s','now'),"
- " strftime('%s','now','-30 seconds'))", (goal1,))
- conn.commit()
- conn.close()
- check(wait_for(lambda: goal_state(goal1)["status"] == "completion_review" or False,
- timeout=60, what="goal agent submits its completion") is not None,
- "`saddle done` in goal context enters completion review")
- state = goal_state(goal1)
- # Plan 16 S1 (canonical-on-accept): while the review is PENDING the
- # goal row carries NO report — the document lives only in its
- # block; completion_report is written solely by the accept verdict.
- conn = db()
- pending_report_column = conn.execute(
- "select completion_report from goals where id = ?", (goal1,)).fetchone()[0]
- conn.close()
- check((pending_report_column is None or pending_report_column == "")
- and (state["completion_report"] or "") == ""
- and "# Completion" in (state["completion_pending_block"].get("body") or "")
- and state["completion_pending"] is True
- and state["completion_pending_block"] is not None
- and state["completion_pending_block"] is not False,
- "the pending report lives ONLY in its block (goal row clean, S1)")
- check(task_state(auth, run_task)["status"] == "blocked",
- "the goal-run task parks on the completion review")
- # Plan 16 S5 (U3): label reconciliation — the goal awaiting its
- # completion review reads "awaiting your review" and the run-task
- # status card agrees (never a bare "blocked" for a review park).
- check(goal_state(goal1)["display"] == "awaiting your review"
- and task_state(auth, run_task)["display"] == "awaiting your review",
- "the goal and the run-task card agree: 'awaiting your review' (U3)")
- check(state["compare_url"].endswith("/compare/main...goal/flange"),
- "the completion review links the compare view")
- # Reject first: feedback returns the goal to active. Plan 16 S2:
- # the goal page's reject posts raw `feedback` — the SERVER wraps
- # it with the completion-review prefix (byte-identical to the
- # historical client-built answer).
- fake.goal_script = [("suspend",)]
- act(auth, state["completion_pending_block"]["answer_action"],
- {"feedback": "Not done — the docs are missing"})
- check(wait_for(lambda: goal_state(goal1)["status"] == "active" or False,
- timeout=30, what="rejected completion reactivates") is not None,
- "rejecting the completion returns the goal to active")
- conn = db()
- rejected_answer = conn.execute(
- "select answer from task_blocks where task_id = ?"
- " and type = 'completion' order by id desc", (run_task,)).fetchone()[0]
- conn.close()
- check(rejected_answer
- == "Review feedback received:\n\nNot done — the docs are missing",
- "the completion rejection wraps feedback with the stable prefix (%r)"
- % rejected_answer)
- conn = db()
- rejected_report = conn.execute(
- "select completion_report from goals where id = ?", (goal1,)).fetchone()[0]
- conn.close()
- check(rejected_report is None or rejected_report == "",
- "reject/revise leaves the goal row clean (no report leaked, S1)")
- # Round 2: submit again and accept.
- fake.goal_script = [("done", "# Completion v2\n\nThe flange ships, with docs.")]
- conn = db()
- conn.execute("insert into tasks (project_id, goal_id, kind, title, status,"
- " priority, completed_at, created_at) values (1, ?, 'task',"
- " 'Warmup2', 'done', 5,"
- " strftime('%s','now'),"
- " strftime('%s','now','-30 seconds'))", (goal1,))
- conn.commit()
- conn.close()
- check(wait_for(lambda: goal_state(goal1)["status"] == "completion_review" or False,
- timeout=60, what="resubmitted completion") is not None,
- "the agent resubmits its completion after feedback")
- state = goal_state(goal1)
- # Regression (plan-15 followup): the daemon's done-park ALSO pushes a
- # review-kind block payload (question "Goal completion review", the
- # real daemon's wording) after the done entry raised the completion
- # row — the two paths must converge on ONE row, not twin reviews.
- fake.push_results("blocked", block={
- "question": "Goal completion review", "type": "review",
- "body": "# Completion v2\n\nThe flange ships, with docs.",
- "options": None})
- conn = db()
- rows = conn.execute(
- "select count(*) from task_blocks where task_id = ?"
- " and answer is null and type in ('completion','review')",
- (run_task,)).fetchone()[0]
- conn.close()
- check(rows == 1,
- "the daemon's review-kind completion push adopts the pending row (no twin)")
- state = goal_state(goal1)
- review_family_in_open = [b for b in state.get("open_blocks", [])
- if b.get("is_completion_review")]
- check(state["completion_pending"] is True and len(review_family_in_open) == 1,
- "the pending completion review renders ONCE — as the rich attention block")
- fake.pending_block = None
- # Raise-side single-row invariant (the render guard and the
- # twin-answer sweep are gone — fresh DBs cannot hold pre-dedupe
- # twins, and 4884259's raise-side cross-dedupe keeps it that way):
- # a REDELIVERED review-kind completion push adopts the same pending
- # row instead of stacking a twin.
- fake.push_results("blocked", block={
- "question": "Goal completion review", "type": "review",
- "body": "# Completion v2\n\nThe flange ships, with docs.",
- "options": None})
- fake.pending_block = None
- conn = db()
- rows = conn.execute(
- "select count(*) from task_blocks where task_id = ?"
- " and answer is null and type in ('completion','review')",
- (run_task,)).fetchone()[0]
- conn.close()
- check(rows == 1,
- "a redelivered review-kind push adopts the pending row (raise-side single-row invariant)")
- # Plan 15 C4: the completion-accept hard gate — the live probe
- # flags still report repob's branch unpushed, so the operator's
- # accept REFUSES with origin behind.
- refusal = action_notify(act(auth, state["completion_pending_block"]["answer_action"],
- {"answer": "Review approved. Continue with the task."}))
- check(goal_state(goal1)["status"] == "completion_review",
- "the accept REFUSES while origin is behind (hard gate, live flags)")
- # One parser, one label: the gate's refusal names the SAME repo-
- # qualified unpushed row the chips render — both surfaces read
- # GoalService.parse_goal_branches (the gate-vs-chips divergence is
- # structurally gone).
- branches = {(b["branch"], b["pushed"]) for b in goal_state(goal1)["push_branches"]}
- check("repob: goal/flange" in (refusal or "")
- and ("repob: goal/flange", False) in branches
- and ("repoa: goal/flange", True) in branches,
- "the refusal and the chips read ONE parser (same labels, same flags)")
- # Push the branch daemon-side and probe again — the flags flip
- # live (no goal-row write), and the accept goes through.
- fake.status_extra = {"goal_branches": [
- {"repo": "repoa", "branch": "goal/flange", "pushed": True},
- {"repo": "repob", "branch": "goal/flange", "pushed": True}]}
- act(auth, fake_card["refresh"], {})
- check(wait_for(lambda: (goal_state(goal1).get("push_branches") or [])
- and all(b["pushed"] for b in goal_state(goal1)["push_branches"])
- or False, timeout=30, what="pushed flags land") is not None,
- "the push chips derive from the LIVE probe flags")
- conn = db()
- no_push_column = conn.execute(
- "select count(*) from pragma_table_info('goals')"
- " where name = 'branch_push_state'").fetchone()[0] == 0
- conn.close()
- check(no_push_column,
- "no stored push snapshot exists (F9: the goal row never records it)")
- state = goal_state(goal1)
- # The passing accept posts the verdict MARKER (the goal page's
- # accept form shape, plan 16 S2) — the server resolves the
- # completion kind and stores the stable approval wording.
- act(auth, state["completion_pending_block"]["answer_action"],
- {"verdict": "accept"})
- check(wait_for(lambda: goal_state(goal1)["status"] == "complete" or False,
- timeout=30, what="accepted completion completes") is not None,
- "accepting the completion completes the goal once origin is current")
- state = goal_state(goal1)
- conn = db()
- accepted_report = conn.execute(
- "select completion_report from goals where id = ?", (goal1,)).fetchone()[0]
- conn.close()
- check(state["terminal"] is True and state.get("cancel") is None
- and accepted_report == "# Completion v2\n\nThe flange ships, with docs."
- and "# Completion v2" in state["completion_report"],
- "a complete goal is terminal with the WINNING block's body as its report (S1)")
- check(wait_for(lambda: task_state(auth, run_task)["status"] == "done" or False,
- timeout=30, what="goal run task closes") is not None,
- "approving the completion closes the goal-run task done")
- conn = db()
- twins_left = conn.execute(
- "select count(*) from task_blocks where task_id = ?"
- " and answer is null and type in ('completion','review')",
- (run_task,)).fetchone()[0]
- conn.close()
- check(twins_left == 0,
- "the verdict answered the pending completion-family row (single-row invariant)")
- print("== plan 15 C3: the narrative derives from the recorded shape ==")
- # Content equivalence with the old projection on this run's exact
- # shape: every kind the goal_events projection produced, keyed the
- # same way, plus the watermark and no-ingest guarantees.
- narrative = goal_state(goal1)["events"]
- kinds = [e["kind"] for e in narrative]
- for expected in ("created", "started", "plan-submitted", "plan-rejected",
- "plan-approved", "task-created", "task-done",
- "completion-submitted", "completion-rejected", "goal-approved"):
- check(expected in kinds,
- "the derived narrative carries the %s row" % expected)
- check(all(k != "task-enqueued" and k != "resumed" and k != "retry" for k in kinds),
- "timestamp-less bookkeeping kinds never return")
- stamps = [e["at_display"] for e in narrative if e.get("at_display")]
- check(stamps == sorted(stamps, reverse=True),
- "the narrative reads newest first")
- conn = db()
- no_event_table = conn.execute(
- "select count(*) from sqlite_master where type = 'table'"
- " and name = 'goal_events'").fetchone()[0] == 0
- conn.close()
- check(no_event_table,
- "the goal_events table is gone (no narrative rows on ingest)")
- print("== plan 12: cancel cascades to the goal's tasks ==")
- goal2 = create_goal({"project_id": "1", "title": "Doomed goal",
- "brief": "This goal will be cancelled.",
- "branch_name": "goal/doomed"})
- fake.goal_script = [("suspend",)]
- act(auth, goal_state(goal2)["start"], {})
- conn = db()
- run2 = conn.execute("select id from tasks where goal_id = ? and kind = 'goal_run'",
- (goal2,)).fetchone()[0]
- conn.close()
- check(wait_for(lambda: task_state(auth, run2)["status"] == "suspended" or False,
- timeout=60, what="doomed goal agent parks") is not None,
- "the doomed goal's agent parks")
- conn = db()
- agent_session2 = conn.execute(
- "select id from sessions where task_id = ? and ended_at is null",
- (run2,)).fetchone()[0]
- conn.close()
- status, body = saddle_api("POST", "/api/saddle/task/create", fake_token, {
- "session_id": agent_session2, "title": "Doomed draft", "mandate": "m"})
- doomed_draft = int(body["task_id"])
- doomed_queued_task = None
- status, body = saddle_api("POST", "/api/saddle/task/create", fake_token, {
- "session_id": agent_session2, "title": "Doomed runner", "mandate": "m"})
- doomed_runner = int(body["task_id"])
- saddle_api("POST", "/api/saddle/task/enqueue", fake_token,
- {"session_id": agent_session2, "task_id": doomed_runner})
- act(auth, goal_state(goal2)["cancel"], {})
- check(goal_state(goal2)["status"] == "cancelled",
- "the goal cancels from active (parked-agent) state")
- check(task_state(auth, run2)["status"] == "failed",
- "the cascade cancels the goal agent's run task")
- check(wait_for(lambda: task_state(auth, doomed_runner)["status"] == "failed"
- or False, timeout=30, what="cascade cancels the queued task") is not None,
- "the cascade cancels the goal's queued task")
- check(not any(t["id"] == doomed_draft
- for t in slot_public(auth, "/tasks", "tasks")["tasks"]),
- "the cascade deletes the goal's draft tasks")
- # A cancelled goal frees its branch name.
- reuse = create_goal({"project_id": "1", "title": "Reused name",
- "brief": "b", "branch_name": "goal/doomed"})
- check(reuse > goal2, "a cancelled goal's branch name is reusable")
- print("== plan 13 B3: goal failure cascades to non-terminal tasks ==")
- # A goal holding a draft, a queued task and a working task: the
- # agent's failure cascades like a cancellation — drafts deleted (no
- # orphan stays dispatchable), queued/working cancelled — and the
- # narrative records the cascade.
- collapse = create_goal({"project_id": "1", "title": "Collapse goal",
- "brief": "Fails with tasks in flight.",
- "branch_name": "goal/collapse"})
- fake.goal_script = [("suspend",)]
- act(auth, goal_state(collapse)["start"], {})
- conn = db()
- run3 = conn.execute("select id from tasks where goal_id = ? and kind = 'goal_run'",
- (collapse,)).fetchone()[0]
- conn.close()
- check(wait_for(lambda: task_state(auth, run3)["status"] == "suspended" or False,
- timeout=60, what="collapse goal agent parks") is not None,
- "the collapse goal's agent parks first")
- conn = db()
- run3_session = conn.execute(
- "select id from sessions where task_id = ? and ended_at is null",
- (run3,)).fetchone()[0]
- conn.close()
- def agent_task(title):
- status, body = saddle_api("POST", "/api/saddle/task/create", fake_token, {
- "session_id": run3_session, "title": title, "mandate": "m"})
- task_id = int(body["task_id"])
- saddle_api("POST", "/api/saddle/task/enqueue", fake_token,
- {"session_id": run3_session, "task_id": task_id})
- return task_id
- status, body = saddle_api("POST", "/api/saddle/task/create", fake_token, {
- "session_id": run3_session, "title": "Collapse draft", "mandate": "m"})
- collapse_draft = int(body["task_id"]) # stays draft (never enqueued)
- collapse_queued = agent_task("Collapse queued")
- collapse_worker = agent_task("Collapse worker")
- # Both queued tasks race for the one saddle; freeze the daemon the
- # moment the first dispatches so one stays genuinely WORKING (and
- # the other queued) when the goal fails.
- fake.hold_state = True
- def collapse_working():
- for task_id in (collapse_queued, collapse_worker):
- if task_state(auth, task_id)["status"] == "working":
- return task_id
- return False
- running_one = wait_for(collapse_working, timeout=60, what="a collapse task works")
- check(running_one is not None,
- "a collapse task reaches working before the failure")
- queued_one = collapse_worker if running_one == collapse_queued else collapse_queued
- status, _ = saddle_api("POST", "/api/saddle/results", fake_token,
- {"session_id": run3_session, "state": "failed",
- "summary": "the foundation crumbled"})
- check(status == 200 and goal_state(collapse)["status"] == "failed",
- "the agent's failure fails the goal")
- check(task_state(auth, run3)["status"] == "failed",
- "the failure fails the goal agent's run task")
- check(wait_for(lambda: task_state(auth, queued_one)["status"] == "failed"
- and task_state(auth, running_one)["status"] == "failed" or False,
- timeout=30, what="failure cascade settles") is not None,
- "the failure cascade cancels the queued and working tasks")
- check(not any(t["id"] == collapse_draft
- for t in slot_public(auth, "/tasks", "tasks")["tasks"]),
- "the failure cascade deletes the goal's draft tasks")
- state = goal_state(collapse)
- check(any(e["kind"] == "cascade" for e in state["events"]),
- "the narrative records the failure cascade")
- check(state["draft_tasks"] == 0 and state["queued_tasks"] == 0,
- "nothing under the failed goal stays dispatchable (no orphan drafts)")
- check(len([e for e in state["events"] if e["kind"] == "goal-failed"]) == 1,
- "the failing goal records exactly one goal-failed event")
- # Plan 16 S1: the FAILURE report keeps its goal-side home (failure
- # is terminal — no pending duplication exists, so unlike the
- # completion report it is written at failure time).
- conn = db()
- failure_report = conn.execute(
- "select completion_report from goals where id = ?", (collapse,)).fetchone()[0]
- conn.close()
- check(failure_report == "the foundation crumbled",
- "fail_goal's failure report keeps its goal-side home (S1)")
- # Plan 16 S4: the narrative renders the task-failed rows from the
- # terminal summary's lead — the run thread carries NO duplicate
- # agent comment of the goal failure text.
- failed_rows = [e for e in state["events"] if e["kind"] == "task-failed"]
- check(failed_rows
- and all(": Cancelled by user" in e["text"] for e in failed_rows),
- "task-failed narrative rows render the summary lead (S4: summary is the terminal cache)")
- run_comments = [c for c in task_state(auth, run3)["comments"]
- if c["author"] == "agent"]
- check(not any("the foundation crumbled" in c["body"] for c in run_comments),
- "the goal failure text never duplicates as an agent comment (S4)")
- fake.hold_state = False
- print("== plan 12: auto-enqueue-after for human tasks ==")
- act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
- "project_id": "1", "title": "Predecessor task", "priority": "4"})
- predecessor = task_id_of("Predecessor task")
- enqueue_task(auth, predecessor)
- act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
- "project_id": "1", "title": "Successor task", "priority": "4",
- "enqueue_after_task_id": str(predecessor)})
- successor = task_id_of("Successor task")
- check(task_state(auth, successor)["status"] == "draft"
- and task_state(auth, successor).get("enqueue_after_task_id") == predecessor,
- "a human task can carry an auto-enqueue predecessor")
- check(wait_task_done(auth, predecessor, timeout=60) is not None,
- "the predecessor completes")
- check(wait_for(lambda: task_state(auth, successor)["status"] in
- ("queued", "assigned", "working") or False,
- timeout=30, what="successor auto-enqueues") is not None,
- "the done predecessor auto-enqueues its dependent")
- # Cycle validation refuses a predecessor chain that loops.
- act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
- "project_id": "1", "title": "Cycle A", "priority": "4"})
- cycle_a = task_id_of("Cycle A")
- act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
- "project_id": "1", "title": "Cycle B", "priority": "4",
- "enqueue_after_task_id": str(cycle_a)})
- cycle_b = task_id_of("Cycle B")
- cycle_directives = act(auth, task_state(auth, cycle_a)["update"], {
- "title": "Cycle A", "description": "x", "priority": "4",
- "enqueue_after_task_id": str(cycle_b)})
- check(any(d.get("type") == "notify" and d.get("kind") == "error"
- and "cycle" in (d.get("message") or "").lower()
- for d in cycle_directives),
- "a predecessor cycle rejects with a naming error")
- # A failed predecessor leaves the dependent in draft.
- fake.fail_next = True
- act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
- "project_id": "1", "title": "Failing predecessor", "priority": "1"})
- failing_pred = task_id_of("Failing predecessor")
- enqueue_task(auth, failing_pred)
- act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
- "project_id": "1", "title": "Stranded successor", "priority": "4",
- "enqueue_after_task_id": str(failing_pred)})
- stranded = task_id_of("Stranded successor")
- check(wait_for(lambda: task_state(auth, failing_pred)["status"] == "failed"
- or False, timeout=60, what="failing predecessor fails") is not None,
- "the scripted-failure predecessor fails")
- check(quiet_window(quiet_seconds(),
- lambda: task_state(auth, stranded)["status"] == "draft",
- "failed predecessor leaves the draft"),
- "a failed predecessor leaves its dependent in draft")
- # Successor cleanup: let the queued successor finish so the
- # shard's app shuts down with an empty queue.
- check(wait_task_done(auth, successor, timeout=90) is not None,
- "the auto-enqueued successor completes")
- print("== feedback: context_update carries rule/note deltas on resume ==")
- # A standalone task parks on a question; while it is parked a rule
- # is attached to the project and a note lands; the resuming
- # assignment carries both as the context_update markdown turn.
- probe_id = create_queued(auth, {"project_id": "1", "title": "Context probe",
- "priority": "4"})
- check(wait_for(lambda: fake.running_task == probe_id
- and task_state(auth, probe_id)["status"] == "working" or False,
- timeout=60, what="probe task runs") is not None,
- "the probe task dispatches")
- fake.set_state("blocked")
- fake.push_results("blocked", block={"type": "question",
- "question": "Which database dialect?"})
- check(wait_for(lambda: task_state(auth, probe_id)["status"] == "blocked" or False,
- timeout=60, what="probe task blocks") is not None,
- "the probe task parks on its question")
- detail = task_state(auth, probe_id)
- open_block = [b for b in detail["blocks"] if not b["answered"]][0]
- # Backdate the parked round's start: stamps are second-resolution
- # epochs, and the delta compares strictly-later — the park must
- # span a real interval like an operator's would.
- conn = db()
- conn.execute("update sessions set started_at = strftime('%s','now') - 60"
- " where id = (select min(id) from sessions where task_id = ?)",
- (probe_id,))
- conn.commit()
- conn.close()
- # While parked: a new rule attached to the project + a new note.
- rules_state = slot_public(auth, "/rules", "rules")
- act(auth, rules_state["create"], {
- "name": "Nightly gate", "body": "Every PR must pass the nightly suite before merge."})
- conn = db()
- rule_id = conn.execute("select id from rules where name = 'Nightly gate'").fetchone()[0]
- conn.close()
- act(auth, slot_public(auth, "/projects/1", "project")["attach_rule"],
- {"rule_id": str(rule_id)})
- conn = db()
- conn.execute("insert into project_notes (project_id, body, created_at)"
- " values (1, 'The demo repo moved its CI to nightly builds.',"
- " strftime('%s','now'))")
- conn.commit()
- check(conn.execute("select count(*) from rule_projects where rule_id = ?",
- (rule_id,)).fetchone()[0] == 1,
- "the rule attaches to the project while the task is parked")
- conn.close()
- probe_assignments = len([p for p in fake.start_payloads
- if int(p.get("task", {}).get("id") or 0) == probe_id])
- act(auth, open_block["answer_action"], {"answer": "postgres"})
- check(wait_for(lambda: len([p for p in fake.start_payloads
- if int(p.get("task", {}).get("id") or 0) == probe_id])
- > probe_assignments or False, timeout=60,
- what="probe task resumes") is not None,
- "answering the block resumes the probe task")
- probe_resume = [p for p in fake.start_payloads
- if int(p.get("task", {}).get("id") or 0) == probe_id][-1]
- delta = probe_resume.get("context_update") or ""
- check("Rules changed" in delta and "Nightly gate" in delta
- and "nightly suite" in delta,
- "the resume's context_update summarises the newly attached rule")
- check("New project notes" in delta and "nightly builds" in delta,
- "the resume's context_update summarises the new project note")
- check("Plan approved" not in delta,
- "standalone resumes carry no plan section")
- check(wait_task_done(auth, probe_id, timeout=60) is not None,
- "the probe task completes after its resume")
- # =============================================================================
- # Bundle: replay — plan 13 B1/B2: the daemon-global entry dedup and the
- # idempotent terminal/narrative projections. Replays the live-failure
- # post-mortem shapes directly against the push channels: an outbox
- # redelivery arriving under the WRONG session id after churn (and after
- # a web restart), and repeated failure pushes that must still project
- # exactly once. Includes this shard's one deliberate app restart (the
- # dedup must survive it — it is database state, not memory).
- # =============================================================================
- def bundle_replay(ctx):
- auth, fake, fake_id, fake_token = ctx.auth, ctx.fake, ctx.fake_id, ctx.fake_token
- app = ctx.app
- def rows_for(session):
- conn = db()
- try:
- return conn.execute(
- "select count(*) from session_commands where session_id = ?",
- (session,)).fetchone()[0]
- finally:
- conn.close()
- def dedup_rows():
- conn = db()
- try:
- return conn.execute("select count(*) from saddle_entry_dedup").fetchone()[0]
- finally:
- conn.close()
- def open_session_on_fake(task_id):
- """A fixture open session row on the shard's saddle (the churn
- shape: a fresh web-side run row the daemon never really drove)."""
- conn = db()
- try:
- conn.execute(
- "insert into sessions (task_id, saddle_id, started_at)"
- " values (?, ?, strftime('%s','now'))", (task_id, fake_id))
- conn.commit()
- return conn.execute(
- "select max(id) from sessions where task_id = ?",
- (task_id,)).fetchone()[0]
- finally:
- conn.close()
- print("== plan 13 B1: entry dedup is daemon-global, not per-session ==")
- # A first task runs to completion; its session is the replay source.
- first = create_queued(auth, {"project_id": "1", "title": "Replay source task",
- "priority": "2"})
- check(wait_task_done(auth, first, timeout=60) is not None,
- "the replay-source task completes")
- conn = db()
- src_session = conn.execute(
- "select max(id) from sessions where task_id = ?", (first,)).fetchone()[0]
- conn.close()
- # The outbox batch: two command entries the daemon already
- # delivered once (their ids landed with the source session).
- burst = [
- {"id": 5001, "command": "make test", "output": "ok", "exit_code": 0,
- "type": "command", "timestamp": "2026-09-11T00:00:00Z"},
- {"id": 5002, "command": "grep -r flange src/", "output": "src/flange.vala",
- "exit_code": 0, "type": "command", "timestamp": "2026-09-11T00:00:01Z"},
- ]
- status, _ = saddle_api("POST", "/api/saddle/results", fake_token,
- {"session_id": src_session, "state": "done",
- "entries": burst})
- check(status == 200 and rows_for(src_session) >= 2,
- "the entries batch stores under its session")
- stored_once = rows_for(src_session)
- dedup_after_first = dedup_rows()
- # Redelivery under the SAME session: consumed, nothing new.
- status, _ = saddle_api("POST", "/api/saddle/results", fake_token,
- {"session_id": src_session, "state": "done",
- "entries": burst})
- check(status == 200 and rows_for(src_session) == stored_once
- and dedup_rows() == dedup_after_first,
- "a same-session redelivery stores nothing new (consumed silently)")
- # Session churn: a brand-new session id on the same saddle replays
- # the SAME entry ids — the post-mortem's mis-keyed outbox send.
- # The entries' ids already populated another session, so the push
- # may not create rows here either.
- conn = db()
- conn.execute(
- "insert into tasks (project_id, title, status, priority, created_at,"
- " completed_at) values (1, 'Churn target task', 'done', 5,"
- " strftime('%s','now'), strftime('%s','now'))")
- conn.commit()
- churn_task = conn.execute(
- "select max(id) from tasks where title = 'Churn target task'").fetchone()[0]
- conn.close()
- churn_session = open_session_on_fake(churn_task)
- status, _ = saddle_api("POST", "/api/saddle/results", fake_token,
- {"session_id": churn_session, "state": "done",
- "entries": burst})
- check(status == 200 and rows_for(churn_session) == 0,
- "replay-under-wrong-session stores nothing new (daemon-global dedup)")
- check(dedup_rows() == dedup_after_first,
- "the consumed redelivery claims no fresh dedup rows")
- # A fresh entry id under the churned session DOES store — the dedup
- # keys on the entry id, never on the session.
- fresh = [{"id": 5003, "command": "git status", "output": "clean",
- "exit_code": 0, "type": "command", "timestamp": "2026-09-11T00:00:02Z"}]
- status, _ = saddle_api("POST", "/api/saddle/results", fake_token,
- {"session_id": churn_session, "state": "done",
- "entries": fresh})
- check(status == 200 and rows_for(churn_session) == 1
- and dedup_rows() == dedup_after_first + 1,
- "a fresh entry id stores under any session of its saddle")
- # Web restart: the dedup survives (database state, not memory).
- app.terminate()
- app.wait(timeout=10)
- app = start_app()
- auth = login("admin", "adminpass123")
- ctx.app = app # teardown tracks the restarted process
- ctx.auth = auth
- # The restart wiped the in-memory ask bookkeeping; the daemon
- # re-establishes sleeping with a fresh ask (the real daemon's
- # idle-awake ask / 15-minute safety self-ask).
- fake.ask_now()
- status, _ = saddle_api("POST", "/api/saddle/results", fake_token,
- {"session_id": churn_session, "state": "done",
- "entries": burst + fresh})
- check(status == 200 and rows_for(churn_session) == 1
- and rows_for(src_session) == stored_once,
- "a post-restart redelivery still stores nothing (dedup is persistent)")
- print("== plan 13 B2: duplicate failed entries project once (task) ==")
- # The post-mortem's honest burst: the same failure entry redelivered
- # under a churned session AND a second, DISTINCT failure entry id
- # in the same session — one transition, one comment, ever.
- def agent_comments(task_id, body=None):
- conn = db()
- try:
- if body is None:
- return conn.execute(
- "select count(*) from task_comments"
- " where task_id = ? and author = 'agent'",
- (task_id,)).fetchone()[0]
- return conn.execute(
- "select count(*) from task_comments"
- " where task_id = ? and author = 'agent' and body = ?",
- (task_id, body)).fetchone()[0]
- finally:
- conn.close()
- conn = db()
- conn.execute(
- "insert into tasks (project_id, title, status, priority, created_at)"
- " values (1, 'Burst failure task', 'working', 5, strftime('%s','now'))")
- conn.commit()
- burst_task = conn.execute(
- "select max(id) from tasks where title = 'Burst failure task'").fetchone()[0]
- conn.close()
- burst_session = open_session_on_fake(burst_task)
- first_fail = [{"id": 6001, "command": "saddle failure",
- "output": "bash -n rejected the script", "exit_code": 1,
- "type": "failed", "timestamp": "2026-09-11T00:01:00Z"}]
- status, _ = saddle_api("POST", "/api/saddle/results", fake_token,
- {"session_id": burst_session, "state": "failed",
- "entries": first_fail,
- "summary": "bash -n rejected the script"})
- check(status == 200
- and task_state(auth, burst_task)["status"] == "failed",
- "the failure entry + state pair fails the task")
- check(agent_comments(burst_task, "bash -n rejected the script") == 0
- and "bash -n rejected the script"
- in (task_state(auth, burst_task).get("summary") or ""),
- "the failure explanation lives ONCE — in tasks.summary, the terminal cache (S4: no duplicate agent comment)")
- # The burst: a DISTINCT failure entry id in the same session, plus
- # both ids redelivered under a churned session — nothing re-runs.
- second_fail = [{"id": 6002, "command": "saddle failure",
- "output": "bash -n rejected the script", "exit_code": 1,
- "type": "failed", "timestamp": "2026-09-11T00:01:01Z"}]
- churned_burst = open_session_on_fake(burst_task)
- for target in (burst_session, churned_burst):
- status, _ = saddle_api("POST", "/api/saddle/results", fake_token,
- {"session_id": target, "state": "failed",
- "entries": first_fail + second_fail,
- "summary": "bash -n rejected the script"})
- check(status == 200,
- "the burst push under %s is consumed, not rejected" % (
- "its session" if target == burst_session else "a churned session"))
- check(task_state(auth, burst_task)["status"] == "failed"
- and agent_comments(burst_task) == 0
- and "bash -n rejected the script"
- in (task_state(auth, burst_task).get("summary") or ""),
- "duplicate failed entries leave one transition, one summary, zero agent comments (S4)")
- print("== plan 13 B2: goal failure projects once ==")
- # Goal context: `saddle failure` fails the goal ONCE — the 115
- # goal-failed events of the live failure are structurally gone.
- def goal_state(goal_id):
- return slot_public(auth, "/goals/%d" % goal_id, "goal")
- def goal_failed_events(goal_id):
- # Plan 15 C3: the narrative derives at read time — the
- # failed-goal row IS the goal-failed record (one row, ever).
- return len([e for e in goal_state(goal_id)["events"]
- if e["kind"] == "goal-failed"])
- act(auth, slot_public(auth, "/goals", "goals")["create"], {
- "project_id": "1", "title": "Burst goal", "brief": "Fails honestly.",
- "branch_name": "goal/burst"})
- conn = db()
- burst_goal = conn.execute(
- "select id from goals where title = 'Burst goal'").fetchone()[0]
- conn.close()
- fake.goal_script = [("suspend",)]
- act(auth, goal_state(burst_goal)["start"], {})
- conn = db()
- run_task = conn.execute(
- "select id from tasks where goal_id = ? and kind = 'goal_run'",
- (burst_goal,)).fetchone()[0]
- conn.close()
- check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
- or False, timeout=60, what="burst goal agent parks") is not None,
- "the burst goal's agent parks first")
- conn = db()
- run_session = conn.execute(
- "select id from sessions where task_id = ? and ended_at is null",
- (run_task,)).fetchone()[0]
- conn.close()
- goal_fail = [{"id": 7001, "command": "saddle failure",
- "output": "the harness collapsed", "exit_code": 1,
- "type": "failed", "timestamp": "2026-09-11T00:02:00Z"},
- {"id": 7002, "command": "saddle failure",
- "output": "the harness collapsed", "exit_code": 1,
- "type": "failed", "timestamp": "2026-09-11T00:02:01Z"}]
- status, _ = saddle_api("POST", "/api/saddle/results", fake_token,
- {"session_id": run_session, "state": "failed",
- "entries": goal_fail, "summary": "the harness collapsed"})
- check(status == 200 and goal_state(burst_goal)["status"] == "failed",
- "the goal agent's failure fails the goal")
- check(goal_failed_events(burst_goal) == 1,
- "a two-entry failure burst records exactly one goal-failed event")
- # Redeliveries — same ids under the run's session AND under a
- # churned session — plus the bare failed state again: still one.
- churned_run = open_session_on_fake(run_task)
- for target in (run_session, churned_run):
- status, _ = saddle_api("POST", "/api/saddle/results", fake_token,
- {"session_id": target, "state": "failed",
- "entries": goal_fail,
- "summary": "the harness collapsed"})
- check(status == 200, "the goal-failure redelivery under %s is consumed"
- % ("its session" if target == run_session else "a churned session"))
- check(goal_state(burst_goal)["status"] == "failed"
- and goal_failed_events(burst_goal) == 1,
- "redelivered failures never re-run the goal failure (one event, ever)")
- check(task_state(auth, run_task)["status"] == "failed",
- "the goal-run task stays failed after the replays")
- conn = db()
- burst_blocks = conn.execute(
- "select count(*) from task_blocks where task_id = ?", (run_task,)
- ).fetchone()[0]
- conn.close()
- check(burst_blocks == 0,
- "the goal-failed path raises ZERO blocks (nothing pins the pile)")
- print("== plan 13 B4: the comment projection stores the body ==")
- # The heredoc shape: the command is the pipe invocation, the OUTPUT
- # is the body. The projections must carry the body — the command
- # echo never becomes comment text, and the literal `saddle
- # comment` string never renders outside the terminal.
- act(auth, slot_public(auth, "/goals", "goals")["create"], {
- "project_id": "1", "title": "Comment goal", "brief": "Reports cleanly.",
- "branch_name": "goal/comment"})
- conn = db()
- comment_goal = conn.execute(
- "select id from goals where title = 'Comment goal'").fetchone()[0]
- conn.close()
- fake.goal_script = [("suspend",)]
- act(auth, goal_state(comment_goal)["start"], {})
- conn = db()
- comment_run = conn.execute(
- "select id from tasks where goal_id = ? and kind = 'goal_run'",
- (comment_goal,)).fetchone()[0]
- conn.close()
- check(wait_for(lambda: task_state(auth, comment_run)["status"] == "suspended"
- or False, timeout=60, what="comment goal agent parks") is not None,
- "the comment goal's agent parks")
- conn = db()
- comment_session = conn.execute(
- "select id from sessions where task_id = ? and ended_at is null",
- (comment_run,)).fetchone()[0]
- conn.close()
- comment_body = "The flange weld holds. First sentence carries the information."
- comment_entry = [{"id": 8001, "command": "cat << 'EOF' | saddle comment",
- "output": comment_body, "exit_code": 0, "type": "comment",
- "timestamp": "2026-09-11T00:03:00Z"}]
- status, _ = saddle_api("POST", "/api/saddle/results", fake_token,
- {"session_id": comment_session, "state": "suspended",
- "entries": comment_entry})
- check(status == 200, "the comment entry push is accepted")
- conn = db()
- task_comment_bodies = [r[0] for r in conn.execute(
- "select body from task_comments where task_id = ?", (comment_run,))]
- tables = {r[0] for r in conn.execute(
- "select name from sqlite_master where type = 'table'").fetchall()}
- conn.close()
- check(task_comment_bodies == [comment_body],
- "the run task's comment stores the body, not the command echo")
- check("goal_comments" not in tables,
- "the goal_comments table is gone (the run task's rows are the thread)")
- narrative_comments = [e["text"] for e in goal_state(comment_goal)["events"]
- if e["kind"] == "comment"]
- check(len(narrative_comments) == 1
- and narrative_comments[0].startswith("The flange weld holds.")
- and "saddle comment" not in narrative_comments[0]
- and "EOF" not in narrative_comments[0],
- "the derived narrative's comment rows carry the body text")
- state = goal_state(comment_goal)
- check(state["current_command"] == comment_body,
- "the goal page's current-activity line shows the comment body")
- check(state["comments"] and state["comments"][0]["body"] == comment_body
- and any(e["kind"] == "comment"
- and e["text"].startswith("The flange weld holds.")
- for e in state["events"]),
- "the goal slot carries the body in its comments and narrative")
- page = http_get_page(auth, "/goals/%d" % comment_goal)
- check("saddle comment" not in page,
- "the rendered goal page never shows the command echo")
- check("event.kind == 'comment' ? ' footnote'" in page,
- "the narrative's comment events render with footnote styling")
- check(page.index('stm-markdown="comment.body"')
- < page.index('stm-text="comment.quote"'),
- "the comment card shows the body before the quote")
- # Drain: nothing queued remains for the shard's shutdown.
- check(queued_count() == 0, "the replay shard drains its queue")
- def bundle_goalui(ctx):
- """Plan 14 workstream B + plan 15 C1: the goal supervision surface.
- B1 — goal-run control parity (Retry supervision / Requeue-to-resume
- on the status head, the /tasks/<goal-run-id> redirect, no
- notify-me toggle), B2 — the plan-review remarks queue, B4 —
- sessions.resume_notice + the terminal dropdown/resume panel, B5 —
- page order, effective rules and per-task capability badges, B6 —
- narrative footnote weights. C1 — the single-row block world: the
- goal's blocks ARE the run task's rows (answered from every
- surface through one path; no dual rows, no stragglers, at most
- one review-kind block per verdict path). C4 — the goal page
- batch: one muted head activity line, plan between attention and
- tasks, answered blocks behind an expander, footnote comments.
- C5 — the dashboard deep-link closure: goal pile cards link the
- goal page, every surface settles the single row, and no
- goal-side store exists to hold stragglers.
- """
- auth, fake, fake_id, fake_token = ctx.auth, ctx.fake, ctx.fake_id, ctx.fake_token
- def goal_state(goal_id):
- return slot_public(auth, "/goals/%d" % goal_id, "goal")
- def run_task_of(goal_id):
- conn = db()
- try:
- return int(conn.execute(
- "select id from tasks where goal_id = ? and kind = 'goal_run'",
- (goal_id,)).fetchone()[0])
- finally:
- conn.close()
- def wait_run(status, timeout=60, what="run status"):
- return wait_for(lambda: task_state(auth, run_task_of(goal))["status"] == status
- or False, timeout=timeout, what=what)
- def pile_questions():
- return [b.get("question") for b in
- slot_public(auth, "/", "dashboard")["pinned"]
- if b.get("item_type") == "goal"]
- def open_session(task_id):
- conn = db()
- try:
- row = conn.execute(
- "select id from sessions where task_id = ? and ended_at is null",
- (task_id,)).fetchone()
- return int(row[0]) if row else None
- finally:
- conn.close()
- print("== B1: the goal-run task page redirects to the goal ==")
- fake.goal_script = [("plan", "# Remarks plan\n\n## Steps\n\n1. First move")]
- act(auth, slot_public(auth, "/goals", "goals")["create"], {
- "project_id": "1", "title": "Goalui goal",
- "brief": "Drive the plan-14 goal surface end to end.",
- "branch_name": "goal/goalui"})
- conn = db()
- goal = conn.execute("select id from goals where title = 'Goalui goal'"
- ).fetchone()[0]
- conn.close()
- act(auth, goal_state(goal)["start"], {})
- wait_run("blocked", what="goalui goal agent parks on its plan") # plan review parks blocked
- run_task = run_task_of(goal)
- frames = directives("/tasks/%d" % run_task, auth)
- navigations = [d for d in frames if d.get("type") == "navigate"]
- check(any(d.get("uri") == "/goals/%d" % goal for d in navigations),
- "the goal-run task page redirects to /goals/<id> (navigate directive)")
- check(slot_public(auth, "/tasks/%d" % run_task, "task")["id"] == run_task,
- "the redirect still hydrates the task slot (sealed refresh + API readers)")
- status, page = http("GET", "/tasks/%d" % run_task, headers={
- "X-Statum-Slot": "key=%s; as_at=%s" % (auth["key"], auth["as_at"])}, timeout=15)
- check(status == 200, "the compiled goal-run page serves")
- act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
- "project_id": "1", "title": "Goalui worker", "priority": "4",
- "description": "A plain worker under no goal."})
- conn = db()
- worker = conn.execute("select id from tasks where title = 'Goalui worker'"
- ).fetchone()[0]
- conn.close()
- check(not [d for d in directives("/tasks/%d" % worker, auth)
- if d.get("type") == "navigate"],
- "worker task pages render normally (no redirect)")
- state = goal_state(goal)
- check("toggle_notify" not in state and state.get("notify") is None,
- "no notify-me toggle on the goal (goals notify unconditionally)")
- goal_page = http_get_page(auth, "/goals/%d" % goal)
- check("Notify me" not in goal_page and "toggle-notify" not in goal_page,
- "the goal page template ships no notify control")
- check(state["branch_name"] == "goal/goalui" and state["base_branch"] == "main"
- and isinstance(state["push_branches"], list)
- and state["priority"] == 3
- and len(state["started_display"]) > 0
- and len(state["agent_saddle"]) > 0,
- "the status head carries the full field set (branch pair, chips list,"
- " priority, timestamps, agent saddle)")
- check("Retry supervision" in goal_page and "Requeue to resume" in goal_page,
- "the status head ships the run-control parity buttons")
- check("status-corner" not in goal_page and "goal complete" not in goal_page
- and goal_page.count("badge-yeehaw") == 1,
- "yeehaw is rows-only (no goal-head corner badge; the row badge remains)")
- check(goal_state(goal)["current_activity"] == "parked awaiting the plan review",
- "the head line reads 'parked awaiting the plan review' while one is open")
- goal_page = http_get_page(auth, "/goals/%d" % goal)
- check(goal_page.index("Goal agent blocks") < goal_page.index("<h3>Plan</h3>")
- < goal_page.index("<h3>Tasks</h3>"),
- "the plan section sits between attention and tasks")
- check("data-plan-review" in goal_page and "Approved plan & revisions" in goal_page,
- "an open review renders expanded beside the collapsed approved-plan expander")
- print("== B2: the plan-review remarks queue ==")
- check(goal_state(goal)["status"] == "plan_review"
- and goal_state(goal)["plan_pending_block"] is not None,
- "the goal sits in plan review with a pending block")
- goal_page = http_get_page(auth, "/goals/%d" % goal)
- review_at = goal_page.index("data-plan-review")
- check(review_at > 0, "the plan review region is marked for the remarks queue")
- check(goal_page.index('data-block-id="goal.plan_pending_block.id"') > 0,
- "the queue keys on the pending goal block id")
- body_at = goal_page.index('data-plan-body')
- remarks_at = goal_page.index("data-plan-remarks")
- check(body_at < remarks_at,
- "the remarks queue renders at the bottom of the plan body")
- for marker in ("data-plan-accept", "data-plan-toggle", "data-plan-submit",
- "data-plan-remove", "plan-popover", "remarksByBlock"):
- check(marker in goal_page, "the remarks queue ships %s" % marker)
- check("(index + 1) + '. '" in goal_page,
- "the rejection payload numbers every queued remark")
- check("rejectOpenByBlock" in goal_page and "statum.afterEvaluate(syncPlanRemarks)" in goal_page,
- "the queue survives pulse refreshes (maps outside statum re-renders)")
- # The rejection payload exactly as the client composes it: the
- # stable verdict wording, then each remark NUMBERED in queue order
- # with its quote prefixed like a comment.
- payload = ("Plan feedback received:\n\n"
- "> ## Steps\n\n1. First move\n\n"
- "1. Add a rollback step\n\n"
- "2. Name the owner of the migration")
- act(auth, goal_state(goal)["plan_pending_block"]["answer_action"],
- {"answer": payload})
- state = goal_state(goal)
- check(state["status"] == "planning",
- "the remarks rejection returns the agent to planning")
- rejected = [r for r in state["plan_revisions"] if r.get("answered")]
- check(any(r["answer"] == payload for r in rejected),
- "the rejection carries the remarks verbatim, in order")
- check(payload.index("1. Add a rollback step") < payload.index("2. Name the owner"),
- "the numbering is queue-ordered")
- print("== B4: resume_notice lands on the resumed session row ==")
- fake.goal_script = [("question", "Which datastore?", ["SQLite", "Redis"])]
- check(wait_for(lambda: goal_state(goal)["status"] == "planning" or False,
- timeout=30, what="rejection observed") is not None,
- "the agent revises")
- # The parked plan agent resumes with the verdict; the next round
- # raises the scripted question.
- check(wait_for(lambda: any(b["question"] == "Which datastore?"
- and not b.get("answered")
- for b in goal_state(goal)["blocks"]) or False,
- timeout=60, what="question raised") is not None,
- "the resumed agent raises its question")
- conn = db()
- notices = dict(conn.execute(
- "select id, resume_notice from sessions where task_id = ?", (run_task,)
- ).fetchall())
- conn.close()
- resumed_ids = [i for i, text in notices.items() if text]
- fresh_ids = [i for i, text in notices.items() if not text]
- check(len(resumed_ids) >= 1 and any("Plan feedback received" in notices[i]
- for i in resumed_ids),
- "the resumed session stores the notice it was allocated")
- check(min(notices) in fresh_ids,
- "session 1 (the fresh opening round) carries no resume notice")
- print("== C1: the single run-task row IS the goal's block ==")
- check("Which datastore?" in pile_questions(),
- "the open goal question pins onto the dashboard pile")
- goal_rows = [b for b in goal_state(goal)["blocks"]
- if b["question"] == "Which datastore?"]
- run_rows = [b for b in task_state(auth, run_task)["blocks"]
- if b["question"] == "Which datastore?"]
- check(len(run_rows) == 1 and len(goal_rows) == 1
- and int(run_rows[0]["id"]) == int(goal_rows[0]["id"]),
- "the goal page and the run task read the SAME single row")
- pile_card = [b for b in slot_public(auth, "/", "dashboard")["pinned"]
- if b.get("question") == "Which datastore?"][0]
- check(pile_card.get("item_type") == "goal"
- and int(pile_card.get("goal_id") or 0) == goal
- and int(pile_card.get("id") or 0) == int(run_rows[0]["id"])
- and pile_card.get("goal_title") == "Goalui goal",
- "the pile card is the run-task row styled as a goal item")
- act(auth, [b for b in goal_state(goal)["blocks"]
- if b["question"] == "Which datastore?" and not b.get("answered")]
- [0]["answer_action"], {"option": "SQLite"})
- conn = db()
- unanswered = conn.execute(
- "select count(*) from task_blocks where question = 'Which datastore?'"
- " and answer is null").fetchone()[0]
- conn.close()
- check(unanswered == 0, "answering from the goal page closes the single row")
- check("Which datastore?" not in pile_questions(),
- "the answered row no longer pins the dashboard pile")
- # The task-page deep link (the AnswerBlockAction API surface)
- # answers the SAME row through the one goal path — there is no
- # second row to sweep and no stale copy can survive.
- conn = db()
- conn.execute("insert into task_blocks (task_id, type, question, created_at)"
- " values (?, 'question', 'Deep link probe?',"
- " strftime('%s','now'))", (run_task,))
- conn.commit()
- conn.close()
- check("Deep link probe?" in pile_questions(),
- "the fixture row pins like any goal question")
- deep_block = [b for b in task_state(auth, run_task)["blocks"]
- if b["question"] == "Deep link probe?" and not b.get("answered")][0]
- act(auth, deep_block["answer_action"], {"answer": "Answered via the deep link"})
- conn = db()
- answered_row = conn.execute(
- "select answer from task_blocks where question = 'Deep link probe?'"
- ).fetchone()[0]
- tables = {r[0] for r in conn.execute(
- "select name from sqlite_master where type = 'table'").fetchall()}
- conn.close()
- check(answered_row == "Answered via the deep link",
- "the task-page deep link answers the same single row (the API path)")
- check("Deep link probe?" not in pile_questions(),
- "the deep-link answer closes the pile card too")
- check("goal_blocks" not in tables,
- "the goal_blocks table is gone (run-task blocks are canonical)")
- print("== C5: deep-link closure on the single-row world ==")
- # The pile card's goal item deep-links the goal page (the answer
- # surface); answering from either surface settled THE row above.
- home_markup = http_get_page(auth, "/")
- check("'/goals/' + blockview.goal_id" in home_markup,
- "the pile card deep-links the goal page for goal items")
- conn = db()
- dupes = conn.execute(
- "select count(*) from (select question from task_blocks where task_id = ?"
- " group by question having count(*) > 1)", (run_task,)).fetchone()[0]
- stragglers = conn.execute(
- "select count(*) from task_blocks where task_id = ? and answer is null",
- (run_task,)).fetchone()[0]
- goal_side_tables = [r[0] for r in conn.execute(
- "select name from sqlite_master where type = 'table'"
- " and name in ('goal_blocks','goal_comments','goal_events')").fetchall()]
- conn.close()
- check(dupes == 0 and stragglers == 0,
- "one row per question on the run task, none left unanswered")
- check(not goal_side_tables,
- "no goal-side store exists — historical stragglers are impossible")
- print("== B4: the terminal session dropdown + resume-info panel ==")
- fake.goal_script = [("suspend",)]
- check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
- or False, timeout=60, what="agent parks suspended") is not None,
- "the agent parks suspended for the requeue scenario")
- terminal = slot_public(auth, "/tasks/%d/terminal" % run_task, "terminal")
- ids = [int(s["id"]) for s in terminal["sessions"]]
- check(ids == sorted(ids, reverse=True),
- "the session dropdown lists newest-first")
- check(int(terminal["session_id"]) == ids[0],
- "the terminal still defaults to the latest session")
- labels = {int(s["id"]): s["label"] for s in terminal["sessions"]}
- check(all(label.startswith("Session #") for label in labels.values()),
- "dropdown labels read 'Session #N — started <time>'")
- # Recompute at the check point: the C1 answers above each
- # resumed the agent too, so the earlier snapshot would go stale.
- conn = db()
- resumed_ids = [int(i) for (i, text) in conn.execute(
- "select id, resume_notice from sessions where task_id = ?",
- (run_task,)).fetchall() if text]
- conn.close()
- resumed_markers = [i for i in ids if "[resumed]" in labels[i]]
- check(set(resumed_markers) == set(resumed_ids),
- "the [resumed] marker rides exactly the notice-carrying sessions")
- notices_by_id = {int(s["id"]): s.get("notice") or "" for s in terminal["sessions"]}
- check("Opening brief" in notices_by_id[min(ids)]
- and "Drive the plan-14 goal surface" in notices_by_id[min(ids)],
- "session 1's panel shows the opening-brief framing (from the goal brief)")
- check(any("Plan feedback received" in (notices_by_id[i] or "") for i in resumed_ids),
- "a resumed session's panel shows the stored resume notice")
- terminal_page = http_get_page(auth, "/tasks/%d/terminal" % run_task)
- check('id="session-select"' in terminal_page
- and "window.terminalPickSession" in terminal_page,
- "the session picker is an options dropdown")
- check('stm-markdown="window.terminalSessionNotice(terminal)"' in terminal_page,
- "the panel renders the selected session's notice as markdown")
- check("No opening brief or resume notice recorded" in terminal_page,
- "the panel ships its graceful empty state")
- css = open(os.path.join(REPO, "src/pages/app.css")).read()
- check("resume-notice-panel" in css and "max-height: 200px" in css,
- "the panel is a 200px vertical-scroll strip (stylesheet)")
- print("== B1: requeue-to-resume from the goal head ==")
- conn = db()
- conn.execute("update saddles set status = 'offline' where id = ?", (fake_id,))
- conn.commit()
- conn.close()
- def requeue_offered():
- # Act on the SAME state that satisfied the wait: a second read
- # once raced the scripted daemon's ask reviving the saddle row,
- # and the conditionally-authored action vanished between the
- # two reads (KeyError on act).
- offered = goal_state(goal)
- return offered if offered.get("requeue_run") is not None else False
- state = wait_for(requeue_offered, timeout=30, what="requeue offered")
- check(state is not None,
- "a parked run on a dead saddle offers Requeue-to-resume")
- check(state.get("retry_run") is None,
- "an active goal offers no Retry supervision")
- open_before = open_session(run_task)
- def parked_session_ended():
- if open_before is None:
- return True
- conn = db()
- try:
- return conn.execute(
- "select ended_at is not null from sessions where id = ?",
- (open_before,)).fetchone()[0] == 1
- finally:
- conn.close()
- act(auth, state["requeue_run"], {})
- check(task_state(auth, run_task)["status"] == "queued",
- "the requeue returns the run to the queue")
- # The PARKED session itself must end (the restore-on-dispatch
- # successor may open immediately — the check keys the old row,
- # not the task's transient open-session window).
- check(wait_for(parked_session_ended, timeout=60,
- what="parked session closed") is not None,
- "the requeue closed the parked session (restore-on-dispatch)")
- conn = db()
- conn.execute("update saddles set status = 'idle' where id = ?", (fake_id,))
- conn.commit()
- conn.close()
- fake.goal_script = [("suspend",)]
- check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
- or False, timeout=60, what="requeued run re-dispatches") is not None,
- "the requeued run re-dispatches and re-parks")
- fresh_session = open_session(run_task)
- conn = db()
- fresh_notice = conn.execute(
- "select resume_notice from sessions where id = ?", (fresh_session,)
- ).fetchone()[0]
- conn.close()
- check(fresh_notice is None,
- "a requeue's fresh session carries no resume notice (empty panel state)")
- print("== B1: Retry supervision revives a failed goal ==")
- fake.push_results("failed", summary="the supervision collapsed (scripted)")
- check(wait_for(lambda: goal_state(goal)["status"] == "failed" or False,
- timeout=30, what="goal fails") is not None,
- "the run's failure fails the goal")
- check(wait_for(lambda: goal_state(goal).get("retry_run") is not None
- or False, timeout=30, what="retry offered") is not None,
- "a failed goal offers Retry supervision")
- fake.goal_script = [("suspend",)]
- act(auth, goal_state(goal)["retry_run"], {})
- state = goal_state(goal)
- check(state["status"] == "active",
- "the retry revives supervision (goal back to active)")
- check(task_state(auth, run_task)["status"] in ("queued", "assigned", "working",
- "suspended", "blocked"),
- "the retry re-offered the run task")
- check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
- or False, timeout=60, what="retried supervision parks") is not None,
- "the retried supervision dispatches and parks again")
- state = goal_state(goal)
- check(any(e["kind"] == "system" and "Supervision retried" in e["text"]
- for e in state["events"]),
- "the narrative records the supervision retry (a system row)")
- print("== B5 + C4: order, effective rules, capability badges ==")
- goal_page = http_get_page(auth, "/goals/%d" % goal)
- markers = [
- ('class="card goal-head"', "status head"),
- ("Agent progress", "progress"),
- ("Goal agent blocks", "attention blocks"),
- ("<h3>Plan</h3>", "plan"),
- ("<h3>Tasks</h3>", "tasks"),
- ("Initial brief", "brief"),
- ("Involved projects", "projects"),
- ("<h3>Rules</h3>", "rules"),
- ("Supervision narrative", "narrative"),
- ("<h3>Comments</h3>", "comments"),
- ("Goal agent runs", "runs"),
- ]
- positions = []
- for marker, _ in markers:
- check(marker in goal_page, "the goal page ships the %s section" % _)
- positions.append(goal_page.index(marker))
- check(positions == sorted(positions),
- "the page reads status → progress → attention → plan → tasks → brief →"
- " rules → narrative → comments → runs (in order)")
- check("goal.current_activity" in goal_page and "Current activity" not in goal_page,
- "the activity lives as ONE muted head line (the card is gone)")
- check(".goal-page .card" in open(os.path.join(REPO, "src/pages/app.css")).read(),
- "the stylesheet restores the goal page's card rhythm (padding regression)")
- rules_state = slot_public(auth, "/rules", "rules")
- act(auth, rules_state["create"], {"name": "goalui-primary", "body": "Primary rule"})
- act(auth, rules_state["create"], {"name": "goalui-codev", "body": "Co-dev rule"})
- act(auth, rules_state["create"], {"name": "goalui-ref", "body": "Reference rule"})
- conn = db()
- rule_ids = {name: int(conn.execute("select id from rules where name = ?",
- (name,)).fetchone()[0])
- for name in ("goalui-primary", "goalui-codev", "goalui-ref")}
- conn.execute("insert into rule_projects (rule_id, project_id, created_at)"
- " values (?, 1, strftime('%s','now'))", (rule_ids["goalui-primary"],))
- conn.execute("insert into projects (name, git_url, default_branch, created_at)"
- " values ('goalui-codev-project', 'https://example.com/g.git',"
- " 'main', strftime('%s','now'))")
- codev_project = conn.execute(
- "select id from projects where name = 'goalui-codev-project'").fetchone()[0]
- conn.execute("insert into projects (name, git_url, default_branch, created_at)"
- " values ('goalui-ref-project', 'https://example.com/r.git',"
- " 'main', strftime('%s','now'))")
- ref_project = conn.execute(
- "select id from projects where name = 'goalui-ref-project'").fetchone()[0]
- conn.execute("insert into rule_projects (rule_id, project_id, created_at)"
- " values (?, ?, strftime('%s','now'))",
- (rule_ids["goalui-codev"], codev_project))
- conn.execute("insert into rule_projects (rule_id, project_id, created_at)"
- " values (?, ?, strftime('%s','now'))",
- (rule_ids["goalui-ref"], ref_project))
- conn.commit()
- conn.close()
- act(auth, goal_state(goal)["add_link"], {"linked_project_id": str(codev_project),
- "relationship": "co-developed"})
- act(auth, goal_state(goal)["add_link"], {"linked_project_id": str(ref_project),
- "relationship": "reference"})
- rule_names = [r["name"] for r in goal_state(goal)["rules"]]
- check(rule_names == ["goalui-primary", "goalui-codev"],
- "the Rules section lists the goal's effective set (primary then"
- " co-developed; reference contributes nothing)")
- act(auth, slot_public(auth, "/saddles", "saddles")["create_capability"],
- {"name": "goalui-capability"})
- conn = db()
- capability = conn.execute(
- "select id from capabilities where name = 'goalui-capability'"
- ).fetchone()[0]
- # The early redirect-fixture worker joins the goal (a fanned
- # worker's shape) so its row carries the badge too.
- conn.execute("update tasks set goal_id = ? where id = ?", (goal, worker))
- conn.execute("insert into task_capabilities (task_id, capability_id)"
- " values (?, ?)", (worker, capability))
- conn.commit()
- conn.close()
- goal_worker = {"id": worker, "caps": None}
- act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
- "project_id": "1", "title": "Goalui fanned worker", "priority": "4",
- "description": "A worker the agent fanned out."})
- conn = db()
- fanned = conn.execute(
- "select id from tasks where title = 'Goalui fanned worker'").fetchone()[0]
- conn.execute("update tasks set goal_id = ? where id = ?", (goal, fanned))
- conn.execute("insert into task_capabilities (task_id, capability_id)"
- " values (?, ?)", (fanned, capability))
- conn.commit()
- conn.close()
- rows = {int(t["id"]): t for t in goal_state(goal)["goal_tasks"]}
- check(rows[fanned].get("capabilities") == ["goalui-capability"]
- and rows[worker].get("capabilities") == ["goalui-capability"],
- "task rows carry their required capability tags")
- goal_page = http_get_page(auth, "/goals/%d" % goal)
- check('stm-for-capability-in="goal_task.capabilities"' in goal_page
- and "badge-capability" in goal_page,
- "the task rows render capability badges")
- print("== B6: narrative weights — events full, comments footnote ==")
- # An AGENT comment projects the narrative's comment event (human
- # comments only thread) — pushed through the entries channel while
- # parked, so the suspended state stays put.
- fake.push_results("suspended", entries=[{
- "id": 9001, "command": "saddle comment", "output": "An agent aside.",
- "exit_code": 0, "type": "comment", "timestamp": "2026-09-12T00:00:00Z"}])
- check(wait_for(lambda: any(e["kind"] == "comment"
- for e in goal_state(goal)["events"]) or False,
- timeout=15, what="comment event lands") is not None,
- "an agent comment lands as a narrative event")
- goal_page = http_get_page(auth, "/goals/%d" % goal)
- check("stm-attribute.class=\"'goal-event' + (event.kind == 'comment'"
- " ? ' footnote' : '')\"" in goal_page.replace("\n", " ").replace(" ", " ")
- or "'goal-event' + (event.kind == 'comment' ? ' footnote' : '')" in goal_page,
- "the WHOLE comment event row carries .footnote (badge included)")
- check("'badge badge-goal-' + event.kind" in goal_page,
- "non-comment events keep their full-weight kind badges")
- css = open(os.path.join(REPO, "src/pages/app.css")).read()
- check(".goal-event.footnote" in css and ".footnote {" in css,
- "the stylesheet ships the footnote row weight beside the utility")
- print("== C2: system rows render distinctly on the ONE thread ==")
- run_comments = task_state(auth, run_task)["comments"]
- system_rows = [c for c in run_comments if c.get("is_system")]
- check(system_rows and all(c["author"] == "system" for c in system_rows)
- and not any(c.get("from_agent") for c in system_rows),
- "the harness bookkeeping rows are authored 'system' (never agent)")
- goal_thread = goal_state(goal)["comments"]
- check(any(c.get("is_system") for c in goal_thread),
- "the goal page reads the same thread (the system rows included)")
- css = open(os.path.join(REPO, "src/pages/app.css")).read()
- check(".comment.system" in css and ".system-label" in css,
- "the stylesheet ships the distinct system-event styling")
- page = http_get_page(auth, "/goals/%d" % goal)
- check("comment.is_system" in page and "system-label" in page,
- "the goal page renders system rows with the labelled muted style")
- task_page = http_get_page(auth, "/tasks/%d" % worker)
- check("comment.is_system" in task_page and "system-label" in task_page,
- "the task page renders system rows with the same style")
- print("== S5 U2: mechanical rows filtered from user-facing timelines ==")
- # The daemon's mechanical system entries (the park notice, the
- # restore footnote) ride the wire typed 'system': the raw terminal
- # row STORES (session_commands), but no comment row, narrative
- # event or dashboard timeline row is authored for them.
- open_run = open_session(run_task)
- mechanical = [
- {"id": 9501, "command": "saddle suspend",
- "output": "goal agent parked until an owned task completes or"
- " fails; resume via /start resume_notice",
- "exit_code": 0, "type": "system", "timestamp": "2026-09-12T00:00:00Z"},
- {"id": 9502, "command": "harness resume",
- "output": "[harness] session context restored — task resumed",
- "exit_code": 0, "type": "system", "timestamp": "2026-09-12T00:00:01Z"},
- ]
- status, _ = saddle_api("POST", "/api/saddle/results", fake_token,
- {"session_id": open_run, "state": "suspended",
- "entries": mechanical})
- check(status == 200, "the mechanical system entries push cleanly")
- conn = db()
- raw_rows = conn.execute(
- "select count(*) from session_commands where session_id = ?"
- " and type = 'system'", (open_run,)).fetchone()[0]
- conn.close()
- check(raw_rows >= 2,
- "the raw terminal keeps the mechanical rows (typed entries store)")
- check(all("parked until" not in c["body"]
- and "session context restored" not in c["body"]
- for c in task_state(auth, run_task)["comments"]),
- "no mechanical comment row lands on the thread (U2)")
- check(all("parked until" not in e["text"]
- and "session context restored" not in e["text"]
- for e in goal_state(goal)["events"]),
- "no mechanical narrative event lands on the goal page (U2)")
- dash_events = slot_public(auth, "/", "dashboard")["activity"]
- check(all("parked until" not in e["text"]
- and "session context restored" not in e["text"]
- for e in dash_events),
- "no mechanical row lands on the dashboard timeline (U2)")
- print("== C4: head line, answered-blocks expander, footnote comments ==")
- state = goal_state(goal)
- check(state["current_activity"] == "parked awaiting its tasks' terminal transitions",
- "a suspended run folds into the one muted head line")
- check(len(state.get("answered_blocks") or []) >= 2
- and len(state.get("open_blocks") or []) == 0,
- "the historic answered blocks expose as their own vector (open ones empty)")
- goal_page = http_get_page(auth, "/goals/%d" % goal)
- check("Answered blocks (" in goal_page and "comment-meta" in goal_page,
- "the answered blocks and the footnote comment meta ship in the markup")
- check("timeless note for a future reader" in goal_page,
- "the composer hint states the timeless-comment rule")
- css = open(os.path.join(REPO, "src/pages/app.css")).read()
- check(".goal-page .card { margin-bottom" in css and ".comment-meta" in css,
- "the stylesheet ships the card rhythm and the comment footnote meta")
- print("== C1: one review block per verdict path (F6) ==")
- # The daemon's honest round pushes the ENTRY and the STATE
- # together — the done pair must raise exactly ONE completion
- # block, and a redelivered pair nothing more.
- fake.goal_script = [("done", "# Completion\n\nOne block only.")]
- conn = db()
- conn.execute("insert into tasks (project_id, goal_id, kind, title, status,"
- " priority, completed_at, created_at) values (1, ?, 'task',"
- " 'Warmup', 'done', 5,"
- " strftime('%s','now'),"
- " strftime('%s','now','-30 seconds'))", (goal,))
- conn.commit()
- conn.close()
- check(wait_for(lambda: goal_state(goal)["status"] == "completion_review" or False,
- timeout=60, what="goalui completion review opens") is not None,
- "the scripted done round enters completion review")
- def completion_block_count():
- conn = db()
- try:
- return conn.execute(
- "select count(*) from task_blocks where task_id = ?"
- " and type = 'completion'", (run_task,)).fetchone()[0]
- finally:
- conn.close()
- check(completion_block_count() == 1,
- "the done state raises exactly ONE completion block")
- # Plan 16 S5 (U1): the verdict-shaped block renders the rich review
- # form — explicit accept/reject affordances with the free-text
- # feedback path, never a free-text-only verdict.
- goal_page = http_get_page(auth, "/goals/%d" % goal)
- check("Accept goal" in goal_page and "Reject with feedback" in goal_page
- and 'name="feedback"' in goal_page,
- "the completion review presents explicit accept/reject + free-text feedback (U1)")
- # The burst shape: the done ENTRY and the done STATE in one push
- # (fresh entry ids), then the whole pair redelivered — the
- # pending-report idempotency leaves the one row alone.
- conn = db()
- burst_session = conn.execute(
- "select id from sessions where task_id = ? and ended_at is null",
- (run_task,)).fetchone()[0]
- conn.close()
- done_pair = [{"id": 9401, "command": "saddle done",
- "output": "# Completion\n\nOne block only.", "exit_code": 0,
- "type": "done", "timestamp": "2026-09-12T00:00:00Z"}]
- status, _ = saddle_api("POST", "/api/saddle/results", fake_token,
- {"session_id": burst_session, "state": "done",
- "entries": done_pair,
- "summary": "# Completion\n\nOne block only."})
- check(status == 200 and completion_block_count() == 1,
- "the done entry + state pair raises nothing more (one block, ever)")
- state = goal_state(goal)
- act(auth, state["completion_pending_block"]["answer_action"],
- {"verdict": "accept"})
- check(wait_for(lambda: goal_state(goal)["status"] == "complete" or False,
- timeout=30, what="goalui goal completes") is not None,
- "accepting the completion completes the goal")
- check(completion_block_count() == 1,
- "the accept path leaves exactly one review-kind block behind")
- conn = db()
- stragglers = conn.execute(
- "select count(*) from task_blocks where task_id = ? and answer is null",
- (run_task,)).fetchone()[0]
- conn.close()
- check(stragglers == 0,
- "no unanswered straggler rows survive the completion")
- print("== S2: server-owned verdicts + the container-resolved reject ==")
- # A fresh goal drives the verdict paths the goal page's forms now
- # use: verdict markers for accept, raw `feedback` for reject, and
- # the block resolution living in the review CONTAINER (not
- # goal.plan_pending_block — the diagnosed completion-review bug).
- fake.goal_script = [("plan", "# S2 plan\n\nOne step")]
- act(auth, slot_public(auth, "/goals", "goals")["create"], {
- "project_id": "1", "title": "S2 verdict goal",
- "brief": "Covers the plan-16 S2 verdict paths.",
- "branch_name": "goal/s2-verdicts"})
- conn = db()
- s2_goal = int(conn.execute(
- "select id from goals where title = 'S2 verdict goal'").fetchone()[0])
- conn.close()
- act(auth, goal_state(s2_goal)["start"], {})
- check(wait_for(lambda: goal_state(s2_goal)["status"] == "plan_review" or False,
- timeout=90, what="S2 goal enters plan review") is not None,
- "the S2 goal enters plan review")
- s2_state = goal_state(s2_goal)
- s2_pending = s2_state["plan_pending_block"]
- # (1) A remark-less, text-less rejection is refused: the action
- # errors and the block keeps waiting.
- refusal = act(auth, s2_pending["answer_action"], {"feedback": " "})
- check(any(d.get("type") == "notify" and d.get("kind") == "error"
- and "answer is required" in (d.get("message") or "").lower()
- for d in refusal),
- "a remark-less, text-less rejection is refused with an error")
- conn = db()
- s2_still_open = conn.execute(
- "select answer from task_blocks where id = ?",
- (s2_pending["id"],)).fetchone()[0]
- conn.close()
- check(s2_still_open is None,
- "the refused rejection leaves the block unanswered")
- # (3) The goal page template ships no verdict constants and its
- # script resolves the review block from ANY [data-plan-review]
- # container via the open-blocks list (the completion-review fix).
- # Fetched while the review is open so the accept form renders.
- s2_page = http_get_page(auth, "/goals/%d" % s2_goal)
- check("Plan approved. Proceed with implementation." not in s2_page
- and "Review approved. Continue with the task." not in s2_page,
- "the goal page ships no verdict constants (server-owned wording)")
- check('name="verdict" value="accept"' in s2_page,
- "the goal page's accept forms post the verdict marker")
- check("resolveReviewBlock" in s2_page and "goal.open_blocks" in s2_page,
- "the reject flow resolves its block from the open-blocks list (container-keyed)")
- check("statum.act(block.answer_action, { feedback: feedback })" in s2_page,
- "the reject flow posts raw feedback (the server wraps the prefix)")
- check("extraText.length === 0" in s2_page,
- "a remark-less rejection with textarea content is allowed (S2)")
- # (2) The goal page's reject rides `feedback` (the client composes
- # the numbered-remarks body) and the SERVER wraps it with the plan
- # prefix — byte-identical to the historical client-built answer.
- act(auth, s2_pending["answer_action"],
- {"feedback": "> ## One step\n\n1. Tighten it\n\n2. Name the owner"})
- conn = db()
- s2_wrapped = conn.execute(
- "select answer from task_blocks where id = ?",
- (s2_pending["id"],)).fetchone()[0]
- conn.close()
- check(s2_wrapped
- == "Plan feedback received:\n\n"
- "> ## One step\n\n1. Tighten it\n\n2. Name the owner",
- "the plan rejection wraps the client-shaped body with the stable prefix (%r)"
- % s2_wrapped)
- check(goal_state(s2_goal)["status"] == "planning",
- "the feedback rejection returns the agent to planning")
- BUNDLES_ORDER = ("pages", "dispatch", "subtasks", "lineage",
- "allocation", "lifecycle", "answers", "push", "goals",
- "replay", "goalui")
- def run_bundle(name):
- """Runs one shard to completion; returns a failure count."""
- started = time.monotonic()
- ctx = start_bundle(name)
- runner = globals()["bundle_" + name]
- try:
- runner(ctx)
- finally:
- # Exit-code diagnostic: distinguishes a crashed app (negative
- # returncode, e.g. -11 SIGSEGV) from a clean shutdown. When
- # tearing down mid-exception, also capture the still-alive
- # app's stacks before killing it (a wedged-but-alive server
- # otherwise leaves no evidence).
- print("(app returncode at teardown: %s)" % ctx.app.poll())
- if ctx.app.poll() is None and sys.exc_info()[0] is not None:
- dump_app_stacks(ctx.app.pid)
- ctx.fake.kill()
- try:
- ctx.app.terminate()
- try:
- ctx.app.wait(timeout=5)
- except subprocess.TimeoutExpired:
- ctx.app.kill()
- check(ctx.app.poll() in (0, -15),
- "bundle %s app shut down cleanly (exit %s)"
- % (name, ctx.app.poll()))
- except OSError:
- pass
- print("%d passed, %d failed" % (passes, failures))
- print("[bundle %s] %.1fs" % (name, time.monotonic() - started))
- return failures
- def run_all_parallel():
- """The default meson entry: one subprocess per bundle (each with its
- own app, daemons and database), verdicts aggregated into one exit
- code and one combined check-count summary."""
- base = os.environ.get("WEBSTACK_WORK", "/tmp/kilo/webstack-e2e")
- log_dir = os.path.join(os.path.dirname(base) or "/tmp/kilo",
- "webstack-shards")
- os.makedirs(log_dir, exist_ok=True)
- procs = {}
- started = time.monotonic()
- for name in BUNDLES_ORDER:
- out_path = os.path.join(log_dir, "bundle-%s.log" % name)
- handle = open(out_path, "w")
- procs[name] = (subprocess.Popen(
- [sys.executable, "-u", os.path.abspath(__file__), APP,
- "--bundle", name],
- stdout=handle, stderr=subprocess.STDOUT), handle, out_path)
- total_pass = total_fail = 0
- broken = []
- for name, (proc, handle, out_path) in procs.items():
- try:
- proc.wait(timeout=SHARD_TIMEOUT_S)
- except subprocess.TimeoutExpired:
- proc.kill()
- proc.wait()
- handle.close()
- text = open(out_path).read()
- match = re.search(r"(\d+) passed, (\d+) failed", text)
- took = re.search(r"\[bundle %s\] ([0-9.]+)s" % name, text)
- bundle_pass = int(match.group(1)) if match else 0
- bundle_fail = int(match.group(2)) if match else -1
- total_pass += max(bundle_pass, 0)
- total_fail += max(bundle_fail, 0)
- status = "ok"
- if proc.returncode != 0 or bundle_fail != 0 or match is None:
- status = "BROKEN (exit %s)" % proc.returncode
- broken.append(name)
- print("[shard %-10s] %6ss %3d passed %3d failed %s"
- % (name, took.group(1) if took else "?",
- bundle_pass, bundle_fail, status))
- if status != "ok":
- tail = "\n".join(text.rstrip().split("\n")[-60:])
- print("--- web-stack/%s tail (full log: %s) ---" % (name, out_path))
- print(tail)
- print("--- end web-stack/%s tail ---" % name)
- print("shards wall: %.1fs (logs under %s)" % (time.monotonic() - started, log_dir))
- print("ALL SHARDS: %d passed, %d failed%s"
- % (total_pass, total_fail,
- "" if not broken else " BROKEN: " + ", ".join(broken)))
- return 1 if broken or total_fail else 0
- def main():
- if not os.path.exists(APP):
- raise SystemExit("app binary not found at %s (build first)" % APP)
- argv = sys.argv[2:]
- if "--bundle" in argv:
- sys.exit(1 if run_bundle(argv[argv.index("--bundle") + 1]) else 0)
- if os.environ.get("WEBSTACK_SEQUENTIAL"):
- # Debug path: every bundle in-process, one after another.
- for name in BUNDLES_ORDER:
- if run_bundle(name):
- return 1
- return 0
- return run_all_parallel()
- if __name__ == "__main__":
- sys.exit(main())
|