fix(tools/_lib): /etc/mosaic host-level fallback for credential resolution #700

Merged
jason.woltje merged 2 commits from fix/credentials-system-fallback into main 2026-07-10 01:57:13 +00:00
Owner

Closes #699.

What

Credential-file resolution in the shared loader gains a host-level fallback and the resolved path is exported:

  1. $MOSAIC_CREDENTIALS_FILE — explicit override, never second-guessed
  2. $HOME/.config/mosaic/credentials.json
  3. /etc/mosaic/credentials.json — host-level fallback (new)

Same chain applied to the inline resolution in git/detect-platform.sh (get_gitea_service_for_host); health/stack-health.sh inherits via the lib it already sources. get_gitea_token sources the lib in a subshell, so it is covered by the lib change.

Why

HOME-redirected profile environments (Hermes profile homes: HOME=.../profiles/<name>/home) make every $HOME-relative default resolve into an empty profile directory — observed in production on the USC host, where all Mosaic wrappers failed auth while tea (own token store) worked. The workaround was exporting MOSAIC_CREDENTIALS_FILE per invocation; this makes one host-level symlink sufficient instead.

Deliberately NOT hardcoding any operator-specific repo path (e.g. a jarvis-brain checkout) into the framework — that stays operator-side as the symlink target.

Verification

  • bash -n + shellcheck -S warning clean on both files
  • Behavior tests: explicit override honored verbatim; unset-var + missing candidates falls through to the prior $HOME default (no behavior change on healthy hosts)
  • tools/git/test-gitea-login-resolution.sh regression harness: passed

🤖 Generated with Claude Code

https://claude.ai/code/session_01RMoEx7hfdFGjUiCHuN1RRi

Closes #699. ## What Credential-file resolution in the shared loader gains a host-level fallback and the resolved path is exported: 1. `$MOSAIC_CREDENTIALS_FILE` — explicit override, never second-guessed 2. `$HOME/.config/mosaic/credentials.json` 3. `/etc/mosaic/credentials.json` — host-level fallback (new) Same chain applied to the inline resolution in `git/detect-platform.sh` (`get_gitea_service_for_host`); `health/stack-health.sh` inherits via the lib it already sources. `get_gitea_token` sources the lib in a subshell, so it is covered by the lib change. ## Why HOME-redirected profile environments (Hermes profile homes: `HOME=.../profiles/<name>/home`) make every `$HOME`-relative default resolve into an empty profile directory — observed in production on the USC host, where all Mosaic wrappers failed auth while `tea` (own token store) worked. The workaround was exporting `MOSAIC_CREDENTIALS_FILE` per invocation; this makes one host-level symlink sufficient instead. Deliberately NOT hardcoding any operator-specific repo path (e.g. a jarvis-brain checkout) into the framework — that stays operator-side as the symlink target. ## Verification - `bash -n` + `shellcheck -S warning` clean on both files - Behavior tests: explicit override honored verbatim; unset-var + missing candidates falls through to the prior `$HOME` default (no behavior change on healthy hosts) - `tools/git/test-gitea-login-resolution.sh` regression harness: passed 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01RMoEx7hfdFGjUiCHuN1RRi
jason.woltje added 1 commit 2026-07-09 21:45:10 +00:00
fix(tools/_lib): add /etc/mosaic host-level fallback to credential resolution
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
806b6f52e1
Under HOME-redirected profile environments (e.g. Hermes profile homes),
$HOME-relative resolution lands in an empty profile directory and every
Mosaic tool fails auth unless MOSAIC_CREDENTIALS_FILE is exported per
invocation.

Resolution chain is now: explicit MOSAIC_CREDENTIALS_FILE (never
second-guessed) -> $HOME/.config/mosaic/credentials.json ->
/etc/mosaic/credentials.json. The resolved path is exported so sourcing
scripts (stack-health.sh) and subprocesses inherit it; the inline site
in detect-platform.sh gets the same chain.

Operator-side per affected host: one-time symlink of
/etc/mosaic/credentials.json to the canonical file. No operator-specific
paths enter the framework.

Closes #699

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RMoEx7hfdFGjUiCHuN1RRi
Author
Owner

VERIFIED REQUEST CHANGES — reviewer-of-record [W-jarvis:reviewer] head 806b6f52e1

Reviewed PR #700 against main at exact head 806b6f52e1ccac8cead371c7f3e74ac58ac2bd42.

Blocking finding:

  • packages/mosaic/framework/tools/git/detect-platform.sh does not exactly match the _lib/credentials.sh resolution chain for the "neither candidate exists" case. _lib/credentials.sh checks $HOME/.config/mosaic/credentials.json, then /etc/mosaic/credentials.json, then preserves the prior default of $HOME/.config/mosaic/credentials.json if neither file exists. The inline detect-platform.sh chain currently does $HOME/.config/mosaic/credentials.json then unconditionally switches to /etc/mosaic/credentials.json when the HOME file is absent, even if /etc is also absent. That violates the requested no-drift criterion and contradicts the PR-body claim that "unset-var + missing candidates falls through to the prior $HOME default."

Suggested minimal fix:

cred_file="$HOME/.config/mosaic/credentials.json"
if [[ ! -f "$cred_file" && -f "/etc/mosaic/credentials.json" ]]; then
    cred_file="/etc/mosaic/credentials.json"
fi

Positive checks completed:

  • Exact-head and branch ref verified: PR ref and fix/credentials-system-fallback both resolve to 806b6f52e1ccac8cead371c7f3e74ac58ac2bd42.
  • Scope verified: diff is confined to the two expected files under packages/mosaic/framework/tools/; git diff --check origin/main...origin/pr/700 clean.
  • _lib/credentials.sh precedence is sane and documented: explicit MOSAIC_CREDENTIALS_FILE remains supreme; user-level $HOME/.config/mosaic/credentials.json wins before /etc/mosaic/credentials.json; resolved path is exported.
  • No new credential values are printed by the changed resolution code; only the credential file path is exported/loggable. No operator-specific hardcoded path beyond /etc/mosaic/credentials.json was introduced.
  • Local verification: bash -n passed on both changed scripts and tools/git/test-gitea-login-resolution.sh; regression harness passed. Manual path-resolution checks verified explicit override, HOME default, and missing-candidates default behavior in _lib/credentials.sh.
  • CI checked: Woodpecker #1663/#1664 are overall success on this head.

No merge performed by reviewer.

VERIFIED REQUEST CHANGES — reviewer-of-record [W-jarvis:reviewer] head 806b6f52e1ccac8cead371c7f3e74ac58ac2bd42 Reviewed PR #700 against `main` at exact head `806b6f52e1ccac8cead371c7f3e74ac58ac2bd42`. Blocking finding: - `packages/mosaic/framework/tools/git/detect-platform.sh` does not exactly match the `_lib/credentials.sh` resolution chain for the "neither candidate exists" case. `_lib/credentials.sh` checks `$HOME/.config/mosaic/credentials.json`, then `/etc/mosaic/credentials.json`, then preserves the prior default of `$HOME/.config/mosaic/credentials.json` if neither file exists. The inline `detect-platform.sh` chain currently does `$HOME/.config/mosaic/credentials.json` then unconditionally switches to `/etc/mosaic/credentials.json` when the HOME file is absent, even if `/etc` is also absent. That violates the requested no-drift criterion and contradicts the PR-body claim that "unset-var + missing candidates falls through to the prior `$HOME` default." Suggested minimal fix: ```bash cred_file="$HOME/.config/mosaic/credentials.json" if [[ ! -f "$cred_file" && -f "/etc/mosaic/credentials.json" ]]; then cred_file="/etc/mosaic/credentials.json" fi ``` Positive checks completed: - Exact-head and branch ref verified: PR ref and `fix/credentials-system-fallback` both resolve to `806b6f52e1ccac8cead371c7f3e74ac58ac2bd42`. - Scope verified: diff is confined to the two expected files under `packages/mosaic/framework/tools/`; `git diff --check origin/main...origin/pr/700` clean. - `_lib/credentials.sh` precedence is sane and documented: explicit `MOSAIC_CREDENTIALS_FILE` remains supreme; user-level `$HOME/.config/mosaic/credentials.json` wins before `/etc/mosaic/credentials.json`; resolved path is exported. - No new credential values are printed by the changed resolution code; only the credential file path is exported/loggable. No operator-specific hardcoded path beyond `/etc/mosaic/credentials.json` was introduced. - Local verification: `bash -n` passed on both changed scripts and `tools/git/test-gitea-login-resolution.sh`; regression harness passed. Manual path-resolution checks verified explicit override, HOME default, and missing-candidates default behavior in `_lib/credentials.sh`. - CI checked: Woodpecker #1663/#1664 are overall success on this head. No merge performed by reviewer.
jason.woltje added 1 commit 2026-07-10 01:31:34 +00:00
fix(tools/git): guard /etc fallback in detect-platform inline chain
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
c2099114a8
Review catch on #700: the inline chain switched to /etc/mosaic/
credentials.json unconditionally when the HOME file was absent, even if
/etc was also absent — diverging from _lib/credentials.sh, which keeps
the $HOME default when neither candidate exists. The /etc switch is now
guarded by existence, restoring lib/inline parity.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RMoEx7hfdFGjUiCHuN1RRi
Author
Owner

VERIFIED APPROVE — reviewer-of-record [W-jarvis:reviewer] head c2099114a8

Re-reviewed PR #700 remediation against main at exact head c2099114a8dc7576faa3e19e861400ab125902e4.

Evidence:

  • Delta from prior reviewed head 806b6f52e1ccac8cead371c7f3e74ac58ac2bd42..c2099114a8dc7576faa3e19e861400ab125902e4 is confined to packages/mosaic/framework/tools/git/detect-platform.sh (1 file, +6/-2): only the /etc/mosaic/credentials.json switch is now guarded with -f, plus the parity-rule comment.
  • Full PR scope remains confined to packages/mosaic/framework/tools/_lib/credentials.sh and packages/mosaic/framework/tools/git/detect-platform.sh; git diff --check origin/main...origin/pr/700 clean.
  • Precedence/parity verified:
    • env-set: explicit MOSAIC_CREDENTIALS_FILE remains supreme in both lib and inline path.
    • HOME-only: $HOME/.config/mosaic/credentials.json wins.
    • /etc-only: inline chain now switches to /etc/mosaic/credentials.json only when the HOME file is absent and /etc/mosaic/credentials.json exists, matching the lib loop.
    • neither exists: inline chain now keeps $HOME/.config/mosaic/credentials.json, matching the lib’s final := fallback and preserving prior behavior.
  • Security checks from prior pass still hold for untouched code: no credential values are echoed/logged/exported by the changed resolution code; only the credential file path is exported. No operator-specific credential path was hardcoded beyond /etc/mosaic/credentials.json.
  • Local verification: bash -n passed on changed scripts plus tools/git/test-gitea-login-resolution.sh; regression harness passed. Manual/traced path-resolution checks verified env-set, HOME-only, and neither-exists cases; /etc-only parity follows directly from the guarded -f /etc/mosaic/credentials.json branch.
  • CI verified: Woodpecker PR pipeline #1668 terminal green on c2099114a8dc; push pipeline #1667 also green on the same head.

No merge performed by reviewer.

VERIFIED APPROVE — reviewer-of-record [W-jarvis:reviewer] head c2099114a8dc7576faa3e19e861400ab125902e4 Re-reviewed PR #700 remediation against `main` at exact head `c2099114a8dc7576faa3e19e861400ab125902e4`. Evidence: - Delta from prior reviewed head `806b6f52e1ccac8cead371c7f3e74ac58ac2bd42..c2099114a8dc7576faa3e19e861400ab125902e4` is confined to `packages/mosaic/framework/tools/git/detect-platform.sh` (`1 file`, `+6/-2`): only the `/etc/mosaic/credentials.json` switch is now guarded with `-f`, plus the parity-rule comment. - Full PR scope remains confined to `packages/mosaic/framework/tools/_lib/credentials.sh` and `packages/mosaic/framework/tools/git/detect-platform.sh`; `git diff --check origin/main...origin/pr/700` clean. - Precedence/parity verified: - env-set: explicit `MOSAIC_CREDENTIALS_FILE` remains supreme in both lib and inline path. - HOME-only: `$HOME/.config/mosaic/credentials.json` wins. - `/etc`-only: inline chain now switches to `/etc/mosaic/credentials.json` only when the HOME file is absent and `/etc/mosaic/credentials.json` exists, matching the lib loop. - neither exists: inline chain now keeps `$HOME/.config/mosaic/credentials.json`, matching the lib’s final `:=` fallback and preserving prior behavior. - Security checks from prior pass still hold for untouched code: no credential values are echoed/logged/exported by the changed resolution code; only the credential file path is exported. No operator-specific credential path was hardcoded beyond `/etc/mosaic/credentials.json`. - Local verification: `bash -n` passed on changed scripts plus `tools/git/test-gitea-login-resolution.sh`; regression harness passed. Manual/traced path-resolution checks verified env-set, HOME-only, and neither-exists cases; `/etc`-only parity follows directly from the guarded `-f /etc/mosaic/credentials.json` branch. - CI verified: Woodpecker PR pipeline #1668 terminal green on `c2099114a8dc`; push pipeline #1667 also green on the same head. No merge performed by reviewer.
jason.woltje merged commit 4df38f7e81 into main 2026-07-10 01:57:13 +00:00
jason.woltje deleted branch fix/credentials-system-fallback 2026-07-10 01:57:14 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#700