music: staging-slot cache with atomic symlink swap for full reindex

Foundation for a safe from-scratch reindex. The live cache path is now a SYMLINK
to a slot dir; readers + incremental writes follow it.

- ensureCacheSetup() (run at sidecar boot): makes `cache` a symlink to a slot,
  migrating an existing real cache dir once (a fast rename, not a copy).
- runBuild(outRoot, prev): the build now writes to a given root and returns the
  manifest without touching live serving state.
- reindexNow(): incremental, in-place live build (manual + localized updates).
- reindexFull(): builds a complete index into a FRESH slot without touching the
  live one, then activateSlot() swaps the symlink atomically (rename-over) ONLY
  on success — a failed rebuild leaves the live index untouched; old slots pruned.

walk() takes the output root. Verified end-to-end: fresh setup, full build +
swap + prune, incremental-through-symlink, and the one-time real-dir migration
(content preserved). Restart officer-music to apply (triggers the migration).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 16:29:41 +00:00
co-authored by Claude Opus 4.8
parent b84f0b0b18
commit b1d1c91968
2 changed files with 102 additions and 20 deletions
+4
View File
@@ -5,6 +5,7 @@ import { createSidecarConnector } from '../connect';
import { streamAudioFile } from './stream-audio';
import {
reindexNow,
ensureCacheSetup,
getIndexStatus,
getManifest,
albumVersion,
@@ -70,6 +71,9 @@ function getFreePort(): number {
const port = getFreePort();
// Ensure the cache is a symlink-to-slot before serving/building, so full reindexes can swap atomically.
await ensureCacheSetup();
const server = Bun.serve({
port,
hostname: '127.0.0.1',