Skip to content

Merge dev → main (automated) - #2086

Merged
SorraTheOrc merged 61 commits into
mainfrom
release/dev-to-main-20260831235847
Sep 1, 2026
Merged

SorraTheOrc merged 61 commits into
mainfrom
release/dev-to-main-20260831235847

Conversation

@SorraTheOrc

Copy link
Copy Markdown
Member

Automated release created by ship skill.\n\nIncludes CHANGELOG.md with work-item summaries from this release.

pi added 30 commits August 25, 2026 20:58
Pair Status+Stage, Priority+Type, Created+Updated and Audit+AuditedAt
onto single rows in the list-mode metadata panel and the detail-view
table via pairMetaRows, a display-only post-processing step over
buildMetaRows output. Incomplete pairs keep their present half as a
single row so no field information is lost; noIcons mode is handled.
Saves 4 vertical rows for fully-populated items.
Operator-sanctioned test-first commit (F1 mirror, plan comment 2026-08-26T01:45):
C1 locks the per-slot cheap-switch gate semantics with unit tests BEFORE the
production gate change, so the implementation child (WL-0MT9FL9JO002JEAZ)
lands against a green, behavior-pinning suite.

EXPECTED-RED at this commit by design — 4 new-behavior pins fail against the
current all-slots-free gate:
  - per-slot 1-free-2-busy fires cheap (AC1)
  - spare-capacity >=1 free fires (AC3)
  - malformed per-slot payloads -> fetchProxyStatus resolves null -> no POST (AC6 x2)
All 35 pre-existing mode-switch tests and the rest of the full suite pass
(5505 passed at C1's verification run; tsc + build clean).

Lifecycle: C1 stage -> in_review / status completed; C2 turns the pins green.
… list

Parent audit (WL-0MT9F67Y3008S0PR belt-and-suspenders) flagged child AC6 as
partial: the malformed-per-slot fixture list did not explicitly cover a
non-finite slot_id, even though parseLlamaStatus rejects NaN/Infinity via
its Number.isFinite/Number.isInteger guard (covered in downtime-worker
tests). Add NaN and Infinity fixtures so the worker-level AC6 pin exercises
the exact contract the parent AC text names. 39/39 mode-switch tests pass;
tsc clean.
…L-0MTA217DZ003H5K8)

The previous fix added isWlModifyingCommand and extended the refresh
condition, but !!wl close/delete/update/search commands bypass
dispatchChordCommand entirely (only !!wl reviewed was routed), so
executeResolvedCommand returned 'callback' and the result === 'dispatched'
refresh check never fired — the selection list stayed stale.

Route these commands through resolveAndRouteCommand in dispatchChordCommand
(mirroring !!wl reviewed) so they return 'dispatched' and the caller's
isWlModifyingCommand check triggers an immediate list refresh after
close/delete/update/search shortcuts (x-c, x-d, u-p-*, u-s, u-t, s).

Update tests that locked in the old 'callback'/fall-through behavior.
…cStalenessMs setting

- NEW: packages/herdr/src/db-change.ts — DB-change signal module with
  resolveCacheDir(), readStateCounter(), and DbChangeTracker
- NEW: packages/herdr/src/db-change.test.ts — 14 tests covering cache-dir
  resolution, state-counter reads, and tracker fail-open behavior
- EXTEND: packages/herdr/src/settings.test.ts — 7 tests for maxSyncStalenessMs
  (default 60s, clamp [1000, 300000], parse/clamp behavior)
- EXTEND: packages/herdr/src/settings.ts — added maxSyncStalenessMs setting
  with MIN_MAX_SYNC_STALENESS_MS and MAX_MAX_SYNC_STALENESS_MS constants
- EXTEND: packages/herdr/src/worklist.test.ts — 10 DB-change gate decision
  tests (refresh skip/run, sync skip/run, first-cycle, fail-open, manual)

All 1,790 herdr tests pass.
Centralize pane-title construction in src/pane-title.ts with three
builders: buildManuallyTriggeredPaneTitle, buildShellPaneTitle, and
buildDowntimePaneTitle. Every spawn path now produces a descriptive,
bounded title instead of generic defaults.

Changes:
- src/pane-title.ts: shared module with builders, truncation, helpers
- index.ts: onCommand handler gains paneTitle arg; agent and shell spawn
  paths compute pane names from title/id
- worklist.ts: onCommand signature extended with optional paneTitle;
  callers compute paneTitle for agent/! commands from selected item
- downtime-worker.ts: spawnAgentPane forwards itemTitle/itemId;
  buildDowntimePaneArgs uses shared builder
- run-in-pane.sh: --pane-name option parsing (overrides env/default)
- 18 existing tests updated for new 5-arg onCommand calls
- 21 new pane-title tests covering all builders and length bounds
Operator directive (context: WL-0MTEZ4XZJ006Y9U7 downtime dispatch):
the marker is an environment-specific toggle, not repo state; stop
tracking it. Authorized by operator.
pi added 28 commits August 30, 2026 06:37
…o-retry

Add patterns for 'all providers exhausted' and 'no provider available'
to DEFAULT_SERVER_ERROR_PATTERNS so the Worklog recovery module
classifies these as ErrorCategory.SERVER_ERROR instead of UNKNOWN.

This enables the existing retry loop (with exponential backoff) to
automatically recover when proxy providers are temporarily exhausted,
instead of halting the session and requiring user intervention.

WL-0MT656GDS0052FTK
- close.ts: Standardize JSON output to {results, closed, failed} shape
  instead of {success, results} — AC4 compliance
- Add batch close integration tests (AC5): multi-id close with
  --force/--reason, partial failure isolation, JSON output structure,
  and human-readable output
- Fix 3 test files that expected old {success, results} JSON format
  from close command to match the new {results, closed, failed} shape
…irst)

Operator-sanctioned test-first commit (per 1f05039 precedent, plan comment):
locks the zombie-leader lease lifecycle with unit tests BEFORE the production
fixes (F4 refreshLease self-heal, F5 per-tick re-derivation + cooldown-exit
renewal) land, so the implementation children land against a behavior-pinning
suite.

EXPECTED-RED at this commit by design — 2 new-behavior pins fail against the
current cached-leaderState code:
  - AC2: lease must be VALID after the cooldown-exit tick (Hole A —
    refreshLease no-ops on owned-but-expired lease; isLeader() requires
    remaining > 10s). FAILS: isLeaseValid(leaseAfterExit) === false.
  - AC3: no zombie dispatch after resume when another instance holds a valid
    lease (Hole B — leaderState cached at :2505, never re-derived while true;
    A dispatches inst-b's item and spawnAgentPane IS called). FAILS: spawn
    1 call + isLeader true.
Regression pins GREEN on current code:
  - AC1: lease content advanced during the no-candidate cooldown pause
    (sibling fix e842208 already runs the leader block before the cooldown
    gate).
  - AC4: worker created while a valid foreign lease is held stays silent
    (leaderState=false at startup; non-leader short-circuit).
All 371 pre-existing downtime-worker tests and the rest of the herdr suite
pass (1831 passed at verification; tsc clean; build excludes test files).
Lifecycle: F2 stage -> in_review; F4/F5 turn AC2 + AC3 green.
…irst)

Operator-sanctioned test-first commit (per 1f05039 precedent): locks the
refreshLease() self-heal semantics (F4) with unit tests BEFORE the production
change, so F4 lands against a behavior-pinning suite.

EXPECTED-RED at this commit by design — 1 new-behavior pin fails against the
current `if (!this.isLeader()) return` guard (Hole A):
  - self-heal AC1: an OWNED-but-EXPIRED lease must be renewed (fresh
    acquiredAt) instead of no-oped. FAILS: acquiredAt unchanged after
    refreshLease() (isLeader() requires > MIN_VALID_LEASE_TTL_MS remaining).
Regression guards GREEN on current code:
  - AC2: never renews a valid foreign lease (content unchanged).
  - AC2b: never steals from an EXPIRED foreign lease (election path owns that).
  - AC3: unreadable/corrupt lease file → no-op, file untouched (fail-safe).
All 36 pre-existing leader-election tests and the rest of the herdr suite pass
(1832 passed at verification; tsc clean). F4 turns AC1 green.

Lifecycle: F3 stage -> in_review; F4 then F5 turn the F2+F3 RED pins green.
…first)

Operator-sanctioned test-first commit (per 1f05039 precedent): adds the
dual-instance fake-timer regression the parent AC wanted BEFORE the production
fixes (F4 self-heal refreshLease, F5 per-tick re-derivation + cooldown-exit
renewal), so the implementations land against a reproducing suite.

EXPECTED-RED at this commit by design — 1 new-behavior pin fails against the
current cached-leaderState code:
  - F1: A elects itself leader, enters the no-candidate cooldown, its tick
    loop freezes mid-pause and its lease expires (TTL 300s < cooldown 600s);
    B detects the stale lease, wins a fresh election and keeps re-offering
    WL-B1 with fresh lastUpdated. On A's resume, the CACHED leaderState=true
    makes A zombie-dispatch B's entry (spawnAgentPane called 1x) and A's
    isLeader getter stays true — both assertions FAIL. (B is busy/0 free
    slots, so the only possible dispatch of the entry is A's zombie.)
The 4 pre-existing integration tests and the rest of the herdr suite pass
(1829 passed at verification; tsc clean). F5 + F4 turn the pins green.

Scenario mechanics: A cooldown 600s > lease TTL (expires mid-pause); B's
check-in interval 300s re-offers within the leader's 300s prune window, so
B's entry survives pruneStaleEntries until A's dispatch tick (age 61s).

Lifecycle: F1 stage -> in_review; F4/F5 turn F2/F3/F1 RED pins green.
…irst)

Operator-sanctioned test-first commit (per 1f05039 precedent): locks the
refreshLease() self-heal semantics (F4) with unit tests BEFORE the production
change, so F4 lands against a behavior-pinning suite.

EXPECTED-RED at this commit by design — 1 new-behavior pin fails against the
current `if (!this.isLeader()) return` guard (Hole A):
  - self-heal AC1: an OWNED-but-EXPIRED lease must be renewed (fresh
    acquiredAt) instead of no-oped. FAILS: acquiredAt unchanged after
    refreshLease() (isLeader() requires > MIN_VALID_LEASE_TTL_MS remaining).
Regression guards GREEN on current code:
  - AC2: never renews a valid foreign lease (content unchanged).
  - AC2b: never steals from an EXPIRED foreign lease (election path owns that).
  - AC3: unreadable/corrupt lease file → no-op, file untouched (fail-safe).
All 36 pre-existing leader-election tests and the rest of the herdr suite pass
(1832 passed at verification; tsc clean). F4 turns AC1 green.

Lifecycle: F3 stage -> in_review; F4 then F5 turn the F2+F3 RED pins green.
F4 turns the F3 RED pin green: refreshLease() now renews by OWNERSHIP
instead of validity. The old `if (!this.isLeader()) return` guard required
> MIN_VALID_LEASE_TTL_MS remaining, so an owned-but-EXPIRED lease made
refreshLease() no-op forever — the zombie could never renew (Hole A).

New behavior:
- lease.leaderId === instanceId (even if expired) → rewrite with fresh
  acquiredAt, same TTL (self-heal).
- missing/unreadable lease (readLeaseFile → null) → no-op (fail-safe).
- FOREIGN lease → no-op (takeover stays on the election path; never renews
  or stops another instance's lease — takeover-race safe).
isLeader() / isLeaseValid unchanged for claim/election paths.

Verification (F3 suite + full herdr): 37/37 leader-election tests pass
incl. F3 AC1 self-heal (previously RED), 1833/1833 full herdr suite,
tsc + build clean. F5 (per-tick leader re-derivation + cooldown-exit
renewal) turns the F2/F1 worker-level RED pins green.
…irst)

Operator-sanctioned test-first commit (per 1f05039 precedent, plan comment):
locks the zombie-leader lease lifecycle with unit tests BEFORE the production
fixes (F4 refreshLease self-heal, F5 per-tick re-derivation + cooldown-exit
renewal) land, so the implementation children land against a behavior-pinning
suite.

EXPECTED-RED at this commit by design — 2 new-behavior pins fail against the
current cached-leaderState code:
  - AC2: lease must be VALID after the cooldown-exit tick (Hole A —
    refreshLease no-ops on owned-but-expired lease; isLeader() requires
    remaining > 10s). FAILS: isLeaseValid(leaseAfterExit) === false.
  - AC3: no zombie dispatch after resume when another instance holds a valid
    lease (Hole B — leaderState cached at :2505, never re-derived while true;
    A dispatches inst-b's item and spawnAgentPane IS called). FAILS: spawn
    1 call + isLeader true.
Regression pins GREEN on current code:
  - AC1: lease content advanced during the no-candidate cooldown pause
    (sibling fix e842208 already runs the leader block before the cooldown
    gate).
  - AC4: worker created while a valid foreign lease is held stays silent
    (leaderState=false at startup; non-leader short-circuit).
All 371 pre-existing downtime-worker tests and the rest of the herdr suite
pass (1831 passed at verification; tsc clean; build excludes test files).
Lifecycle: F2 stage -> in_review; F4/F5 turn AC2 + AC3 green.
…irst)

Operator-sanctioned test-first commit (per 1f05039 precedent): locks the
refreshLease() self-heal semantics (F4) with unit tests BEFORE the production
change, so F4 lands against a behavior-pinning suite.

EXPECTED-RED at this commit by design — 1 new-behavior pin fails against the
current `if (!this.isLeader()) return` guard (Hole A):
  - self-heal AC1: an OWNED-but-EXPIRED lease must be renewed (fresh
    acquiredAt) instead of no-oped. FAILS: acquiredAt unchanged after
    refreshLease() (isLeader() requires > MIN_VALID_LEASE_TTL_MS remaining).
Regression guards GREEN on current code:
  - AC2: never renews a valid foreign lease (content unchanged).
  - AC2b: never steals from an EXPIRED foreign lease (election path owns that).
  - AC3: unreadable/corrupt lease file → no-op, file untouched (fail-safe).
All 36 pre-existing leader-election tests and the rest of the herdr suite pass
(1832 passed at verification; tsc clean). F4 turns AC1 green.

Lifecycle: F3 stage -> in_review; F4 then F5 turn the F2+F3 RED pins green.
…first)

Operator-sanctioned test-first commit (per 1f05039 precedent): adds the
dual-instance fake-timer regression the parent AC wanted BEFORE the production
fixes (F4 self-heal refreshLease, F5 per-tick re-derivation + cooldown-exit
renewal), so the implementations land against a reproducing suite.

EXPECTED-RED at this commit by design — 1 new-behavior pin fails against the
current cached-leaderState code:
  - F1: A elects itself leader, enters the no-candidate cooldown, its tick
    loop freezes mid-pause and its lease expires (TTL 300s < cooldown 600s);
    B detects the stale lease, wins a fresh election and keeps re-offering
    WL-B1 with fresh lastUpdated. On A's resume, the CACHED leaderState=true
    makes A zombie-dispatch B's entry (spawnAgentPane called 1x) and A's
    isLeader getter stays true — both assertions FAIL. (B is busy/0 free
    slots, so the only possible dispatch of the entry is A's zombie.)
The 4 pre-existing integration tests and the rest of the herdr suite pass
(1829 passed at verification; tsc clean). F5 + F4 turn the pins green.

Scenario mechanics: A cooldown 600s > lease TTL (expires mid-pause); B's
check-in interval 300s re-offers within the leader's 300s prune window, so
B's entry survives pruneStaleEntries until A's dispatch tick (age 61s).

Lifecycle: F1 stage -> in_review; F4/F5 turn F2/F3/F1 RED pins green.
F4 turns the F3 RED pin green: refreshLease() now renews by OWNERSHIP
instead of validity. The old `if (!this.isLeader()) return` guard required
> MIN_VALID_LEASE_TTL_MS remaining, so an owned-but-EXPIRED lease made
refreshLease() no-op forever — the zombie could never renew (Hole A).

New behavior:
- lease.leaderId === instanceId (even if expired) → rewrite with fresh
  acquiredAt, same TTL (self-heal).
- missing/unreadable lease (readLeaseFile → null) → no-op (fail-safe).
- FOREIGN lease → no-op (takeover stays on the election path; never renews
  or stops another instance's lease — takeover-race safe).
isLeader() / isLeaseValid unchanged for claim/election paths.

Verification (F3 suite + full herdr): 37/37 leader-election tests pass
incl. F3 AC1 self-heal (previously RED), 1833/1833 full herdr suite,
tsc + build clean. F5 (per-tick leader re-derivation + cooldown-exit
renewal) turns the F2/F1 worker-level RED pins green.
F5 turns the F1/F2 RED pins green (Hole B: leaderState was derived once at
worker creation and only updated inside the takeover branch — a lease that
expired mid-pause left the worker dispatching with cached leadership as a
zombie). Changes in the leader block of tick():

- AC2 self-heal FIRST: leaderManager.refreshLease() now runs UNCONDITIONALLY
  each tick. With F4's ownership-based refresh this renews an
  owned-but-EXPIRED lease; foreign/missing leases stay untouched (no-op).
  Must precede re-derivation: detectStaleLeader() returns false for our own
  lease, so an expired owned lease would otherwise fall through BOTH the
  leader branch (isLeader false) AND the takeover branch (hasLease true +
  detectStaleLeader false), starving the self-heal entirely.
- AC1 per-tick re-derivation: leaderState = leaderManager.isLeader() every
  tick (one cheap lease-file read). If another instance won the lease during
  a pause, the re-derived state yields at the non-leader short-circuit —
  never fights the new leader, never double-dispatches (AC4). Non-leader
  with valid foreign lease stays silent (AC5); missing/unreadable lease ⇒
  not leader (AC6, preserved).
- AC3 cooldown-exit: the resume tick's leader block runs BEFORE the cooldown
  gate, so renewal + re-derivation always precede any poll/dispatch decision
  on the resume path — even if the tick loop stalled mid-pause (documented
  at the gate).

Verification: full herdr suite 1838/1838 (F2 cooldown AC1-AC4 + F3 self-heal
+ F1 zombie integration all GREEN; 378/378 in the two worker suites), tsc +
build clean. Parent AC suite now fully satisfied by F2+F3+F1 (tests) with
F4+F5 (impl).

Lifecycle: F5 stage -> in_review; sibling branch recomposition for parent.
Update docs/dev/downtime-dispatcher.md to the corrected lease contract
implemented by F4+F5:
- Lease section: refresh happens during the no-candidate cooldown pause
  too; refreshLease() is ownership-based (leaderId === instanceId), so an
  owned-but-EXPIRED lease is still renewed (self-heal — never silently
  lost).
- New bullet: leadership re-derived every tick (cheap lease-file read) —
  a lease expired mid-pause routes the worker out of zombie dispatch; a
  foreign winner during the pause yields silently (no fight/double dispatch).
- Cooldown section: self-heal keeps the renewal path alive through the
  pause; cooldown-exit renewal + re-derivation run BEFORE the cooldown gate
  on the resume tick, so no dispatch decision can use stale leadership even
  if the tick loop stalled mid-pause.
docs/ARCHITECTURE.md verified — no cooldown/lease content (only Git file
lock) → untouched per AC2. Inline code comments were synced to the new
contract in F4 (leader-election.ts refreshLease) and F5 (downtime-worker.ts
leader block + cooldown gate) already.

Verification: full herdr suite 1838/1838 on the complete F2→F3→F1→F4→F5
chain + this branch; tsc + build clean. Full-repo non-herdr failures
(plugin/sync/completion/validator/grouping/smoke/doctor) reproduce
identically in the F2 test-only worktree — pre-existing base-commit
environments, unrelated to F1-F6.

Lifecycle: F6 stage -> in_review; all six children terminal.
… (WL-0MTGGGP2000642JS)

When the local store holds more records than the remote tip (e.g. after a
foreign-author full snapshot replaced the remote with fewer records), the
watermark heuristic alone cannot detect this — records older than the
watermark are assumed 'already synced' even though the remote never received
them. The §5.4 zero-change fast path silently skips export+push, causing
the remote to diverge (missing ~102 of 124 local items).

Fix: compare local item/comment counts against the remote tip during sync.
When local > remote, emit a visible warning, force a full snapshot export,
and prevent the zero-change fast path from short-circuiting. The remote
converges to the local state on the next sync (self-healing, AC2).

Changes:
- Added count-based divergence detection in performSync()
- Moved syncMode computation after divergence detection (syncMode was
  previously computed before, causing delta mode even when full was forced)
- Added console.warn() for operator visibility (AC1)
- Added zero-change fast path exclusion when divergence is detected (AC3)
- Added regression test: sync-divergence-detection.test.ts
…al deletes

F1 (regression tests): 14 new tests covering:
  - same-timestamp sortIndex-only never bumps updatedAt (AC1)
  - metadata conflict bumps once then stabilises (AC2)
  - unattributed delete never overrides live open (AC3 same-ts & newer)
  - attributed delete propagates — newer & equal-timestamp (AC4)
  - both-sides-deleted converges; close-preserved stays quiescent

Adapted existing close-conflict test: bumped-at assertion replaced with
quiescent semantics — per F2 design, no-content-change merge must NOT bump
updatedAt (the unconditional bump was the sync-conflict-loop cause).

F2 (same-timestamp merge bump fix): only bump updatedAt when
mergedFields.length > 0 (non-sortIndex user-visible change) AND merged
result differs from local. Conflicts still reported; bump only on real change.

F3 (delete-side protection): in both mergeSameTimestampItems and
mergeDifferentTimestampItems, unattributed soft-deletes (empty deletedBy/
deleteReason) never override a live item's status. Only attributed deletes
or both-sides-deleted converge to deleted.

F4 (attribute real deletes): WorklogDatabase.delete() now accepts optional
deletedBy/deleteReason. CLI delete and REST API attribute via configured git
identity (getConfiguredUserEmail()). ConflictFieldDetail.chosenSource
extended with 'both' variant for both-sides-deleted.

Pre-existing test failures (5) in sync-delta-fallback/push/convergence:
verified as dev-HEAD baseline failures, not introduced by this change.
…so delta pushes aren't forced to full

The WL-0MTGGGP2000642JS count-based divergence detection fired on any
local > remote count mismatch, but that is the normal transient state
during a delta push (a freshly created local item not yet on remote).
Genuine divergence is only when the mismatch cannot be repaired by a
delta export — i.e. no dirty records exist to carry the missing data
(the zero-change fast path would silently skip). Gate item/comment
divergence checks on dirty.items === 0 / dirty.comments === 0.

Fixes 5 failing tests: sync-delta-push (AC1-AC4, cadence),
sync-delta-fallback (AC1/AC2/AC5, delta tip),
sync-integration-convergence (AC7).
@SorraTheOrc
SorraTheOrc merged commit 3188201 into main Sep 1, 2026
@SorraTheOrc
SorraTheOrc deleted the release/dev-to-main-20260831235847 branch September 1, 2026 00:00
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