feat(fleet): implement durable fleet refill and attempt terminal lifecycle - #2006
feat(fleet): implement durable fleet refill and attempt terminal lifecycle#2006Authentis wants to merge 59 commits into
Conversation
The /calm toggle and session_start previously cleared the footer status (setStatus(key, undefined)), so active Calm was indistinguishable from no Calm. The footer now always shows the current choice: '● calm on' (accent) when active, '○ calm off' (dim) when not, set on every session start and toggle, with a plain-string fallback when ui.theme.fg is unavailable. Tests updated to require the indicator instead of the cleared status; home-resolution + presentation suites pass (the follow-up e2e flake is pre-existing, verified identical with these changes stashed).
The 5x 'wake the crew' recurrence root cause was a window bug (completed agents counted active for 25 min), fixed in the sentinel (window 25->5). This script is the lead-side mechanical obligation: run it as the FIRST action of any turn; it prints DISPATCH-NEEDED + exits 1 when the manifest-scoped battery is below threshold with open dispatchable work. The exit code is the reminder, not memory.
…guid#1798) * feat(session-start): order the startup digest for truncation safety and bound its bulk The digest is delivered through a harness that truncates an oversized payload from the tail, and it really has been truncated: a 70KB digest arrived as lines 1-435 of 578, cutting off eight lines before the live-task inventory. That session took the helm without ever seeing which tasks were live or where their endpoints were. Three changes, one file's worth of composition: - FLEET STATE is emitted before CONTEXT, so a truncated tail drops curated memory - stable session to session, already governed by a captain-set budget, recoverable with one targeted read - instead of live fleet identity. The LOCK/BOOTSTRAP/WAKE-QUEUE safety preamble keeps its order. The read-once contract moves out of the closing reminder into its own section ahead of both, and now names the condition that voids it: a stage the truncation banner reports as never emitted. - Status-tail lines are capped per line, reusing the cut the wake digest's OPEN DECISIONS section already applies. An observed tail line ran 865 characters and nothing bounded it. The cut and its marker now live in one place, bin/fm-line-cap-lib.sh, so the two digests cannot drift apart; each task's full status log path is still printed beside its tail. - The backlog listing is composed as a recovery input: done rows are never listed, every in-flight, held, and blocked row is shown in full with its hold and blocked-by metadata, and only the dispatchable-now listing is bounded - with an exact remainder count and the command that shows the rest. FM_SESSION_START_QUEUED_LIMIT (default 20) replaces FM_SESSION_START_BACKLOG_LIMIT, which bounded the whole listing indiscriminately and so could drop a held or blocked row. Tests exercise the real digest output: section ordering with the preamble pinned, the per-line cap and its marker, and the backlog composition including the remainder counters on both the tasks-axi and manual paths. * no-mistakes(document): Clarify digest source recovery comments
…kunchenguid#1842) A captain decision opened by a keyed needs-decision:/blocked: status line orphaned as permanently open whenever the answer kicked off work: the worker's next event is working [key=<workstream>] in a different key namespace, so no resolved [key=<decision>] ever landed and the OPEN DECISIONS fold kept listing the answered decision forever. Remove the writer-dependency at its source: the answering firstmate already holds the decision key when it sends the answer, so fm-send's new --resolve-key flag (repeatable) appends the closing resolved line to this home's own state/<id>.status after the submit is confirmed. The close is a local ledger append for crewmates, local secondmates, and remote secondmates alike - a remote mate's escalations reach this ledger through the parent-replies ingest, so only the answer message crosses the transport. Safety: each named key must currently be open per the authoritative status_open_decisions fold or fm-send refuses before sending; a failed or unconfirmed send never closes a key; an append failure after a delivered answer exits nonzero with the manual close command so the decision re-surfaces instead of silently vanishing; a send without the flag closes nothing, and working:/done: still never clear a captain decision. Complementary fixes: the wake-drain OPEN DECISIONS section prints the answer-with-close command hint at the moment of use; brief scaffolds separate resolved's two duties (keyed-phase end vs decision closure) and state that a done:/working: line never closes a decision even when the answer started that work, keeping worker self-close for blockers that clear without a firstmate reply; AGENTS.md and docs/architecture.md carry the one-line pointers to the fm-send contract.
…1836) * feat(secondmate): seed a remote home from a supplied project origin Remote seeding required a local projects/<name> clone purely to read `git remote get-url origin` into the provisioning manifest, so setting up a remote second mate forced disposable clones and no-mistakes inits in the primary home for projects that home has no reason to hold. Firstmate now resolves the origin itself and names it as <project>=<origin-url>. The seed validates and transports what it is given, and the receiving host re-validates it rather than trusting the sender; bin/fm-project-origin-lib.sh is the single owner of which URLs are accepted, refusing executable remote-helper transports, option-shaped values, and unusable spellings at both ends. A bare <project> still reads an already-present clone's origin, so nothing that works today has to change. Registry consistency is unchanged: an unregistered or local-only project is still refused. A remote seed therefore creates nothing in the primary home beyond the route, the charter, and its launch record. The lifecycle test now seeds a registered project the primary has never cloned and asserts the primary project tree is byte-identical afterwards, alongside refusals for a missing origin, an unsafe origin, a local-only project, and an unregistered project. * no-mistakes(review): Clarify project origin documentation ownership * no-mistakes(document): Document supplied-origin remote seeding contract * feat(secondmate): accept project origins from any host or forge Firstmate is a shared template, so a project origin must be able to name any host: GitHub Enterprise on a private domain, GitLab hosted or self-hosted, Bitbucket, Gitea, Codeberg, sr.ht, a bare IP, an SSH config alias, or a plain server nobody else has heard of. The validator already decided on structure rather than on a forge allowlist, and this makes that guarantee explicit and closes the two gaps that a host-agnostic rule exposed: - a bracketed IPv6 literal in the scp-like form is now accepted, so a host reachable only by address is not excluded - a "/../" traversal inside a local or file: origin is now refused, because that names a path on the cloning host's own filesystem The library is the single owner of the accepted forms, and its header says plainly that there is no host, domain, or forge allowlist and there must never be one. The skill keeps its distinct agent-operating lines (the agent resolves and supplies the origin; a remote seed creates nothing in the primary home beyond the route, the charter, and its launch record) and points at the library for URL acceptance and at the operator doc for the rest. The lifecycle test now drives Bitbucket, a self-hosted enterprise domain, a self-hosted GitLab over ssh with a port, and a bare scp-like custom host through the real seed, manifest, transport, and remote provisioning path in one seed, asserting each URL reaches git unchanged and each clone carries its own origin's content. The unit matrix leads with non-GitHub hosts for the same reason. * no-mistakes(review): Validate project origin authorities safely * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes
…guid#1851) * fix(tests): copy the whole bin/ tree into the old-vs-new conformance shim main went red on tests/fm-backend.test.sh's "fm-send --key: old vs new exit code" assertion, which reads as an fm-send fail-closed regression from build_old_bin enumerated by hand the sibling scripts it copied into the synthetic pre-refactor tree. kunchenguid#1842 made bin/fm-send.sh source bin/fm-line-cap-lib.sh (added by kunchenguid#1798) and the list never learned about it, so the pinned old fm-send.sh aborted at `. "$SCRIPT_DIR/fm-line-cap-lib.sh"` under set -eu and exited 1 before parsing a single argument, while the current one delivered the key and exited 0. The parity check compared a crashed process against a working one and reported a behavior divergence that never happened - the more so because BASE_REF collapses to HEAD on main, where both sides run byte-identical source and a genuine divergence is impossible. fm-send's --key exit path is unchanged and its fail-closed contract is intact. Copy the tree whole instead of enumerating it. An enumerated list has to be extended by hand every time an entrypoint gains a dependency and is the only thing that knows; it has been patched a dozen times for exactly that. A whole-tree copy has nothing to forget. Extracting a refactored entrypoint the baseline does not have now fails loudly instead of writing an empty file. Only old-vs-new parity covered that exit contract, and parity is near-vacuous on main. Pin it directly: tests/fm-send-strict.test.sh drives delivery both ways from one stub and asserts an undelivered key exits nonzero naming the key, so swallowing that error fails the suite. * no-mistakes(review): Materialize historical fixture dependencies from baseline * no-mistakes(document): Clarify fm-send key regression scope
* fix(bin): mirror the whole remote secondmate status stream A remote secondmate's reply channel required corr=<16hex> on every line and failed the entire delta when one line lacked it, so the cursor could never advance past that line and the channel wedged permanently. The charter tells a secondmate to report its own progress phases and to raise new decisions with no correlation token, because correlation only answers a marked parent request. Those lines were therefore unrepresentable on the remote channel, while a local secondmate writes them straight into the parent's status file. Treat the channel as what it is: a mirror of the mate's status stream. A remote mate now presents the same status and decision model as a local one, so a newly raised needs-decision reaches the parent's open-decision fold identically, and correlation goes back to being a per-line property that settles a pending request rather than a gate on the stream. Only what crossing a machine boundary genuinely adds stays behind: cursor continuity, confined document fetch and rewrite, at-most-once append, and control-byte normalization that rewrites bytes without ever dropping a line. Line framing and size bounding already belong to fm-remote-delta-read.sh. A document the remote reader refuses is named in one escalation instead of stalling the stream, while an unavailable transport still leaves the delta for the existing retry. * refactor(bin): give the remote reply stream one append owner Every line entering the parent status stream - a mirrored line, the continuity escalation, and the undelivered-document escalation - now goes through one at-most-once append, so the idempotence a replayed generation depends on is stated once instead of copied at three call sites. * no-mistakes(review): Keep local document transfer failures retryable * no-mistakes(review): Isolate reply headers and normalize payload bytes * no-mistakes(review): Correct remote reply mirror contract wording * no-mistakes(review): Update remote reply script catalog description * no-mistakes(document): Document remote status-stream mirroring
…chenguid#1847) fm_secondmate_parent_record_parse read the .fm-secondmate-parent record with bash's read, which drops NUL bytes - and different bash generations disagree on the result: 3.2 truncates the value at the NUL while 5.x splices the surrounding bytes together. A NUL-bearing parent_home could therefore resolve to a home the record's bytes never name contiguously, and which home fm-teardown.sh's promised-public-reply resolution read (registration, registry, relay state) - or whether that protection engaged at all - depended on which interpreter ran the cleanup. Reproduced end to end: the same NUL-bearing record cleaned up under bash 5.x by resolving the spliced-together registered parent, while bash 3.2 refused it as unresolved, and a literal truncated path refused under both. Reject any NUL byte in the record before field parsing, putting corrupt records in the same fail-closed bucket as duplicate fields, malformed local bindings, unsupported routes, and symlinked records. The regression test drives the real bin/fm-teardown.sh over the proven clean-cleanup fixture with a NUL spliced mid-path into the recorded parent_home, so before the fix it reproduced the wrong-home cleanup and now it must refuse with the explicit binding refusal.
…unchenguid#1853) * docs(secondmate-provisioning): require record intake for an inherited domain A new mate seeded for an existing or inherited domain previously pulled in charter, inherited config, captain-shared preferences, project clones, and queued backlog rows with zero instruction about the domain's shipped history, so it assumed a greenfield domain. A live backlog keeps only the configured recent Done entries, so an inherited queue structurally over-represents plans and under-represents deliveries, and already-delivered work resurfaced as open. Add a record-intake step to the creation/seed path: classify greenfield versus existing or inherited, and for the latter reconcile every inherited plan against origin/main plus the live deployment, take only genuinely open work and still-live durable knowledge, never carry a plan row for shipped work, and record what could not be reconciled. Greenfield domains are untouched. The skill owns the procedure; the backlog handoff section carries a one-line reinforcement at the point where plan rows actually move. * no-mistakes(document): Clarify secondmate record-intake scope
…guid#1860) * perf(session-start): run every network check off the blocking path The session-start digest runs on a session-open hook that blocks session initialization, and every external-network call it made was individually unbounded: `gh auth status`, secondmate liveness, secondmate convergence, pending remote handoff delivery, and the fleet-sync fetch. One unreachable remote secondmate could consume the whole FM_SESSION_START_TIMEOUT and truncate the digest, so a slow network could cost the work queue itself. Measured against a host hanging 25s per SSH connection, that startup took 1m18s. The digest is now composed from local reads alone. bin/fm-startup-network.sh runs the same checks concurrently in a bounded detached worker and the digest harvests whatever finished, without ever waiting. Same fixture: 0.84s. Nothing is dropped. fm-bootstrap.sh stays the single owner of every sweep and still runs all of them, through a new FM_BOOTSTRAP_NETWORK phase split whose `skip` and `only` halves are a partition of the unsplit run. Deferral is safe because the sweeps are idempotent detectors, the result is durable and always surfaces (inline, or as a `check: startup-network` wake), and the worker re-verifies that the fleet lock still names the session that asked before it mutates anything. While the worker is still running the digest names exactly what is unconfirmed rather than implying it passed. A relaunch performed by the deferred pass is now always reported, because the digest that printed the superseded endpoint record is already out. Also collapses the duplicate tasks-axi compatibility probe: the verdict is computed once and handed to the bootstrap child for one process hop, then consumed so it never reaches a spawned agent's environment. 10 tasks-axi invocations per startup become 7. Verified on Claude Code 2.1.222 that a worker detached by the session-open hook survives the hook returning, the one vendor behavior this design needs and no portable test can see. Re-landed on current main, superseding PR kunchenguid#1845, which was cut from a pre-kunchenguid#1842 base. The digest's section numbering in AGENTS.md section 3 now states the emission order directly - supervision block and its read-once contract, fleet state, network checks, then context - which keeps kunchenguid#1826's fleet-state-before-context ordering. The old-bin test shim keeps main's git-archive baseline from kunchenguid#1851, which already subsumes this branch's reason for widening that shim. * docs(verification): re-measure the deferred startup stage on the current base Re-runs the unreachable-remote latency fixture against default-branch tip 8398d31 rather than the now-historical 345de4e, and records the sweep-result comparison the deferral's safety argument rests on: the deferred worker's published report is byte-identical to the three sweep lines the blocking baseline printed, with the unreachable route preserved in both. * no-mistakes(review): Fail deferred startup when report publication fails * no-mistakes(document): Document deferred startup network behavior accurately
* fix(procevent): apply a captured adapter result in code, not by instruction A remote secondmate's reply was captured and announced, but never applied. Nothing dispatched the reply adapter's `handle` on a `procevent remote-reply` wake, and the handling instruction named only the generic acknowledgement, so the wake was retired while everything it carried was dropped: the reply never reached the secondmate's local status mirror, the request it answered kept escalating as a missed report, and the relay - whose registration each capture retires, and which only that same handling re-arms - was left dead until the next session start armed it again. Applying such a result carries no judgement, so it belongs in code. After publishing, the runner now calls `bin/fm-procevent-<adapter>.sh autohandle <source-id> <sequence> <result-file>` and lets the adapter apply and acknowledge its own result, through the same kind of seam that already owns the terminal verdict. It runs strictly after terminal retirement, because a handling adapter re-arms its own next source and retiring afterwards would drop that fresh registration. An adapter with no such command, or one whose pass does not complete, leaves the result unacknowledged and therefore still announced, so a handler receives it exactly as before. Resolving the request was not enough on its own either. An escalation opens a durable keyed decision in the parent status log, and nothing ever closed it, so a request the remote had answered kept surfacing in every later open-decisions fold. The pending-reply library now owns both ends of that decision: it opens one under a per-request key rather than the shared default key, and closes it once the record resolves, appending the closing line only while that exact decision is still open in the fold so it can neither double-close nor clear an unrelated decision that has since taken the same key. The handling instruction still routes a wake to its adapter, now as the idempotent confirmation of what the runner already did rather than as the guarantee. Verified end to end in a throwaway isolated home driving the real armed source, blocking delta reader, runner, and wake queue, with the handler doing only the generic acknowledgement and no part of the ingest stubbed: before, seven failed observations reproducing the incident; after, none. Each half is independently load-bearing - without the runner change the reply never reaches the mirror, without the escalation close the settled request still surfaces as an open decision. * no-mistakes(review): Prevent legacy reply closure from masking decisions * no-mistakes(review): Serialize pending reply resolution and escalation closure * no-mistakes(review): Serialize pending reply escalation with resolution * no-mistakes(review): Clarify guarded legacy escalation closure behavior * no-mistakes(review): Guard legacy closure and reserve pending reply keys * no-mistakes(review): Match pending reply escalations by construction * no-mistakes(document): Document automatic remote reply resolution * no-mistakes(lint): Fix unused concurrent escalation loop variable * no-mistakes(lint): Fix unused concurrent resolution loop binding * no-mistakes(review): Version fold cache and gate autohandle on publication * no-mistakes(document): Clarify remote reply relay documentation
…kill (kunchenguid#1841) Bring the public installer-facing stow skill up to the internal skill's current curation behavior while keeping it fully standalone: - Replace the total-capture thesis with the compact-operating-map framing. - Add read-the-destination-before-writing with the inspect-then-update triad (supersedes what, one-sentence rewrite, delete stale now). - Add the concrete prune list together with its unique-fact guard, as an accuracy discipline with no size-budget machinery. - Curate every memory file the pass has open, not only the routed one. - Add the standing-decisions sweep category. - Add the stronger-owner pointer-over-copy test before filing. - Add tool-agnostic task-note discipline (inspect, classify, considered replacement body, never blind-append) and blocked-on recording. - Give .stow-notes.md a closed set of three exits. - Forbid storing, creating, or editing a skill as a stow destination. - Report per-file action verbs in the completion receipt. - Consolidate the repeated local-vs-external and .gitignore prose and fix the second-person voice slip, so the file does not grow (11334 -> 11276 bytes).
Extend the bounded read-only serialization-debt probe to also report when the canonical Decision OS checkout has remained off the configured base branch (any other branch, or a detached HEAD) strictly beyond the shift threshold. The checkout transition is dated from the most recent checkout: entry in the bounded HEAD reflog window, never from the target commit's age, so switching to an old branch cannot false-positive. Missing, malformed, or truncated reflog evidence fails closed as SERIALIZATION-DEBT-EVIDENCE-UNAVAILABLE. The probe never switches the checkout. Tests cover clean base, freshly switched non-base, the exact age boundary, over-age non-base debt, detached HEAD, missing and truncated reflog evidence, and propagation through the fleet refill cadence.
…probe The standing serialization-debt/refill cadence now invokes the Decision OS executable lane-contract checker (<project>/scripts/check_lane_contract.py, override with --lane-checker or FM_LANE_CONTRACT_CHECKER) with --repo <project> and maps its exit code to debt: 0 adds nothing, 1 surfaces each stdout violation line as SERIALIZATION-DEBT source=lane-contract-checker, and any other nonzero exit surfaces each stderr reason as SERIALIZATION-DEBT-EVIDENCE-UNAVAILABLE. An unlaunchable checker (missing, not executable, timed out, over-bounded output) is the same concrete evidence-unavailable debt. Checker debt is appended to other collected debt, never printed instead of it. The checker owns its invariants; the probe only maps exit codes. Behavioral tests cover clean, exit-1 violation, exit-2 cannot-run, and unavailable-checker outcomes including proof that the unavailable checker does not hide other debt, plus propagation through fm-fleet-refill.sh.
…tation plan Implementation-ready plan for the accepted design in docs/architecture.md section "Durable implementation capacity and attempt lifecycle design", composing Decision OS plan Tasks 7.1-7.6 with one shared capacity classifier, one structured cleanup operation, claim-before-allocation, ordered terminal orchestration, migration stages, rollback to alert-only, and deletion of legacy arithmetic after measured parity. Local main owns bin/fm-fleet-refill.sh absent from origin/main (merge-base 2cf0283); both histories preserved, local main at 38b4eb6 is the canonical base. Adds the plan's mandatory doc-audience classification entry.
…1-F8 and fleet-depth correction Addresses the single canonical review's eight material findings exactly: write-once receipt-derived attempts with no mutable phase or parallel obligation files (F1); one outer non-reentrant terminal transaction with lock-held primitives and preflight-all-refusals-before-effects (F2); real history reconciliation in Task 0 with shadow-only consumers until post-parity cutover (F3); structured crew-state contract and enforceable total deadline (F4); Decision OS steward transaction against installed br/storage contracts with pathspec-only guarded fast-forward publish (F5); centralized live disposition with fresh per-effect authority (F6); attempt-bound copy ownership, final landing receipt, frozen planned-path evidence, and pre-land actual-diff recheck (F7); deterministic parity, brief wiring, exhaustive file map, and Tasks 5/8 split (F8). Adds the fleet-depth correction: private cron sentinel quarantined in Task 3, removed after cutover proof in Task 13, verified gone in Task 15. Normalized to 16 tasks (Task 0 through Task 15).
… base Local main owns bin/fm-fleet-refill.sh and the accepted fleet lifecycle design (merge-base 2cf0283). Both histories are preserved: this merge keeps every local-only commit (refill, serialization probes, design docs, calm footer, bead closure discipline) and every upstream-only commit (833a9a2, be32879, 167ff42, 60eb534, 06b33aa). The twelve duplicated PRs resolve as identical content. fm-fleet-refill.sh is preserved from the local side. # Conflicts: # AGENTS.md # bin/fm-bootstrap.sh # bin/fm-startup-network.sh # tests/fm-bootstrap.test.sh # tests/fm-startup-network.test.sh
The fleet-depth quarantine makes legacy capacity unknown: the owned-manifest and output-mtime battery count and the DISPATCH-NEEDED verdict are disabled, and the script never stages work. Serialization-debt and the authoritative bead-query diagnostics remain. Focused regression tests pin the quarantined verdict shape, the ignored legacy manifest, and the surviving debt diagnostic.
…with total deadline
…uarantined verdict unchanged
…d durable pending
…th identical behavior
…final landing receipt once
… pre-land diff recheck
…omatic gate and cron sentinel removal
… acceptance evidence
… budget Per the captain-approved latency follow-up, FM_CAPACITY_PARALLEL defaults to 4 (the smallest measured value clearing the 2000 ms real-home budget) instead of shortening the per-endpoint timeout: slow-but-live workers keep their full per-row timeout, rows stay deterministically sorted, and the total deadline still bounds the whole collection. Regression tests pin byte-identical parallel output at the default, the total deadline under parallelism, and the JSON-null attempt_id shape on ambiguous rows (found via the real-home read).
The coverage guard's comm calls ran under the ambient locale while their inputs are LC_ALL=C-sorted, so an en_US.UTF-8 environment produced 'not in sorted order' warnings that failed the runner test. The gotmp fixture's fake roots predated the Task 4/9 cleanup extraction and were missing the fm-disposition-lib / fm-cleanup-lib / fm-attempt-lib siblings teardown now sources.
… E2E flake pre-existing
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 658773222a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| # and owned provider identity | ||
| bead=$(fm_attempt_load "$attempt" | jq -r '.envelope.task_key') | ||
| [ -n "$bead" ] || { echo "terminal: no bead binding for $attempt" >&2; exit 1; } | ||
| bead_live=$(br show --json "$bead" 2>/dev/null) || bead_live= |
There was a problem hiding this comment.
Resolve bead reads inside the registered clone
Captain, automatic reconciliation invokes this script from the Firstmate home, but br discovers .beads/*.db from the current directory. The added live contract explicitly documents this at tests/live-decision-os-contract.test.sh:34-37, and the refill reader correctly uses cd "$PROJECT" at bin/fm-fleet-refill.sh:180; this unscoped read therefore fails before every automatic terminal transaction can close the bead or clean up the task. Run both terminal br show reads from the registered project and preserve the read-only flag.
AGENTS.md reference: AGENTS.md:L354-L356
Useful? React with 👍 / 👎.
| live=$(cleanup_copy_live_pids "$FM_CLEANUP_COPY" 2>/dev/null) || { | ||
| echo "cleanup: refused: cannot determine live processes under $FM_CLEANUP_COPY" >&2; return 1; } | ||
| [ -z "$live" ] || { echo "cleanup: refused: live process(es) in copy $FM_CLEANUP_COPY: $(printf '%s' "$live" | tr '\n' ' ')" >&2; return 1; } |
There was a problem hiding this comment.
Exclude the owned endpoint from process preflight
For a completed tmux, Herdr, Zellij, or cmux task whose pane still exists, the pane's shell or agent process itself has a cwd under the recorded worktree. Because this scan runs before cleanup.endpoint stops that validated endpoint, live is nonempty and every normal terminal cleanup refuses at preflight; the tests miss this by using fake endpoints with no corresponding process. Distinguish the owned endpoint process tree from leaked descendants, or stop the validated endpoint before checking for remaining processes.
Useful? React with 👍 / 👎.
| fm_refill_automatic() { # 0 when automatic refill is authorized by the gate | ||
| [ "${FM_REFILL_AUTO:-0}" = 1 ] && return 0 | ||
| [ -f "$CONFIG_DIR/refill-auto" ] && return 0 | ||
| return 1 |
There was a problem hiding this comment.
Enable refill on the production path
The newly added fleet contract requires safe P0/P1 capacity to refill without an operator request, but the default path always returns false here because no tracked code creates config/refill-auto or sets FM_REFILL_AUTO. Consequently the session, watcher, and terminal calls to --refill only print fleet-ok: alert-only after installation and never claim or launch ready work, leaving the primary behavior introduced by this commit disabled indefinitely.
AGENTS.md reference: AGENTS.md:L280-L281
Useful? React with 👍 / 👎.
| # and FM_REFILL_CURRENT_EVIDENCE_FILE are TEST-ONLY overrides; | ||
| # every other knob is a real runtime default. | ||
|
|
||
| FM_REFILL_HARNESS="${FM_REFILL_HARNESS:-pi}" |
There was a problem hiding this comment.
Resolve dispatch profile and quota before claiming work
When the automatic gate is enabled, refill hard-codes pi and proceeds to allocate and claim without consulting dispatch profiles or quota-axi. In a home whose matched profile selects another runtime, or whose Pi quota lacks headroom, this can claim a bead and create a reserved attempt before fm-spawn fails, or consume the wrong quota despite quota headroom being a required admission condition. Resolve the normal per-intake profile and current quota before creating the claim request.
AGENTS.md reference: AGENTS.md:L181-L185
Useful? React with 👍 / 👎.
| echo "error: cannot resolve generation for $ATTEMPT_ID; refusing to allocate without the claim" >&2 | ||
| return 1 | ||
| } | ||
| source_hash=$(cd "$repo" 2>/dev/null && sha256sum .beads/issues.jsonl 2>/dev/null | cut -d' ' -f1) || { |
There was a problem hiding this comment.
Use a portable SHA-256 command for attempt claims
On a standard macOS installation sha256sum is absent while shasum -a 256 is available, so every attempt-bound spawn reaches this command with an empty hash and refuses before allocation. The repository already provides portable SHA-256 selection in fm_pr_sha256; use that shared behavior here and in the new receipt/refill hash sites so Decision OS dispatch is not Linux-only.
Useful? React with 👍 / 👎.
| find "$dir/attempts" -maxdepth 1 -name '*.json' ! -name '*.request.*.json' -printf 'attempt:%f\n' 2>/dev/null \ | ||
| | sed 's/\.json$//' |
There was a problem hiding this comment.
Enumerate attempts portably on BSD find
BSD find on macOS does not support -printf, and stderr is suppressed here, so attempt records disappear from the entity list. Attempts that have metadata become false record_disappeared ambiguities, while claim-pending attempts with no metadata are omitted entirely; the latter are not counted as reserved ownership and repeated refill waves can allocate additional attempts despite the safe-capacity requirement. Emit basenames through a portable loop or -exec.
AGENTS.md reference: AGENTS.md:L280-L281
Useful? React with 👍 / 👎.
| proc_root=${FM_PROC_ROOT_OVERRIDE:-/proc} | ||
| for pid_dir in "$proc_root"/*/; do |
There was a problem hiding this comment.
Refuse cleanup when the process source is unavailable
On systems without /proc, notably macOS, this glob is left literal and every iteration is skipped, so the function returns success with empty output instead of reporting that live-process inspection was unavailable. With the quiet interval disabled or bypassed, cleanup can then force-return a worktree while an unobserved child still has that directory as its cwd and may hold or create uncommitted work. Validate that the process root is usable or fall back to a portable process scanner before reporting no live PIDs.
AGENTS.md reference: AGENTS.md:L30-L32
Useful? React with 👍 / 👎.
|
|
||
| FM_REFILL_HARNESS="${FM_REFILL_HARNESS:-pi}" | ||
| FM_REFILL_MODE="${FM_REFILL_MODE:-direct-PR}" | ||
| FM_REFILL_PRIORITY_THRESHOLD="${FM_REFILL_PRIORITY_THRESHOLD:-2}" |
There was a problem hiding this comment.
Limit automatic candidates to P0 and P1
The priority parser maps P2 to numeric 2, and availability accepts every value less than or equal to this default, so an enabled refill wave admits P2 beads alongside P0/P1 work. That consumes productive and reserved capacity outside the explicitly authorized automatic-refill scope and can delay the urgent beads the feature is intended to fill first; default the threshold to 1.
AGENTS.md reference: AGENTS.md:L280-L281
Useful? React with 👍 / 👎.
…ery branch PR kunchenguid#2006's base advanced from 833a9a2 to 74230fc (docs: add project vision kunchenguid#1997, feat(stow): tiered decaying memory kunchenguid#1984, feat(bin): deterministic agent lifecycle control kunchenguid#1568). Merging the advanced base into the delivery branch keeps Task 0's reconciliation merge (454b10d) and both original lines (local main d40fd7f, origin/main 833a9a2) as ancestors instead of rebasing them away, per the plan's preservation invariant. Conflict resolutions (minimal; both sides preserved): - bin/fm-spawn.sh: keep main's --relaunch adoption/validation flow and the branch's Task 6/7 attempt claim (claim-aware treehouse get --lease) in one if/elif; the meta writer keeps both attempt= and the relaunch publish flow (preserve_relaunch_meta, control_relaunch_tx) under SPAWN_META_PATH. - bin/fm-teardown.sh: keep the branch's structured attempt wrapper (cleanup functions live in fm-cleanup-lib.sh); keep main's lifecycle locks (CONTROL_LOCK/META_LOCK/descendant task-set locks) and the META_LOCK release at the teardown tail. - bin/fm-cleanup-lib.sh: adopt main's fm-control-lib.sh-owned turn-end token path contract (remove_grok/kimi_turnend_auth) and remove the new control-relaunch state files with the task state.
|
Automated reminder: thanks for the PR! This branch currently has a merge conflict with the base branch. When you get a chance, please rebase onto (or merge) the latest base branch, resolve the conflict, and push. After that, checks will re-run and the PR will get looked at again. Noted for firstmate#2006 at |
Intent
Implement the accepted and reviewed 16-task plan at docs/superpowers/plans/2026-08-08-fleet-refill-terminal-lifecycle.md end to end on this single delivery branch. Preserve Task 0's explicit reconciliation merge of the original local main and origin/main histories, prove both original lines remain ancestors, retain the full green baseline and single canonical review.sh verdict evidence, and preserve the dependency-ordered, test-driven, small-green-commit implementation of Tasks 1 through 15. The accepted design in docs/architecture.md remains authoritative, and Decision OS beads remain the sole authority for work identity, priority, readiness, dependencies, claims, ownership, and closure. Apply firstmate-coding-guidelines: one owner per contract, valid documentation audiences, one sentence per Markdown line, shellcheck-clean scripts, behavioral tests rather than source-byte assertions, complete documentation review, and compatibility coverage across every affected supported worker runtime and provider.
Do not introduce a scheduler, daemon, dashboard, wrapper, parser, mutable phase model, duplicate capacity counter, legacy-arithmetic fallback, or second task authority. Automatic refill must remain inactive until fixture parity, live parity, latency, full tests, lint, documentation, and safety gates pass. Private-home operations involving data/fleet-depth-check.sh, crontab, config/refill-sentinel, or config/refill-auto must not modify anything outside this isolated delivery flow; keep refill-sentinel/refill-auto private activation prohibited until this PR is explicitly approved and merged, while preserving truthful read-only evidence of already-verified private state. Never delete, overwrite, reset, stash, rebase away, replace, or discard unknown or unlanded branches, copies, refs, stashes, attempt records, tracker evidence, or any pipeline fix commit. Final acceptance requires every plan checkbox and file-map check to have evidence, all tests and canonical lint green, documentation audiences valid, both original histories preserved, the fleet-depth legacy path removed only after proven cutover, rollback alert-only, and no unresolved decision or cleanup obligation.
Preserve and validate all captain-approved review corrections already committed through 8bd867a: distinct claim and closure receipt effects; normal lifecycle launch and exact landing receipts; complete terminal disposition classification for open/closed/merged PRs and authorized local-only merges using worker, endpoint, Git, forge, and exact-content evidence; propagation of the authoritative pre-land actual-diff refusal; tracker preflight before mutation plus recovery of already-applied transitions; authoritative endpoint stop and confirmed absence; verified preservation refs before destructive cleanup; effect-by-effect forward cleanup recovery; preflight and provider-owned return support for every supported runtime/provider; effective FM_HOME and home-local context in capacity deadline children; installed array-shaped bead JSON normalization; bounded refill waves and propagated claim/launch failures; full admission checks for paths, exclusive seams, shared mutable state, semantic dependencies, lane contracts, and same-wave conflicts; migration binding only after live reconciliation with no synthetic effects; terminal and refill-sentinel recovery wired through existing heartbeat/completion/session owners without a new scheduler; truthful sentinel-removal verification; shared canonical repository identity normalization for observed paths and frozen remote URLs while preserving local-only exact proof, with remote-backed regression coverage; terminal replay after runtime-record removal may bypass the pre-land diff gate only when the exact cleanup.endpoint receipt proves the endpoint stopped; and spawn resume may recover a missing launch receipt only when the duplicate guard verifies the exact recorded endpoint identity is live. Keep Claude disabled and use the configured non-Claude [pi, codex] routing. Deliver one reviewed PR containing the reconciled history, implementation, and all pipeline-applied fixes.
What Changed
bin/fm-attempt-lib.sh(immutablefm-attempt.v1envelopes, effect receipts, append-only observation journal, obligations derived from missing receipts), the single shared capacity projectionbin/fm-capacity-lib.sh(fm-fleet-capacity.v1over structuredfm-crew-state.sh --json), the centralized live disposition readerbin/fm-disposition-lib.sh, and a read-only legacy migration helperbin/fm-attempt-migrate.sh.bin/fm-terminal.sh(sole terminal orchestrator composing structured per-effect cleanup frombin/fm-cleanup-lib.shand tracker receipts via the attended Decision OS steward adapterbin/fm-br-receipt.sh, inside one outer non-reentrant attempt lock), the--refilladmission action inbin/fm-fleet-refill.shwith full admission checks and the private consumer-onlybin/fm-refill-sentinel.sh, claim-before-allocation and attempt-bound provider ownership inbin/fm-spawn.sh/bin/fm-home-seed.sh/bin/fm-backend.sh, and provisional forge observations plus the write-once final landing receipt in the PR delivery scripts; automatic refill stays gated (alert-only withoutconfig/refill-autoorFM_REFILL_AUTO=1).fm-fleet-capacity.v1, the legacy owned-manifest/output-mtime arithmetic is deleted with alert-only rollback, attempt reconciliation is surfaced through the existing heartbeat and session-start owners without a new scheduler, and the branch lands ~20 new behavioral test files, the reviewed 16-task plan and design documentation, thedocs/verification/fleet-capacity.mdacceptance record, the reconcile commit merging localmainandorigin/mainwith both histories preserved, and the persistent Calm footer indicator in.pi/extensions/fm-calm.ts.Risk Assessment
✅ Low: All three authorized corrections are implemented exactly as instructed with discriminating behavioral tests and verified invariants (automatic refill still fails closed, automatic terminal reconciliation still retains the quiet gate, terminal success preserved on refill refusal); no new material defect or intent contradiction was found in the fix round.
Testing
Round-1 auto-fixes (orca claim-effect name, calm getMarkdownTransformers stub) were re-verified passing on the final HEAD ebcf659: fm-backend-orca 53/53 and the calm renderer/lifecycle test including the branch's new '● calm on / ○ calm off' footer-status assertions. All 26 targeted fleet-refill/terminal/documentation suites passed. The branch's real-Pi TUI E2E coverage was exercised individually for the first time (the pre-existing followup flake aborts the full script): hidden-block geometry and working-ship geometry pass; test_interactive_terminal_e2e fails consistently in this sandbox ('/export did not complete while calm mode was on') and fails identically on a base 833a9a2 snapshot, confirming pre-existing Pi 0.84.1 environment drift rather than a branch regression - reported as an informational warning. The end-to-end CLI transcript was rebuilt on the final HEAD against the real binaries and demonstrates the complete user-visible lifecycle: shared fm-fleet-capacity.v1 projection, alert-only sentinel verdict, in-flight terminal refusal that preserves ownership with zero destructive effects, and the full landed terminal run (pre-land diff gate, attended Decision OS steward bead closure, per-effect structured cleanup, retirement, fresh post-terminal projection, and the real refill consumer's 'fleet-ok: alert-only' verdict), ending with the complete receipt chain claim/provider/launch/landing/tracker/cleanup.*/retirement. Worktree left clean; evidence in /tmp/no-mistakes-evidence/01KZJRV1Y3DBPJEDXAJBAQVRER/.
Evidence: End-to-end fleet-refill + terminal-lifecycle CLI transcript (real binaries, HEAD ebcf659)
Evidence: Reproducible E2E demo harness (real bin/ binaries, hermetic external owners)
Evidence: Consolidated test evidence summary (round 2)
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
.agents/skills/harness-adapters/SKILL.md- branch carries 25 commit(s) that exist on your local main branch but were never pushed to origin/main; rebasing would bundle this unrelated work (76 file(s)) into the PR:Push main to origin, or rebase your branch onto origin/main, before gating.
🔧 **Review** - 5 issues found → auto-fixed ✅
bin/fm-terminal.sh:339- Step 8 treats a nonzerofm-fleet-refill.sh --refillexit as a terminal failure (exit 1after the attempt was already retired with all receipts observed). The refill can legitimately exit nonzero for environmental reasons unrelated to the attempt's outcome:REFILL-UNSAFEwhen any other row of the fresh projection is ambiguous, orfm_refill_query_candidatesfailing whenbr readyis unavailable in the decision-os clone. The stderr message 'post-retirement refill unavailable or refused' then propagates to the watch/session-start heartbeat callers as a reconciliation failure for an attempt that is already retired and therefore never retried (retired rows are not reconciliation-required), so the nonzero is both misleading and silent. Refill after retirement is a best-effort notification step; the exit code should reflect the terminal outcome (log the refusal, exit 0).bin/fm-fleet-refill.sh:364- In the documented Task 15 alert-only rollback mode,fm_refill_admit_and_dispatchruns the live candidates query (fm_refill_query_candidates, which requiresbr readyagainst the decision-os clone) and the lane-contract checker (line 366, invoked with no timeout) unconditionally BEFORE the automatic-gate check at line 409. On a host wherebris missing or the clone is broken,--refillexits 1 without ever printing the header-promisedfleet-ok: alert-onlyverdict (header lines 20-31), contradicting the rollback contract that rollback reports and never dispatches. The sentinel handles the identical absence safely (refill_candidates_jsonfalls back toecho '[]'), and fm-serialization-debt.sh bounds the same checker at 90s while this call has no bound (a hung checker would wedge the terminal's step-8 refill, which the watcher runs synchronously). Recommend: in alert-only mode tolerate candidate-query failure (empty candidates, serialize) and bound the checker invocation, still printing the verdict.bin/fm-cleanup-lib.sh:640- The default 2h terminal-quiet gate (FM_TERMINAL_QUIET_SECS=7200, age measured from the launch receipt) also applies to the operator-initiatedfm-teardown.sh <id>compatibility wrapper for attempt-bound tasks. A captain who tears down a task less than 2h after launch gets 'cleanup: refused: quiet interval immature' and a 1 exit, with only the undocumented FM_TERMINAL_QUIET_SECS=0 as an override; every retry within the window fails the same way. The quiet gate is deliberate for automated terminal reconciliation, but for an explicit operator teardown it turns a previously-immediate reclaim into a 2h refusal loop. Confirm that operator-initiated teardown of attempt-bound tasks is intended to inherit the quiet window (and if so, the refusal should name the override).bin/fm-capacity-lib.sh:58- The new projection and structured-cleanup paths rely on GNU-only tooling:find -printf(fm-capacity-lib.sh:58),xargs -P(fm-capacity-lib.sh:196), GNUtimeout(fm_capacity_crew_state),date -d(fm-cleanup-lib.sh:460), and a /proc cwd readlink walk (fm-cleanup-lib.sh:437), whereas existing supervision code carries Darwin branches (e.g. stat -f/-c). On a non-Linux host the projection silently drops attempt rows (a short undercount window when the task meta is also gone) and the cleanup live-process scan silently reports 'no live processes' instead of refusing, so the preflight's fail-safe behavior differs by platform. Target host and CI are Linux, so this is informational.bin/fm-backend.sh:881-fm_backend_stop_receiptis defined and unit-tested (tests/fm-backend-orca.test.sh) but never called by the production cleanup path: fm-cleanup-lib.sh builds a richer inline endpoint receipt (backend/endpoint/task_id/copy/confirmed_gone plus orca_worktree_id/tasktmp). The helper is dead code whose evidence shape can drift from the real receipt contract; either wire it into the endpoint-receipt construction or drop it.🔧 Fix: Fix refill exit, alert-only tolerance, teardown quiet bypass
✅ Re-checked - no issues remain.
tests/fm-backend-orca.test.sh:1353- New Task-7 test code in this branch could never pass: the fake attended steward (make_orca_claim_steward) observed the 'tracker' receipt effect, but fm-spawn.sh's claim handshake verifies .receipts.claim[] (and the real bin/fm-br-receipt.sh maps claim -> effect=claim), so every attempt-bound orca spawn refused with 'claim_pending: <id> (reconcile before allocation)'. Fixed by observing the 'claim' effect; also corrected the follow-on assertion (a successful spawn publishes the launch receipt, so the outstanding obligation is 'landing', not 'launch'; crash-after-allocation launch-obligation coverage already exists in tests/fm-spawn-worktree-settle.test.sh). Test now passes 53/53 across consecutive runs. Fix is applied in the working tree.tests/fm-calm-pi-extension.test.sh:830- The InteractiveMode test stub (operationalMode) lacked getMarkdownTransformers(), which Pi >= 0.84's addMessageToChat requires ('TypeError: this.getMarkdownTransformers is not a function'); test_rendering_and_session_lifecycle died before reaching this branch's new calm footer-status assertions (statuses.get('firstmate-calm') === theme.fg('accent', '● calm on')). Added the stub method (returns [] — inert on older Pi, correct on 0.84.1); the renderer test now passes including the branch's footer assertions. Fix is applied in the working tree.tests/fm-calm-pi-extension.test.sh:1561- test_operational_followup_turn_e2e fails in this sandbox ('Pi follow-up loaded_off case rendered a duplicate captain answer') against the real Pi 0.84.1 TUI: the tmux pane-capture assertion is timing-sensitive with this Pi version (answer absent when the case runs alone, duplicated in the full suite). Verified pre-existing: fails identically on base 833a9a2, and this branch only changed calm footer-status assertions, not the follow-up path. Environment/upstream drift, not a branch regression; CI runners with a different Pi version may pass it.tests/fm-teardown.test.sh:2182- test_leaked_worktree_process_is_reaped fails in this sandbox because the real lsof binary is not installed (pids_with_cwd_under and the extraction are byte-identical to base, and the test fails identically on base 833a9a2 — environment issue, not a regression). With a faithful /proc-based lsof-equivalent prepended to PATH the entire fm-teardown suite passes, including the leaked-process reap, tasktmp reap, exec-changed reap, and the extraction-identity byte-for-byte test. GitHub CI runners ship lsof, so CI should be unaffected.bin/fm-test-run.sh tests/fm-attempt.test.sh tests/fm-capacity.test.sh tests/fm-disposition.test.sh tests/fm-br-receipt.test.sh tests/fm-cleanup.test.sh(5/5 pass)bin/fm-test-run.sh tests/fm-terminal.test.sh tests/fm-attempt-migrate.test.sh tests/fm-refill-admission.test.sh tests/fm-refill-sentinel.test.sh tests/fm-fleet-refill.test.sh(5/5 pass)bin/fm-test-run.sh tests/fm-crew-state.test.sh tests/fm-spawn-worktree-settle.test.sh tests/fm-brief.test.sh tests/fm-teardown-endpoint-safety.test.sh tests/fm-pr-merge.test.sh tests/fm-secondmate-safety.test.sh tests/fm-decision-os-review-migrate.test.sh tests/fm-spawn-decision-os-reviews.test.sh tests/fm-fleet-snapshot-view.test.sh tests/fm-gotmp.test.sh(11/11 pass)bin/fm-test-run.sh tests/fm-pr-check-security.test.sh tests/fm-session-start.test.sh tests/fm-watch-triage.test.sh(3/3 pass)tests/fm-backend-orca.test.shx2 after auto-fix (53/53 ok, exit 0 both runs)tests/fm-calm-pi-extension.test.shafter auto-fix (renderer/lifecycle contract incl. new footer-status assertions pass)PATH=/tmp/fm-fake-lsof-bin:$PATH tests/fm-teardown.test.sh(full suite pass incl. leaked-process-reap, extraction byte-for-byte)Base-commit comparison on worktree at 833a9a2:tests/fm-teardown.test.shandtests/fm-calm-pi-extension.test.shfail identically (pre-existing environment issues, not regressions)Manual E2E CLI demo/tmp/fm-e2e-demo.sh:fm-fleet-refill.sh --count-jsonemits fm-fleet-capacity.v1 (refill_safe=true),fm-refill-sentinel.shemits alert-only REFILL-ALERT,fm-terminal.shrefuses in-flight attempt preserving ownership,fm-terminal.shruns a landed attempt through tracker close -> cleanup -> retirement -> post-terminal capacity projection -> alert-only refill🔧 Fix: Auto-fixed orca claim and calm stub tests pass; Pi E2E flake pre-existing
1 warning still open:
tests/fm-calm-pi-extension.test.sh:2821- test_interactive_terminal_e2e fails in this sandbox with the real Pi 0.84.1 TUI: 'not ok - /export did not complete while calm mode was on'. Fails consistently (3 consecutive runs) on HEAD ebcf659 and fails identically on a base 833a9a2 snapshot, so it is live-Pi TUI timing/behavior drift in this environment, not a branch regression (the branch's calm change is the footer-status indicator, untouched by the /export path). Same category as the previously reported test_operational_followup_turn_e2e flake but a distinct test that had never been run (the followup flake aborts the script before it). CI runners with a different Pi version may pass it.bash tests/fm-backend-orca.test.sh- 53/53 ok (round-1 claim-effect fix verified)bash tests/fm-calm-pi-extension.test.sh- rendering/lifecycle test with branch's footer-status assertions passes; only pre-existing followup E2E flake failsbash tests/fm-terminal.test.sh(17 ok),fm-fleet-refill.test.sh(25 ok),fm-refill-admission.test.sh(16 ok),fm-refill-sentinel.test.sh(10 ok),fm-capacity.test.sh(17 ok),fm-disposition.test.sh(8 ok),fm-br-receipt.test.sh(7 ok),fm-cleanup.test.sh(12 ok),fm-attempt.test.sh(11 ok),fm-attempt-migrate.test.sh(4 ok),fm-crew-state.test.sh(51 ok),fm-spawn-worktree-settle.test.sh(12 ok),fm-brief.test.sh(23 ok),fm-teardown-endpoint-safety.test.sh(5 ok),fm-pr-merge.test.sh(12 ok),fm-secondmate-safety.test.sh(72 ok),fm-decision-os-review-migrate.test.sh(8 ok),fm-spawn-decision-os-reviews.test.sh(5 ok),fm-fleet-snapshot-view.test.sh(17 ok),fm-gotmp.test.sh(3 ok),fm-pr-check-security.test.sh(40 ok),fm-session-start.test.sh(44 ok),fm-watch-triage.test.sh(49 ok),fm-gate-refuse.test.sh(7 ok),fm-documentation-audiences.test.sh(4 ok) - all passReal-Pi TUI E2E run individually via patched copies:test_hidden_block_geometry_e2eok,test_working_ship_geometry_and_lifecycleok,test_interactive_terminal_e2efails '/export did not complete while calm mode was on' - re-verified identical on agit archive 833a9a2base snapshot (pre-existing env drift)End-to-end CLI demo (real bin/fm-fleet-refill.sh --count-json, bin/fm-refill-sentinel.sh, bin/fm-terminal.sh with hermetic gh/br/treehouse/tmux/steward/crew-state fakes): count-json emits fm-fleet-capacity.v1 (2 active rows, refill_safe=true); sentinel reports mode=alert-only REFILL-ALERT productive 2 < 6; terminal on in-flight attempt refuses 'ownership preserved for reconciliation' exit 1 with receipts claim,launch,provider unchanged; terminal on landed attempt runs diff gate -> steward tracker close -> structured cleanup -> retirement -> fresh projection -> real refill 'fleet-ok: alert-only', final receipts claim,provider,launch,landing,tracker,cleanup.endpoint,cleanup.branch,cleanup.provider,cleanup.runtime,retirementConstraint check:rgconfirms nothing in bin/ creates config/refill-auto (only reads it); automatic refill stays inactive, sentinel and refill both report alert-onlygit status --shortclean after testing; transient demo dirs and patched copies removeddocs/superpowers/plans/2026-08-08-fleet-refill-terminal-lifecycle.md:1- The plan is implemented end to end with evidence recorded in docs/verification/fleet-capacity.md (Task-by-task commits, tests, lint, doc-audience), but all 96 execution checkboxes remain '- [ ]' and none are ticked, so the plan's own tracking state claims the work was never done. Ticking is executing-plans bookkeeping tied to the final-acceptance evidence audit, so the docs phase did not mutate it; the executor should decide whether the acceptance pass ticks the boxes after verifying each step's evidence or leaves them as the plan record.docs/verification/fleet-capacity.md:264- The Final acceptance file-map section records the branch diff state at the d769ccc era (80 paths; non-map deviations = 34 Task-0 files + live-decision-os-contract.test.sh). This docs phase adds two further non-map diff paths (docs/scripts.md inventory rows for the 10 new scripts, docs/architecture.md design-framing fixes), and the later review commits already added more. The final Step 5 file-map re-check must record these as documented deviations before acceptance; the plan itself anticipates the follow-up docs commit modifying the design owner.🔧 Fix: docs: tick plan checkboxes and record current-head evidence
1 error still open:
bin/fm-lint.sh:1- The canonical lint gate (pinned by .no-mistakes.yaml tobin/fm-lint.sh, ShellCheck 0.11.0) exits 1 at the current head d670a84 with eight findings in seven locations across six files: bin/fm-br-receipt.sh (SC1010 line 129, SC2015 line 217), bin/fm-fleet-refill.sh (SC2329 line 78, fm_refill_paths_overlap unused after the rework), bin/fm-capacity-lib.sh (SC1007 line 226), bin/fm-session-start.sh (SC1007 line 738), bin/fm-watch.sh (SC1007 line 689), and tests/fm-capacity.test.sh (SC2016 line 195). The same command was clean at the Task 15 head f2383b8 (rc 0) and the full canonical set was clean at the base 833a9a2 (rc 0); all eight findings were introduced by the review commit 4f6e780, so the user intent's mandatory 'all tests and canonical lint green' acceptance criterion is currently violated at HEAD. The documentation phase cannot fix shellcheck findings in executable code, so the downstream lint gate must resolve these before final acceptance; the exact finding list and the dated clean baselines are recorded in docs/verification/fleet-capacity.md (Current-head record).🔧 **Lint** - 1 issue found → auto-fixed ✅
🔧 Fix: Fix shellcheck findings across receipt refill and capacity scripts
✅ Re-checked - no issues remain.
✅ **Push** - passed
✅ No issues found.