App-karr

 view release on metacpan or  search on metacpan

ex/README.md  view on Meta::CPAN

$ karr-foundation chain
step docs (shell) in /srv/docs-site: done — exit=0
step smoke (shell) in /srv/webapp: done — exit=0
step registry (question): left pending — question #1 is unanswered (policy: block)
chain 20260818T053250Z-18641c: 2 done, 1 pending
```

**What a failure does to the DAG: nothing** — and that's the design, not an
omission. A step only becomes ready when everything it `needs` is `done`. A step
that ends as `failed` or `stale` therefore stops its own branch
**constructively**: its dependents never become ready, no cascade has to be
computed, and every branch that doesn't run through it continues. The chain can
then no longer finish, and exactly this unreachability is the signal that
`on_stall: plan` names.

Three outcomes are deliberately **not** failures, because none of them is a
statement about the plan:

- A **common error** (a rate-limited or broken agent command) puts the step back
  to `pending`. The board's cooldown and the agent's availability record have
  already been written by the drain; the step just doesn't belong to this

lib/App/karr/Foundation/Executor.pm  view on Meta::CPAN


  $self->_say( $self->_describe($step) . ": $state -- "
    . ( $verdict->{detail} // '' ) );

  return { state => $state } unless $state eq 'failed';

  # on_stall, at the seam. The policy the spec writes is `plan`, and this
  # executor cannot plan -- so it records that the planner is wanted instead
  # of pretending, and the dependents of this step simply never become ready
  # (ready_steps releases a step only when every step it needs is done), which
  # is the branch pruning itself without anybody computing a cascade.
  my $policy = $step->{on_stall} // 'plan';
  $store->log_run( $run, event => 'planner', step => "$id",
    policy => "$policy", reason => ( $verdict->{detail} // 'the step failed' ) );
  return { state => $state, planner => "on_stall: $policy" };
}

# Back to pending: nothing about the plan was learned, so nothing about the
# plan is written down. The attempt counter the claim bumped stays as it is --
# it is the record of how often this step has been tried, which is what makes
# a step that can never run visible to whoever reads the chain.

lib/App/karr/Foundation/Executor.pm  view on Meta::CPAN

half of the link, is not part of the fact either: C<< --resolve >> settles on
C<settled> alone, and a second opinion here would mean a far card whose author
forgot the C<< escalated-from: >> tag could never settle anything.

=head2 What a failure does to the DAG

Nothing, and that is the design rather than an omission.
L<App::karr::Foundation::ChainStore/ready_steps> releases a step only when every
step it C<needs> is C<done>, so a step that ends C<failed> or C<stale> stops its
own branch B<by construction>:
its dependents never become ready, no cascade has to be computed, and every
branch that does not run through it carries on. The chain then cannot finish,
and that unreachability is exactly the signal C<on_stall: plan> names.

The planner itself does not run in here. Where the spec says "call the planner"
this executor B<records that the planner is wanted> -- a C<planner> entry in the
run log naming the step and why, and a line of output at the end of the tick --
and writes nothing about the plan that a planner would have to undo. What has
changed with #210 is only who hears it: the recorded entries are handed to
L<App::karr::Foundation::Coordinator> when the tick is over, and where the fleet
marks an agent C<< role: coordinator >> F<karr-foundation> calls it B<once> for

t/202-foundation-chain-executor.t  view on Meta::CPAN

  ] );

  my ( $out ) = tick($hub);

  is step_of( $hub, 1 )->{state}, 'failed', 'the step that exited non-zero failed';
  is step_of( $hub, 1 )->{result}{exit}, 3, 'with the exit code recorded';

  is step_of( $hub, 2 )->{state}, 'pending',
    'its dependent never became ready -- ready_steps releases a step only when '
    . 'everything it needs is DONE, so the branch prunes itself and nobody has '
    . 'to compute a cascade';
  ok !$after->exists, 'and it certainly did not run';

  is step_of( $hub, 3 )->{state}, 'done',
    'a step on another branch is unaffected: that is what the missing edge means';
  is_deeply [ lines_of($other) ], ['other'], 'and it ran';

  my ( $planner ) = grep { $_->{event} eq 'planner' } log_of($hub);
  is $planner->{step}, '1', 'the planner is wanted for the step that failed';
  is $planner->{policy}, 'plan', 'under the on_stall policy the spec writes';
  like $out, qr/the planner is wanted for step\(s\) 1/,



( run in 1.298 second using v1.01-cache-2.11-cpan-e7c6538aa59 )