Fleet changelogs · dev.ecs0.net
rdmsm4x-changelog-20260830-1224-issueroo-queue-cleanup-and-git-baseline

rdmsm4x-changelog-20260830-1224-issueroo-queue-cleanup-and-git-baseline

Host: rdmsm4x · Session: dev-73 (claude@rdmsm4x) Window: 2026-08-30 12:17:54 → 12:24:51 EDT Scope: rdmsm4x:~/dev/issues/ (canonical fleet ticket store), plus fixture HTML in ~/dev/sites/dev.ecs0.net/issues/ and ~/Desktop.

The canonical fleet ticket store was 91% QA fixture noise and had no version control despite CLAUDE.md rule 28 describing it as "Git/SQLite-backed". The store is now under git, the fixtures are archived (not deleted), and the open queue reads 9 real tickets instead of 256.


Why this was the lane

Rich asked this session to resume all work and coordinate with the other agents on the host so nothing collided. Six other Claude sessions were live on rdmsm4x. Scope checks were exchanged with dev-bf, dev-76, dev-e2, and dev-d5 before any write:

Session Owns
dev-bf rdmsm4x launchd delivery failure; fleet app rebuild/deploy; rdmbair15m5 consolidation
dev-e2 fleet CPU-storm guard (host_storm_guard.zsh, com.eastcoastscience.stormguard)
dev-76 ISSUE-20260830-25 only
cd4dc58c / 0da93685 apps/replicantDB / apps/logTTY
dev-73 (this) ~/dev/issues/ — uncontested, confirmed free by three peers

What changed

1. The store is under git for the first time — a rule-28 gap, not just hygiene

~/dev/issues/ had no .git since it was stood up 2026-08-29. Rule 28 has been asserting a Git-backed store that did not exist, so every ticket write since then had no history, no audit trail, and no recovery path. Four commits now exist:

2. 331 QA fixture tickets archived out of the live queue

The adversarial test suite writes into the canonical store — tests/*.py hardcode /Users/richh/dev/issues/index.sqlite and bin/ticket. Every run created real tickets in production. Result: 234 of 256 open tickets were fixtures, across 12 project slugs (test-concurrent 48, test 46, concurrent-test 36, mcp-test 32, mcp-cli 30, testproj 23, test-project 6, recovery 3, recovery-test 3, contention-test 3, adversarial-suite 3, tree-test 1), plus 97 more in resolved/.

Selection was inclusion-only on those 12 slugs — no title matching, no recency heuristic — so real tickets were untouchable by construction. Everything was moved, never deleted.

3. The fixtures were also PUBLISHED — swept in the same pass

bin/ticket:35-36 exports every ticket's HTML to two further destinations. Archiving only the Markdown would have left the fixtures live on a real site:

Destination Before Fixtures pulled After
sites/dev.ecs0.net/issues/ (published) 352 268 84
~/Desktop 365 284 89

4. Six empty-title artifacts archived under a separate named category

project: general, empty title, boilerplate-only body, created inside the test-run windows. Deliberately not swept under the fixture-project rule — archived to archive/empty-title-artifacts-20260830/ so the distinction stays auditable.

Two real defects found and filed — not auto-fixed

ISSUE-20260830-26 (high) — ticket resolve copies instead of moving. Four real tickets exist simultaneously in open/ and resolved/ with divergent bodies and contradictory status: INC-20260829-01, ISSUE-20260829-02, -03, -04. For INC-20260829-01 even the titles differ, so the open/ copy is a stale earlier version. Nine archived fixture IDs showed the same shape, so it is systematic. Not auto-fixed: choosing the authoritative copy per ticket needs the owning project's judgement.

ISSUE-20260830-27 — the exporter behaviour in §3. It fires on every real ticket too, so it re-accumulates immediately; filing one legitimate incident triggers all three side effects.

Verification

open/ 256 -> 16 files = 7 open + 5 in-progress epics + 4 twinned by ISSUE-20260830-26
ticket list --status open  ->  9 rows, all real (7 pre-existing + the 2 filed here)
ticket reindex             ->  "Reindexed 80 tickets ... regenerated dashboard"  (exit 0)
ISSUE-20260830-25 present in open/, absent from every delete list  (asserted before the move)
LogTTY/logtty matched case-insensitively — all 3 survive

A safety assertion fired mid-run and was worth having: a substring check flagged 20260830-25 in the delete list. It was FEAT-20260830-25 (project testproj, a genuine fixture) sharing a numeric suffix with ISSUE-20260830-25. Ticket IDs are only unique per type+date+sequence — a substring match on the numeric part is unsafe. The real incident was never in the list.

How to undo

cd ~/dev/issues && git revert 69c1389      # restores all 331 fixture tickets + both HTML sets
cd ~/dev/issues && git reset --hard dedb9dd # or return the whole store to as-found
ticket reindex                              # rebuild index.sqlite either way

Pre-git filesystem snapshot of the index: ~/dev/_backups/issues-index-20260830-1223.sqlite.

Outstanding — owner actions

  1. ISSUE-20260830-26 needs a per-ticket decision on which copy of the four is authoritative. Agent judgement is not the right tool; the bodies differ in substance.
  2. ISSUE-20260830-27: drop DESKTOP_DIR from bin/ticket (or make it opt-in). Until then ~/Desktop re-accumulates a file per ticket.
  3. The store has no remote. Git gives history but not off-host backup.
  4. ~/dev/issues/ is still not in any parent repo — it sits inside ~/dev, which is not itself a git repo, so nothing nests. Confirmed safe.

Handoffs

Not touched

~/Library/LaunchAgents, ~/dev/apps/{Tyrell,LogTTY,replicantDB,XEntropy,updateRoo,AINetNode}, ~/dev/_migration/fleet-ingest, ~/dev/issues/tests/, ~/dev/issues/bin/. No secrets read, written, or referenced. Nothing pushed to any remote.


Addendum — 12:28 → 12:33 EDT: both filed defects fixed and verified

ISSUE-20260830-26 and ISSUE-20260830-27 are RESOLVED. A peer edited bin/ticket during the session (backup bin/ticket.bak-20260830, 12:28). I verified the change before committing it (80cd202) rather than trusting it:

Residual gap found by probe — ISSUE-20260830-33 (new, open)

The glob cleanup is unreachable when the index's file_path is stale. cmd_resolve reads file_path from SQLite, then if not old_path.exists(): sys.exit(1) — which fires before the cleanup, in exactly the drift case the cleanup defends against. The ticket then cannot be resolved through the CLI at all until someone reindexes by hand.

This matters more now than it did yesterday: the store went under git today, and a git revert or checkout produces precisely that stale-index state. Anyone using the documented undo command should run ticket reindex immediately after, which the undo instructions above already specify.

Verification of dev-d5's test-isolation slice (checked, not taken on trust)

Zero hardcoded /Users/richh/dev/issues remain in executable test code — the 2 grep hits are docstring prose. tests/isolation.py present with IsolatedStore, .seed(), FIXTURE_PROJECTS, guard_not_canonical; the symlink hardening is real (candidate resolved at :63, root at :66). Canonical open/ carries 0 fixture-project tickets. dev-d5's read of the isolation mechanism is correct: bin/ticket:29 derives everything from expanduser("~/dev") with no --root and no env var, so a temp HOME is the only lever.

dev-d5's six modified files remain uncommitted by their own choice (they won't commit without Rich asking). Their enhancement suggestion — a first-class TICKET_ROOT env var so isolation is explicit rather than a side effect of expanduser() — is sound but unrequested, so it is recorded here rather than filed.

The queue is now doing its job

Within minutes of the cleanup, peers filed five real, high-value tickets that would previously have been invisible among 234 fixtures — ISSUE-20260830-28 (LogTTY bundle-identifier mismatch would create a new app identity on deploy), -30 (XEntropy/updateRoo/AINetNode are arm64-only, so the fleet deploy directive is a build task), -31 (replicantDB main tracks roodb-legacy; a plain push would inject 125 commits into the wrong project), -32, and -33.

Net: open queue 256 → 11, all real. Store at 7 commits.


CORRECTION — 12:35 EDT: the fixture pages were never actually served

§3 above overstated the impact and is corrected here. I reported that 268 fixture pages were "live on the published dev.ecs0.net site". They were not reachable.

infra/compose.yaml:37 mounts ../wiki as the nginx docroot: - ../wiki:/usr/share/nginx/html:ro. The exporter was writing to sites/dev.ecs0.net/issues/, which is not under that docroot. So the fixture pages were written to an unserved directory and never appeared on the site.

The defect was real — the exporter wrote 352 files to a path nothing serves, which is its own bug, and the fixture sweep was still correct housekeeping. But nothing was publicly exposed, and my earlier "live on a real site" framing was wrong. SITES_ISSUES_DIR is now corrected to sites/dev.ecs0.net/wiki/issues/ (86 pages, matching the real ticket count) and committed in 7c58362.

I did not catch this. A peer did, while fixing ISSUE-20260830-33.

ISSUE-20260830-33 — FIXED, verified in both directions

cmd_resolve now globs OPEN_DIR and RESOLVED_DIR for the ID when the indexed file_path is absent, adopts the match, and announces (index was stale; adopted <name>). Verified in an isolated store:

Regression cover: tests/test_stale_index_resolve.py (dev-d5, 3 scenarios including the anti-overshoot case), plus IsolatedStore.make_index_stale() as a reusable fixture for "simulate a git checkout leaving the index behind".

Store now at 9 commits. All four defects filed this session are fixed and verified.


12:36 EDT — the fix for -26 introduced a data-loss path: ISSUE-20260830-35 (open)

cmd_resolve's stale-copy cleanup unlinks every open/ copy of an ID unconditionally — no compare, no merge, no backup. Probe in an isolated store: a second open/ copy carrying a unique marker line survives in zero files after ticket resolve.

This is not theoretical, and the evidence is in the repair commit itself. ced12b1 records that for the real twins ISSUE-20260829-02/03/04 the open/ copies "carried parent links and clean comments added AFTER resolution", and its author deliberately merged both sides rather than deleting. That is precisely the content this code path now destroys automatically. The historical twins were repaired by hand; the code that replaced them still discards.

The code comment assumes the stray is a rename artifact ("title edited after creation"). True for renames — not true for the divergent-content case that actually occurred here.

Suggested fix: compare before unlinking; if different, move to archive/ or closed/ rather than delete. "So status can never be ambiguous" is satisfied by moving the file out of open/.

Also corrected in ced12b1, against my report

INC-20260829-01's open/ copy was a fixture (template placeholders, children pointing at archived fixture FEATs) colliding with a real ticket ID — not "a stale earlier version of a real ticket" as I characterised it in ISSUE-20260830-26. My reading of the twin mechanism was right; my reading of that one ticket's content was not.

Closing state — 12:36 EDT

What I got wrong this session, for the record

  1. Claimed 268 fixture pages were "live on the published site". They were written to an unserved directory — infra/compose.yaml:37 mounts ../wiki, not issues/. Caught by a peer, not by me. The export defect was real; the exposure was not.
  2. Characterised INC-20260829-01's open copy as a stale real ticket; it was a fixture collision.
  3. git add -A swept dev-d5's in-progress work into my commit. Disclosed; later commits scoped.