This commit is contained in:
@@ -24,6 +24,14 @@
|
||||
# $HOME points at a per-profile directory that has no credentials file.
|
||||
# Operators symlink /etc/mosaic/credentials.json to the host's canonical
|
||||
# file once, instead of exporting MOSAIC_CREDENTIALS_FILE per invocation.
|
||||
#
|
||||
# Gitea has one additional identity-aware path. When the resolved git identity
|
||||
# names a fleet seat, gitea-mosaicstack and gitea-usc obtain the token through
|
||||
# tools/git/git-credential-mosaic rather than reading a slot directly. That
|
||||
# production entrypoint enforces the clean-environment and process-ancestry
|
||||
# fence before it reads a seat slot. A seat-slot miss is terminal: this loader
|
||||
# never substitutes the service-store token for it. URLs remain provider
|
||||
# configuration and continue to come from this loader's service store.
|
||||
|
||||
if [[ -z "${MOSAIC_CREDENTIALS_FILE:-}" ]]; then
|
||||
for _cand in "$HOME/.config/mosaic/credentials.json" "/etc/mosaic/credentials.json"; do
|
||||
@@ -94,6 +102,85 @@ _mosaic_load_woodpecker_legacy() {
|
||||
_mosaic_sync_woodpecker_env "$WOODPECKER_INSTANCE" "$WOODPECKER_URL" "$WOODPECKER_TOKEN"
|
||||
}
|
||||
|
||||
_mosaic_resolve_git_identity() {
|
||||
local ident="${MOSAIC_GIT_IDENTITY:-}"
|
||||
if [[ -z "$ident" ]]; then
|
||||
ident="$(git config --get mosaic.gitIdentity 2>/dev/null || true)"
|
||||
fi
|
||||
printf '%s' "$ident"
|
||||
}
|
||||
|
||||
_mosaic_git_identity_is_seat() {
|
||||
local ident="$1" brain_home
|
||||
[[ -n "$ident" ]] || return 1
|
||||
brain_home="${MOSAIC_BRAIN_HOME:-$HOME/.mosaic}"
|
||||
[[ -d "$brain_home/fleet/agents/$ident" ]]
|
||||
}
|
||||
|
||||
_mosaic_gitea_seat_token_from_helper() {
|
||||
# Use the production wrapper, not its Bash implementation. The wrapper
|
||||
# removes BASH_ENV/function injection before the implementation evaluates
|
||||
# MOSAIC_AGENT_NAME ancestry, so a loader consumer cannot bypass that fence.
|
||||
local host="$1" ident="$2" script_dir helper response key value
|
||||
local username="" password="" username_seen=0 password_seen=0
|
||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
helper="$script_dir/../git/git-credential-mosaic"
|
||||
|
||||
if [[ ! -x "$helper" ]]; then
|
||||
echo "Error: Gitea seat credential helper is unavailable: $helper" >&2
|
||||
return 1
|
||||
fi
|
||||
if ! response="$(printf 'protocol=https\nhost=%s\n\n' "$host" | "$helper" get)"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
while IFS='=' read -r key value; do
|
||||
[[ -n "$key" ]] || continue
|
||||
case "$key" in
|
||||
username)
|
||||
if (( username_seen )); then
|
||||
echo 'Error: Gitea seat credential helper returned duplicate username fields' >&2
|
||||
return 1
|
||||
fi
|
||||
username="$value"
|
||||
username_seen=1
|
||||
;;
|
||||
password)
|
||||
if (( password_seen )); then
|
||||
echo 'Error: Gitea seat credential helper returned duplicate password fields' >&2
|
||||
return 1
|
||||
fi
|
||||
password="$value"
|
||||
password_seen=1
|
||||
;;
|
||||
*)
|
||||
echo 'Error: Gitea seat credential helper returned an invalid protocol field' >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
done <<< "$response"
|
||||
|
||||
if [[ "$username_seen" -ne 1 || "$password_seen" -ne 1 || "$username" != "$ident" || -z "$password" ]]; then
|
||||
echo "Error: Gitea seat credential helper did not return a valid credential for '$ident'" >&2
|
||||
return 1
|
||||
fi
|
||||
printf '%s' "$password"
|
||||
}
|
||||
|
||||
_mosaic_gitea_token() {
|
||||
# $1 is the Gitea host and $2 is the legacy service-store jq path.
|
||||
# Seats are delegated to the fenced helper; all other identities retain the
|
||||
# existing service-store behavior. A failed seat delegation returns nonzero
|
||||
# to the caller and deliberately cannot fall through to _mosaic_read_cred.
|
||||
local host="$1" service_jq_path="$2" ident
|
||||
ident="$(_mosaic_resolve_git_identity)"
|
||||
if _mosaic_git_identity_is_seat "$ident"; then
|
||||
_mosaic_gitea_seat_token_from_helper "$host" "$ident"
|
||||
return
|
||||
fi
|
||||
_mosaic_read_cred "$service_jq_path"
|
||||
}
|
||||
|
||||
load_credentials() {
|
||||
local service="$1"
|
||||
|
||||
@@ -183,16 +270,31 @@ EOF
|
||||
;;
|
||||
gitea-mosaicstack)
|
||||
export GITEA_URL="${GITEA_URL:-$(_mosaic_read_cred '.gitea.mosaicstack.url')}"
|
||||
export GITEA_TOKEN="${GITEA_TOKEN:-$(_mosaic_read_cred '.gitea.mosaicstack.token')}"
|
||||
GITEA_URL="${GITEA_URL%/}"
|
||||
[[ -n "$GITEA_URL" ]] || { echo "Error: gitea.mosaicstack.url not found" >&2; return 1; }
|
||||
# An explicit caller value retains the loader's established precedence.
|
||||
# Otherwise, a known seat delegates to the ancestry-fenced helper and a
|
||||
# non-seat identity uses the established service-store lookup.
|
||||
if [[ -z "${GITEA_TOKEN:-}" ]]; then
|
||||
local _gitea_token
|
||||
_gitea_token="$(_mosaic_gitea_token 'git.mosaicstack.dev' '.gitea.mosaicstack.token')" || return 1
|
||||
GITEA_TOKEN="$_gitea_token"
|
||||
fi
|
||||
# Preserve the loader's contract even when the caller supplied an
|
||||
# unexported shell variable before invoking load_credentials.
|
||||
export GITEA_TOKEN
|
||||
[[ -n "$GITEA_TOKEN" ]] || { echo "Error: gitea.mosaicstack.token not found" >&2; return 1; }
|
||||
;;
|
||||
gitea-usc)
|
||||
export GITEA_URL="${GITEA_URL:-$(_mosaic_read_cred '.gitea.usc.url')}"
|
||||
export GITEA_TOKEN="${GITEA_TOKEN:-$(_mosaic_read_cred '.gitea.usc.token')}"
|
||||
GITEA_URL="${GITEA_URL%/}"
|
||||
[[ -n "$GITEA_URL" ]] || { echo "Error: gitea.usc.url not found" >&2; return 1; }
|
||||
if [[ -z "${GITEA_TOKEN:-}" ]]; then
|
||||
local _gitea_token
|
||||
_gitea_token="$(_mosaic_gitea_token 'git.uscllc.com' '.gitea.usc.token')" || return 1
|
||||
GITEA_TOKEN="$_gitea_token"
|
||||
fi
|
||||
export GITEA_TOKEN
|
||||
[[ -n "$GITEA_TOKEN" ]] || { echo "Error: gitea.usc.token not found" >&2; return 1; }
|
||||
;;
|
||||
woodpecker-*)
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
#!/usr/bin/env bash
|
||||
# Hermetic regression for load_credentials Gitea seat-slot resolution.
|
||||
#
|
||||
# It runs against copied framework tools under a fake HOME, fixture credentials,
|
||||
# and fake seat slots only. No real credential path is read.
|
||||
#
|
||||
# Contract pinned here:
|
||||
# G1-G3 an ancestry-owned seat resolves its own host-scoped token through
|
||||
# the production git-credential-mosaic entrypoint, whether identity
|
||||
# comes from env or per-worktree git config.
|
||||
# G4 an owned seat with no slot fails closed and never uses the service
|
||||
# token.
|
||||
# G5 a seat cannot request another seat's slot through load_credentials;
|
||||
# the helper's ancestry fence remains the authorization boundary.
|
||||
# G6/G7 no seat identity and a non-seat identity retain service-store
|
||||
# behavior.
|
||||
# G8-G10 explicitly supplied GITEA_TOKEN values keep their established
|
||||
# precedence and are exported for child tool processes.
|
||||
# G11 non-Gitea services remain unaffected.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
WORK_DIR="${MOSAIC_TEST_WORK_DIR:-$PWD/.mosaic-test-work/credentials-gitea-seats}"
|
||||
FAKE_HOME="$WORK_DIR/home"
|
||||
FRAMEWORK_TOOLS="$FAKE_HOME/.config/mosaic/tools"
|
||||
BRAIN_DIR="$WORK_DIR/brain"
|
||||
REPO_DIR="$WORK_DIR/repo"
|
||||
CREDENTIALS_FILE="$WORK_DIR/credentials.json"
|
||||
SPOOL_DIR="$WORK_DIR/spool"
|
||||
LOADER="$FRAMEWORK_TOOLS/_lib/credentials.sh"
|
||||
HELPER="$FRAMEWORK_TOOLS/git/git-credential-mosaic"
|
||||
|
||||
rm -rf "$WORK_DIR"
|
||||
trap 'rm -rf "$WORK_DIR"' EXIT
|
||||
mkdir -p "$FRAMEWORK_TOOLS/_lib" "$FRAMEWORK_TOOLS/git" "$BRAIN_DIR/fleet/agents" \
|
||||
"$REPO_DIR" "$SPOOL_DIR"
|
||||
|
||||
cp "$SCRIPT_DIR/credentials.sh" "$LOADER"
|
||||
cp "$SCRIPT_DIR/../git/git-credential-mosaic" "$HELPER"
|
||||
cp "$SCRIPT_DIR/../git/git-credential-mosaic.impl" "$FRAMEWORK_TOOLS/git/git-credential-mosaic.impl"
|
||||
chmod +x "$HELPER" "$FRAMEWORK_TOOLS/git/git-credential-mosaic.impl"
|
||||
|
||||
git -C "$REPO_DIR" init -q
|
||||
git -C "$REPO_DIR" config user.name 'Credential seat test'
|
||||
git -C "$REPO_DIR" config user.email '[email protected]'
|
||||
|
||||
cat > "$CREDENTIALS_FILE" <<'JSON'
|
||||
{
|
||||
"gitea": {
|
||||
"mosaicstack": {
|
||||
"url": "https://git.mosaicstack.dev",
|
||||
"token": "fixture-service-token"
|
||||
},
|
||||
"usc": {
|
||||
"url": "https://git.uscllc.com",
|
||||
"token": "fixture-usc-service-token"
|
||||
}
|
||||
},
|
||||
"woodpecker": {
|
||||
"default": "mosaic",
|
||||
"mosaic": {
|
||||
"url": "https://ci.example.invalid",
|
||||
"token": "fixture-woodpecker-token"
|
||||
}
|
||||
}
|
||||
}
|
||||
JSON
|
||||
|
||||
for seat in seat-owner seat-config seat-usc seat-victim empty-seat; do
|
||||
mkdir -p "$BRAIN_DIR/fleet/agents/$seat/secrets"
|
||||
done
|
||||
printf '%s' 'fixture-owner-slot-token' > "$BRAIN_DIR/fleet/agents/seat-owner/secrets/gitea-mosaicstack-seat-owner.token"
|
||||
printf '%s' 'fixture-config-slot-token' > "$BRAIN_DIR/fleet/agents/seat-config/secrets/gitea-mosaicstack-seat-config.token"
|
||||
printf '%s' 'fixture-usc-slot-token' > "$BRAIN_DIR/fleet/agents/seat-usc/secrets/gitea-usc-seat-usc.token"
|
||||
printf '%s' 'fixture-victim-slot-token' > "$BRAIN_DIR/fleet/agents/seat-victim/secrets/gitea-mosaicstack-seat-victim.token"
|
||||
chmod 600 "$BRAIN_DIR"/fleet/agents/*/secrets/*.token
|
||||
|
||||
# Establishes a seat identity in an exec-frozen ancestor. The empty-name root
|
||||
# carries the lineage fence, preventing the helper from seeing this suite's
|
||||
# real parent process outside its hermetic fixture.
|
||||
cat > "$WORK_DIR/lineage-root.sh" <<'ROOT'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
caller="$1"
|
||||
carrier="$2"
|
||||
shift 2
|
||||
env MOSAIC_AGENT_NAME="$caller" PATH="$PATH" HOME="$HOME" \
|
||||
bash "$carrier" "$@"
|
||||
ROOT
|
||||
|
||||
cat > "$WORK_DIR/lineage-carrier.sh" <<'CARRIER'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
loader="$1"
|
||||
brain="$2"
|
||||
credentials="$3"
|
||||
repo="$4"
|
||||
spool="$5"
|
||||
identity_source="$6"
|
||||
target="$7"
|
||||
preexisting_token="$8"
|
||||
service="${9:-gitea-mosaicstack}"
|
||||
|
||||
cd "$repo"
|
||||
unset GITEA_URL GITEA_TOKEN MOSAIC_GIT_IDENTITY
|
||||
git config --unset mosaic.gitIdentity 2>/dev/null || true
|
||||
case "$identity_source" in
|
||||
env) export MOSAIC_GIT_IDENTITY="$target" ;;
|
||||
config) git config mosaic.gitIdentity "$target" ;;
|
||||
none) ;;
|
||||
*) echo "unknown identity source: $identity_source" >&2; exit 2 ;;
|
||||
esac
|
||||
if [[ "$preexisting_token" != '-' ]]; then
|
||||
export GITEA_TOKEN="$preexisting_token"
|
||||
fi
|
||||
export MOSAIC_BRAIN_HOME="$brain"
|
||||
export MOSAIC_CREDENTIALS_FILE="$credentials"
|
||||
export MOSAIC_CREDENTIAL_SPOOL="$spool"
|
||||
# shellcheck source=/dev/null
|
||||
source "$loader"
|
||||
load_credentials "$service"
|
||||
printf 'url=%s\ntoken=%s\n' "$GITEA_URL" "$GITEA_TOKEN"
|
||||
CARRIER
|
||||
chmod +x "$WORK_DIR/lineage-root.sh" "$WORK_DIR/lineage-carrier.sh"
|
||||
|
||||
run_lineage() {
|
||||
local caller="$1" source="$2" target="$3" preset="$4" service="${5:-gitea-mosaicstack}"
|
||||
env -i HOME="$FAKE_HOME" PATH="$PATH" MOSAIC_CREDENTIAL_LINEAGE_FENCE=1 \
|
||||
bash "$WORK_DIR/lineage-root.sh" "$caller" "$WORK_DIR/lineage-carrier.sh" \
|
||||
"$LOADER" "$BRAIN_DIR" "$CREDENTIALS_FILE" "$REPO_DIR" "$SPOOL_DIR" \
|
||||
"$source" "$target" "$preset" "$service"
|
||||
}
|
||||
|
||||
run_plain() {
|
||||
local source="$1" target="$2" preset="$3" service="${4:-gitea-mosaicstack}"
|
||||
env -i HOME="$FAKE_HOME" PATH="$PATH" MOSAIC_BRAIN_HOME="$BRAIN_DIR" \
|
||||
MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" MOSAIC_CREDENTIAL_SPOOL="$SPOOL_DIR" \
|
||||
LOADER="$LOADER" REPO_DIR="$REPO_DIR" IDENTITY_SOURCE="$source" TARGET="$target" \
|
||||
PRESET="$preset" SERVICE="$service" bash -c '
|
||||
set -euo pipefail
|
||||
cd "$REPO_DIR"
|
||||
unset GITEA_URL GITEA_TOKEN MOSAIC_GIT_IDENTITY
|
||||
git config --unset mosaic.gitIdentity 2>/dev/null || true
|
||||
case "$IDENTITY_SOURCE" in
|
||||
env) export MOSAIC_GIT_IDENTITY="$TARGET" ;;
|
||||
config) git config mosaic.gitIdentity "$TARGET" ;;
|
||||
none) ;;
|
||||
*) exit 2 ;;
|
||||
esac
|
||||
if [[ "$PRESET" != "-" ]]; then export GITEA_TOKEN="$PRESET"; fi
|
||||
source "$LOADER"
|
||||
load_credentials "$SERVICE"
|
||||
printf "url=%s\\ntoken=%s\\n" "$GITEA_URL" "$GITEA_TOKEN"
|
||||
'
|
||||
}
|
||||
|
||||
run_unexported_token() {
|
||||
local service="$1"
|
||||
env -i HOME="$FAKE_HOME" PATH="$PATH" MOSAIC_BRAIN_HOME="$BRAIN_DIR" \
|
||||
MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" LOADER="$LOADER" SERVICE="$service" \
|
||||
bash -s <<'UNEXPORTED'
|
||||
set -euo pipefail
|
||||
unset GITEA_URL GITEA_TOKEN MOSAIC_GIT_IDENTITY
|
||||
GITEA_TOKEN='fixture-unexported-token'
|
||||
source "$LOADER"
|
||||
load_credentials "$SERVICE"
|
||||
child_token="$(bash -c 'printf "%s" "${GITEA_TOKEN:-}"')"
|
||||
[[ "$child_token" == "$GITEA_TOKEN" ]] || {
|
||||
echo 'GITEA_TOKEN was not exported to a child process' >&2
|
||||
exit 1
|
||||
}
|
||||
printf 'url=%s\ntoken=%s\n' "$GITEA_URL" "$GITEA_TOKEN"
|
||||
UNEXPORTED
|
||||
}
|
||||
|
||||
fail=0
|
||||
assert_success() {
|
||||
local desc="$1" expected_token="$2" expected_url="$3"
|
||||
shift 3
|
||||
local err="$WORK_DIR/stderr.tmp" out rc token url
|
||||
: > "$err"
|
||||
set +e
|
||||
out=$("$@" 2>"$err")
|
||||
rc=$?
|
||||
set -e
|
||||
if [[ "$rc" -ne 0 ]]; then
|
||||
echo "FAIL: $desc — expected success, got rc=$rc" >&2
|
||||
cat "$err" >&2
|
||||
fail=1
|
||||
return
|
||||
fi
|
||||
token="$(printf '%s\n' "$out" | awk -F= '/^token=/{print substr($0, 7)}')"
|
||||
url="$(printf '%s\n' "$out" | awk -F= '/^url=/{print substr($0, 5)}')"
|
||||
if [[ "$token" != "$expected_token" ]]; then
|
||||
echo "FAIL: $desc — resolved the wrong token source" >&2
|
||||
fail=1
|
||||
fi
|
||||
if [[ "$url" != "$expected_url" ]]; then
|
||||
echo "FAIL: $desc — URL did not come from provider configuration" >&2
|
||||
fail=1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_refused() {
|
||||
local desc="$1" expected_reason="$2"
|
||||
shift 2
|
||||
local err="$WORK_DIR/stderr.tmp" out rc
|
||||
: > "$err"
|
||||
set +e
|
||||
out=$("$@" 2>"$err")
|
||||
rc=$?
|
||||
set -e
|
||||
local diagnostics
|
||||
diagnostics="$(cat "$err")"
|
||||
if [[ "$rc" -eq 0 ]]; then
|
||||
echo "FAIL: $desc — expected refusal, got success" >&2
|
||||
fail=1
|
||||
fi
|
||||
if [[ -n "$out" ]]; then
|
||||
echo "FAIL: $desc — refusal emitted credential output" >&2
|
||||
fail=1
|
||||
fi
|
||||
if [[ "$diagnostics" != *"$expected_reason"* ]]; then
|
||||
echo "FAIL: $desc — refusal did not retain helper reason $expected_reason" >&2
|
||||
fail=1
|
||||
fi
|
||||
if [[ "$out$diagnostics" == *'fixture-service-token'* || "$out$diagnostics" == *'fixture-victim-slot-token'* ]]; then
|
||||
echo "FAIL: $desc — refusal exposed or fell back to another store" >&2
|
||||
fail=1
|
||||
fi
|
||||
}
|
||||
|
||||
# G1: env identity, owning seat, populated Mosaic slot.
|
||||
assert_success 'G1 env-owned seat uses its Mosaic slot through the helper' 'fixture-owner-slot-token' 'https://git.mosaicstack.dev' \
|
||||
run_lineage seat-owner env seat-owner -
|
||||
|
||||
# G2: the same contract when the helper and loader resolve git config identity.
|
||||
assert_success 'G2 config-owned seat uses its Mosaic slot through the helper' 'fixture-config-slot-token' 'https://git.mosaicstack.dev' \
|
||||
run_lineage seat-config config seat-config -
|
||||
|
||||
# G3: the USC arm remains host-scoped rather than borrowing Mosaic credentials.
|
||||
assert_success 'G3 USC-owned seat uses its USC slot through the helper' 'fixture-usc-slot-token' 'https://git.uscllc.com' \
|
||||
run_lineage seat-usc env seat-usc - gitea-usc
|
||||
|
||||
# G4: a seat slot miss must be terminal, never service-store fallback.
|
||||
assert_refused 'G4 empty owned seat refuses without service fallback' 'no-token-for-identity' \
|
||||
run_lineage empty-seat env empty-seat -
|
||||
|
||||
# G5: a child cannot select another seat by rewriting MOSAIC_GIT_IDENTITY.
|
||||
assert_refused 'G5 cross-seat identity is refused by ancestry fencing' 'cross-seat-identity-refused' \
|
||||
run_lineage seat-owner env seat-victim -
|
||||
|
||||
# G6/G7: non-seat paths retain the existing shared service-store behavior.
|
||||
assert_success 'G6 no identity keeps the service-store path' 'fixture-service-token' 'https://git.mosaicstack.dev' \
|
||||
run_plain none '' -
|
||||
assert_success 'G7 non-seat identity keeps the service-store path' 'fixture-service-token' 'https://git.mosaicstack.dev' \
|
||||
run_plain env service-automation -
|
||||
|
||||
# G8: caller-provided env values retain the established loader precedence.
|
||||
assert_success 'G8 explicit GITEA_TOKEN remains caller-owned' 'fixture-preexisting-token' 'https://git.mosaicstack.dev' \
|
||||
run_lineage seat-owner env seat-owner fixture-preexisting-token
|
||||
|
||||
# G9/G10: pre-existing shell variables keep the loader's export contract.
|
||||
assert_success 'G9 unexported Mosaic token reaches child processes' 'fixture-unexported-token' 'https://git.mosaicstack.dev' \
|
||||
run_unexported_token gitea-mosaicstack
|
||||
assert_success 'G10 unexported USC token reaches child processes' 'fixture-unexported-token' 'https://git.uscllc.com' \
|
||||
run_unexported_token gitea-usc
|
||||
|
||||
# G11: only Gitea has seat slots; Woodpecker remains service-scoped.
|
||||
wp_out=$(env -i HOME="$FAKE_HOME" PATH="$PATH" MOSAIC_BRAIN_HOME="$BRAIN_DIR" \
|
||||
MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" MOSAIC_GIT_IDENTITY=seat-owner \
|
||||
LOADER="$LOADER" bash -c '
|
||||
set -euo pipefail
|
||||
unset WOODPECKER_URL WOODPECKER_TOKEN
|
||||
source "$LOADER"
|
||||
load_credentials woodpecker
|
||||
printf "%s|%s" "$WOODPECKER_URL" "$WOODPECKER_TOKEN"
|
||||
')
|
||||
if [[ "$wp_out" != 'https://ci.example.invalid|fixture-woodpecker-token' ]]; then
|
||||
echo 'FAIL: G11 Woodpecker changed under a Gitea seat identity' >&2
|
||||
fail=1
|
||||
fi
|
||||
|
||||
if [[ "$fail" -eq 0 ]]; then
|
||||
echo 'credentials Gitea seat-store regression passed'
|
||||
fi
|
||||
exit "$fail"
|
||||
@@ -44,8 +44,8 @@ account/token configured through `tools/_lib/credentials.sh`. That means every a
|
||||
fleet commits, pushes, and opens PRs under one identity — with no cryptographic
|
||||
separation between an author and a reviewer.
|
||||
|
||||
Both `git-credential-mosaic` and `get_gitea_token()` resolve an optional **per-agent
|
||||
identity**:
|
||||
`git-credential-mosaic`, `get_gitea_token()`, and the `gitea-mosaicstack` /
|
||||
`gitea-usc` arms of `load_credentials` resolve an optional **per-agent identity**:
|
||||
|
||||
1. `MOSAIC_GIT_IDENTITY` environment variable, or
|
||||
2. `git config --get mosaic.gitIdentity` (set per-worktree; persists on disk across
|
||||
@@ -65,6 +65,12 @@ The store is chosen by what the identity **is**, not by which file happens to ex
|
||||
`<brain>` is `MOSAIC_BRAIN_HOME` if set, else `~/.mosaic` — the same resolution
|
||||
`packages/mosaic/src/fleet/brain-home.ts` performs.
|
||||
|
||||
The Gitea arms of `load_credentials` obtain a seat token through the production
|
||||
`git-credential-mosaic` entrypoint, rather than reading the slot directly. That retains
|
||||
the entrypoint's clean-environment and process-ancestry fence. The Gitea URL remains
|
||||
provider configuration from the service store. A caller-supplied `GITEA_TOKEN` retains
|
||||
its established environment precedence.
|
||||
|
||||
**There is no precedence between the two stores and no fallback from one to the other.**
|
||||
A seat whose slot is empty is refused even when a same-named token sits in the framework
|
||||
store. One credential lives in exactly one location: a second copy is drift rather than
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user