Every job that changes the filesystem records effects (created, moved, trashed with where it went, replaced with where the previous bytes went, removed, attributes before and after) in a job_journal table through ctx.record. The trash reports locations: NSFileManager on macOS, the trash crate's listing elsewhere, and a .spacedrive/trash/<job>/ directory on volumes with none. Overwrites in copy, merge and extract stash the previous file there. files.undo previews the reverse as an FsPlan, refuses what changed since, and its job writes its own journal.
Also: files.rename on preflight with per-filesystem name rules and case probing, files.rename_batch with ordered rules and a job that never overwrites; remove_extras on files.merge for a mirror; files.organize and files.flatten as in-place renames; files.archive and files.extract for zip and tar.zst, planned from the archive's directory; files.set_attributes and files.link; files.trash_list and files.trash_empty; jobs.journal and a journal summary on jobs.list. The copy job now honors the skip policy and merges a folder onto an existing folder instead of overwriting inside it, and the copy preview plans a keep-both copy at its numbered name.
30 tests over the fixture. Docs, the plan's status table and PROJECT_STATUS updated.
Delete and duplicate in the explorer, rename and batch rename on preflight, an operation journal with undo and trash restore, mirror as a merge option, organize and flatten, archive and extract, attributes, links and a trash view. Seven decisions are open.
files.delete takes a Duplicates target: a scope with keep first (walk order) or a list of copies to keep with every other copy of their content going. The job reads each surplus copy and its keeper in full before removing, records the hashes it learns, and checkpoints the compare cursor so it resumes. Preflight validates the scope and keep list and previews keepers as skips and surplus copies as deletes.
Store gains duplicated_contents_beneath and copies_beneath. files.duplicates is registered as a core query and reports each copy's SdPath. CLI: sd file dedupe with --keep, --keep-under, --min-size, --dry-run.
Two opt-in methods per action, validate: and preview:, over the exact
input the action takes. Validation answers findings with stable codes and
the facts of the execution, runs again at dispatch and refuses on an
error. Preview answers an FsPlan read from the index, kept under a handle
so files.directory_listing can serve the directory as it would look after
the operation.
Copy, move, merge and delete answer both. Merge is new: a resumable job
that recurses into matching folders, reads both sides before skipping a
duplicate, applies a per-leaf conflict policy, leaves conflicts alone and
can consume its sources. Delete's validation counts, across every store,
which files are the last copy of their content.
The ValidationResult and ConfirmationRequest hook is gone, and so is the
CLI's local conflict detection. file copy, merge and delete render
preflight before the prompt and take --dry-run.
files.delete takes a comparison as its targets alongside a path list, so
the client sends five fields and the daemon derives the set from the
index as the job runs. The job drains the matcher the compare query
pages, checkpoints its cursor after each batch, and for `both` reads
whichever side lacks an integrity hash before removing a file, writing
what it learned back to the store. Pairs whose bytes differ, or whose
copy in B is gone by then, stay and are reported. The CLI gains
`sd file delete A --against B --show both` behind a y/N prompt, plus the
plain paths form.
Compare matched on content uuids, which re-derive from the integrity
hash once a copy is verified, so a copy verified on one side stopped
matching its unread twin. It now matches on the sampled hash every store
keys a content by, with integrity hashes deciding where both sides have
them; FsEntry carries both hashes for that.
Left and right only meant first and second argument, and nothing on
screen said which folder was which. "Same" meant different things per
mode, "changed" implied a direction compare can't know, and "location"
collided with the retired locations.
The output now names both folders as A and B up front, says how files
matched, then counts Only in A, Only in B, In both, and Different (by
path only). --by location becomes --by path and the --show values
follow, as do the API types, generated TS types, skill, and docs.
Single-side listings get Size and Modified columns under a title naming
the set, and --help explains each value.
Two opt-in rails on actions, both taking the action's exact input:
validate answers whether and how an operation runs and is re-run at
dispatch, preview answers what will exist afterward as an FsPlan from
the index, browsable through plan handles as an overlay on directory
listings. Folder merge is the first tenant. The plan catalogs both
rails across copy, move, merge, delete, batch rename, and dedupe, and
registers the teardown of the unreachable ValidationResult rail and
the CLI's local conflict logic.
paths.compare answers what differs between two folders from the source
stores at query time. By location it merges both sides in index order
into only-left, only-right, changed and same. By content it streams one
side and asks the other side's stores which content ids they hold, so a
renamed or moved copy still matches. Pages resume from a cursor and the
first page carries every set's count. Unhashed pairs fall back to size
and mtime and get counted, so a page says how much rests on that.
sd-cli file compare wraps it.
On the store side, files_beneath takes a Start, including one that
resumes a nested store at its first directory, and contents_beneath
answers the content lookup a batch at a time. Working out which stores a
path reaches moved into ops/paths/reach.rs, shared with search.media and
paths.context.
No catalog.db: the P7 row, the filesystem-intelligence plan, the previs
and the design docs now treat cross-source questions as store queries.
Photos listed only a folder's direct children and never saw the explorer's
search, so searching from a folder of folders showed no photos.
search.media pages images and videos straight from source SQLite. Files come
out in the order the directory_path and sibling indexes already keep, so a
page reads its own rows and resumes from a (source, directory, name) cursor.
Extension, kind and hidden narrow in SQL; name, size, date and tags are
judged per row the way the store backend of search.files judges them. A
folder reaches its innermost source and any sources nested beneath it.
The navigation focus now carries the running search, and Photos lists it in
place of the folder. Folders list everything beneath them. Pages grow the
grid as they land, and tile identities name their drive's cache, so a
library-wide search across drives renders.
The first client to connect starts automatic discovery: it tracks the
system volume, adds the home folder as a source and walks every attached
drive. For a library meant to hold only what you add, like one whose data
directory lives on the drive it catalogs, that indexes the Mac into it.
sd start --no-default-sources, and the same flag on restart and on
sd-daemon, now leaves those three out. The pass still restores snapshots,
heals coverage for sources already tracked and hashes them, so a source
you added keeps working across restarts. sd-server keeps the defaults.
Tags get their own section after Places, one row per tag with its color,
each opening that tag's files. The section only renders once the library
has tags, and it reads the same tags.search list tag mode does, so a tag
edit refreshes both.
The tag view now includes files under child tags, which files.by_tag
documents as the client default, and stops sending min_confidence, which
the input no longer has. The default tag color moves into one tagColor
helper instead of nine copies.
Space opens the photo under the cursor in the system Quick Look panel, so
HEIC, RAW, and video open at full size straight from the file. Space or Esc
closes it.
A controller hung off the window's next responder takes control of the
panel and serves it the one file on show. Keys the panel doesn't use go
back to the gpui view, so the arrows still move the cursor and the preview
follows, and the number keys still tag in tag mode. On drop the controller
leaves the chain and the panel looks again, so it never holds a controller
that's gone.
Photos now selects the way the explorer does: click, cmd-click to toggle,
shift-click for a run, cmd-A, arrows, Esc to clear. T opens the same tag
bar the explorer has, with the library's first ten tags on the number keys.
A key takes its tag off the selection when every selected photo carries it
and puts it on all of them otherwise. Tagged tiles get dots in the corner.
The listing now carries each cell's record and tags, and the plane follows
the file rows the daemon announces. Indexing announces rows without their
tags, so a row naming none is believed only for records this window is
changing and read back with files.by_id otherwise. Tag changes queue on a
per-folder channel from the moment the folder is listed.
Nothing built spacedrive-native, so the Apps menu looked beside the desktop
binary, found nothing, and logged the error to the dev terminal only.
just dev-desktop now builds sd-native first. A failed build only warns,
since sd-native is not a default member and can trail core.
A launch that fails now shows a dialog. Photos quits when its window
closes, and launching it while it runs brings its window forward through
NSRunningApplication, which the launcher's doc already claimed it did.
The byte plane already streams a peer's files over the loopback share with
ranges and a block cache, but the app never uses it. Open and QuickPreview
hand a remote file's path to the local opener and asset protocol, so they
fail, or open a local file that happens to share the path.
The plan fixes that first, then streams originals inside Spacedrive and
gives other apps a path one of two ways: streamed through an attached mount,
or copied to a read-only temporary file. Two on/off settings per device:
download before opening (off) and cache streamed files on disk (on).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The hot cache is raw BGRA and disposable, so nothing durable held a tile:
a wiped cache meant decoding every original again, and a replica could
only show a peer's thumbnails while that peer was awake to send them.
Every tile baked now also goes into its source's sidecars.db as WebP,
5 to 15 KB against around 300 KB in the cache, keyed by record uuid and
tile version. Content hashes would have deduped copies, but a content id
is re-derived when a full hash lands, which would orphan anything keyed
by it. The cache refills from a sidecar at the current version instead
of baking; a forced bake still reads the file. thumbs generate also
encodes a sidecar from the cache for any cached tile without one, so an
existing cache converts without touching the originals.
Each store counts its writes and stamps rows with the count, paired with
an id drawn at creation. Listings advertise both, and after each
replication pass a replica pulls anything newer with FetchSidecars, a
page at a time, into mounts-remote/<device>/<source>.sidecars.db. Remote
tile requests restore from that copy before asking the owner, so they
work with the owner offline. An owner answering FetchTiles sends a
current sidecar's WebP as stored, and tiles fetched on demand land in
the replica's copy too.
Titan converted 97,728 cached tiles into 1.05 GB of sidecars, down from
21 GB of cache. The Mac copied all of them and drew titan tiles it had
never fetched with titan's daemon stopped.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A replica lists a peer's files but can't read them, so it couldn't bake
their tiles, and thumbs.request refused any path on another device. Now a
request for a file in a replica answers at once with the version held here
and fetches the rest from the owner in the background.
The owner gets FetchTiles: a batch of paths, each with the version the
asker holds, answered one Tile frame per ask in the order they finish, so a
slow video bake holds back only its own answer. Each answer is Current, a
WebP tile, or Missing, and the owner bakes on demand what it lacks. Asks are
resolved by path like Stat and Read, and the owner's version is stored as
given since the asker can't derive it without the file.
Fetched tiles land in volumes/<owner volume>/thumbs.pvcache beside the local
ones and are announced as the usual thumbnail event, so no client changed.
A tile asked of an owner isn't asked again for ten minutes, since clients
re-request every cell on mount.
Directory listings and this share one replica lookup, peer::share_for,
which now prefers the deepest matching root.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A replica carries the index alone, so titan's 99,137 tiles are invisible from
the Mac. The cache is machine-local because the thumbnail request answers with
a path the client maps, which is also why the client needs no change: fill a
local cache file for the remote volume and the same answer works.
Adds the fetch design (where a remote tile lands, why the identities already
line up, why the owner's version has to travel with the tile, FetchTile and
WebP on the wire) as P6, and the durable sidecar tier as P7 behind L5. Records
the deployed rebuild and thumbnail results in the register.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The registry fix is on titan. Restoring the 1.76M-record replica from disk
at startup went from 56 minutes to 19.3 seconds, and the refetch that
follows a restart from 60 minutes to 8, nearly all of it now the download.
The restart still refetches: the restore publishes its share ten seconds
after the first sync pass asks for it. Speed alone will not close that.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rebuilding a replica's arena from a delivered database adds rows one at a
time through Arena::add_entry, and that method built a FileTypeRegistry per
call. Building one parses every built-in definition: 92 KB of TOML across
15 files and 349 types, about 3 ms. That was the whole cost of an insert.
Titan spent about 57 minutes rebuilding the Mac's 1.76M-entry home source
after each transfer. The database was never the problem: the full join scan
is 3 s, and one 2,000 row page is 7 ms against an optimal plan.
The built-in set never changes after startup, so one instance serves the
process. 20,000 inserts drop from 60 s to 143 ms, 3.00 ms to 7.2 µs each,
which puts that rebuild near 13 s in a debug build. The batch path built one
per batch and now shares the same instance.
core/tests/arena_insert_cost.rs fails if an insert starts rebuilding it.
Titan runs the store-revision build since 21:34. In the 19.5 hours before
the deploy it transferred nothing for the Mac's unchanged home source, where
the old file-metadata generation would have made about 18 full copies.
The NAS had FFmpeg all along, three static builds under the CCTV remux
tools, just not anywhere the daemon looks: its PATH is
/usr/local/bin:/usr/bin:/bin:/usr/games and no path variable was set.
bin/ffmpeg and bin/ffprobe are now n8.1.2 copies living beside the daemon,
so thumbnails do not depend on the cctv dataset, and the start command sets
SPACEDRIVE_FFMPEG_PATH and SPACEDRIVE_FFPROBE_PATH. A forced tile on a CCTV
clip spawned that ffmpeg with the 384px thumbnail filter and baked a frame.
Titan also has an RTX 3050 and a build with cuda, vaapi, qsv and vulkan,
unmeasured for single-frame seeks.
The restart exposed the next defect. A replica publishes its share only
after rebuilding its arena, about an hour for the home database, and the
first sync pass runs 30 seconds after the peer connects. It finds no share,
never reaches transfer_due, and refetches 736 MB it already had on disk with
the right generation in its manifest. Recorded as P1 in the replication
plan, where R3 already promised restarts would not reload.
A nested source's generation came from the size and mtime of data.db and
its WAL. SQLite deletes the WAL when a pool's last connection closes and
recreates it on the next read, and sqlx closes idle connections after 10
minutes, so the generation moved without any row changing. Titan re-copied
the Mac's unchanged home store in full four times overnight, each round a
736 MB transfer and a 57-minute arena rebuild.
Stores now keep a _revision row, a store id and a count of committed
changes, maintained by triggers on every tracked table. Updates that change
nothing, or only restamp scan_epoch or indexed_at, don't count. Listings and
database headers use the revision instead of file metadata.
The triggers put each single-row store write in a statement journal: the
300k-file scale test builds in 16-18s instead of 12-14s. temp_store=MEMORY
brings it to 9-10s but moves large sorts on the writable pool into memory,
so that's an open decision in the new incremental replication plan.
Verified live: after one switch-over copy, titan made no transfer for twelve
minutes, including across a Mac daemon restart.
Titan runs 4e58ddca5. Its library was backed up to
data/backup/pre-entries-drop before the FD4 migration converged it on the
14-table schema, with its nine sources, nine volumes, and pins intact. The
daemon it replaced had aborted on a failed allocation that morning, so the
reliability row carries what the restarted one looks like.
The device facts row moves to live on both machines and records two bugs
the deploy turned up: the periodic reconnect never dials a peer that was
down when the Mac started, and a rename over an existing file inside a
source fails to land in its store.
Paired devices answer a new DeviceFacts request with their hardware and
mounted volumes, under the ids their sources anchor to. The peer fetches
them on every sync beside the device summary and keeps them in the replica
manifest with its own receipt time, so an offline device still describes
itself as it last did.
volumes.list lists those volumes under their owner, mounted only while it's
connected, and devices.list fills in a paired device's hardware. The
overview folds replicas into their owner's volume bars the way local
sources fold into theirs. Another device's volumes show when they were last
seen and hide the local volume actions on desktop and mobile.
Volume classifications travel as names, so one a peer's build doesn't know
reads as unknown instead of failing the response.
AGENTS.md and CLAUDE.md flip their current direction from tearing down the
entries world to it being gone, and drop the ephemeral note T6.7 retired.
The locations page is deleted; the overview page that explained pins as
location rows becomes Add Sources and Pins, and the data model, library,
architecture, sync, and CLI pages describe pins as Space items. The React
explorer and normalized cache guides use sources and spaces for their
examples, the snapshot format doc points at the moved module, and
CONTRIBUTING tracks a source instead of adding a location.
The final-drop plan records FD5 with what still fails and why, the
convergence plan notes the old world is gone, and the registers move FD5 to
done with FDA ready.
FD4 of the final entries drop. One migration drops 29 retired tables inside
its own transaction, children before parents: with foreign keys on, dropping
a parent makes SQLite parse every child's constraints, and the first attempt
failed on sidecar, whose other parent entries was already gone. That attempt
also showed Migrator::up leaves SQLite migrations unwrapped, which is why
the migration opens its own transaction. Every library now converges on 14
tables. Proven on a copy of the live library, on a library the pre-drop
build left half-migrated, on a brand-new library, and on the live Mac
library after a backup; every dropped table held zero rows there apart from
the seeded kind lookup.
Locations leave with the schema: ops, domain type, entity, CLI domain, and
every consumer in the web and mobile clients. PathBar pins become
space-level Path items listed under Pinned, the sidebar's Places come from
a new paths.system_folders query, Add Storage tracks sources and validates
through sources.validate_path, device views list sources, and library
statistics count sources as source_count. Content kind statistics read the
source stores instead of a lookup table.
Fourteen entry-era models lose their sync registrations and Syncable impls:
entry, content_identity, sidecar, mime_type, the three media_data tables,
collection, collection_entry, location, tag, tag_relationship,
user_metadata, and user_metadata_tag. Entry's apply_state_change, tombstone
gating, and closure and directory-path rebuilds go with them, as do
backfill's entry hierarchy sort with its per-record self-referential FK
resolution and the sync helpers' directory-path payloads.
The engine survives for what still syncs: device, volume, space,
space_group, space_item, and audit_log, over the same transport, HLC,
watermarks, and dependency tracking the library-sync research plans to
evolve. A registry test now refuses any retired model that tries to
register again. Live capability audit passed: replica sources listed,
remote ops answered by titan, replica listings served, remote job
reporting responsive.
FD0 and FD1 of the final entries drop. The baseline is recorded in the
plan: the reference inventory, the 43 tables a fresh library still
creates, the registered sync models, and the pre-existing entry-era test
failures, with the live fleet standing as the fixture.
File::route_from_dependency is deleted rather than rewired: the only
emitters of file dependency events were legacy row-sync applies, and file
payloads have announced directly from the volume index since the tags
work. The ancestor entry-ID cache, the dead entries_for_content field, and
the SQL path resolver leave the indexer; pairing discovery totals read
source rows, with sources standing in for the retired location count; and
the indexing module docs stop describing a writer that no longer exists.
Library statistics counted the legacy tag table, which would have read zero
forever; the instance count now reads definitions across staging and the
stores, and the pre-open static count reads staging with the instance
recount behind it. tagging.mdx describes the built system instead of the
old design sketch, library.mdx stops claiming the library owns tags, and
the design doc carries its three reconciliations. The plans and register
record stretch 1 closed: delivery verified against titan, FD4's
preconditions banked, phase 4 landed.
The tags plan carries its execution record: P1 through P5 landed and
live-verified, the delivery ack waits on titan running this build, and P6
lists what remains. The final-drop plan drops FD2's migration machinery in
favor of the clean build, since every legacy tag table was verified empty,
and the register rows follow.
The R6 subsection carries the landed commits, the live dev-tools
database delivery proof, the scale measurement, and the follow-ons the
plan itself declares optional. Register row updated to the fleet's
actual state.
R7 ran against the live fleet on 4a690c84a: all ten stores validated
by direct SQL with zero dangling ancestry, the home store needed no
repair, offline cold-restart serves and searches all nine replicas,
byte reads and remote ops verified, and reconvergence transferred
nothing. Register row rewritten to current state. Also folds in the
Codex volume audit note and its research doc: Untitled is detected but
unmapped this session because hot-plug does not invoke the startup
mapping path.
Locations go in the breaking release without row migration or API
compatibility. Policies accept SdPath and persist a separate
{ source_id, relative_path } target resolved through the source layer;
SdPath gains no source variant; Space items own navigation. The
final-drop boundaries, teardown status, sync research, and core docs
pages now agree. Register brought current through 5a65d0998 and the
search refinement bar.
The source runtime reliability plan graduates from draft to the
executing plan its register row already tracks. Library-sync research
records the split-library state between the Mac and titan and proposes
the small library-state contract; locations research records the
revised direction of configured source subtrees with continuous
processing rules over pins alone, cross-referenced from the
entries-teardown T4.4 row. The working register carries today's landed
work: search phases, fleet statistics, storage replicas, the hashing
loop fix, and the first reliability slices, with the code baseline
brought current.
From a parallel session. Inset content is the default: navigation sits
on the window background and the explorer renders as an inset sheet.
Settings > Appearance > Layout retains the floating-panels mode. Both
modes share mounted content and stable preview hosts through a shell
geometry context, the layout preference propagates to other windows on
the same origin, and the sidebar footer returns to circular Activity
and Settings controls with the activity count and busy indicator.
Preview and Size view follow the sheet bounds, with Size thumbnails in
container coordinates.
From a parallel session. thumbs.generate accepts a file or an indexed
folder with missing, stale, and force modes, runs in resumable batches,
and versions video tiles against host FFmpeg availability so a tile
regenerated with better tooling replaces its predecessor in place. File
and folder context menus and the new sd thumbs CLI domain call the same
operation. paths.context now reports whether the exact path has a
committed source record, and the path bar surfaces it with a refresh
affordance. Coverage and resume semantics are documented in
docs/core/thumbnail-generation.mdx.
Phase 1 of docs/plans/2026-09-15-search-refinement.md. Every parameter
search.files accepts is now either honored or fails closed; nothing is
validated, logged, and ignored anymore.
Ordering and pagination live in a new pipeline module with a
backend-neutral candidate type, per R6 of the source-runtime plan: one
deterministic global sort before final pagination, equal keys settling
on case-folded name and record id so results stop reshuffling between
keystrokes. Library search sums each partition's full filtered match
count into total_found and contributes only the page window of
candidates to the merge, so a partition can never lose a result that
belongs on the page. The 200-result cap and the synthetic pagination
block are gone; the response reports the caller's window over the true
total (a "spacedrive" search now reports 496 matches instead of a
complete-looking 200).
Filter fixes: extensions compare case-insensitively, hidden entries
are excluded by default and included with include_hidden (advertised
in available_filters), and a missing timestamp fails a date filter
instead of passing it, so the unanswerable AccessedAt and IndexedAt
fields fail closed rather than silently matching everything.
Deletes dead code the plan condemned: sorting.rs and the unused
facets.rs duplicates, plus the legacy entry-era imports in query.rs.
Verified live: --limit 5 returns five with the true total, size and
name sorts order and reverse, offset pages advance.
The plan owns everything above matching, which 4bc442e43 made correct:
scopes become folder/source/library with Location retired as vocabulary,
sort and pagination get implemented rather than deleted, the filter
panel ships the set the daemon can answer honestly, tags follow through
the library-db path-set join, and the redundancy views get a phase to
verify and fix their mechanism, since they drive search.files with
filters the ephemeral path ignores. Register rows record the plan and
the landed search fixes.
PROJECT_STATUS.md now sits on the b653676e8 baseline: the sidebar,
path-status, thumbnail, host-media, and startup rows cite their landed
commits with live proofs still open, the finished docs rows moved to
Recent landed, and the multi-device commits since 50fdbad59 are
recorded. The drive-catalog row flags that packages/drives is missing
from the checkout. Adds the entries final-drop plan and its references
in the teardown, convergence, and locations plans.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
README and CONTRIBUTING updates, the working register brought current,
locations and data-model pages reconciled, redundancy and overview
polish, media view tweaks, regenerated schemas, and the new Spacedrive
icon asset.
Library loading no longer dispatches a filesystem discovery pass. The
desktop requests one idempotent pass after the connected shell
renders, a bare daemon initiates nothing, and sd index start
--defaults gives CLI and server operators the same explicit path.
Restored volume snapshots prevent repeat full walks.
A machine-scoped registry discovers host FFmpeg and companion tools,
surfaced in Services settings. The thumbnail service uses them for
video posters and on-demand scrub sheets: thumbstrips bake per request
into the volume hot tier and serve over the local HTTP servers with
immutable content-versioned URLs, wired to the timeline scrubber.
The path bar reads a typed path-context query backed by the daemon's
source, volume map, watcher, persistence, and pin state, replacing the
location/index-mode indicator. Containment resolves on the daemon with
path components after volume alias normalization; a pin no longer
implies indexing or watching, and the docs say so.
One library scope, flat daily destinations, Places computed from
system folders without persisting them as locations, canonical rows,
and an Activity/Settings footer. Topology groups retire; their pins
survive seed convergence. Replica sources render under their owning
device and route into the explorer with that device's slug.
Replaces the Mintlify setup (mint.json, docs.json, snippets) with a
standalone Fumadocs app in docs/: Next.js app router, meta.json nav per
section, its own bun install. Run with `just dev-docs`.
Content pass alongside the migration: core architecture, data model,
CLI, and networking pages rewritten around the daemon/source-store/
library split, new product-direction.mdx defining the product boundary,
new drive-catalog plan, and the physical-drives plan updated to
reference it. Whitepaper page retired.
SourceConfig is the typed shape of the source row's config column,
parsed with defaults so old rows and future fields both survive.
sources.update renames a source or changes its capture policy, with
the one asymmetry named: widening dispatches a walk for what the rules
skipped, narrowing removes nothing because hiding is the lens's job.
Tracking only ever widens the policy, so a casual re-track cannot
demote an archival source. The locations-demoted doc gains the policy
taxonomy this settles: capture on the source, attention on the
location, display in the lens.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Groups carry the vdev tree and minimum member count, since "which boxed
drives make the pool, and how many suffice" is the question ZFS labels
cannot answer with the drives unplugged. zpool membership moves from D3
to D1 because the observation window closes at export. The runbook
gains the TrueNAS config backup, the encryption check, and the clean
export step.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
What the store captures (verified against stat on titan), the deploy,
the per-dataset order and pass/fail gate, the pre-export steps, and the
known issues with workarounds.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>