Implementation plan: cross-platform account switch truth

Status: reviewedDate: 2026-09-03Base: master @ eabbc76Risk: large / credentials + lifecycle

Contract

This plan implements the reviewed cross-platform account switch truth specification. Every task cites its acceptance criteria. Work is split into four reviewable pull requests; each leaves the product internally consistent and independently testable.

Invariant summary

Delivery sequence

flowchart LR
  P1[PR 1: truthful credential transaction] --> P2[PR 2: session evidence model]
  P2 --> P3[PR 3: service identity and native control]
  P3 --> P4[PR 4: supervisors, environment, rollout]
  P4 --> SEC[Security audit]
  SEC --> E2E[Three-OS evidence]
flowchart LR
  P1[PR 1: truthful credential transaction] --> P2[PR 2: session evidence model]
  P2 --> P3[PR 3: service identity and native control]
  P3 --> P4[PR 4: supervisors, environment, rollout]
  P4 --> SEC[Security audit]
  SEC --> E2E[Three-OS evidence]
PREnd-to-end user valueRelease posture
1Use Account stops lying, detects split stores, fences legacy writers, and gives precise repair/restart outcomes.Can ship alone only with the fail-closed writer fence enabled; old session labels remain historical.
2Session dashboard/statusline distinguish started, observed, pending, pinned, and unverified.Can ship after PR 1.
3Status/restart find the real jacked instance and never kill a foreign listener.Protocol lands before supervisor migration.
4All three OSes use reconciled supervisors and sanitized process environments.Migration release with explicit upgrade notes.

Planned file structure

FileResponsibility
jacked/credentials/models.pyEnums and typed results for capabilities, observations, stores, transactions, and activation axes.
jacked/credentials/capabilities.pyExact-build/config registry, authority/required/optional consumer roles, read-only checks, writer protocol epoch and mutation kill switch.
jacked/credentials/file_store.pySafe file reads, secret temp staging, durable POSIX replace, Windows ReplaceFile/write-through and link checks.
jacked/credentials/macos_store.pySecurity.framework Keychain read/update/add, full item locator, duplicate handling, typed noninteractive/interactive failures.
jacked/credentials/resolver.pyThe only active-identity resolution path; store consensus, freshness, evidence, conflict, confidence, and atomic secret-free status snapshots.
jacked/credentials/transaction.pyLock order, pending journal, transcript HMAC, store phases, result axes, final DB commit and events.
jacked/credentials/recovery.pyPending-operation classification, explicit abandon-after-reauth, cleanup and startup gating.
jacked/credentials/writer_fence.pyMinimum writer-protocol discovery and fail-closed legacy mutation gate delivered in PR 1.
jacked/service/instance.pyKernel lifetime lease, machine/session identity, private manifest, ownership inspection and quarantine discovery.
jacked/service/ipc.pyVersioned UDS/named-pipe protocol, peer identity, nonce/generation validation and command allowlist.
jacked/service/spec.pyCanonical ServiceSpec, artifact ownership and generation contract delivered before supervisor-mediated control.
jacked/service/environment.pyCurated cross-platform environment construction and typed structured redaction.
jacked/service/launcher/Minimal pre-Python OS launchers in immutable versioned paths; create an explicit clean environment and validate the runtime entry point.
jacked/service/supervisors/*.pylaunchd, systemd-user, Task Scheduler and manual adapters rendered from ServiceSpec.
jacked/web/database.pyAdditive schemas and atomic methods for credential switches, action nonces, observation inbox and session evidence.
jacked/api/routes/auth.pyLocal mutation gateway, structured outcomes, active resolver and v1/v2 session APIs.
jacked/data/web/js/components/account-actions.jsPrecise switch states, local-only guard, repair and restart guidance.
jacked/data/web/js/components/sessions.jsEvidence-qualified session labels and pending/conflict states.

Tasks

PR 1: Truthful credential activation

Task 1.1: Freeze the capability and outcome contracts

Traces to AC1, AC3, AC4, AC6, AC8, AC9.

uv run python -m pytest tests/unit/test_credential_capabilities.py tests/unit/test_use_account.py -q
uv run ruff check jacked/credentials tests/unit/test_credential_capabilities.py

Task 1.2: Build safe store adapters and canonical resolution

Traces to AC1, AC2, AC3, AC6, AC8, AC9.

uv run python -m pytest tests/unit/test_credential_sync.py tests/unit/test_active_account_cc_skip.py tests/unit/test_credential_resolver.py -q
uv run ruff check jacked/credentials jacked/api/credential_helpers.py jacked/api/usage_monitor.py jacked/web/auth.py jacked/web/oauth.py jacked/launch.py

Task 1.3: Implement the durable switch state machine

Traces to AC1, AC2, AC4, AC7, AC8, AC9.

uv run python -m pytest tests/unit/test_credential_transactions.py tests/unit/test_credential_recovery.py tests/unit/test_credential_writer_fence.py tests/unit/test_multi_org.py -q
uv run ruff check jacked/credentials jacked/web/database.py

Task 1.4: Wire manual, OAuth, launch and auto-swap behavior end to end

Traces to AC1, AC2, AC4, AC7, AC8, AC14.

uv run python -m pytest tests/unit/test_use_account.py tests/unit/test_dual_token.py tests/unit/test_oauth_manual_flow.py tests/unit/test_web_js_swap_ui.py tests/unit/test_web_js_account_menu.py tests/unit/test_auth_action_nonce.py -q
uv run ruff check jacked/api/routes/auth.py jacked/web/auth.py jacked/web/oauth.py jacked/launch.py
PR 1 exit gate: fault-inject the exact incident (global file staging succeeds, Keychain returns ACL denial), plus an old writer between readback and DB finalization and an external overwrite immediately after an uncooperative readback. The first response is not success; the second is refused or indeterminate; the third is observed_target_unfenced, never committed. DB/audits remain truthful and no secret appears in logs/API/process arguments.

PR 2: Evidence-based session identity

Task 2.1: Add additive session observation storage

Traces to AC4, AC5, AC6, AC15.

uv run python -m pytest tests/unit/test_session_accounts.py tests/unit/test_active_sessions.py tests/unit/test_session_identity_migration.py -q
uv run ruff check jacked/web/database.py

Task 2.2: Make hooks and statusline scope-aware

Traces to AC4, AC5, AC6, AC9.

uv run python -m pytest tests/unit/test_session_start_hook.py tests/unit/test_launch.py tests/unit/test_statusline.py -q
uv run ruff check jacked/data/hooks/session_account_tracker.py jacked/statusline.py jacked/launch.py

Task 2.3: Ship truthful session API and UI

Traces to AC4, AC5, AC6, AC14, AC15.

uv run python -m pytest tests/unit/test_active_sessions.py tests/unit/test_web_js_sessions.py tests/unit/test_websocket.py tests/unit/test_statusline.py -q
uv run ruff check jacked/api/routes/auth.py jacked/api/watchers.py
PR 2 exit gate: in a mixed global/scoped fixture, changing desired global account leaves pinned spans unchanged, marks global spans pending, and never populates runtime-verified identity. A metadata-target/Keychain-old conflict renders desired target plus runtime unknown in the UI and statusline. Legacy rows render unknown, not active.

PR 3: Provable service ownership

Task 3.1: Add the lease, manifest and inspection state machine

Traces to AC9, AC10, AC11, AC12, AC15.

uv run python -m pytest tests/unit/service/test_instance.py tests/unit/service/test_process.py tests/unit/service/test_updater.py -q
uv run ruff check jacked/service/instance.py jacked/service/process.py jacked/service/updater.py

Task 3.2: Add private native control and discoverable quarantine

Traces to AC10, AC11, AC12, AC14.

uv run python -m pytest tests/unit/service/test_ipc.py tests/unit/service/test_quarantine.py tests/unit/service/test_bind.py tests/unit/service/test_cli.py -q
uv run ruff check jacked/service/ipc.py jacked/service/bind.py jacked/cli.py

Task 3.3: Make status/restart health-verifying

Traces to AC10, AC11, AC12.

uv run python -m pytest tests/unit/service/test_cli.py tests/unit/service/test_restart.py tests/unit/service/test_update_phases.py -q
uv run ruff check jacked/cli.py jacked/service
PR 3 exit gate: PID reuse between final inspection and signal, missing/altered manifest, wrong UID/SID, wrong executable, foreign supervisor artifact, foreign HTTP server and foreign port owner all result in zero signals or supervisor actions against the foreign process. The current orphan scenario becomes either verified recovery or quarantine with explicit guidance.

PR 4: Native supervision, environment hygiene and rollout

Task 4.1: Install a pre-interpreter sanitized launcher

Traces to AC9, AC11, AC13.

uv run python -m pytest tests/unit/service/test_environment.py tests/unit/service/test_launcher.py tests/unit/service/test_updater.py -q
uv run ruff check jacked/service/environment.py jacked/service/updater.py jacked/cli.py

Task 4.2: Reconcile native supervisors on all three OSes

Traces to AC10, AC11, AC12, AC13, AC15.

uv run python -m pytest tests/unit/service/test_platform.py tests/unit/service/test_native_restart.py tests/unit/service/test_supervisors.py tests/unit/test_windows_update_batch.py -q
uv run ruff check jacked/service/supervisors jacked/service/platform.py

Task 4.3: Migrate safely and expand CI

Traces to AC11, AC12, AC13, AC15, AC16.

uv run python -m pytest -q
uv run ruff check .
uv run ruff format --check .
PR 4 exit gate: all three OS lanes pass and recorded real-OS smoke evidence exists before each platform flag is enabled; hostile startup and secret canaries remain absent; supervisor definitions reconcile idempotently; old launchers retire only after a verified handoff cycle; an invalid candidate runtime reports repair-required without port-based false health; docs make no unsupported live-switch or pinned-session claim.

Final verification

  1. Run the full suite with uv run python -m pytest, then ruff check/format.
  2. Run $dcr because credentials, local control, process termination, CSRF and cross-platform service installation are security-sensitive.
  3. Run $cso; no high/critical finding may remain.
  4. Run $qa against the local account/session UI. Cover committed, ACL denied, interactive pending, diverged, indeterminate, remote read-only, session pending, scoped and legacy unknown states.
  5. Use disposable homes and processes for CLI/service end-to-end tests. Never use the maintainer's real Keychain, accounts, port 8321 listener or live Claude sessions.
  6. Capture exact evidence for AC1-AC16 in the PR description: command, platform, result and artifact/log location.
  7. After dependency and workflow changes, run $lockdown --workspace=/Users/jackneil/Github before release.

Required model-based failure points

Terminate or fault the credential engine before/after pending commit, staging, authority call, authority readback, mirror publication, DB finalization and broadcast. Combine each with concurrent switch request, same-account token rotation, two Claude builds with reversed precedence, capability/writer epoch change, old-writer race, recovery-key loss, SQLite busy/read-only, response loss, service restart and abandoned interactive operation. Also inject PID reuse after final inspection, foreign supervisor artifacts, IPC replay/manifest copy, 8321 plus quarantine, broken candidate launch, locked Windows launcher, reboot/logon, missing systemd user bus, managed pre-v2 downgrade, and direct execution of an old binary followed by a credential write. Every state must resolve to a documented outcome without automatic secret rewrite, foreign control, or secret exposure.

Rollout and rollback

  1. Release PR 1 first. It prevents false success, centralizes identity, and includes the minimum writer-protocol fence. If a legacy/direct writer cannot be excluded, mutation is disabled.
  2. Release PR 2 second. Keep the v1 active-sessions route for one release and mark it deprecated in response metadata.
  3. Release PR 3 before enabling supervisor migration. New ownership proof must exist before any artifact tries to adopt or stop legacy processes.
  4. Release PR 4 behind a migration feature flag. Enable per platform after its real-OS smoke lane passes. Keep manual service mode as the safe fallback.
  5. Rollback behavior: schema changes are additive; old account/session reads continue. Managed service/manual entry points enforce the minimum writer epoch, so disabling transaction v2 fails closed and managed pre-v2 downgrade requires explicit migration. Direct old same-user binaries are outside that prevention boundary; detected writes make state indeterminate or disable mutation. If supervisor v2 is disabled, the owned new service can run manual/quarantined without deleting native artifacts. The previous versioned launcher is retained until candidate handoff proves healthy.
  6. Live maintainer repair: only after the release is installed, explicitly inspect and repair the current Keychain/service state. That operation is not part of implementation testing.

Resolved decisions


Plan reviewed through three independent verification/adversarial rounds and a clean recursive design review. Generated from the jacked HTML artifact template.