2026-09-29 14:42:09 +02:00
""" UI for runbook-pack/v0.2 runs.
Three views of one run :
flow the guided questionnaire : stages , the recommended next action , steps , gates , questions ( main )
data every datapoint , grouped or filtered by dimension , editable independently of the flow
overview progress , gate states , datapoint states , deviations , mode history
Server - rendered forms ; the only script is the small one in ui . page ( ) .
"""
from __future__ import annotations
import html
from urllib . parse import quote
2026-09-29 15:50:56 +02:00
from . import artefacts as A , run as R1 , run2 as R
2026-09-29 14:42:09 +02:00
from . flow import Flow
DONE = ( " done " , " skipped " , " pushed " )
def e ( s ) - > str :
return html . escape ( str ( s ) )
def sp ( state : str ) - > str :
return f " <span class=pill data-s= ' { e ( state ) } ' > { e ( state ) } </span> "
# ---- starting a run -------------------------------------------------------------------------------------------
def start_form ( app , i : int , pack ) - > str :
fl = Flow ( pack . data )
fields = " "
for d in fl . datapoints . values ( ) :
if d . get ( " produced_by " ) :
continue
name , default = f " p_ { d [ ' id ' ] } " , d . get ( " default " , " " )
if d . get ( " values " ) :
ctl = f " <select name= ' { name } ' > " + " " . join ( f " <option { ' selected ' if o == default else ' ' } > { e ( o ) } </option> " for o in d [ " values " ] ) + " </select> "
elif d [ " type " ] == " bool " :
ctl = f " <select name= ' { name } ' ><option value= ' ' >(not set)</option><option>true</option><option>false</option></select> "
else :
ctl = f " <input name= ' { name } ' value= ' { e ( default ) } ' > "
dims = " " . join ( f " <span class=chip> { e ( k ) } : { e ( v ) } </span> " for k , v in ( d . get ( " dimensions " ) or { } ) . items ( ) )
fields + = f " <p><label> { e ( d [ ' label ' ] ) } <br> { ctl } </label> { dims } <br><span class=note> { e ( d . get ( ' help ' , ' ' ) ) } Never enter secrets.</span></p> "
irrev = [ s for s in fl . steps . values ( ) if not s . get ( " reversible " , True ) ]
pre = " "
if irrev :
pre = ( " <fieldset><legend>Pre-authorize irreversible steps (auto-confirm only)</legend> "
" <p class=note>An auto-confirm run never confirms an irreversible step by itself unless you name it here. Steps marked "
" manual-only are never automated.</p> "
+ " " . join ( f " <label><input type=checkbox name= ' pre_ { e ( s [ ' id ' ] ) } ' > { e ( s [ ' title ' ] ) } "
f " { ' (manual-only) ' if s . get ( ' auto_confirm ' ) == ' manual-only ' else ' ' } </label><br> " for s in irrev ) + " </fieldset> " )
return ( f " <form method=post action= ' /run/new/ { i } ' > { app . hidden ( ) } { fields } "
" <fieldset><legend>Mode</legend> "
" <label><input type=radio name=mode value=manual checked> <b>Manual</b>: you confirm every check and gate.</label><br> "
" <label><input type=radio name=mode value=auto-confirm> <b>Auto-confirm</b>: the engine confirms when preconditions hold. "
" It does not run commands; you (or another tool) do the work.</label></fieldset> "
2026-09-29 15:50:56 +02:00
f " { pre } { app . who ( ) } <button>Start run</button></form> " )
2026-09-29 14:42:09 +02:00
def create_run ( app , i : int , form : dict ) :
pack = app . packs [ i ]
params = { k [ 2 : ] : v for k , v in form . items ( ) if k . startswith ( " p_ " ) and v != " " }
pre = [ k [ 4 : ] for k , v in form . items ( ) if k . startswith ( " pre_ " ) ]
return R . create ( pack , form . get ( " operator " , " " ) , params , mode = form . get ( " mode " , " manual " ) , preauthorize = pre )
# ---- context -------------------------------------------------------------------------------------------------------
class Ctx :
def __init__ ( self , app , i : int , rid : str , run , view : str = " flow " , query : dict | None = None ) :
self . app , self . i , self . rid , self . run , self . view = app , i , rid , run , view
self . q = query or { }
self . fl = R . flow ( run )
self . base = f " /run/ { i } / { rid } "
self . na = R . next_actions ( run )
self . num = { sid : k + 1 for k , sid in enumerate ( x for x in self . fl . order if self . fl . kind ( x ) == " step " ) }
self . manual = run . data [ " mode " ] == " manual "
self . finished = bool ( run . data . get ( " outcome " ) )
def h ( self ) - > str :
return self . app . hidden ( )
# ---- page ---------------------------------------------------------------------------------------------------------------
def run_page ( app , i : int , rid : str , msg : str = " " , query : dict | None = None ) - > bytes :
from . ui import page
pack = app . packs [ i ]
run = R1 . load ( pack , rid )
q = { k : ( v [ 0 ] if isinstance ( v , list ) else v ) for k , v in ( query or { } ) . items ( ) }
view = q . get ( " view " , " flow " ) if q . get ( " view " ) in ( " flow " , " data " , " overview " ) else " flow "
c = Ctx ( app , i , rid , run , view , q )
d = run . data
ctx = d . get ( " context " ) or { }
where = " "
if ctx . get ( " project " ) :
where = ( f " · project <a href= ' /project/ { e ( ctx [ ' project ' ] ) } ' > { e ( ctx [ ' project ' ] ) } </a> "
2026-09-29 20:08:17 +02:00
f " { ' @ ' + e ( ctx [ ' target ' ] ) if ctx . get ( ' target ' ) else ' ' } · { e ( ctx . get ( ' environment ' , ' ' ) ) } "
f " { ' · part of <a href= ' + chr ( 39 ) + ' /project-run/ ' + e ( ctx [ ' project_run ' ] ) + chr ( 39 ) + ' >project run ' + e ( ctx [ ' project_run ' ] ) + ' </a> ' if ctx . get ( ' project_run ' ) else ' ' } " )
2026-09-29 14:42:09 +02:00
ov = R . overview ( run )
prog = ov [ " progress " ]
out = [ f " <h1> { e ( pack . data [ ' title ' ] ) } </h1> "
f " <p> { sp ( d [ ' mode ' ] ) } { sp ( d [ ' outcome ' ] ) if d . get ( ' outcome ' ) else ' ' } "
f " <span class=note>run { e ( rid ) } · operator { e ( d [ ' operator ' ] ) } { where } </span></p> "
2026-09-29 15:50:56 +02:00
f " <p class=note> { times_line ( run ) } </p> "
2026-09-29 14:42:09 +02:00
f " <p class=eyebrow> { prog [ ' done ' ] } of { prog [ ' total ' ] } done "
f " { ' · ' + str ( prog [ ' pushed ' ] ) + ' pushed through ' if prog [ ' pushed ' ] else ' ' } </p> "
f " <progress max= { prog [ ' total ' ] } value= { prog [ ' done ' ] + prog [ ' pushed ' ] } style= ' width:100% ' ></progress> " ]
if d [ " deviations " ] :
out . append ( f " <p class=note>⚑ { len ( d [ ' deviations ' ] ) } deviation(s) recorded: this run can be finished but will never count towards "
" maturity. <a href= ' ?view=overview ' >See them</a>.</p> " )
if msg :
out . append ( f " <p class=bad> { e ( msg ) } </p> " )
tabs = " " . join ( f " <a class= ' tab { ' on ' if view == v else ' ' } ' href= ' ?view= { v } ' > { label } </a> "
for v , label in ( ( " flow " , " Guided flow " ) , ( " data " , " Data " ) , ( " overview " , " Overview " ) ) )
out . append ( f " <nav class=tabs> { tabs } </nav> " )
if view == " flow " :
out . append ( _flow_view ( c , ov ) )
elif view == " data " :
out . append ( _data_view ( c ) )
else :
out . append ( _overview_view ( c , ov ) )
return page ( pack . data [ " title " ] , " " . join ( out ) )
2026-09-29 15:50:56 +02:00
def stamp ( iso : str | None ) - > str :
return iso [ : 16 ] . replace ( " T " , " " ) + " UTC " if iso else " - "
def times_line ( run ) - > str :
""" Started, finished and how long: the run ' s timestamps at a glance. """
t = R1 . run_times ( run )
if t [ " finished_run " ] :
return ( f " Started { e ( stamp ( t [ ' started ' ] ) ) } · finished { e ( stamp ( t [ ' finished ' ] ) ) } · took <b> { e ( R1 . fmt_duration ( t [ ' elapsed ' ] ) ) } </b> "
f " { ' · working ' + e ( R1 . fmt_duration ( t [ ' working ' ] ) ) if t [ ' working ' ] else ' ' } " )
return ( f " Started { e ( stamp ( t [ ' started ' ] ) ) } · running for <b> { e ( R1 . fmt_duration ( t [ ' elapsed ' ] ) ) } </b> "
f " { ' · working ' + e ( R1 . fmt_duration ( t [ ' working ' ] ) ) if t [ ' working ' ] else ' ' } " )
2026-09-29 14:42:09 +02:00
# ---- flow view ---------------------------------------------------------------------------------------------------------
def _flow_view ( c : Ctx , ov : dict ) - > str :
run , fl = c . run , c . fl
out = [ ]
framing = list ( run . pack . data . get ( " framing " , [ ] ) )
if framing :
out . append ( " <details class=framing open><summary><b>Keep in mind</b></summary><ul> " + " " . join ( f " <li> { e ( x ) } </li> " for x in framing ) + " </ul></details> " )
out . append ( _mode_panel ( c , ov ) )
out . append ( _next_banner ( c ) )
for st in fl . data [ " stages " ] :
out . append ( _stage ( c , st ) )
2026-09-29 18:03:12 +02:00
if c . finished or _all_complete ( run ) or R1 . latest_feedback ( run ) :
2026-09-29 14:42:09 +02:00
out . append ( c . app . feedback_form ( c . i , c . rid , run ) )
out . append ( _close ( c ) )
return " " . join ( out )
def _all_complete ( run ) - > bool :
fl = R . flow ( run )
return all ( R . is_complete ( run , x ) for x in fl . order )
def _mode_panel ( c : Ctx , ov : dict ) - > str :
run = c . run
if c . finished :
return " "
if c . manual :
return ( f " <div class=panel><b>Manual mode.</b> <span class=note>You confirm every check and gate. Unmet preconditions can be "
f " overridden with a reason; each override is recorded.</span> "
f " <form method=post action= ' { c . base } /mode ' class=row> { c . h ( ) } <input type=hidden name=to value=auto-confirm> "
" <button>Hand over to auto-confirm</button></form></div> " )
ap = run . data [ " auto_confirm " ]
last = ap . get ( " last " )
status = " "
if ap . get ( " stopped " ) :
status = f " <p class=bad>Stopped at { e ( ap [ ' stopped ' ] [ ' item ' ] ) } : { e ( ap [ ' stopped ' ] [ ' reason ' ] ) } </p> "
elif last :
status = f " <p class=note>Last: { e ( last [ ' action ' ] ) } { ' ' + e ( last [ ' item ' ] ) if last . get ( ' item ' ) else ' ' } { e ( last . get ( ' detail ' , ' ' ) ) } </p> "
pre = ap . get ( " preauthorized " ) or [ ]
return ( f " <div class=panel data-s= ' auto ' ><b>Auto-confirm mode.</b> <span class=note>The engine confirms when preconditions hold. It never "
" runs commands: do the work yourself, then check again. Irreversible steps wait for you unless pre-authorized "
f " { ' ( ' + e ( ' , ' . join ( pre ) ) + ' ) ' if pre else ' ' } .</span> { status } "
f " <form method=post action= ' { c . base } /tick ' class=row> { c . h ( ) } "
" <button name=n value=1>Check once</button> <button name=n value=50>Run until it waits</button></form> "
f " <form method=post action= ' { c . base } /mode ' class=row> { c . h ( ) } <input type=hidden name=to value=manual> "
" <button>Take over (manual)</button></form></div> " )
def _title_of ( c : Ctx , id_ : str ) - > str :
fl = c . fl
node = fl . node ( id_ ) if fl . kind ( id_ ) != " ? " else { }
return node . get ( " title " , id_ )
def _next_banner ( c : Ctx ) - > str :
na = c . na
if c . finished :
return " "
rec = na [ " recommended " ]
if rec is None :
if _all_complete ( c . run ) :
return " <div class=banner><b>Everything is done.</b> Close the run below.</div> "
b = na [ " blocked " ] [ 0 ] if na [ " blocked " ] else None
return f " <div class=banner><b>Nothing can start.</b> { e ( _title_of ( c , b [ ' id ' ] ) ) if b else ' ' } waits: { e ( ' ; ' . join ( b [ ' unmet ' ] ) ) if b else ' ' } </div> "
kind = c . fl . kind ( rec )
others = [ x for x in na [ " ready " ] + na [ " running " ] if x != rec ]
links = " " . join ( f " <a class=chip href= ' #item- { e ( x ) } ' > { e ( _title_of ( c , x ) ) } </a> " for x in others )
verb = " Continue " if rec in na [ " running " ] else ( " Evaluate gate " if kind == " gate " else " Next " )
return ( f " <div class=banner><b> { verb } :</b> <a href= ' #item- { e ( rec ) } ' > { e ( _title_of ( c , rec ) ) } </a> <span class=note>(recommended)</span> "
f " { ' <br><span class=note>Also possible in parallel:</span> ' + links if links else ' ' } </div> " )
def _stage ( c : Ctx , st : dict ) - > str :
run , fl = c . run , c . fl
done = R . is_complete ( run , st [ " id " ] )
members = fl . members ( st [ " id " ] , include_gates = False )
n_done = sum ( run . data [ " steps " ] [ m ] [ " state " ] in DONE for m in members )
body = [ f " <p class=note><b>Goal:</b> { e ( st [ ' goal ' ] ) } </p> " ]
if st . get ( " framing " ) :
body . append ( " <ul class=note> " + " " . join ( f " <li> { e ( x ) } </li> " for x in st [ " framing " ] ) + " </ul> " )
q = _questions ( c , st )
if q :
body . append ( q )
for s in st . get ( " steps " , [ ] ) :
body . append ( _step ( c , s ) )
for child in st . get ( " stages " , [ ] ) :
body . append ( _stage ( c , child ) )
for g in st . get ( " gates " , [ ] ) :
body . append ( _gate ( c , g ) )
return ( f " <details class= ' stage ' id= ' stage- { e ( st [ ' id ' ] ) } ' { ' ' if done else ' open ' } ><summary><span class=head> "
f " <span><b> { e ( st [ ' title ' ] ) } </b> <span class=note> { n_done } / { len ( members ) } steps</span></span> "
f " { sp ( ' complete ' if done else ' in progress ' ) } </span></summary> { ' ' . join ( body ) } </details> " )
def _questions ( c : Ctx , st : dict ) - > str :
dps = [ d for d in c . fl . datapoints . values ( ) if d . get ( " ask_in " ) == st [ " id " ] and not d . get ( " produced_by " ) ]
if not dps or c . finished :
return " "
rows = [ ]
2026-09-29 15:50:56 +02:00
locked = False
2026-09-29 14:42:09 +02:00
for d in dps :
rec = c . run . data [ " datapoints " ] [ d [ " id " ] ]
2026-09-29 15:50:56 +02:00
users = R . datapoint_users ( c . run , d [ " id " ] )
locked = locked or bool ( users and rec . get ( " value " ) is not None )
rows . append ( f " <p> { _input ( d , rec , users = users ) } <br><span class=note> { e ( d . get ( ' help ' , ' ' ) ) } </span></p> " )
2026-09-29 14:42:09 +02:00
return ( f " <div class=questions><b>Questions for this stage</b> <span class=note>(values used by its steps; never enter secrets)</span> "
f " <form method=post action= ' { c . base } /data ' > { c . h ( ) } <input type=hidden name=next value= ' { c . base } #stage- { e ( st [ ' id ' ] ) } ' > "
2026-09-29 15:50:56 +02:00
f " { ' ' . join ( rows ) } { _override_fields ( c , locked ) } <button>Save answers</button></form></div> " )
def _override_fields ( c : Ctx , locked : bool ) - > str :
""" Only shown when some value is already in use by started steps: changing it is a recorded deviation. """
if not locked :
return " "
if not c . manual :
return " <p class=note>Values already in use by steps can only be changed in manual mode: take over first.</p> "
return ( " <details class=override><summary>Changing a value that steps already used (override)</summary> "
" <p class=bad>Risk: steps that already ran used the previous value, so their results may no longer match the new one. "
" The change is recorded as a deviation.</p> "
" <p><input name=override_reason class=grow style= ' width:100 % ' placeholder= ' why you change it now (required for values in use) ' ></p> "
" <p><label><input type=checkbox name=acknowledge_risk value=1> I acknowledge the risk.</label></p></details> " )
2026-09-29 14:42:09 +02:00
2026-09-29 15:50:56 +02:00
def _input ( d : dict , rec : dict , editable : bool = True , users : list [ str ] | None = None ) - > str :
2026-09-29 14:42:09 +02:00
name = f " d_ { d [ ' id ' ] } "
val = rec . get ( " value " , " " )
dis = " " if editable else " disabled "
if d . get ( " values " ) :
ctl = f " <select name= ' { name } ' { dis } > " + " " . join ( f " <option { ' selected ' if o == val else ' ' } > { e ( o ) } </option> " for o in d [ " values " ] ) + " </select> "
elif d [ " type " ] == " bool " :
ctl = ( f " <select name= ' { name } ' { dis } ><option value= ' ' >(not set)</option> "
+ " " . join ( f " <option { ' selected ' if o == val else ' ' } > { o } </option> " for o in ( " true " , " false " ) ) + " </select> " )
else :
ctl = f " <input name= ' { name } ' value= ' { e ( val ) } ' { dis } > "
err = f " <span class=bad> { e ( rec . get ( ' error ' , ' ' ) ) } </span> " if rec [ " state " ] == " err " else " "
2026-09-29 15:50:56 +02:00
use = ( f " <span class=chip title= ' changing it is a recorded deviation ' >in use by { e ( ' , ' . join ( users ) ) } </span> "
if users and rec . get ( " value " ) is not None else " " )
return f " <label> { e ( d [ ' label ' ] ) } { sp ( rec [ ' state ' ] ) } { use } <br> { ctl } </label> { err } "
2026-09-29 14:42:09 +02:00
# ---- steps ------------------------------------------------------------------------------------------------------------------
def _step ( c : Ctx , s : dict ) - > str :
run , fl = c . run , c . fl
sid = s [ " id " ]
st = run . data [ " steps " ] [ sid ]
state = st [ " state " ]
eff = R . step_def ( run , sid )
n = c . num [ sid ]
url = f " { c . base } / { e ( sid ) } "
marks = [ ]
if s . get ( " idempotent " ) or eff . get ( " idempotent " ) :
marks . append ( f " <span class=chip> { e ( eff . get ( ' idempotent ' ) or s . get ( ' idempotent ' ) ) } </span> " )
if not eff . get ( " reversible " , True ) or any ( not a . get ( " reversible " , s . get ( " reversible " , True ) ) for a in s . get ( " one_of " , [ ] ) ) :
marks . append ( " <span class=chip data-s= ' irreversible ' >irreversible</span> " )
if s . get ( " auto_confirm " ) == " manual-only " :
marks . append ( " <span class=chip>manual-only</span> " )
2026-09-29 15:50:56 +02:00
took = R1 . run_times ( run ) [ " step_seconds " ] . get ( sid )
if took is not None and state in DONE :
marks . append ( f " <span class=note>took { e ( R1 . fmt_duration ( took ) ) } </span> " )
2026-09-29 14:42:09 +02:00
title = eff . get ( " title " , s [ " title " ] ) if st . get ( " alternative " ) else s [ " title " ]
actor = fl . actors . get ( s [ " actor " ] , { } ) . get ( " role " , s [ " actor " ] )
body = [ ]
if s . get ( " description " ) :
body . append ( f " <p class=note> { e ( s [ ' description ' ] ) } </p> " )
unmet = R . unmet ( run , sid ) if state == " todo " else [ ]
if state == " todo " :
body . append ( _start_control ( c , s , sid , url , unmet ) )
elif state == " running " :
body . append ( _running ( c , s , eff , st , sid , url ) )
elif state == " failed " :
idem = eff . get ( " idempotent " ) or s . get ( " idempotent " )
ack = ( " " if idem == " idempotent " else
" <label><input type=checkbox name=acknowledge_partial value=1> I have checked that the earlier attempt did not partly apply</label> " )
body . append ( f " <p class=bad>Failed: { e ( st . get ( ' note ' ) or ' (no comment) ' ) } </p> "
f " <form method=post action= ' { url } /retry ' > { c . h ( ) } <p class=row> { ack } <button>Retry</button></p></form> " )
elif state == " pushed " :
body . append ( " <p class=note>Passed over by push-through without verification: not done, and recorded as a deviation.</p> " )
elif state == " done " and st . get ( " note " ) :
body . append ( f " <p class=note>Comment: { e ( st [ ' note ' ] ) } </p> " )
if state == " done " or state == " pushed " :
cmd = _command ( run , sid )
if cmd :
body . insert ( 0 , f " <pre> { e ( cmd ) } </pre> " )
body . append ( c . app . capture_html ( url , eff , st , state , c . finished ) )
current = " id=current " if sid == c . na [ " recommended " ] else " "
fold = " " if state in DONE else " open "
return ( f " <details class= ' step { e ( state ) } ' id= ' item- { e ( sid ) } ' { fold } ><summary { current } ><span class=head> "
f " <span><b> { n } . { e ( title ) } </b> <span class=note>[ { e ( actor ) } · { e ( s [ ' owner ' ] ) } ]</span> { ' ' . join ( marks ) } </span> "
f " { sp ( state ) } </span></summary> { ' ' . join ( body ) } </details> " )
def _command ( run , sid : str ) - > str | None :
try :
return R . command_for ( run , sid )
except ( KeyError , ValueError ) as ex :
return f " (needs a value that is not set: { ex } ) "
def _start_control ( c : Ctx , s : dict , sid : str , url : str , unmet : list [ str ] ) - > str :
run = c . run
if c . finished :
return " "
alts = s . get ( " one_of " , [ ] )
rec = R . recommended_alt ( run , sid ) if alts else None
alt_html = " "
if alts :
alt_html = " <p><b>Choose one:</b><br> " + " " . join (
f " <label><input type=radio name=alt value= ' { e ( a [ ' id ' ] ) } ' { ' checked ' if ( a [ ' id ' ] == rec or ( rec is None and a is alts [ 0 ] ) ) else ' ' } > "
f " { e ( a [ ' title ' ] ) } { ' <span class=note>(recommended)</span> ' if a [ ' id ' ] == rec else ' ' } </label><br> " for a in alts ) + " </p> "
irrev = ( not s . get ( " reversible " , True ) ) or any ( not a . get ( " reversible " , s . get ( " reversible " , True ) ) for a in alts )
phrase = " "
if irrev :
phrase = ( f " <p>This step is <b>irreversible</b>. Type the confirmation phrase to start it:<br> "
f " <code> { e ( R . phrase_for ( run , sid ) ) } </code><br><input name=phrase autocomplete=off class=grow style= ' width:100% ' ></p> " )
if not unmet :
if s . get ( " auto_confirm " ) == " manual-only " and not c . manual :
return " <p class=note>Manual-only: auto-confirm will not start this step. Take over to do it yourself.</p> "
return f " <form method=post action= ' { url } /start ' > { c . h ( ) } { alt_html } { phrase } <button>Start this step</button></form> "
risk = R . _risk_text ( run , sid )
wait = f " <p class=note>Waiting for: { e ( ' ; ' . join ( unmet ) ) } </p> "
if not c . manual :
return wait + " <p class=note>Overrides are made in manual mode.</p> "
return ( wait + f " <details class=override><summary>Start anyway (override)</summary> "
f " <form method=post action= ' { url } /start ' > { c . h ( ) } <p class=bad>Downstream risk: { e ( risk ) } </p> { alt_html } { phrase } "
" <p><input name=override_reason class=grow style= ' width:100 % ' placeholder= ' why you continue anyway (required) ' required></p> "
" <p><label><input type=checkbox name=acknowledge_risk value=1 required> I acknowledge the risk. Record this as a deviation.</label></p> "
" <button>Override and start</button></form></details> " )
def _running ( c : Ctx , s : dict , eff : dict , st : dict , sid : str , url : str ) - > str :
run = c . run
out = [ ]
cmd = _command ( run , sid )
if cmd :
out . append ( f " <pre> { e ( cmd ) } </pre><button type=button data-copy>Copy</button> " )
ver = eff [ " verify " ]
exp = ver . get ( " expect " ) or ( None if ver . get ( " datapoints_ok " ) else " manual " )
said = ver . get ( " done_when " ) or ( " datapoints ok: " + " , " . join ( ver . get ( " datapoints_ok " , [ ] ) ) )
v = st . get ( " verification " )
if exp == " manual " :
out . append ( f " <form method=post action= ' { url } /verify ' > { c . h ( ) } <p>Is this true? <b> { e ( said ) } </b></p> "
" <button name=ok value=1>Yes, it is</button> <button name=ok value=0>Not yet</button></form> " )
else :
what = ( f " <code> { e ( _render_check ( run , ver ) ) } </code> " if ver . get ( " command " ) else " the datapoint states " )
out . append ( f " <form method=post action= ' { url } /verify ' > { c . h ( ) } <input type=hidden name=yes value=1> "
f " <p class=note>Checks: { what } </p><button>Run check</button></form> " )
if not v :
out . insert ( 0 , " <p class=note>1. Do the work, then check → 2. Mark done to lock it in</p> " )
elif v [ " passed " ] :
out . append ( " <p><b>✔ Check passed.</b> Press <b>Mark done</b> to lock it in.</p> " )
else :
out . append ( " <p class=bad>✘ Check not passed. Fix the cause and check again, or mark the step failed.</p> " )
ev = " "
if v and v [ " passed " ] :
ev = " " . join ( f " <label> { e ( x ) } <input name= ' e_ { e ( x ) } ' required></label> " for x in s . get ( " evidence " , [ ] ) )
ev = f " <p> { ev } </p> " if ev else " "
done_btn = f " <button formaction= ' { url } /done ' >Mark done</button> " if v and v [ " passed " ] else " "
out . append ( f " <form method=post action= ' { url } /fail ' > { c . h ( ) } { ev } <p class=row> { done_btn } "
f " <button formaction= ' { url } /fail ' formnovalidate data-fail>Mark failed</button> "
" <input name=note class=grow placeholder= ' comment (what happened; no secrets) ' ></p> "
" <p class= ' note bad ' data-warn hidden>Add a comment about what went wrong, or click <b>Mark failed</b> again to fail without one.</p></form> " )
if eff . get ( " rollback " ) :
out . append ( f " <p class=note>Rollback: { e ( _render_text ( run , eff [ ' rollback ' ] ) ) } </p> " )
return " " . join ( out )
def _render_text ( run , text : str ) - > str :
try :
return R . render ( text , run . data [ " params " ] , run . pack )
except ( KeyError , ValueError ) :
return text
def _render_check ( run , ver : dict ) - > str :
return _render_text ( run , ver . get ( " command " , " " ) )
# ---- gates ---------------------------------------------------------------------------------------------------------------------
def _gate ( c : Ctx , g : dict ) - > str :
run , fl = c . run , c . fl
gid = g [ " id " ]
gs = run . data [ " gates " ] [ gid ]
state = gs [ " state " ]
unmet = R . unmet ( run , gid ) if state not in R . DONE_GATE else [ ]
rows = [ ]
for idx , ck in enumerate ( g [ " checks " ] ) :
res = gs [ " results " ] . get ( str ( idx ) )
exp = ck . get ( " expect " ) or ( None if ck . get ( " datapoints_ok " ) else " manual " )
text = ck . get ( " done_when " ) or " datapoints ok: " + " , " . join ( ck . get ( " datapoints_ok " , [ ] ) )
pill = sp ( " passed " if res and res [ " passed " ] else " failed " ) if res else sp ( " not checked " )
ctl = " "
if state not in R . DONE_GATE and not c . finished :
ctl = _gate_check_form ( c , gid , idx , ck , exp , unmet )
rows . append ( f " <li> { pill } { e ( text ) } { ' <br> ' + ctl if ctl else ' ' } </li> " )
waive = " "
w = g . get ( " waivable " )
if w and state in ( " open " , " failed " ) and not c . finished :
role = fl . actors . get ( w [ " by " ] , { } ) . get ( " role " , w [ " by " ] )
waive = ( f " <details class=override><summary>Waive this gate (declared waivable by { e ( role ) } )</summary> "
f " <form method=post action= ' { c . base } /gate/ { e ( gid ) } /waive ' > { c . h ( ) } "
f " <p class=bad>Downstream risk: { e ( R . _risk_text ( run , gid ) ) } </p> "
" <p><input name=reason class=grow style= ' width:100 % ' placeholder= ' reason (required) ' required></p> "
" <button>Waive and record as a deviation</button></form></details> " )
extra = " "
if state == " pushed " :
extra = " <p class=note>Passed over by push-through without passing: recorded as a deviation.</p> "
if state == " waived " :
extra = f " <p class=note>Waived: { e ( gs [ ' waiver ' ] [ ' reason ' ] ) } </p> "
current = " open " if state not in R . DONE_GATE else " "
marker = " id=current " if gid == c . na [ " recommended " ] else " "
return ( f " <details class= ' step gate { e ( state ) } ' id= ' item- { e ( gid ) } ' { current } ><summary { marker } ><span class=head> "
f " <span><b>Gate: { e ( g [ ' title ' ] ) } </b> <span class=note>quality goal: { e ( g [ ' goal ' ] ) } "
f " { ' · blocking ' if g . get ( ' blocking ' , True ) else ' ' } </span></span> { sp ( state ) } </span></summary> "
f " <ul class=checks> { ' ' . join ( rows ) } </ul> { extra } { waive } </details> " )
def _gate_check_form ( c : Ctx , gid : str , idx : int , ck : dict , exp : str | None , unmet : list [ str ] ) - > str :
run = c . run
action = f " { c . base } /gate/ { e ( gid ) } /check "
hidden = f " { c . h ( ) } <input type=hidden name=idx value= { idx } > "
if exp == " manual " :
ctl = " <button name=ok value=1>Yes, it is</button> <button name=ok value=0>Not yet</button> "
else :
cmd = f " <code> { e ( _render_check ( run , ck ) ) } </code> " if ck . get ( " command " ) else " "
ctl = f " <input type=hidden name=yes value=1><button>Run check</button> { cmd } "
if not unmet :
return f " <form method=post action= ' { action } ' > { hidden } <p class=row> { ctl } </p></form> "
if not c . manual :
return f " <span class=note>Waiting for: { e ( ' ; ' . join ( unmet ) ) } </span> "
return ( f " <span class=note>Waiting for: { e ( ' ; ' . join ( unmet ) ) } </span> "
f " <details class=override><summary>Check anyway (override)</summary> "
f " <form method=post action= ' { action } ' > { hidden } <p class=bad>Downstream risk: { e ( R . _risk_text ( run , gid ) ) } </p> "
" <p><input name=override_reason class=grow style= ' width:100 % ' placeholder= ' why you continue anyway (required) ' required></p> "
" <p><label><input type=checkbox name=acknowledge_risk value=1 required> I acknowledge the risk. Record this as a deviation.</label></p> "
f " <p class=row> { ctl } </p></form></details> " )
# ---- data view -----------------------------------------------------------------------------------------------------------------
def _data_view ( c : Ctx ) - > str :
run , fl = c . run , c . fl
group = c . q . get ( " group " ) or None
flt = c . q . get ( " filter " ) or None
keys = R . dimension_keys ( run )
base = " ?view=data "
def link ( g = None , f = None , label = " " , cls = " " ) :
qs = base + ( f " &group= { quote ( g ) } " if g else " " ) + ( f " &filter= { quote ( f ) } " if f else " " )
return f " <a class= ' chip { cls } ' href= ' { qs . replace ( ' & ' , ' & ' ) } ' > { e ( label ) } </a> "
ctl = ( " <p><b>Group by</b> " + link ( None , flt , " none " , " on " if not group else " " ) + " "
+ " " . join ( link ( k , flt , k , " on " if group == k else " " ) for k in keys ) + " </p> " )
if flt :
ctl + = f " <p><b>Filter</b> <span class=chip on> { e ( flt ) } </span> " + link ( group , None , " clear filter " ) + " </p> "
groups = R . datapoint_rows ( run , group = group , flt = flt )
out = [ f " <p class=note>Every datapoint of this run, independent of the flow. Tags (dimensions) come from the pack; click one to filter.</p> { ctl } " ]
editable = not c . finished
2026-09-29 15:50:56 +02:00
any_locked = any ( R . datapoint_users ( run , x [ " id " ] ) and x [ " value " ] is not None and x [ " editable " ]
for rows_ in groups . values ( ) for x in rows_ )
2026-09-29 14:42:09 +02:00
out . append ( f " <form method=post action= ' { c . base } /data ' > { c . h ( ) } <input type=hidden name=next value= ' { c . base } ?view=data "
f " { ' &group= ' + quote ( group ) if group else ' ' } { ' &filter= ' + quote ( flt ) if flt else ' ' } ' > " )
for gname , rows in groups . items ( ) :
if gname :
out . append ( f " <h3> { e ( group ) } : { e ( gname ) } </h3> " )
trs = " "
for r in rows :
rec = run . data [ " datapoints " ] [ r [ " id " ] ]
d = fl . datapoints [ r [ " id " ] ]
dims = " " . join ( link ( group , f " { k } : { v } " , f " { k } : { v } " ) for k , v in r [ " dimensions " ] . items ( ) )
src = " · " . join ( x for x in ( f " asked in { e ( r [ ' ask_in ' ] ) } " if r [ " ask_in " ] else " " , f " produced by { e ( r [ ' produced_by ' ] ) } " if r [ " produced_by " ] else " " ,
f " confirmed by { e ( r [ ' confirmed_by ' ] ) } " if r [ " confirmed_by " ] else " " , f " how: { e ( r [ ' how ' ] ) } " if r [ " how " ] else " " ) if x )
2026-09-29 15:50:56 +02:00
ctl = _input ( d , rec , editable and r [ " editable " ] , R . datapoint_users ( run , r [ " id " ] ) ) if ( editable and r [ " editable " ] ) else (
2026-09-29 14:42:09 +02:00
f " { e ( r [ ' label ' ] ) } { sp ( r [ ' state ' ] ) } <br><code> { e ( r [ ' value ' ] if r [ ' value ' ] is not None else ' ' ) } </code> " )
trs + = f " <tr><td> { ctl } </td><td> { dims } </td><td class=note> { src } </td></tr> "
out . append ( f " <table class=data><tr><th>Datapoint</th><th>Dimensions</th><th>Provenance</th></tr> { trs } </table> " )
if not groups :
out . append ( " <p class=note>No datapoints match.</p> " )
if editable :
2026-09-29 15:50:56 +02:00
out . append ( _override_fields ( c , any_locked ) + " <button>Save changed values</button> " )
2026-09-29 14:42:09 +02:00
out . append ( " </form> " )
arts = R . artefact_rows ( run )
if arts :
2026-09-29 15:50:56 +02:00
out . append ( " <h2>Artefacts</h2><table><tr><th>Artefact</th><th>Where it lives</th><th>Open</th><th>Produced by</th><th>Until</th></tr> " + " " . join (
2026-09-29 14:42:09 +02:00
f " <tr><td> { e ( a [ ' title ' ] ) } </td><td><code> { e ( a [ ' location ' ] or ' (not known yet) ' ) } </code></td> "
2026-09-29 15:50:56 +02:00
f " <td> { _artefact_links ( c , a ) } </td> "
2026-09-29 14:42:09 +02:00
f " <td> { e ( a [ ' produced_by ' ] ) } { sp ( a [ ' produced_state ' ] ) } </td><td> { e ( a [ ' lives_until ' ] or ' - ' ) } </td></tr> " for a in arts ) + " </table> " )
if fl . actors :
counts : dict [ str , int ] = { }
for s in fl . steps . values ( ) :
counts [ s [ " actor " ] ] = counts . get ( s [ " actor " ] , 0 ) + 1
out . append ( " <h2>Actors</h2><table><tr><th>Role</th><th>Description</th><th>Privileges</th><th>Steps</th></tr> " + " " . join (
f " <tr><td> { e ( a [ ' role ' ] ) } <span class=note>( { e ( a [ ' id ' ] ) } )</span></td><td> { e ( a [ ' description ' ] ) } </td> "
f " <td> { e ( ' , ' . join ( a . get ( ' privileges ' , [ ] ) ) ) } </td><td> { counts . get ( a [ ' id ' ] , 0 ) } </td></tr> " for a in fl . actors . values ( ) ) + " </table> " )
return " " . join ( out )
2026-09-29 15:50:56 +02:00
def _artefact_links ( c : Ctx , a : dict ) - > str :
loc = a [ " location " ]
if loc and A . is_url ( loc ) :
return f " <a href= ' { e ( loc ) } ' target=_blank rel= ' noopener noreferrer ' >Open link</a> "
path , why = A . check ( loc )
if path is None :
return f " <span class=note> { e ( why ) } </span> "
url = f " { c . base } /artefact/ { e ( a [ ' id ' ] ) } "
return f " <a href= ' { url } ' target=_blank rel=noopener>Open</a> · <a href= ' { url } ?download=1 ' >Download</a> "
def serve_artefact ( app , i : int , rid : str , aid : str ) - > tuple [ int , bytes , str , str | None ] :
""" (status, body, content type, download filename or None). Refusals come back as short plain text. """
run = R1 . load ( app . packs [ i ] , rid )
if run . pack . spec != " runbook-pack/v0.2 " :
return 404 , b " no artefacts " , " text/plain; charset=utf-8 " , None
row = next ( ( a for a in R . artefact_rows ( run ) if a [ " id " ] == aid ) , None )
if row is None :
return 404 , b " no such artefact " , " text/plain; charset=utf-8 " , None
path , why = A . check ( row [ " location " ] )
if path is None :
return 403 , f " not served: { why } " . encode ( ) , " text/plain; charset=utf-8 " , None
body , ctype , is_text = A . content ( path )
return 200 , body , ctype , ( None if is_text else path . name )
2026-09-29 14:42:09 +02:00
# ---- overview ------------------------------------------------------------------------------------------------------------------
def _overview_view ( c : Ctx , ov : dict ) - > str :
rows = " "
for s in ov [ " stages " ] :
gates = " " . join ( f " { e ( g [ ' title ' ] ) } { sp ( g [ ' state ' ] ) } " for g in s [ " gates " ] )
prob = " "
if s [ " steps_failed " ] :
prob + = f " <span class=bad> { s [ ' steps_failed ' ] } failed</span> "
if s [ " steps_pushed " ] :
prob + = f " { s [ ' steps_pushed ' ] } pushed "
if s [ " steps_running " ] :
prob + = f " <span class=note> { s [ ' steps_running ' ] } running</span> "
rows + = ( f " <tr><td style= ' padding-left: { 0.5 + s [ ' depth ' ] * 1.2 } rem ' ><a href= ' ?view=flow#stage- { e ( s [ ' id ' ] ) } ' > { e ( s [ ' title ' ] ) } </a> "
f " <br><span class=note> { e ( s [ ' goal ' ] ) } </span></td> "
f " <td> { s [ ' steps_done ' ] } / { s [ ' steps_total ' ] } { prob } </td><td> { gates or ' - ' } </td> "
f " <td> { sp ( ' complete ' if s [ ' complete ' ] else ' in progress ' ) } "
f " { ' <br><span class=note> ' + e ( ' ; ' . join ( s [ ' unmet ' ] ) ) + ' </span> ' if s [ ' unmet ' ] else ' ' } </td></tr> " )
dps = " " . join ( f " { sp ( k ) } { v } " for k , v in sorted ( ov [ " datapoints " ] . items ( ) ) )
out = [ " <h2>Stages</h2><table><tr><th>Stage</th><th>Steps</th><th>Gates</th><th>State</th></tr> " + rows + " </table> " ,
f " <h2>Datapoints</h2><p> { dps } </p><p class=note><a href= ' ?view=data ' >Open the data view</a></p> " ]
if ov [ " deviations " ] :
out . append ( " <h2>Deviations</h2><table><tr><th>Kind</th><th>Item</th><th>Mode</th><th>Unmet</th><th>Reason</th><th>When</th></tr> " + " " . join (
f " <tr><td> { sp ( d [ ' kind ' ] ) } </td><td> { e ( d [ ' item ' ] ) } </td><td> { e ( d [ ' mode ' ] ) } </td><td> { e ( ' ; ' . join ( d [ ' unmet ' ] ) ) } </td> "
f " <td> { e ( d [ ' reason ' ] ) } { ' <br><span class=note>risk: ' + e ( d [ ' downstream_risk ' ] ) + ' </span> ' if d . get ( ' downstream_risk ' ) else ' ' } </td> "
f " <td class=note> { e ( d [ ' at ' ] [ : 19 ] ) } </td></tr> " for d in ov [ " deviations " ] ) + " </table> " )
else :
out . append ( " <h2>Deviations</h2><p class=note>None. A run without deviations can count towards maturity.</p> " )
if ov [ " mode_history " ] :
out . append ( " <h2>Mode changes</h2><ul> " + " " . join ( f " <li> { e ( m [ ' from ' ] ) } → { e ( m [ ' to ' ] ) } · { e ( m [ ' at ' ] [ : 19 ] ) } </li> " for m in ov [ " mode_history " ] ) + " </ul> " )
if ov [ " stopped " ] :
out . append ( f " <p class=bad>Auto-confirm stopped at { e ( ov [ ' stopped ' ] [ ' item ' ] ) } : { e ( ov [ ' stopped ' ] [ ' reason ' ] ) } </p> " )
return " " . join ( out )
# ---- closing the run -----------------------------------------------------------------------------------------------------
def _close ( c : Ctx ) - > str :
run = c . run
if c . finished :
2026-09-29 18:05:34 +02:00
note = f " <p>Comment: { e ( run . data [ ' outcome_note ' ] ) } </p> " if run . data . get ( " outcome_note " ) else " "
return ( f " <div id=close class=step><h2>Run finished: { e ( run . data [ ' outcome ' ] ) } </h2> { note } { c . app . locked_note ( run ) } "
2026-09-29 21:35:03 +02:00
" <p class=note>The outcome is part of this runbook ' s score and maturity evidence at this revision.</p> "
f " <p><a href= ' { c . base } /record.json ' >Download the run record (JSON)</a> "
" <span class=note>Check it anywhere with <code>rtut seal <file></code>.</span></p></div> " )
2026-09-29 14:42:09 +02:00
fl = c . fl
steps = [ run . data [ " steps " ] [ s ] [ " state " ] for s in fl . steps ]
gates = [ run . data [ " gates " ] [ g ] [ " state " ] for g in fl . gates ]
btns = [ ]
if all ( x == " done " for x in steps ) and all ( x in ( " passed " , " waived " ) for x in gates ) :
btns . append ( " <button name=outcome value=succeeded>Finish: succeeded</button> " )
if { " failed " , " pushed " } & set ( steps ) or { " failed " , " pushed " } & set ( gates ) :
btns . append ( " <button name=outcome value=failed onclick= \" return confirm( ' Finish this run as failed? ' ) \" >Finish: failed</button> " )
btns . append ( " <button name=outcome value=abandoned onclick= \" return confirm( ' Abandon this run? ' ) \" >Abandon run</button> " )
return ( " <div id=close class=step><h2>Close this run</h2> "
" <p class=note>Finishing records the outcome. A run with deviations can be finished but does not count towards maturity. "
" A finished run is read-only.</p> "
f " <form method=post action= ' { c . base } /finalize ' > { c . h ( ) } <p><input name=note class=grow style= ' width:100% ' placeholder= ' comment (optional) ' ></p> "
2026-09-29 18:03:12 +02:00
f " { c . app . close_feedback_fields ( run ) } <p class=row> { ' ' . join ( btns ) } </p></form></div> " )
2026-09-29 14:42:09 +02:00
# ---- actions -------------------------------------------------------------------------------------------------------------------
def _safe_next ( app , form : dict , default : str ) - > str :
n = form . get ( " next " , " " )
return n if n . startswith ( " /run/ " ) and " // " not in n and " \n " not in n and " \r " not in n else default
def handle_post ( app , i : int , rid : str , parts : list [ str ] , form : dict ) - > str :
""" Perform a v0.2 run action; return the URL to redirect to. Raises RunError/ValueError/KeyError on a refused action. """
pack = app . packs [ i ]
run = R1 . load ( pack , rid )
base = f " /run/ { i } / { rid } "
if parts == [ " feedback " ] :
R1 . add_feedback ( run , form . get ( " rating " , " " ) , form . get ( " comment " , " " ) )
return f " { base } #feedback "
if parts == [ " finalize " ] :
2026-09-29 18:03:12 +02:00
R1 . finalize ( run , form . get ( " outcome " , " " ) , form . get ( " note " , " " ) , rating = form . get ( " rating " ) or None , comment = form . get ( " fb_comment " , " " ) )
2026-09-29 14:42:09 +02:00
return f " { base } #close "
if parts == [ " mode " ] :
R . switch_mode ( run , form . get ( " to " , " " ) )
return base
if parts == [ " tick " ] :
n = max ( 1 , min ( int ( form . get ( " n " , " 1 " ) or 1 ) , 50 ) )
last = None
for _ in range ( n ) :
last = R . auto_confirm_tick ( run )
if last [ " action " ] in ( " waiting " , " awaiting-operator " , " stopped " , " finished " , " push-through " ) :
break
run . data [ " auto_confirm " ] [ " last " ] = last
run . save ( )
return f " { base } #current "
if parts == [ " data " ] :
bad = [ ]
2026-09-29 15:50:56 +02:00
changes = { }
2026-09-29 14:42:09 +02:00
for k , v in form . items ( ) :
if not k . startswith ( " d_ " ) :
continue
dp = k [ 2 : ]
cur = run . data [ " datapoints " ] . get ( dp )
if cur is None or v == " " or ( cur [ " state " ] in ( " set " , " ok " ) and cur . get ( " value " ) == v ) :
continue # unchanged or left empty
2026-09-29 15:50:56 +02:00
changes [ dp ] = v
need = R . changes_needing_override ( run , changes ) # all or nothing: check before applying any
reason , ack = form . get ( " override_reason " , " " ) , bool ( form . get ( " acknowledge_risk " ) )
if need and not ( reason . strip ( ) and ack ) :
raise R1 . RunError ( " already in use by started steps: " + " ; " . join ( f " { d } ( { ' , ' . join ( u ) } ) " for d , u in need . items ( ) )
+ " . To change them give a reason and acknowledge the risk; each change is recorded as a deviation. " )
for dp , v in changes . items ( ) :
if R . set_datapoint ( run , dp , v , override_reason = reason , acknowledge_risk = ack ) == " err " :
2026-09-29 14:42:09 +02:00
bad . append ( f " { dp } ( { run . data [ ' datapoints ' ] [ dp ] [ ' error ' ] } ) " )
if bad :
raise ValueError ( " invalid value for: " + " , " . join ( bad ) )
return _safe_next ( app , form , base )
if len ( parts ) == 3 and parts [ 0 ] == " gate " and parts [ 2 ] in ( " check " , " waive " ) :
gid = parts [ 1 ]
if parts [ 2 ] == " waive " :
R . waive ( run , gid , form . get ( " reason " , " " ) )
else :
R . gate_check ( run , gid , int ( form . get ( " idx " , " -1 " ) ) , confirm = bool ( form . get ( " yes " ) ) ,
manual_ok = ( form . get ( " ok " ) == " 1 " ) if not form . get ( " yes " ) else None ,
override_reason = form . get ( " override_reason " , " " ) , acknowledge_risk = bool ( form . get ( " acknowledge_risk " ) ) )
return f " { base } #item- { gid } "
if len ( parts ) == 2 :
sid , action = parts
if action == " start " :
R . start ( run , sid , alt = form . get ( " alt " ) or None , phrase = form . get ( " phrase " ) or None ,
override_reason = form . get ( " override_reason " , " " ) , acknowledge_risk = bool ( form . get ( " acknowledge_risk " ) ) )
elif action == " verify " :
R . verify ( run , sid , confirm = bool ( form . get ( " yes " ) ) , manual_ok = ( form . get ( " ok " ) == " 1 " ) if not form . get ( " yes " ) else None )
elif action == " done " :
R . complete ( run , sid , { k [ 2 : ] : v for k , v in form . items ( ) if k . startswith ( " e_ " ) } , form . get ( " note " , " " ) )
elif action == " fail " :
R . fail ( run , sid , form . get ( " note " , " " ) )
elif action == " retry " :
R . retry ( run , sid , acknowledge_partial = bool ( form . get ( " acknowledge_partial " ) ) )
elif action == " capture " :
R1 . add_capture ( run , sid , form . get ( " output " , " " ) , form . get ( " command " , " " ) )
elif action == " verify-pasted " :
R1 . verify_pasted ( run , sid , form . get ( " output " , " " ) , form . get ( " command " , " " ) )
else :
raise ValueError ( f " unknown action { action } " )
return f " { base } #current "
raise ValueError ( " unknown action " )