Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .chezmoiignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Library/LaunchAgents/com.mgm.aws-sso-login.plist
{{ end }}
{{ if ne .machine_type "mac-mini" }}
.config/atuin-server/
Library/LaunchAgents/dev.dotfiles.runtime-state.plist
.local/bin/configure-runtime-state
{{ end }}
{{ if eq .machine_type "lmsh" }}
*
Expand Down
1 change: 1 addition & 0 deletions docs/specs/shell_auth_startup/BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@
| AUTH-010 | Deny-by-default lmsh source target boundary | 2026-07-25 | `4fdc25e` |
| AUTH-010-session-credentials | Session-isolated GCP credentials with stale-file rematerialization | 2026-08-01 | `a8e0d9a` |
| AUTH-011-cache-relocation | Routed supported CLI caches and Pulumi home through sentinel-guarded native paths; paused PyCharm, Prefect, and Codex | 2026-08-09 | `712e31e` |
| AUTH-013-runtime-state-relocation | Routed validated Prefect, Codex, and PyCharm state externally with team-safe defaults and runtime fallback | 2026-08-09 | `0e3e9dc` |
14 changes: 14 additions & 0 deletions docs/specs/shell_auth_startup/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

## 2026-08-09

- Routed validated Prefect and Codex homes through their native
controls and PyCharm system state through custom properties. All settings are
Mac-mini-only, sentinel-guarded, and excluded from teammate defaults.
- Preserved 802 Prefect runs; copied databases passed SQLite integrity checks,
Prefect server health, Codex GUI file-handle checks, and PyCharm control
restarts. A login controller reconciles GUI state at load, sentinel changes,
and 60-second intervals while preserving unrelated inherited values.
- Deployed the shell profile and LaunchAgent, then moved internal rollback copies
to `/Volumes/ext/state/archive/runtime-state-rollback-20260809`. Post-move
activation remained external and reclaimed about 12 GiB internally.
- Validation: 173 tests, Bash/Zsh syntax, plist/render checks, SQLite integrity,
launchd state, and post-move GUI/runtime smoke passed. Accepted risk:
`AUTH-013-AR1`. Implementation Gate Commit: `0e3e9dc`.

- Routed Playwright, uv, pre-commit, npm, and Pulumi through their native path
controls only when the existing external-state sentinel is present. Inherited
managed values clear on the fallback path without overriding unrelated values.
Expand Down
28 changes: 23 additions & 5 deletions docs/specs/shell_auth_startup/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,10 +335,15 @@ Glossary:
- **Invariant:** Playwright, uv, pre-commit, and npm use only their
documented native path controls; shell-wide `XDG_CACHE_HOME` and cache
symlinks are not used.
- **Invariant:** Pulumi uses its documented `PULUMI_HOME`; Prefect databases and
Codex session state remain internal until their installed runtimes can pass
migration integrity tests. PyCharm remains internal until its external paths
can fail back when the volume is unavailable.
- **Invariant:** Pulumi, Prefect, and Codex use their documented native home
controls only on the Mac mini with the external-state sentinel present.
- **Invariant:** Codex GUI processes receive the same native `CODEX_HOME` through
the Mac-mini-only login environment; missing-sentinel startup removes only the
managed value.
- **Invariant:** PyCharm settings and plugins remain internal; its regenerable
system directory uses `idea.system.path` only on the Mac mini.
- **Pre:** migrated Prefect and Codex SQLite files pass integrity and runtime
activation checks before internal rollback copies may be removed.
- **Invariant:** `TerminalTargetAllowlist` denies all targets by default and
re-includes only `.bash_profile`, `.bashrc`, `.common_profile`,
`.config/atuin/config.toml`, `.config/starship.toml`, and
Expand Down Expand Up @@ -371,6 +376,14 @@ Glossary:
- **Post:** remote failure does not prevent local history search or capture.

### Accepted Risk
- `AUTH-013-AR1`: on 2026-08-09 the operator approved storing Prefect history and
Codex authentication/session state, and PyCharm Local History on the existing
unencrypted, `noowners` external volume after each exposure was reported.
Sentinel fallback, validated copies, and physical custody reduce availability
and migration risk but do not provide encryption or local ownership isolation.
Owner: operator. Review before the volume leaves trusted custody, another local
account gains access, or the storage policy changes; encrypt the volume when
operationally feasible.
- `AUTH-011-AR1`: the operator approved moving Pulumi credentials and executable
caches to the existing unencrypted, `noowners` external state volume after the
trust limitation was reported. The state sentinel, retained internal rollback
Expand All @@ -389,7 +402,12 @@ Glossary:
- Shell syntax checks pass for edited scripts.
- Rendered Mac mini shell startup selects external CLI caches only with the
sentinel present; absent-sentinel startup retains local defaults.
- PyCharm custom properties remain absent after the attempted migration rollback.
- Prefect preserves 802 runs, passes SQLite `quick_check`, and serves a healthy
local API from the external home.
- Codex CLI and GUI use the external home; copied SQLite databases pass
`quick_check` and live file handles resolve externally.
- PyCharm control restart opens the external system directory without internal
cache file handles.
- Static search confirms no Herdr profile auto-`secret` block remains.
- Static search confirms Clockify poller has no `op read` call.
- Static search confirms Databricks config has no `onepasswordRead` call.
Expand Down
6 changes: 6 additions & 0 deletions dot_common_profile.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,18 @@ if [ -f "/Volumes/ext/state/.dotfiles-ai-state" ]; then
export PRE_COMMIT_HOME="$DOTFILES_CACHE_ROOT/pre-commit"
export npm_config_cache="$DOTFILES_CACHE_ROOT/npm"
export PULUMI_HOME="/Volumes/ext/state/pulumi"
export PREFECT_HOME="/Volumes/ext/state/prefect/seo-data-science"
export CODEX_HOME="/Volumes/ext/state/codex/home"
export PYCHARM_PROPERTIES="/Volumes/ext/state/jetbrains/PyCharm2026.2/idea.properties"
else
[ "${PLAYWRIGHT_BROWSERS_PATH:-}" = "/Volumes/ext/state/cache/playwright" ] && unset PLAYWRIGHT_BROWSERS_PATH
[ "${UV_CACHE_DIR:-}" = "/Volumes/ext/state/cache/uv" ] && unset UV_CACHE_DIR
[ "${PRE_COMMIT_HOME:-}" = "/Volumes/ext/state/cache/pre-commit" ] && unset PRE_COMMIT_HOME
[ "${npm_config_cache:-}" = "/Volumes/ext/state/cache/npm" ] && unset npm_config_cache
[ "${PULUMI_HOME:-}" = "/Volumes/ext/state/pulumi" ] && unset PULUMI_HOME
[ "${PREFECT_HOME:-}" = "/Volumes/ext/state/prefect/seo-data-science" ] && unset PREFECT_HOME
[ "${CODEX_HOME:-}" = "/Volumes/ext/state/codex/home" ] && unset CODEX_HOME
[ "${PYCHARM_PROPERTIES:-}" = "/Volumes/ext/state/jetbrains/PyCharm2026.2/idea.properties" ] && unset PYCHARM_PROPERTIES
[ "${DOTFILES_CACHE_ROOT:-}" = "/Volumes/ext/state/cache" ] && unset DOTFILES_CACHE_ROOT
fi
{{ end -}}
Expand Down
12 changes: 12 additions & 0 deletions dot_local/bin/executable_configure-runtime-state
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/sh

codex_home=/Volumes/ext/state/codex/home
pycharm_properties=/Volumes/ext/state/jetbrains/PyCharm2026.2/idea.properties
if [ -f /Volumes/ext/state/.dotfiles-ai-state ]; then
/bin/launchctl setenv CODEX_HOME "$codex_home"
/bin/launchctl setenv PYCHARM_PROPERTIES "$pycharm_properties"
else
[ "$(/bin/launchctl getenv CODEX_HOME)" = "$codex_home" ] && /bin/launchctl unsetenv CODEX_HOME
[ "$(/bin/launchctl getenv PYCHARM_PROPERTIES)" = "$pycharm_properties" ] && /bin/launchctl unsetenv PYCHARM_PROPERTIES
fi
exit 0
20 changes: 20 additions & 0 deletions private_Library/LaunchAgents/dev.dotfiles.runtime-state.plist.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>dev.dotfiles.runtime-state</string>
<key>ProgramArguments</key>
<array>
<string>{{ .chezmoi.homeDir }}/.local/bin/configure-runtime-state</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>WatchPaths</key>
<array>
<string>/Volumes/ext/state/.dotfiles-ai-state</string>
</array>
<key>StartInterval</key>
<integer>60</integer>
</dict>
</plist>
68 changes: 68 additions & 0 deletions tests/test_terminal_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ def text(path):


def test_lmsh_profile_is_portable_and_excludes_credentials():
assert '"machine_type" "Machine type (macbook/mac-mini/lmsh)" "macbook"' in text(
".chezmoi.toml.tmpl"
)
assert "macbook/mac-mini/lmsh" in text(".chezmoi.toml.tmpl")
assert "atuin_sync_address" in text(".chezmoi.toml.tmpl")
ignored = text(".chezmoiignore")
Expand Down Expand Up @@ -39,6 +42,9 @@ def test_mac_mini_uses_native_external_cache_paths():
'PRE_COMMIT_HOME="$DOTFILES_CACHE_ROOT/pre-commit"',
'npm_config_cache="$DOTFILES_CACHE_ROOT/npm"',
'PULUMI_HOME="/Volumes/ext/state/pulumi"',
'PREFECT_HOME="/Volumes/ext/state/prefect/seo-data-science"',
'CODEX_HOME="/Volumes/ext/state/codex/home"',
'PYCHARM_PROPERTIES="/Volumes/ext/state/jetbrains/PyCharm2026.2/idea.properties"',
):
assert setting in profile
for variable in (
Expand All @@ -48,6 +54,9 @@ def test_mac_mini_uses_native_external_cache_paths():
"PRE_COMMIT_HOME",
"npm_config_cache",
"PULUMI_HOME",
"PREFECT_HOME",
"CODEX_HOME",
"PYCHARM_PROPERTIES",
):
assert f'unset {variable}' in profile

Expand All @@ -69,6 +78,9 @@ def test_external_cache_exports_and_fallback(tmp_path):
"PRE_COMMIT_HOME": "/Volumes/ext/state/cache/pre-commit",
"npm_config_cache": "/Volumes/ext/state/cache/npm",
"PULUMI_HOME": "/Volumes/ext/state/pulumi",
"PREFECT_HOME": "/Volumes/ext/state/prefect/seo-data-science",
"CODEX_HOME": "/Volumes/ext/state/codex/home",
"PYCHARM_PROPERTIES": "/Volumes/ext/state/jetbrains/PyCharm2026.2/idea.properties",
}

shells = [shell for shell in ("/bin/bash", "/bin/zsh") if Path(shell).exists()]
Expand All @@ -91,6 +103,62 @@ def test_external_cache_exports_and_fallback(tmp_path):
assert f"{name}=" not in fallback


def test_mac_mini_gui_state_paths_are_scoped_and_native():
ignored = text(".chezmoiignore")
mac_mini = ignored.split('{{ if ne .machine_type "mac-mini" }}', 1)[1].split("{{ end }}", 1)[0]
assert "Library/LaunchAgents/dev.dotfiles.runtime-state.plist" in mac_mini
assert ".local/bin/configure-runtime-state" in mac_mini
runtime_state = text("dot_local/bin/executable_configure-runtime-state")
assert "/Volumes/ext/state/.dotfiles-ai-state" in runtime_state
assert "codex_home=/Volumes/ext/state/codex/home" in runtime_state
assert "pycharm_properties=/Volumes/ext/state/jetbrains/PyCharm2026.2/idea.properties" in runtime_state
for variable in ("CODEX_HOME", "PYCHARM_PROPERTIES"):
assert f"/bin/launchctl setenv {variable}" in runtime_state
assert f"/bin/launchctl unsetenv {variable}" in runtime_state
launch_agent = text("private_Library/LaunchAgents/dev.dotfiles.runtime-state.plist.tmpl")
assert "<key>WatchPaths</key>" in launch_agent
assert "<key>StartInterval</key>" in launch_agent


def test_gui_state_controller_tracks_sentinel_without_overwriting_custom_values(tmp_path):
sentinel = tmp_path / "sentinel"
state = tmp_path / "launchctl-state"
state.mkdir()
launchctl = tmp_path / "launchctl"
launchctl.write_text(
"""#!/bin/sh
case "$1" in
setenv) printf '%s' "$3" > "$LAUNCHCTL_STATE/$2" ;;
getenv) cat "$LAUNCHCTL_STATE/$2" 2>/dev/null ;;
unsetenv) rm -f "$LAUNCHCTL_STATE/$2" ;;
esac
"""
)
launchctl.chmod(0o755)
script = (
text("dot_local/bin/executable_configure-runtime-state")
.replace("/Volumes/ext/state/.dotfiles-ai-state", str(sentinel))
.replace("/bin/launchctl", str(launchctl))
)
env = os.environ | {"LAUNCHCTL_STATE": str(state)}

sentinel.touch()
subprocess.run(["/bin/sh", "-c", script], env=env, check=True)
assert (state / "CODEX_HOME").read_text() == "/Volumes/ext/state/codex/home"
assert (state / "PYCHARM_PROPERTIES").read_text() == (
"/Volumes/ext/state/jetbrains/PyCharm2026.2/idea.properties"
)

sentinel.unlink()
subprocess.run(["/bin/sh", "-c", script], env=env, check=True)
assert not list(state.iterdir())

for variable in ("CODEX_HOME", "PYCHARM_PROPERTIES"):
(state / variable).write_text("/custom")
subprocess.run(["/bin/sh", "-c", script], env=env, check=True)
assert {path.read_text() for path in state.iterdir()} == {"/custom"}


def test_lmsh_targets_are_deny_by_default():
ignored = text(".chezmoiignore")
lmsh = ignored.split('{{ if eq .machine_type "lmsh" }}', 1)[1].split("{{ end }}", 1)[0]
Expand Down
Loading