Skip to content
Merged
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
eaeca51
fix(retention): resolve the canonical workspace by git-dir ownership,…
nish3451 Aug 14, 2026
9aa94d4
docs(ops): record the lane-1 retention gate stale-twin fix report
nish3451 Aug 14, 2026
18918c8
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 14, 2026
cd2eb1f
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 14, 2026
2bbd874
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 14, 2026
80b9de7
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 14, 2026
976de78
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 14, 2026
f9d8c71
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 14, 2026
4421f62
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 14, 2026
bddf7c3
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 14, 2026
331e4db
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 14, 2026
22cb368
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 14, 2026
0366d1c
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 14, 2026
db7fe1c
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 14, 2026
c435e86
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 14, 2026
f2a272a
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 14, 2026
f7b0797
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 14, 2026
0cc4992
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 15, 2026
3a99ec2
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 15, 2026
e5ea81f
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 15, 2026
ced8981
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 15, 2026
3c6640f
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 15, 2026
633ab46
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 15, 2026
276f7dc
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 15, 2026
1e88156
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 15, 2026
7f97e2c
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 15, 2026
549b968
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 15, 2026
b6340d7
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 15, 2026
7022f42
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 15, 2026
ee24d04
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 15, 2026
e2fd7a6
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 15, 2026
0250c23
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 15, 2026
2e673a1
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 17, 2026
f838e02
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 17, 2026
c2fcaf5
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 17, 2026
c26acc6
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 17, 2026
8f1a12e
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 17, 2026
e824522
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 17, 2026
f0c5ce2
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 17, 2026
3f1a60b
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 18, 2026
374a979
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 19, 2026
f4206eb
Merge branch 'main' into fix/retention-gate-stale-twin-20260814
nish3451 Aug 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions .lane/reports/fix-retention-gate-stale-twin-20260814.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Lane 1 report — Friday retention automation still passes on a stale, empty workspace after the closed twin

Branch: `fix/retention-gate-stale-twin-20260814`
Item: `[unreviewed-by-opus] Friday retention automation still passes on a stale, empty workspace after the closed twin`
Date: 2026-08-14

## Root cause

The Friday gate resolves the canonical retention workspace via
`canonicalMainWorktree()` in `scripts/lib/retention-preflight.mjs`. The
pre-#105 code returned the worktree holding `refs/heads/main`; #105 changed it
to return the **first** `git worktree list` entry, which is only *conventionally*
the main worktree.

On this machine the live state is exactly the closed-twin shape:

- Main worktree: `/home/nish/workspaces/products/tinystudio-in` (owns `.git`),
routinely checked out onto feature branches (reflog shows daily
checkouts/resets), currently on `chore/pin-required-verifiers`.
- Twin: `/home/nish/workspaces/products/tinystudio-in-autonomous-service`
holds `refs/heads/main` at `24e516a` (2026-08-06), has **0 clients**,
**0 prospects with `service-day0.json`**, and no `service-decisions` /
`runs/service-engine` roots. It is stale (remote main is `a8d4da2`,
2026-08-14) and empty.
- The canonical workspace `/home/nish/workspaces/products/tinystudio-in` is
running the **pre-#105 gate code** (its `canonicalMainWorktree` resolves to
the worktree holding `main` = the twin, and it lacks the #105
canonical-workspace staleness check).

Because the gate resolved the canonical workspace to the twin, it inspected the
twin's empty state roots, saw an aligned (if stale) HEAD, and passed — while the
real client records live in the main worktree.

## Fix

Rewrite `canonicalMainWorktree()` to resolve the canonical workspace by
**git-dir ownership**, not list position and not branch holder:

- The main worktree is the entry whose `git rev-parse --git-dir` equals the
repository's common git dir (`.git` at the repo root).
- Every linked worktree resolves to `<common>/.git/worktrees/<name>`, so a
stale empty twin can never be substituted.
- Falls back to `repoRoot` on any git failure, preserving prior behavior.

`git worktree list --porcelain` in git 2.54 emits **no `gitdir` lines** (the
porcelain format only guarantees `worktree`/`HEAD`/`branch`/`detached`), so the
implementation reads the `worktree` path from porcelain and runs
`git rev-parse --git-dir` per entry — this also handles relative git-dir paths
correctly via `join(worktree, gitdir)`.

## Files changed

| File | Change |
| --- | --- |
| `scripts/lib/retention-preflight.mjs` | Rewrote `canonicalMainWorktree()` to resolve by git-dir ownership. |
| `scripts/test-retention-automation.mjs` | Added regression assertion in the closed-twin block: the canonical workspace must be the git-dir owner even when the twin holds `main` and heads the porcelain list. |

## Evidence

### Before (old gate code, live repo, canonical workspace)

```
status: fail ... repo: /home/nish/workspaces/products/tinystudio-in-autonomous-service
clientCount: 0, roots all zero
failures: remote freshness proof unavailable ... Automation does not point at the TinyStudio repo
```

The old code resolved the canonical workspace to the **twin** (empty, stale) and
would pass on a stale aligned twin or a no-client twin.

### After (fixed gate, live repo)

```
status: fail
repo (canonical): /home/nish/workspaces/products/tinystudio-in
clientCount: 3
failures:
- remote freshness proof unavailable: checkout is behind or diverged from remote main
- aggregate parity: service-decisions count 0 is below active client count 3
- aggregate parity: runs/service-engine count 0 is below active client count 3
exit=1
```

The gate now inspects the **main worktree** (3 real clients) and fails closed on
the stale canonical workspace, instead of hollow-passing on the empty twin.

### Tests

```
$ node scripts/test-retention-automation.mjs
Retention automation applicability checks passed.
EXIT=0

$ node --check scripts/lib/retention-preflight.mjs && node --check scripts/test-retention-automation.mjs
syntax ok
```

### Canonical resolution smoke test on the live repo

```
canonicalMainWorktree(/home/nish/workspaces/products/tinystudio-in) = /home/nish/workspaces/products/tinystudio-in
is main worktree (owns .git)? true
is stale empty twin? false
```

## Operational note (not in scope of this change)

The canonical workspace `/home/nish/workspaces/products/tinystudio-in` is
running pre-#105 gate code because it is on `chore/pin-required-verifiers`.
Merging this fix brings the corrected resolver to main; the canonical workspace
must be moved onto a main-based branch (or the automation updated) for the live
Friday gate to pick it up. The stale twin
`/home/nish/workspaces/products/tinystudio-in-autonomous-service` also still
holds `main` at an Aug-6 commit and is empty; closing/pruning that twin is the
other half of the "closed twin" remediation.

## PR

Branch `fix/retention-gate-stale-twin-20260814` pushed to origin, commit
`eaeca51`. PR opened against `main`.