App-karr

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

      per-repo steps (`_drain_repo` below it, `_process_repo` itself
      from #162), so a refusal from the wholesale-wipe guard, the
      board-identity guard, or the unapplied-refs guard warns and lets
      the run continue — and the board whose pull refused is not then
      processed as if it were up to date (#168).

    - Four fixes in karr's character/octet boundary and refs-backed
      storage guarantees (tickets #155, #156, #157, #167). `karr restore`
      is now atomic across its write phase: `replace_board_refs` snapshots
      every `refs/karr/*` OID and every ref the snapshot is about to
      introduce before the first `_write_ref_oid` call, and any die out
      of the write loop unwinds every ref that landed — restoring the
      original OID for refs that existed, deleting refs the snapshot
      managed to create — so the board reads back exactly as it did
      before the failed restore. `Cmd::Restore`'s POD promise ('a snapshot
      karr cannot apply ... is refused with the board exactly as it was')
      is now true for the directory/file name conflict that previously
      half-applied, and for the CAS-exhaustion path that previously
      half-applied without any manual editing at all (#155). The
      activity log no longer loses entries under concurrency: log_entry
      wraps its read-and-write in `write_ref_cas` + `retry_contended`,
      matching `save_task_cas` and `allocate_next_id_ref`, so the
      existing CAS plumbing handles contention transparently and a
      board running N parallel `karr create` writes N log entries
      (#156). `git_user_name` and friends no longer leak libgit2's
      octets into karr's character strings: `Git.pm:_config_string`
      and `_run_git`'s captured stderr decode through `from_octets`,
      so a non-ASCII `user.name` is no longer written double-encoded
      into the log ref and `karr repair` does not need to undo it on
      read (#157). `%ENV` is now an octet crossing `App::karr::Encoding`
      owns: two new helpers, `to_octets_for_env` and
      `from_octets_from_env`, match the POD style of the existing
      helpers and delegate to the canonical codec, and the three
      `Foundation/Runner.pm` writes go through `to_octets_for_env` —
      so the 'Wide character in setenv' warning on a non-ASCII prompt
      is gone, and the house rule that Encoding owns every crossing
      is complete (#167).

    - Three board commands no longer treat a value the user did pass as
      if it had not been given (tickets #151, #152, #153). `Cmd/Log.pm`
      refused `--last < 1` only via truth, so `karr log --last 0` dumped
      the full log (the bound silently removed) and `karr log --last -3`
      reported an empty log and exited 0 — indistinguishable from a board
      with no activity. `Cmd/Archive.pm:55` read `$pos[0] under `or die`,
      so the truthy comma in `karr archive ,` passed the guard, parse_ids
      split to nothing, and run_batch iterated zero items with no output
      and exit 0. `Cmd/{Edit,Create,Handoff}.pm` carried 17 sibling
      options whose presence was tested with `if ($self->foo)` rather
      than `defined && length`, so the literal value `0` was
      indistinguishable from "not given" — the write still ran, `updated`
      was bumped, an activity-log entry was appended, the command printed
      success, and `--block 0` left the card unblocked (the sharp edge:
      `karr pick` would have handed it out). The fix is the rule already
      written down for `--body` in ticket #78 (`defined && length`)
      applied to the siblings; `--last < 1` raises a usage error matching
      `Show.pm:161-162` and `Context.pm:97-99` exactly (same exit 2,
      same error format); `karr archive ,` raises the same usage error
      as `move ,` / `edit ,` / `delete ,` already do. The audit trail no
      longer records edits that did not happen.

    - `karr-foundation` now keeps an agent it started alive in three
      situations where it used to silently lose it: a pipeline/`&`/shell-
      builtin command where the real agent was the shell's child, not the
      shell (#148); an agent that closed its stdout before max_runtime
      elapsed, where the runner fell through to a bare blocking waitpid
      that held `.karr.lock` forever (#161); and a SIGTERM/INT/HUP to
      foundation mid-drain, where the agent was reparented to init and
      `.karr.lock` named a dead pid the next tick read as free (#163).
      The runner wraps every agent in its own process group with
      `setpgid(0,0)` in the child and `setpgid($pid,$pid)` in the parent
      (the second call wins the fork race idempotently); the timeout,
      SIGTERM and SIGKILL all signal the group with a negative pid, so
      the shell, the agent and any grandchildren the agent forked all
      receive the kill. `max_runtime` is now enforced independently of
      IO activity by a SIGALRM handler that closes the read end of the
      pipe, and the post-EOF wait is a deadline-aware WNOHANG poll that
      falls through to the SIGTERM/SIGKILL/reap path when the wall clock
      beats the child. Foundation installs a SIGTERM/INT/HUP handler for
      the lifetime of `run()` that kills the agent's group, force-releases
      the lock, and `POSIX::_exit(128 + signum)` — the conventional shell
      exit shape, so systemd/cron see a signal-death exit and an operator
      reading the log does not need a special case for "killed cleanly
      mid-drain".

    - `.karr.lock` is now a `flock(2)` on an open file descriptor the
      foundation keeps for the lifetime of the lock, not an advisory pid
      that two ticks could each write their own value into (#162). Two
      ticks that overlap — the normal case, since a drain may run for
      `max_runtime` (default 1800s) while cron fires every few minutes —
      race on the file: the second tick gets `EWOULDBLOCK` from
      `LOCK_EX|LOCK_NB` and returns immediately, without overwriting the
      existing pid. `_release_lock` closes the open fd (closing drops the
      flock) and only unlinks the file if the recorded pid still matches
      `$$`, so a pid-recycled foundation cannot unlock its successor's
      lock. `_lock_held` is the flock check, not a `kill(0,$pid)` against
      a recorded pid the foundation wrote itself — a stale lock whose
      holder died is held=false and a fresh tick takes over without
      manual cleanup. Path::Tiny's `slurp_utf8` does an internal blocking
      flock that hangs forever when the same process already holds one,
      so the metadata read is a raw `sysread` loop.

    - An agent killed by a signal is now booked as `128 + signum`, not as
      a clean exit 0 (#164). The runner used to compute `$exit_code =
      $? >> 8` — the high 8 bits, which are 0 for any child that died
      from a signal. The OOM-killer, an external SIGTERM, a SIGSEGV, and
      any other signal-death shape were all booked as a clean run:
      `last_error` stayed unset, the cooldown that exists to back off
      after a machine-killing agent never engaged, and the next cron
      tick re-launched at full rate. The fix reads both halves of `$?`:
      signal death becomes `128 + signum` (the shell convention, so
      SIGTERM=143, SIGKILL=137, SIGSEGV=139, SIGINT=130); a normal exit
      falls through to `( $? >> 8 ) & 255`. The timeout path's exit code
      already used this convention; the classifier now matches it for
      every signal, including the ones we don't fire ourselves.

    - `karr pick` ranks candidates and `karr context --sections in-progress`
      lists in-progress tasks by the board's own `priorities` and `classes`
      lists, not by a hardcoded table (ticket #149). `Cmd/Pick.pm` used to
      sort through `App::karr::Config->priority_order` and `->class_order` —
      class methods that only knew the four default priorities and four
      default classes. On a board imported from kanban-md with a longer



( run in 1.931 second using v1.01-cache-2.11-cpan-14f38c9f855 )