Skip to content

feat(bin): add independent sovereign ledger redundancy - #2449

Closed
coreldh wants to merge 12 commits into
kunchenguid:mainfrom
coreldh:fm/c0815-fm-ledger-r4
Closed

coreldh wants to merge 12 commits into
kunchenguid:mainfrom
coreldh:fm/c0815-fm-ledger-r4

Conversation

@coreldh

@coreldh coreldh commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Intent

Repair the fourth-round sovereign ledger redundancy failure on the exact 897bccd base. Establish, rather than proxy, that an exact four-member replica bundle is independently stored: enumeration must fail closed and prove the exact known manifest and read denominator; no replica inode may overlap any primary inode; BSD and GNU stat semantics must be selected safely or refused; and primary/replica containment must be explicitly rejected. Preserve round 3's correct per-member symlink and hard-link protections. Prove the result with scoped fixtures only, including real primary destruction and byte recovery for hash, unterminated-bracket, space, newline, and Unicode paths; positive and negative controls; and a mutation denominator with one recorded substitution per mutant. Do not touch the live ledger, data, or state beyond the designated status line. Publish only through the no-mistakes PR pipeline; never merge.

What Changed

  • Add snapshot, refresh, and verification commands for an exact four-member sovereign ledger replica bundle, failing closed on incomplete, stale, or divergent layouts.
  • Enforce byte parity, disjoint file identities, portable BSD/GNU stat handling, and explicit containment, symlink, and hard-link rejection.
  • Add fixture-scoped destruction, adversarial-path, and reproducible mutation coverage, including contained evidence publication.

Risk Assessment

✅ Low: Captain, the canonical 156-cell matrix is now embedded in the committed evidence exactly in the generator’s output shape, with cell rows, defender claims, per-mutant totals, and aggregate totals produced by one stream.

Testing

Baseline identity and delta inspection, the public snapshot/verify/refresh fixture, real primary destruction across hash, unterminated-bracket, space, newline, and Unicode paths, BSD/GNU stat controls, containment and inode-overlap refusals, and the complete mutation population all passed. The regenerated evidence exactly matches the tracked record; reviewer-visible CLI and mutation artifacts were captured, and the worktree remained clean.

Evidence: End-to-end CLI fixture transcript
T1 complete, independent bundle preconditions
  PASS  verify REFUSES a nonexistent ledger directory
  PASS  snapshot REFUSES an incomplete primary bundle
  PASS  snapshot REFUSES a primary bundle without its contract
  PASS  snapshot REFUSES the same primary and replica directory
  PASS  snapshot atomically CREATES the complete second ledger bundle
  PASS  verify PASSES for the exact independent replica
  PASS  snapshot CREATES an executable-bit fixture bundle
  PASS  verify REFUSES a non-executable replica verifier
  PASS  verify REFUSES when exact bundle enumeration fails
T2 staleness is loud and refresh accepts only an append-only prefix
  PASS  verify names a verified stale replica and its refresh remedy
  PASS  refresh advances the verified append-only replica
  PASS  verify PASSES after refresh
  PASS  refresh REFUSES an equal-length replica rather than treating it as stale
  PASS  snapshot CREATES a non-prefix refresh fixture
  PASS  refresh REFUSES a verifying replica that is not a byte-exact prefix
  PASS  snapshot CREATES a shorter non-prefix refresh fixture
  PASS  refresh REFUSES a shorter verifying replica that is not byte-exact
  PASS  snapshot CREATES an empty-prefix guard fixture
  PASS  refresh REFUSES an empty replica ledger under GNU head semantics even when the fixture verifier accepts it
T3 source loss leaves the four required fixture rulings provable
  PASS  fixture recheck reports all four removed sources as SOURCE_GONE
  PASS  redundancy verify PASSES after the four sources are gone
  PASS  replica returns exact ruling-1 text after source loss
  PASS  replica returns exact ruling-2 text after source loss
  PASS  replica returns exact ruling-3 text after source loss
  PASS  replica returns exact ruling-4 text after source loss
T4 every bundle-file symlink is refused before it can masquerade as a copy
  PASS  snapshot CREATES a ledger.tsv symlink fixture
  PASS  verify REFUSES a symlinked ledger.tsv
  PASS  snapshot CREATES a CONTRACT.md symlink fixture
  PASS  verify REFUSES a symlinked CONTRACT.md
  PASS  snapshot CREATES a fm-sovereign-ledger.sh symlink fixture
  PASS  verify REFUSES a symlinked fm-sovereign-ledger.sh
  PASS  snapshot CREATES a tests.sh symlink fixture
  PASS  verify REFUSES a symlinked tests.sh
T5 non-regular bundle members are refused before comparison or execution
  PASS  snapshot CREATES a non-regular member fixture bundle
  PASS  verify REFUSES a FIFO bundle member
T6 replica bytes are compared before replica-controlled code can execute
  PASS  snapshot CREATES an ordering fixture bundle
  PASS  verify REFUSES a changed replica verifier
  PASS  changed replica verifier never executed
T7 divergence and extra files are detected and never repaired
  PASS  verify FAILS when replica contract bytes diverge
  PASS  snapshot REFUSES to overwrite a divergent replica
  PASS  verify REFUSES an unexpected replica file
  PASS  verify PASSES after fixture restore
T8 an invalid primary is never copied
  PASS  snapshot REFUSES a primary rejected by its own verifier
  PASS  rejected primary leaves no replica directory behind
T9 each identity check fires independently
  PASS  snapshot CREATES an identity-check fixture bundle
  PASS  snapshot CREATES an identity-check replica bundle
  PASS  verify REFUSES a shared lstat identity even when stat identities differ
  PASS  verify REFUSES a shared stat identity even when lstat identities differ
T10 every bundle member must have separate lstat and stat identities
  PASS  snapshot CREATES a ledger.tsv hard-link fixture
  PASS  verify REFUSES a hard-linked ledger.tsv by lstat identity
  PASS  snapshot CREATES a CONTRACT.md hard-link fixture
  PASS  verify REFUSES a hard-linked CONTRACT.md by lstat identity
  PASS  snapshot CREATES a fm-sovereign-ledger.sh hard-link fixture
  PASS  verify REFUSES a hard-linked fm-sovereign-ledger.sh by lstat identity
  PASS  snapshot CREATES a tests.sh hard-link fixture
  PASS  verify REFUSES a hard-linked tests.sh by lstat identity
T11 adversarial ledger paths certify real copies that survive primary destruction
  PASS  snapshot CREATES an independent replica under a hash path
  PASS  verify CERTIFIES the independent replica under a hash path
  PASS  all four bundle members survive primary destruction under a hash path
  PASS  all four rulings remain byte-exact under a hash path
  PASS  snapshot CREATES an independent replica under a unterminated bracket path
  PASS  verify CERTIFIES the independent replica under a unterminated bracket path
  PASS  all four bundle members survive primary destruction under a unterminated bracket path
  PASS  all four rulings remain byte-exact under a unterminated bracket path
  PASS  snapshot CREATES an independent replica under a space path
  PASS  verify CERTIFIES the independent replica under a space path
  PASS  all four bundle members survive primary destruction under a space path
  PASS  all four rulings remain byte-exact under a space path
  PASS  snapshot CREATES an independent replica under a newline path
  PASS  verify CERTIFIES the independent replica under a newline path
  PASS  all four bundle members survive primary destruction under a newline path
  PASS  all four rulings remain byte-exact under a newline path
  PASS  snapshot CREATES an independent replica under a unicode path
  PASS  verify CERTIFIES the independent replica under a unicode path
  PASS  all four bundle members survive primary destruction under a unicode path
  PASS  all four rulings remain byte-exact under a unicode path
T12 the instrument distinguishes independent and non-independent replicas
  PASS  positive control REFUSES a fully symlinked replica under a hash path
T13 containment is an explicit property
  PASS  verify REFUSES a replica contained by the primary explicitly
  PASS  snapshot REFUSES a replica target contained by the primary explicitly
  PASS  verify REFUSES a primary contained by the replica explicitly
T14 identity reads are fail-closed under BSD and GNU stat semantics
  PASS  snapshot CREATES a stat-dialect primary fixture
  PASS  snapshot CREATES a stat-dialect replica fixture
  PASS  verify PASSES an independent replica with bsd stat semantics
  PASS  snapshot CREATES the bsd hard-link fixture
  PASS  verify REFUSES a hard link with bsd stat semantics
  PASS  verify PASSES an independent replica with gnu stat semantics
  PASS  snapshot CREATES the gnu hard-link fixture
  PASS  verify REFUSES a hard link with gnu stat semantics
  PASS  verify REFUSES when stat cannot establish a numeric identity
T15 the complete replica and primary inode sets must be disjoint
  PASS  snapshot CREATES a cross-member identity fixture
  PASS  verify REFUSES a replica member sharing storage with a different primary member

91 passed, 0 failed
Evidence: Mutation run log

MUTATION SUMMARY killed=47 survived=25 void=0 denominator=72; all 72 mutants recorded exactly one substitution; no-op control survived.

M001 SURVIVED substitutions=1 status=0
M002 KILLED substitutions=1 status=1
M003 KILLED substitutions=1 status=1
M004 SURVIVED substitutions=1 status=0
M005 KILLED substitutions=1 status=1
M006 KILLED substitutions=1 status=1
M007 SURVIVED substitutions=1 status=0
M008 KILLED substitutions=1 status=1
M009 KILLED substitutions=1 status=1
M010 KILLED substitutions=1 status=1
M011 KILLED substitutions=1 status=1
M012 KILLED substitutions=1 status=1
M013 KILLED substitutions=1 status=1
M014 KILLED substitutions=1 status=1
M015 KILLED substitutions=1 status=124
M016 KILLED substitutions=1 status=1
M017 SURVIVED substitutions=1 status=0
M018 SURVIVED substitutions=1 status=0
M019 KILLED substitutions=1 status=1
M020 SURVIVED substitutions=1 status=0
M021 KILLED substitutions=1 status=1
M022 SURVIVED substitutions=1 status=0
M023 KILLED substitutions=1 status=1
M024 SURVIVED substitutions=1 status=0
M025 SURVIVED substitutions=1 status=0
M026 KILLED substitutions=1 status=1
M027 KILLED substitutions=1 status=1
M028 SURVIVED substitutions=1 status=0
M029 KILLED substitutions=1 status=1
M030 KILLED substitutions=1 status=1
M031 SURVIVED substitutions=1 status=0
M032 KILLED substitutions=1 status=1
M033 KILLED substitutions=1 status=1
M034 SURVIVED substitutions=1 status=0
M035 KILLED substitutions=1 status=1
M036 SURVIVED substitutions=1 status=0
M037 KILLED substitutions=1 status=1
M038 KILLED substitutions=1 status=1
M039 KILLED substitutions=1 status=1
M040 SURVIVED substitutions=1 status=0
M041 KILLED substitutions=1 status=1
M042 KILLED substitutions=1 status=1
M043 KILLED substitutions=1 status=1
M044 KILLED substitutions=1 status=1
M045 KILLED substitutions=1 status=1
M046 SURVIVED substitutions=1 status=0
M047 KILLED substitutions=1 status=1
M048 KILLED substitutions=1 status=1
M049 KILLED substitutions=1 status=1
M050 KILLED substitutions=1 status=1
M051 KILLED substitutions=1 status=1
M052 KILLED substitutions=1 status=1
M053 KILLED substitutions=1 status=1
M054 KILLED substitutions=1 status=1
M055 KILLED substitutions=1 status=1
M056 KILLED substitutions=1 status=1
M057 KILLED substitutions=1 status=1
M058 KILLED substitutions=1 status=1
M059 KILLED substitutions=1 status=1
M060 KILLED substitutions=1 status=1
M061 KILLED substitutions=1 status=1
M062 SURVIVED substitutions=1 status=0
M063 SURVIVED substitutions=1 status=0
M064 SURVIVED substitutions=1 status=0
M065 SURVIVED substitutions=1 status=0
M066 KILLED substitutions=1 status=1
M067 SURVIVED substitutions=1 status=0
M068 SURVIVED substitutions=1 status=0
M069 SURVIVED substitutions=1 status=0
M070 SURVIVED substitutions=1 status=0
M071 SURVIVED substitutions=1 status=0
M072 SURVIVED substitutions=1 status=0
CONTROL SURVIVED substitutions=1 status=0
MUTATION SUMMARY killed=47 survived=25 void=0 denominator=72
Evidence: Regenerated mutation evidence
# Sovereign ledger redundancy mutation evidence

Audience: maintainer verification.

Verified on 2026-08-15 against the R4 sovereign-ledger redundancy implementation.
The owned denominator is 72 enforcing clauses, ten more than round 3's 62 attempted mutants because R4 added exact enumeration and read denominators, portable BSD/GNU identity selection, containment rejection, and full cross-member inode-set enforcement.
Every run copied the implementation under one scoped `mktemp -d`; the live ledger, `data/`, and `state/` were never inputs or targets.

`` `sh
tests/fm-sovereign-ledger-redundancy.mutation.sh --write-evidence sovereign-ledger-redundancy-mutation.candidate.md
`` `

Evidence publication rejects existing destinations, so the candidate is reviewed against this record before replacement rather than overwriting it in place.

Observed summary: `killed=47 survived=25 void=0 denominator=72`; the intentional no-op control recorded `substitutions=1 status=0 outcome=SURVIVED`.

| Mutant | Stable exact clause anchor | Substitutions | Outcome | Status | Unenforced claim when survived |
| --- | --- | ---: | --- | ---: | --- |
| `M001` | `strict-mode.errexit` | 1 | SURVIVED | 0 | Shell failures are not allowed to fall through. |
| `M002` | `manifest.denominator` | 1 | KILLED | 1 | - |
| `M003` | `canonical.directory-exists` | 1 | KILLED | 1 | - |
| `M004` | `canonical.physical-path` | 1 | SURVIVED | 0 | Containment uses physical directory paths. |
| `M005` | `manifest.fourth-member` | 1 | KILLED | 1 | - |
| `M006` | `enumeration.minimum-depth` | 1 | KILLED | 1 | - |
| `M007` | `enumeration.maximum-depth` | 1 | SURVIVED | 0 | Only top-level members are counted. |
| `M008` | `enumeration.record-per-entry` | 1 | KILLED | 1 | - |
| `M009` | `enumeration.line-denominator` | 1 | KILLED | 1 | - |
| `M010` | `enumeration.exact-count` | 1 | KILLED | 1 | - |
| `M011` | `enumeration.known-manifest` | 1 | KILLED | 1 | - |
| `M012` | `require.known-manifest` | 1 | KILLED | 1 | - |
| `M013` | `require.member-exists` | 1 | KILLED | 1 | - |
| `M014` | `require.no-symlink` | 1 | KILLED | 1 | - |
| `M015` | `require.regular-file` | 1 | KILLED | 124 | - |
| `M016` | `require.read-denominator` | 1 | KILLED | 1 | - |
| `M017` | `require.enumerates-directory` | 1 | SURVIVED | 0 | Bundle validation independently enumerates the directory. |
| `M018` | `require.exact-read-count` | 1 | SURVIVED | 0 | The manifest read count must equal four. |
| `M019` | `require.executable-verifier` | 1 | KILLED | 1 | - |
| `M020` | `layout.enumerate-primary` | 1 | SURVIVED | 0 | Layout comparison enumerates the primary independently. |
| `M021` | `layout.capture-primary` | 1 | KILLED | 1 | - |
| `M022` | `layout.enumerate-replica` | 1 | SURVIVED | 0 | Layout comparison enumerates the replica independently. |
| `M023` | `layout.capture-replica` | 1 | KILLED | 1 | - |
| `M024` | `layout.equal-manifests` | 1 | SURVIVED | 0 | Primary and replica layouts must match. |
| `M025` | `bytes.enumerate-primary` | 1 | SURVIVED | 0 | Byte comparison owns a fresh manifest enumeration. |
| `M026` | `bytes.skip-denominator` | 1 | KILLED | 1 | - |
| `M027` | `bytes.skip-only-selected` | 1 | KILLED | 1 | - |
| `M028` | `bytes.quiet-compare` | 1 | SURVIVED | 0 | Member comparison uses cmp status as its verdict. |
| `M029` | `bytes.reject-difference` | 1 | KILLED | 1 | - |
| `M030` | `bytes.read-denominator` | 1 | KILLED | 1 | - |
| `M031` | `bytes.exact-read-count` | 1 | SURVIVED | 0 | The byte-read count must equal its owned denominator. |
| `M032` | `identity.follow-selection` | 1 | KILLED | 1 | - |
| `M033` | `identity.bsd-follow` | 1 | KILLED | 1 | - |
| `M034` | `identity.gnu-follow` | 1 | SURVIVED | 0 | GNU followed identity uses stat -L. |
| `M035` | `identity.bsd-lstat` | 1 | KILLED | 1 | - |
| `M036` | `identity.gnu-lstat` | 1 | SURVIVED | 0 | GNU non-followed identity uses lstat semantics. |
| `M037` | `identity.numeric-shape` | 1 | KILLED | 1 | - |
| `M038` | `identity.lstat-wrapper` | 1 | KILLED | 1 | - |
| `M039` | `identity.stat-wrapper` | 1 | KILLED | 1 | - |
| `M040` | `identity.enumerate-primary` | 1 | SURVIVED | 0 | Identity verification enumerates the owned manifest independently. |
| `M041` | `identity.primary-lstat-read` | 1 | KILLED | 1 | - |
| `M042` | `identity.replica-lstat-read` | 1 | KILLED | 1 | - |
| `M043` | `identity.primary-stat-read` | 1 | KILLED | 1 | - |
| `M044` | `identity.replica-stat-read` | 1 | KILLED | 1 | - |
| `M045` | `identity.read-denominator` | 1 | KILLED | 1 | - |
| `M046` | `identity.exact-read-count` | 1 | SURVIVED | 0 | Identity reads must cover exactly four members. |
| `M047` | `identity.replica-cross-product` | 1 | KILLED | 1 | - |
| `M048` | `identity.primary-cross-product` | 1 | KILLED | 1 | - |
| `M049` | `identity.lstat-disjoint` | 1 | KILLED | 1 | - |
| `M050` | `identity.lstat-member-attribution` | 1 | KILLED | 1 | - |
| `M051` | `identity.stat-disjoint` | 1 | KILLED | 1 | - |
| `M052` | `identity.stat-member-attribution` | 1 | KILLED | 1 | - |
| `M053` | `containment.distinct-paths` | 1 | KILLED | 1 | - |
| `M054` | `containment.primary-outside-replica` | 1 | KILLED | 1 | - |
| `M055` | `containment.replica-outside-primary` | 1 | KILLED | 1 | - |
| `M056` | `verifier.public-verify-command` | 1 | KILLED | 1 | - |
| `M057` | `prefix.primary-line-count` | 1 | KILLED | 1 | - |
| `M058` | `prefix.replica-line-count` | 1 | KILLED | 1 | - |
| `M059` | `prefix.nonempty-replica` | 1 | KILLED | 1 | - |
| `M060` | `prefix.strictly-shorter` | 1 | KILLED | 1 | - |
| `M061` | `prefix.leading-bytes` | 1 | KILLED | 1 | - |
| `M062` | `preflight.require-primary` | 1 | SURVIVED | 0 | Pair preflight validates the primary bundle. |
| `M063` | `preflight.layout` | 1 | SURVIVED | 0 | Pair preflight compares the exact layouts. |
| `M064` | `preflight.nonledger-bytes` | 1 | SURVIVED | 0 | Pair preflight compares replica-controlled code before execution. |
| `M065` | `exact.all-bytes` | 1 | SURVIVED | 0 | Exact verification compares all four members. |
| `M066` | `exact.disjoint-identities` | 1 | KILLED | 1 | - |
| `M067` | `exact.verify-primary` | 1 | SURVIVED | 0 | Exact verification validates the primary ledger. |
| `M068` | `exact.verify-replica` | 1 | SURVIVED | 0 | Exact verification validates replica ledger data. |
| `M069` | `copy.enumerate-primary` | 1 | SURVIVED | 0 | Copying starts from an independently enumerated manifest. |
| `M070` | `copy.preserve-mode` | 1 | SURVIVED | 0 | Bundle copying preserves required executable modes. |
| `M071` | `copy.private-umask` | 1 | SURVIVED | 0 | Bundle staging uses a private creation mask. |
| `M072` | `copy.exact-read-count` | 1 | SURVIVED | 0 | Copying must cover exactly four members. |

## Evidence publication containment

The complete fixture results, cell outcomes, alternate defenders, per-mutant totals, and aggregate denominator below come from one canonical generated stream.

# Evidence publication containment matrix

Generated by `tests/fm-sovereign-ledger-evidence-publish.mutation.sh --markdown`.

`` `text
  PASS  absolute destination aimed at fake data is refused
  PASS  dot-dot traversal aimed at fake data is refused
  PASS  symlinked leaf aimed at fake data is refused
  PASS  symlinked parent directory is refused
  PASS  symlinked parent aimed at fake state is refused
  PASS  destination resolving outside after parent resolution is refused
  PASS  existing hard-linked destination is refused without mutation
  PASS  unresolved destination parent is refused
  PASS  existing destination is refused without mutation
  PASS  symlinked evidence scope is refused
  PASS  unresolved evidence scope is refused
  PASS  unsafe destination leaf is refused
  PASS  legitimate in-scope publication remains exact
CONTAINMENT FIXTURES passed=13 failed=0
PUBLISH CELL mutant=P001 anchor=resolved-path-validator-call substitutions=1 fixture=absolute outcome=KILLED defender=-
PUBLISH CELL mutant=P001 anchor=resolved-path-validator-call substitutions=1 fixture=traversal outcome=KILLED defender=-
PUBLISH CELL mutant=P001 anchor=resolved-path-validator-call substitutions=1 fixture=symlink-leaf-data outcome=SURVIVED defender=exclusive-c

... [15139 bytes truncated] ...

rent-resolution substitutions=1 fixture=unresolved-parent outcome=SURVIVED defender=natural-unresolved-parent
PUBLISH CELL mutant=P009 anchor=canonical-parent-resolution substitutions=1 fixture=existing outcome=SURVIVED defender=existing-leaf-guard
PUBLISH CELL mutant=P009 anchor=canonical-parent-resolution substitutions=1 fixture=scope-symlink outcome=SURVIVED defender=scope-symlink-guard
PUBLISH CELL mutant=P009 anchor=canonical-parent-resolution substitutions=1 fixture=scope-unresolved outcome=SURVIVED defender=canonical-scope-resolution
PUBLISH CELL mutant=P009 anchor=canonical-parent-resolution substitutions=1 fixture=unsafe-leaf outcome=SURVIVED defender=unsafe-leaf-guard
PUBLISH MUTANT P009 anchor=canonical-parent-resolution substitutions=1 killed=0 survived=12 void=0
PUBLISH CELL mutant=P010 anchor=final-structural-containment substitutions=1 fixture=absolute outcome=SURVIVED defender=absolute-path-guard
PUBLISH CELL mutant=P010 anchor=final-structural-containment substitutions=1 fixture=traversal outcome=SURVIVED defender=unsafe-component-guard
PUBLISH CELL mutant=P010 anchor=final-structural-containment substitutions=1 fixture=symlink-leaf-data outcome=SURVIVED defender=symlink-leaf-guard
PUBLISH CELL mutant=P010 anchor=final-structural-containment substitutions=1 fixture=symlink-parent outcome=SURVIVED defender=symlink-component-precheck
PUBLISH CELL mutant=P010 anchor=final-structural-containment substitutions=1 fixture=symlink-parent-state outcome=SURVIVED defender=symlink-component-precheck
PUBLISH CELL mutant=P010 anchor=final-structural-containment substitutions=1 fixture=resolved-outside outcome=SURVIVED defender=canonical-structural-containment
PUBLISH CELL mutant=P010 anchor=final-structural-containment substitutions=1 fixture=hard-link outcome=SURVIVED defender=existing-leaf-guard
PUBLISH CELL mutant=P010 anchor=final-structural-containment substitutions=1 fixture=unresolved-parent outcome=SURVIVED defender=canonical-parent-resolution
PUBLISH CELL mutant=P010 anchor=final-structural-containment substitutions=1 fixture=existing outcome=SURVIVED defender=existing-leaf-guard
PUBLISH CELL mutant=P010 anchor=final-structural-containment substitutions=1 fixture=scope-symlink outcome=SURVIVED defender=scope-symlink-guard
PUBLISH CELL mutant=P010 anchor=final-structural-containment substitutions=1 fixture=scope-unresolved outcome=SURVIVED defender=canonical-scope-resolution
PUBLISH CELL mutant=P010 anchor=final-structural-containment substitutions=1 fixture=unsafe-leaf outcome=SURVIVED defender=unsafe-leaf-guard
PUBLISH MUTANT P010 anchor=final-structural-containment substitutions=1 killed=0 survived=12 void=0
PUBLISH CELL mutant=P011 anchor=scope-symlink-guard substitutions=1 fixture=absolute outcome=SURVIVED defender=absolute-path-guard
PUBLISH CELL mutant=P011 anchor=scope-symlink-guard substitutions=1 fixture=traversal outcome=SURVIVED defender=unsafe-component-guard
PUBLISH CELL mutant=P011 anchor=scope-symlink-guard substitutions=1 fixture=symlink-leaf-data outcome=SURVIVED defender=symlink-leaf-guard
PUBLISH CELL mutant=P011 anchor=scope-symlink-guard substitutions=1 fixture=symlink-parent outcome=SURVIVED defender=symlink-component-precheck
PUBLISH CELL mutant=P011 anchor=scope-symlink-guard substitutions=1 fixture=symlink-parent-state outcome=SURVIVED defender=symlink-component-precheck
PUBLISH CELL mutant=P011 anchor=scope-symlink-guard substitutions=1 fixture=resolved-outside outcome=SURVIVED defender=canonical-structural-containment
PUBLISH CELL mutant=P011 anchor=scope-symlink-guard substitutions=1 fixture=hard-link outcome=SURVIVED defender=existing-leaf-guard
PUBLISH CELL mutant=P011 anchor=scope-symlink-guard substitutions=1 fixture=unresolved-parent outcome=SURVIVED defender=canonical-parent-resolution
PUBLISH CELL mutant=P011 anchor=scope-symlink-guard substitutions=1 fixture=existing outcome=SURVIVED defender=existing-leaf-guard
PUBLISH CELL mutant=P011 anchor=scope-symlink-guard substitutions=1 fixture=scope-symlink outcome=SURVIVED defender=canonical-scope-resolution
PUBLISH CELL mutant=P011 anchor=scope-symlink-guard substitutions=1 fixture=scope-unresolved outcome=SURVIVED defender=canonical-scope-resolution
PUBLISH CELL mutant=P011 anchor=scope-symlink-guard substitutions=1 fixture=unsafe-leaf outcome=SURVIVED defender=unsafe-leaf-guard
PUBLISH MUTANT P011 anchor=scope-symlink-guard substitutions=1 killed=0 survived=12 void=0
PUBLISH CELL mutant=P012 anchor=canonical-scope-resolution substitutions=1 fixture=absolute outcome=SURVIVED defender=absolute-path-guard
PUBLISH CELL mutant=P012 anchor=canonical-scope-resolution substitutions=1 fixture=traversal outcome=SURVIVED defender=unsafe-component-guard
PUBLISH CELL mutant=P012 anchor=canonical-scope-resolution substitutions=1 fixture=symlink-leaf-data outcome=SURVIVED defender=symlink-leaf-guard
PUBLISH CELL mutant=P012 anchor=canonical-scope-resolution substitutions=1 fixture=symlink-parent outcome=SURVIVED defender=symlink-component-precheck
PUBLISH CELL mutant=P012 anchor=canonical-scope-resolution substitutions=1 fixture=symlink-parent-state outcome=SURVIVED defender=symlink-component-precheck
PUBLISH CELL mutant=P012 anchor=canonical-scope-resolution substitutions=1 fixture=resolved-outside outcome=SURVIVED defender=canonical-structural-containment
PUBLISH CELL mutant=P012 anchor=canonical-scope-resolution substitutions=1 fixture=hard-link outcome=SURVIVED defender=existing-leaf-guard
PUBLISH CELL mutant=P012 anchor=canonical-scope-resolution substitutions=1 fixture=unresolved-parent outcome=SURVIVED defender=canonical-parent-resolution
PUBLISH CELL mutant=P012 anchor=canonical-scope-resolution substitutions=1 fixture=existing outcome=SURVIVED defender=existing-leaf-guard
PUBLISH CELL mutant=P012 anchor=canonical-scope-resolution substitutions=1 fixture=scope-symlink outcome=SURVIVED defender=scope-symlink-guard
PUBLISH CELL mutant=P012 anchor=canonical-scope-resolution substitutions=1 fixture=scope-unresolved outcome=SURVIVED defender=canonical-parent-resolution
PUBLISH CELL mutant=P012 anchor=canonical-scope-resolution substitutions=1 fixture=unsafe-leaf outcome=SURVIVED defender=unsafe-leaf-guard
PUBLISH MUTANT P012 anchor=canonical-scope-resolution substitutions=1 killed=0 survived=12 void=0
PUBLISH CELL mutant=P013 anchor=unsafe-leaf-guard substitutions=1 fixture=absolute outcome=SURVIVED defender=absolute-path-guard
PUBLISH CELL mutant=P013 anchor=unsafe-leaf-guard substitutions=1 fixture=traversal outcome=SURVIVED defender=unsafe-component-guard
PUBLISH CELL mutant=P013 anchor=unsafe-leaf-guard substitutions=1 fixture=symlink-leaf-data outcome=SURVIVED defender=symlink-leaf-guard
PUBLISH CELL mutant=P013 anchor=unsafe-leaf-guard substitutions=1 fixture=symlink-parent outcome=SURVIVED defender=symlink-component-precheck
PUBLISH CELL mutant=P013 anchor=unsafe-leaf-guard substitutions=1 fixture=symlink-parent-state outcome=SURVIVED defender=symlink-component-precheck
PUBLISH CELL mutant=P013 anchor=unsafe-leaf-guard substitutions=1 fixture=resolved-outside outcome=SURVIVED defender=canonical-structural-containment
PUBLISH CELL mutant=P013 anchor=unsafe-leaf-guard substitutions=1 fixture=hard-link outcome=SURVIVED defender=existing-leaf-guard
PUBLISH CELL mutant=P013 anchor=unsafe-leaf-guard substitutions=1 fixture=unresolved-parent outcome=SURVIVED defender=canonical-parent-resolution
PUBLISH CELL mutant=P013 anchor=unsafe-leaf-guard substitutions=1 fixture=existing outcome=SURVIVED defender=existing-leaf-guard
PUBLISH CELL mutant=P013 anchor=unsafe-leaf-guard substitutions=1 fixture=scope-symlink outcome=SURVIVED defender=scope-symlink-guard
PUBLISH CELL mutant=P013 anchor=unsafe-leaf-guard substitutions=1 fixture=scope-unresolved outcome=SURVIVED defender=canonical-scope-resolution
PUBLISH CELL mutant=P013 anchor=unsafe-leaf-guard substitutions=1 fixture=unsafe-leaf outcome=SURVIVED defender=existing-leaf-guard
PUBLISH MUTANT P013 anchor=unsafe-leaf-guard substitutions=1 killed=0 survived=12 void=0
PUBLISH MATRIX SUMMARY mechanisms=14 written_boundaries=13 natural_boundaries=1 mutants=13 fixtures=12 cells=156 killed=7 survived=149 void=0
`` `

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 2 issues found → auto-fixed (7) ✅
  • 🚨 bin/fm-sovereign-ledger-redundancy.sh:251 - snapshot checks that the destination is absent, but mv later treats a concurrently created destination directory as a container: the verified stage is nested inside it and the command still prints SNAPSHOT PASS without verifying the intended replica path. Publish with a fail-if-present/no-replace primitive and verify the published path before reporting success.
  • 🚨 tests/fm-sovereign-ledger-redundancy.test.sh:363 - The required criterion—“a mutation denominator with one recorded substitution per mutant”—is absent: the changed test ends with its ordinary pass/fail summary and the branch contains no mutation manifest or harness recording mutant anchors, exactly-one substitution counts, denominator, and outcomes. Add reproducible fixture-scoped mutation evidence or obtain explicit approval to omit this required proof.

🔧 Fix: Add fixture-scoped sovereign ledger mutation evidence
1 error still open:

  • 🚨 tests/fm-sovereign-ledger-redundancy.mutation.sh:199 - The required boundary says “Do not touch the live ledger, data, or state beyond the designated status line,” but --write-evidence accepts any lexically root-prefixed path. It therefore permits writes to $ROOT/data/... or $ROOT/state/...; .., an existing symlink target, or a symlinked parent can also escape the worktree entirely. Restrict output to the designated evidence file (or a physically canonicalized approved evidence directory), reject protected paths and symlinks, then publish only after validating the run.

🔧 Fix: Contain sovereign ledger evidence publication
1 error still open:

  • 🚨 tests/fm-sovereign-ledger-evidence-publish.mutation.sh:44 - The required repair says “neutralize the containment enforcement and prove every escape input turns red,” but P001 removes only the validator call and classifies the entire fixture suite from one aggregate exit status. O_EXCL still refuses symlinked/existing/hard-linked leaves, and unresolved parents still fail naturally, so those inputs remain green while other failures kill P001; the reported enforcing_lines=1 and denominator do not prove each listed shape. Mutate each independent enforcing boundary and record each fixture’s mutant outcome separately.

🔧 Fix: Record containment mutant fixture matrix
1 error still open:

  • 🚨 tests/fm-sovereign-ledger-redundancy.mutation.sh:157 - The required matrix includes “a destination resolving outside only after canonical resolution,” but resolved-outside is an ordinary symlinked-parent fixture. It is rejected at the explicit -L check before canonical_evidence_dir and the structural containment comparison run, so it duplicates symlink-parent and leaves the canonical-resolution boundary unproven and absent from the mutant denominator. Add separate mutants for the symlink-parent precheck and canonical containment clause, with a scoped fake-outside fixture that reaches the latter.

🔧 Fix: Prove canonical evidence containment boundary
1 error still open:

  • 🚨 tests/fm-sovereign-ledger-redundancy.mutation.sh:200 - The required “every independent enforcing boundary” denominator is still incomplete: the matrix reports five mechanisms, but its own survivor attributions identify unmutated written boundaries for absolute paths, unsafe components, symlinked leaves, existing leaves, and canonical parent resolution; line 88 also supplies a second final structural-containment refusal. Consequently P004/resolved-outside is falsely marked KILLED only because the fixture requires the parent-check message—after P004, the final containment check still refuses safely with different text. Enumerate each written defender as its own exact-one-substitution mutant and classify refusals by behavior while recording the actual alternate boundary.

🔧 Fix: Complete containment boundary mutation denominator
1 error still open:

  • 🚨 docs/verification/sovereign-ledger-redundancy-mutation.md:120 - The accepted repair requires recording “every mutant-by-fixture pair” and updating “every cell plus per-shape defender claims,” but the committed evidence and generated candidate retain only 13 per-mutant totals and the aggregate summary. The 156 PUBLISH CELL records—including each fixture outcome and alternate defender—exist only on transient stdout, so the committed proof cannot substantiate the claimed matrix. Persist the generated cell rows into the evidence artifact (preferably from the same results stream used for totals).

🔧 Fix: Persist complete containment mutation matrix
1 error still open:

  • 🚨 tests/fm-sovereign-ledger-redundancy.mutation.sh:467 - The accepted repair requires the matrix and totals to come from the same stream and the committed candidate to be regenerated, but cat "$PUBLICATION_MATRIX" appends the full matrix to every newly generated candidate while the committed sovereign-ledger-redundancy-mutation.md ends with only a link. Thus a clean-checkout regeneration cannot reproduce the committed evidence, and its earlier per-mutant summary remains separately hardcoded. Make the committed artifact match the generator exactly, or generate and publish both canonical linked artifacts together from one results stream.

🔧 Fix: Make mutation evidence exactly reproducible
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • pwd -P; git status --short; git rev-parse HEAD; focused base-to-target diff inspection
  • tests/fm-sovereign-ledger-redundancy.test.sh
  • tests/fm-sovereign-ledger-redundancy.mutation.sh --emit-evidence
  • Compared regenerated mutation evidence byte-for-byte with docs/verification/sovereign-ledger-redundancy-mutation.md using cmp -s
  • Verified 72 mutant rows, 72 one-substitution rows, zero void rows, and a clean worktree
✅ **Document** - passed

✅ No issues found.

🔧 **Lint** - 1 issue found → auto-fixed ✅
  • ⚠️ linter found issues (exit code 1)

🔧 Fix: Suppress intentional literal-anchor ShellCheck warnings
✅ Re-checked - no issues remain.

✅ **Push** - passed

✅ No issues found.

@coreldh

coreldh commented Sep 25, 2026

Copy link
Copy Markdown
Contributor Author

Closing: this came from local workflow policy in my fork rather than a general Firstmate fix, and it is superseded by #2473, which I'm closing as well.

@coreldh coreldh closed this Sep 25, 2026
neel-mishra pushed a commit to neel-mishra/firstmate that referenced this pull request Sep 28, 2026
A Herdr pane created with --no-focus is not rendered until its tab has
been the active tab of a focused workspace once. Until then the launch
still executes but `pane read` stays empty and Herdr's screen-based agent
state never observes the worker, so a spawned worker's terminal reads
blank and `agent prompt` stalls (Herdr issue kunchenguid#2449).

The spawn now activates the task endpoint immediately before delivering
the launch command and restores the exact prior focused workspace and
tab right after the Enter, matching the tmux backend where
`capture-pane` reads the live pane terminal directly.

Adds bin/backends/herdr.sh task-rendering activation primitives, the
fm-spawn wiring, an updated focus note in docs/herdr-backend.md, and a
portable fake-CLI regression in tests/fm-backend-herdr.test.sh.
neel-mishra added a commit to neel-mishra/firstmate that referenced this pull request Sep 28, 2026
#3)

* fix: grant Claude workers access to Firstmate task channels (kunchenguid#5884)

* fix(bin): grant Claude workers their task-channel dirs via --add-dir

Since Claude Code 2.1.257, a file-tool read (Read/Glob/Grep, and an
Edit's mandatory prior read) of a path outside the working directories
parks --permission-mode auto panes on a one-time interactive question,
and a "Block" answer lands permissions.blockReadsOutsideWorkingDirectories
in user settings, refusing the same reads even under bypass. Firstmate
launches Claude with no --add-dir, so a secondmate's parent-home steering
inbox and a ship or scout worker's launch record, steering inbox, brief
dir, and code-root .agents/skills were all outside: workers wedged on
the question the first time they read a steer.

Every Claude launch, spawn and relaunch, in both permission modes, now
grants exactly the task's channel directories: state/<id>.inbox for a
secondmate (in the parent home), or state/operational-inbox,
state/<id>.inbox, data/<id>, and the code root's .agents/skills for a
ship or scout. Paths resolve to real paths and lazily created channel
dirs are made before launch so the grant never names a not-yet-existing
directory; the whole state/ is deliberately never granted.

The grant keeps the bypass-mode launch argv changed on purpose: it also
protects bypass workers against a machine-recorded Block answer.

* no-mistakes(document): Consolidate Claude launch guidance in configuration reference

* no-mistakes(document): Clarify Claude permission documentation reference

* fix(bin): prevent idle recovery loops without stranding wakes (kunchenguid#4819)

* fix(supervision): prevent idle recovery loops without stranding wakes

* no-mistakes(review): Remove unused wake-append rollback helper

* fix: reduce remote worker and polling helper process churn (kunchenguid#5889)

* fix(bin): stop the remote-job worker busy-polling an idle queue

The serving loop slept 50ms between passes and re-ran state preparation
(chmod on every queue directory), the heartbeat publish, and the stale sweep
on every pass. It now blocks on a worker.wake FIFO that staging,
cancellation, and lane exit nudge, keeps a short fast-poll window after
activity, refreshes the heartbeat at most once a second, and runs the sweep
(which re-applies the queue directories' 0700 modes) at startup and then on
a bounded interval. Lane-owned records are no longer re-read every pass.

Measured with a fork/execve-interposing counter on a --serve worker in a
disposable HOME and queue, bash 3.2, 20-second windows (the counter slows
the old loop to about 5 passes a second, so real-host rates were higher):
  idle worker             146 forks/s, 61 execs/s -> 4.8 forks/s, 3.1 execs/s
  one running long job    232 forks/s, 100 execs/s -> 15 forks/s, 11 execs/s
Stage-to-result latency for a no-op job, idle and back to back, stayed at
about 0.8-1.2s in both versions (dominated by job execution, not pickup).

* perf(bin): drop per-cycle forks from watcher, drain, and lock helpers

The watcher, drain, inactive-reconcile scan, and branch-outcome reads forked
small external commands on every cycle where bash can do the same work.

- fm-wake-lib.sh gains fm_dirname_to, fm_basename_to, and
  fm_epoch_seconds_to, exact stand-ins for $(dirname --), $(basename --),
  and $(date +%s); the clock uses printf %(%s)T on bash 4.2+ and still forks
  date exactly once on stock macOS bash 3.2.
- fm_lock_abs_path, fm_wake_signal_seen_path, fm_path_age, the watcher's
  age_of and wedge timer, and the recovery-marker line count use them or
  plain reads instead of dirname/basename/tr/date/wc.
- window_to_task reads a meta file once instead of two
  grep | tail -1 | cut -d= -f2- pipelines per file per call.
- fm-classify-lib.sh reads uname -s once at source time instead of in every
  status stat helper.
- Libraries sourced every cycle derive their own directory without forking
  dirname, including the backend adapter siblings a subshell re-sources on
  each probe.

tests/fm-fork-free-helpers.test.sh pins each replacement against the command
it replaces on edge-case inputs, under every available bash and both the C
and a UTF-8 locale; CI's stock macOS bash lane runs it under /bin/bash 3.2.

Measured with a fork/execve-interposing counter in a disposable home, one
tmux crew task, FM_POLL=1 (forks and execs per watcher cycle, per run
otherwise):
  watcher cycle      bash 5.3  299/138 -> 199/66   bash 3.2  341/146 -> 224/80
  drain              bash 5.3  492/238 -> 430/200  bash 3.2  567/250 -> 491/212
  inactive scan      bash 5.3   27/14  ->  17/4    bash 3.2   37/14  ->  17/4
  branch-outcome     bash 5.3   40/21  ->  35/16   bash 3.2   48/24  ->  38/19

* test: note the interpreter-expanded version probe for shellcheck

* no-mistakes(review): Fix worker idle bounds and fork-free contributions snapshot

* no-mistakes(review): Coalesce buffered worker wake nudges into one wake

* no-mistakes(review): Coalesce wake nudges via pending marker so publishers never block

* no-mistakes(review): Claim wake nudges atomically via noclobber pending marker

* no-mistakes(review): Release abandoned wake claims only after a 30-second bound

* no-mistakes(review): Drop worker wake FIFO; load path helpers side-effect free

* no-mistakes(document): Document remote worker polling and preemption cadence

* no-mistakes(ci): Fixed both failing CI shards: isolated remote and teardown test fixtures now include fm-path-lib.sh, which fm-wake-lib.sh requires. The three affected tests, fm-lint.sh, and git diff --check pass locally

* fix: keep remote reply listeners and watcher cycles running (kunchenguid#5941)

* fix: keep a successor watcher and remote-reply listeners across the gaps that dropped them

A main-only supervision pass-through exited without leaving a watcher, and each remote-reply poll released its claim until the next cycle, so short-lived listeners stayed down.

* no-mistakes(document): Clarify listener and supervision continuity documentation

* no-mistakes(ci): Fixed the three Greptile findings: failed ingestion leaves one durable capture, failed reads exit instead of relistening, and the disposable-checkout guard rejects state paths outside the marked lab. Added behavioral tests; the remote-reply and watcher-lock suites, shell syntax checks, and git diff checks passed

* no-mistakes(ci): Fixed a race in the wake-queue interruption test: it now waits for the drain to own the lock and enter handling before signaling it. The wake-queue suite, shell syntax check, and diff check pass

* fix: make attended cutover outcome re-presentation check-first (kunchenguid#5925)

* fix: date replayed branch outcomes and ask main to check current state first

A captain outcome main never acknowledged is presented again, which after a
harness or posture switch, or the first drain after the upgrade whose earlier
presenter never advanced the read cursor, can be days after its situation
settled. The replay read as fresh news, so a PR since merged looked ready.

bin/fm-branch-outcome.sh now adds a "recordedAgo" age (minutes, hours, then
days) to present and unprocessed rows, one owner of that wording for both
presenters. The drain's BRANCH OUTCOMES captain lines and the Pi branch's
processing request name that age and ask main to check the task's current
state first; an outcome already settled needs only the acknowledgement, with
nothing relayed to the captain. Nothing is adopted as processed, so a fresh
home's first outcome is still presented until acknowledged.

* no-mistakes(review): Absent processed marker reads 0; never adopt read cursor

* no-mistakes(review): Require recordedAgo in Pi requests; report undated rows to main

* no-mistakes(review): Keep recordedAgo on captain rows only in present output

* no-mistakes(document): Correct cutover documentation and retire stale migration guidance

* fix: keep settled branch outcomes out of main's reply to the captain

A live Pi primary that took over a host-drain home received the carried-over
outcomes dated and check-first, but its processing reply still told the
captain about an outcome whose decision had since been answered. The request
also claimed every outcome was already shown as an anchor entry in this
transcript, which is false for an outcome carried over from before a restart
or a switch of primary.

The Pi processing request now says each outcome was recorded earlier and may
already have been seen or handled, and that a settled outcome gets no
captain-facing mention at all in the reply or any recap, not even that it is
settled. The drain's BRANCH OUTCOMES header and the supervision docs state the
same rule, and the tests check both delivered texts.

* fix: scope main's outcome reply to what is still open

Telling main what not to say about a settled outcome was not enough: in two
live Pi trials the processing reply still told the captain that an answered
decision was settled. Main now sorts the outcomes by current state first, and
its reply to the captain covers only the still-open ones, written as if the
settled ones had never been listed. With that framing three live Pi trials
kept the settled outcome out of the reply and relayed the open one each time.

The drain's BRANCH OUTCOMES header and the supervision docs use the same
framing, and the tests check both delivered texts.

* no-mistakes(review): Clarify that main acknowledges every presented captain outcome

* no-mistakes(document): Clarify outcome cursor ownership across Pi and host

* no-mistakes(ci): Fixed Pi replay by batching unprocessed captain outcomes oldest-first and acknowledging only through each batch. Verified a marker-less backlog over 1 MiB replays through all batches. A real-drain regression confirms an older keyed decision remains under OPEN DECISIONS after a newer branch row is acknowledged; the check-first instruction now names those decisions. Relevant targeted tests and branch-supervision tests passed; the full host suite timed out

* no-mistakes(ci): Fixed the host drain’s check-first wording in bin/fm-wake-drain.sh; the CI fixture now passes. The full host suite passed the affected fixtures but timed out later. Syntax and diff checks passed

* no-mistakes(ci): Fixed ci-1: abbreviated Pi outcome summaries now stay within 1,024 characters and include a row-specific full-outcome lookup command. The delivered instruction requires reading the full outcome before acting, relaying, or acknowledging it. The new extension-driver regression failed before the fix and passes now; the Pi and supervision-host suites pass

* no-mistakes(ci): Corrected the batching sentence in docs/pi-supervision-branch.md. The cancelled CI check needs no code fix; its clean rerun passed. The Pi branch extension suite and git diff check passed

* fix: restore primary rewakes after attended main-only closes (kunchenguid#5961)

* fix: wake an idle Claude primary for attended main-only hand-backs

An attended main-only pass-through confirmed a handling handoff for the
successor it leaves running, which flipped the recovery marker to handling.
The Claude Stop hook only rewakes main while that marker reads downtime, so
the close reached no one and an idle primary slept with wakes queued.

The pass-through now leaves the marker at downtime, and a close that turns
main-only at its turn hands the consumed handoff back to downtime before it
reaches main. Regression tests drive the real Stop hook around the real host
on both paths and for the successor's own later close, and a new opt-in live
guard proves it against an idle interactive Claude primary with a pre-fix
negative control.

* no-mistakes(review): Assert live lab Stop-hook registration via parsed settings JSON

* no-mistakes(document): Correct supervision hand-back documentation

* no-mistakes(ci): Fixed the failed downtime-write path so the Stop hook notifies main instead of silently dropping the close. Corrected the live guard’s tracked-hook check and added the requested at-turn main-only scenario. The new regression failed before the fix and passed after it; the host suite, syntax checks, and diff check pass. The credentialed live guard was not run in this CI phase because it writes outside the worktree

* no-mistakes(ci): Fixed the Stop hook’s retry ordering: a crashed host gets its bounded retry before a non-crash hand-back failure is reported. The Stop-hook suite passes, including the crash regression. The host suite passed the failed-marker-write regression but timed out before completing; syntax and diff checks pass

* Clarify live Claude login for opted-in tests (kunchenguid#5975)

* fix(bin): ensure resumed worker launches enter their recorded worktree (kunchenguid#5916)

* Fix worker launches to enter recorded worktrees

* no-mistakes(review): placeholder

* no-mistakes(document): Update agent-control.md worktree-refusal note to match new universal cd+assert

* no-mistakes(review): Add regression tests for Orca spawn/relaunch worktree carve-outs

* Fix PR relaunch and prelaunch cwd verification

* no-mistakes(document): Fix docs/agent-control.md: worktree cwd check is pre-launch, not post-launch

* fix: slim worktree launch change onto upstream main

* fix(bin): make a Herdr task pane render before its launch is delivered

A Herdr pane created with --no-focus is not rendered until its tab has
been the active tab of a focused workspace once. Until then the launch
still executes but `pane read` stays empty and Herdr's screen-based agent
state never observes the worker, so a spawned worker's terminal reads
blank and `agent prompt` stalls (Herdr issue kunchenguid#2449).

The spawn now activates the task endpoint immediately before delivering
the launch command and restores the exact prior focused workspace and
tab right after the Enter, matching the tmux backend where
`capture-pane` reads the live pane terminal directly.

Adds bin/backends/herdr.sh task-rendering activation primitives, the
fm-spawn wiring, an updated focus note in docs/herdr-backend.md, and a
portable fake-CLI regression in tests/fm-backend-herdr.test.sh.

---------

Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>
Co-authored-by: Joseph Kim <jokim1@gmail.com>
Co-authored-by: Mehul Bhagwani <mehulbhagwani@gmail.com>
Co-authored-by: Neel Mishra <neelmishra@Neels-Mac-Mini.local>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant