Phase 1 Data Model: SPEC_KITTY_HOME State Isolation

This fix introduces no new persisted entities. The "data model" here is the path resolution model — the authoritative root and the state surfaces derived from it.


Entity: RuntimeRoot (existing — src/specify_cli/paths/windows_paths.py)

Frozen dataclass; the single source of the global-state base directory.

Field / propertyTypeMeaningChange in this mission
platformLiteral["win32","darwin","linux"]Resolved OSunchanged
basePathAuthoritative global-state rootNow honors SPEC_KITTY_HOME
auth_dirPath (base/auth)Auth store dirunchanged (derives from base)
sync_dirPath (base/sync)Daemon sync dirunchanged
daemon_dirPath (base/daemon)Windows daemon dirunchanged
tracker_dirPath (base/tracker)Windows tracker dirunchanged
cache_dirPath (base/cache)Cache dirunchanged

Resolution rule (get_runtime_root())

env := os.environ.get("SPEC_KITTY_HOME")
if env (non-empty):     base = Path(env)            # all platforms
elif platform == win32: base = platformdirs.user_data_dir("spec-kitty", appauthor=False, roaming=False)
else:                   base = Path.home() / ".spec-kitty"

Invariants

  • INV-1 (purity): resolution performs no filesystem I/O and creates no directories (NFR-002).
  • INV-2 (env precedence): a non-empty SPEC_KITTY_HOME wins on every platform (FR-011).
  • INV-3 (empty = unset): empty string falls through to the platform default (FR-012).
  • INV-4 (no migration): changing the root never moves existing data (C-001).

State Surface Map (relative to base)

The contract is the suffix below stays constant per platform; only base changes when SPEC_KITTY_HOME is set. POSIX = flat; Windows = normalized onto the platformdirs base (decision DM-01KW1KDHVGWZ0QERDMV1CRJ15S).

SurfaceModule (call site)POSIX suffix (preserved)FR
Sync configsync/config.py SyncConfigconfig.tomlFR-001
Credentials (sync)sync/queue.py _credentials_pathcredentialsFR-001/008
Auth session dir (queue)sync/queue.py _auth_session_store_dirauthFR-002
Legacy queue DBsync/queue.py _legacy_queue_db_pathqueue.dbFR-004
Scoped queue dirsync/queue.py _scoped_queue_dirqueues/FR-005
Active queue scopesync/queue.py _active_scope_pathactive_queue_scopeFR-005
Daemon rootsync/daemon.py _daemon_root`` (base, flat)FR-006
Daemon sync rootsync/daemon.py _sync_rootsync/FR-006
Daemon constantsync/daemon.py SPEC_KITTY_DIR`` (→ make lazy)FR-006
Lamport clocksync/clock.py default + load()clock.jsonFR-007
Auth store (POSIX)auth/secure_storage/file_fallback.pyauth/FR-002
Auth store (Windows)auth/secure_storage/windows_storage.pyauth/ (normalized)FR-002
Refresh lockauth/token_manager.py _refresh_lock_pathauth/refresh.lockFR-003
Tracker credstracker/credentials.py _tracker_root`credentials`FR-008
Tracker DBtracker/store.py _spec_kitty_dir/_trackers_dirtrackers/FR-008
Doctor global-syncstate/doctor.py (~141, ~253)`` (base) + surface patternsFR-009
Contract GLOBAL_SYNCstate/contract.py STATE_SURFACESdeclarative patterns (unchanged); resolution honors baseFR-009/010

Note: state/contract.py defines the relative surface patterns and remains the single source of those patterns; state/doctor.py is where absolute resolution must adopt get_runtime_root().base.


Out-of-model (explicitly NOT global state)

(worktree / ".spec-kitty"), not ~/.spec-kitty. Untouched.

var. Untouched except as the consistency reference for the read idiom.

  • src/specify_cli/review/lock.py.spec-kitty is a worktree-local review lock
  • get_kittify_home() asset home (.kittify) — separate concept; already honors the env