pub const TRAIN_FLOW_TEMPLATE: &str = "# The merge train. Bind a ticket to it with `flow: train` or\n# `sloop post --flow train`; the `default` flow is untouched and still what\n# a ticket gets when it names none.\n#\n# The problem it solves: what lands on the default branch is not the run\n# branch, it is the *merge* of the two \u{2014} a tree no stage ever tested. While\n# a run is in flight the default branch keeps moving, so by the time the\n# merge stage runs, the thing every earlier stage judged may no longer be\n# the thing about to be integrated.\n#\n# The train closes that gap with ordinary stages. `sync` integrates the\n# default branch into the run branch, `verify` tests the tree that produces,\n# and the merge is `ff_only` \u{2014} a fast-forward, which can only succeed while\n# the default branch is still the one `sync` integrated. If it moved in\n# between, the fast-forward is impossible, the stage fails without touching\n# anything, and `return_to: sync` runs the train around again.\nstages:\n - name: build\n # An agent action takes its prompt from the ticket, never from here.\n action: agent\n result_check: { builtin: commits }\n fail_action: fail\n\n # Merges the default branch into the run branch, inside the run worktree.\n # Passes when that commits cleanly or there was nothing to integrate;\n # fails on a conflict, leaving no in-progress merge behind, so the stage\n # this returns to starts from a clean tree. The conflict itself lands in\n # the run log, and so in the re-entered agent\'s prompt.\n - name: sync\n action: { builtin: sync }\n result_check: none\n fail_action: { return_to: build, attempts: 1 }\n\n # Everything that decides whether the work is good runs *after* the sync,\n # because the merged tree is the one that will land. Replace this command\n # with your own; `sloop init` fills in `flow.test_cmd` from config.yaml\n # when you have one configured.\n - name: verify\n action: { exec: {test_cmd} }\n result_check: none\n fail_action: { return_to: build, attempts: 1 }\n\n # `ff_only` refuses the merge commit that would otherwise paper over a\n # default branch that moved. `result_check: none` is not a shortcut: the\n # merge is the one irreversible act in the flow, and nothing that runs\n # after it could un-take it, so its own outcome is the only verdict it\n # can honestly carry.\n - name: merge\n action: { builtin: merge, ff_only: true }\n result_check: none\n fail_action: { return_to: sync, attempts: 3 }\n";Expand description
The merge-train flow, materialized beside default rather than in place of
it. Its verify stage is a {test_cmd} placeholder that
render_train_flow fills in, so the shipped copy is not a flow file until
it has been rendered.