# Live GitHub Two-Identity Rehearsal

This is a manual end-to-end rehearsal for running Rho through a real GitHub
repository from one local machine while simulating two users.

Use a throwaway private GitHub repository first.

Assumptions:

- `install.sh` has installed the Rho binaries.
- `rho install-shell` has installed shell helpers.
- Your current shell has been reopened or sourced after `rho install-shell`.
- `rho-use` works in your shell.
- You have two GitHub identities or handles: `<owner>` and `<collab>`.

Rho accepts shorthand IDs in commands. In this tutorial `github/$OWNER` means
`rho://id/github/$OWNER`, and `$OWNER/$REPO` means
`rho://repo/github/$OWNER/$REPO`.

## 0. Setup Vars

In any shell:

```bash
export OWNER=<owner>
export COLLAB=<collab>
export REPO=rho-live-tutorial-test
export OWNER_GH_HOST=github-$OWNER
export COLLAB_GH_HOST=github-$COLLAB
export OWNER_GH_URL=git@$OWNER_GH_HOST:$OWNER/$REPO.git
export COLLAB_GH_URL=git@$COLLAB_GH_HOST:$OWNER/$REPO.git
export COLLAB_FORK_URL=git@$COLLAB_GH_HOST:$COLLAB/$REPO.git
export RHO_REPO_ID=$OWNER/$REPO
```

Configure SSH so Git uses the intended GitHub account for each clone and push.
`rho-use` selects the active Rho identity; it does not change GitHub auth.
`rho gh switch` selects the active `gh` account, and `rho commit` commits with
the commit name/email from the active Rho identity if one is bound, otherwise
from the active `gh` account.

```sshconfig
Host github-<owner>
  HostName github.com
  User git
  IdentityFile ~/.ssh/<owner-github-key>
  IdentitiesOnly yes

Host github-<collab>
  HostName github.com
  User git
  IdentityFile ~/.ssh/<collab-github-key>
  IdentitiesOnly yes
```

Create the GitHub repo as the owner through the GitHub UI, or with `gh`:

```bash
rho gh switch "$OWNER"
gh repo create "$OWNER/$REPO" --private --confirm
```

For a private repository, the collaborator GitHub account must be able to read
the repo before it can clone or fork it. Invite the collaborator as a GitHub
outside collaborator, or use a public throwaway repository. Rho membership is
separate from GitHub repository access.

## 1. Owner: Init Repo

Terminal 1:

```bash
rho-use github/$OWNER
rho gh switch "$OWNER"

mkdir -p ~/rho-tutorial
cd ~/rho-tutorial
git clone "$OWNER_GH_URL" owner-main
cd owner-main

rho id init github/$OWNER
```

`rho id init` prints a `github profile proof URL` like:

```text
https://github.com/<owner>#rho.claim/id/github/<owner>/key/ssh-ed25519/sha256-...
```

It also asks whether to bind the active `gh` account for future `rho commit`
commands. Accept the default if the active `gh` account is the same person as
the Rho identity you just created.

Copy that full URL into your public GitHub profile where it is visible on
`https://github.com/<owner>`. The fragment starting with `rho.claim/...` is the
claim Rho verifies.

Verify the identity, initialize the repo using detected defaults, add yourself
as a participant, and protect your inbox:

```bash
rho id verify-github --identity github/$OWNER
rho id list

rho repo init --repo-id "$RHO_REPO_ID" --yes
rho add user github/$OWNER

rho repo protect-path "rho/messages/inbox/$OWNER/**" \
  --recipient github/$OWNER
```

Before the first commit, prove the protected inbox is encrypted in Git while
still readable in your working tree:

```bash
mkdir -p "rho/messages/inbox/$OWNER/self-test"
cat > "rho/messages/inbox/$OWNER/self-test/message.yaml" <<EOF
version: 1
message:
  from: rho://id/github/$OWNER
  to: rho://id/github/$OWNER
  type: self-test
  body: "hello encrypted rho inbox"
EOF

git add "rho/messages/inbox/$OWNER/self-test/message.yaml"

# Plaintext working-tree view for the active local identity.
cat "rho/messages/inbox/$OWNER/self-test/message.yaml"

# Rho storage view: what Git stages after the clean filter runs.
rho status
rho crypto view "rho/messages/inbox/$OWNER/self-test/message.yaml"
```

`cat path` shows the local working-tree file, so it remains plaintext for the
active identity. `rho crypto view path` is the Rho equivalent of `git show
:path` for encrypted storage. It should show a `rho_recipient_envelope` with
ciphertext, not the plaintext message body. By default `rho-use` uses the
shared `~/.rho` home unless you pass `--home`.

If a GUI Git client such as SourceTree still reports a `.rho-env` path, reinstall
the repo filters from this repository:

```bash
rho-use github/$OWNER
rho repo protect-path "rho/messages/inbox/$OWNER/**" \
  --recipient github/$OWNER

git config --local --get-regexp '^filter\.rho-crypt\.'
```

The filter commands should point at `~/.rho`, or the explicit home you chose,
not an old `.rho-env` directory.

Commit and push:

```bash
git add .
rho commit -m "Initialize Rho project"

# Committed storage view: the Git object is encrypted.
git show HEAD:rho/messages/inbox/$OWNER/self-test/message.yaml

# Working-tree view: the active identity still sees plaintext.
cat "rho/messages/inbox/$OWNER/self-test/message.yaml"

git push origin main
```

## 2. Collaborator: Join PR

Terminal 2:

```bash
rho-use github/$COLLAB

cd ~/rho-tutorial
rho gh switch "$COLLAB"
gh repo fork "$OWNER/$REPO" --clone=false
git clone "$COLLAB_GH_URL" collab-join
cd collab-join
git remote rename origin upstream
git remote add origin "$COLLAB_FORK_URL"

rho repo init --yes

rho id init github/$COLLAB
```

Copy the full `github profile proof URL` from `rho id init` into the
collaborator GitHub profile. If prompted, bind the active collaborator `gh`
account so `rho commit` uses the collaborator commit author. Then verify it
before committing the participant file:

```bash
rho id verify-github --identity github/$COLLAB
rho id list

git checkout -b "$COLLAB/join-rho"
rho add user github/$COLLAB

git add rho/participants/$COLLAB.yaml rho/membership.yaml
rho commit -m "Request Rho access for $COLLAB"
git push origin "$COLLAB/join-rho"
```

Open a PR on GitHub from `$COLLAB:$COLLAB/join-rho` into `$OWNER:main`.

## 3. Owner: Admit Collaborator

Terminal 1:

```bash
rho-use github/$OWNER
rho gh switch "$OWNER"

cd ~/rho-tutorial
git clone "$OWNER_GH_URL" owner-review-join
cd owner-review-join
rho repo init --yes

git fetch "$COLLAB_FORK_URL" "$COLLAB/join-rho"
git checkout -b "review/$COLLAB/join-rho" FETCH_HEAD

rho id show github/$COLLAB
rho id verify-github --identity github/$COLLAB
rho add user github/$COLLAB

rho repo protect-path "rho/messages/inbox/$COLLAB/**" \
  --recipient github/$COLLAB

rho repo doctor

git add .gitattributes rho
rho commit -m "Admit $COLLAB to Rho project"
git push origin "review/$COLLAB/join-rho"
```

Merge that PR into `main`.

## 4. Owner: Add Mock Dataset

Terminal 1:

```bash
rho-use github/$OWNER
rho gh switch "$OWNER"

cd ~/rho-tutorial
git clone "$OWNER_GH_URL" owner-data
cd owner-data
git pull origin main
rho repo init --yes

mkdir -p data/private data/mock
cat > data/private/prices-real.csv <<'EOF'
date,symbol,price
2026-01-01,A,100.01
2026-01-02,B,200.02
2026-01-03,C,813.00
EOF

cat > data/mock/prices-mock.csv <<'EOF'
date,symbol,price
2026-01-01,A,10.00
2026-01-02,B,20.00
EOF

UUID="$(uuidgen | tr '[:upper:]' '[:lower:]')"

rho dataset \
  --name prices \
  --owner github/$OWNER \
  --real data/private/prices-real.csv \
  --mock data/mock/prices-mock.csv \
  --share-dir users/$OWNER/datasets/share \
  --private-dir users/$OWNER/datasets/private \
  --uuid "$UUID" \
  --description "Tutorial prices dataset"

rho publish "$OWNER" "$UUID" --source-root users --target-root datasets

git add datasets
rho commit -m "Publish prices mock dataset"
git push origin main

echo "$UUID"
```

Save the printed `UUID`.

## 5. Collaborator: Submit Request

Terminal 2:

```bash
rho-use github/$COLLAB

cd ~/rho-tutorial
rho gh switch "$COLLAB"
git clone "$COLLAB_GH_URL" collab-request
cd collab-request
git remote rename origin upstream
git remote add origin "$COLLAB_FORK_URL"
git pull origin main
rho repo init --yes

git checkout -b "$COLLAB/prices-request"

mkdir -p workspace "rho/messages/inbox/$OWNER/req-$UUID"

cat > workspace/sum_prices.py <<'EOF'
import csv
import sys
from decimal import Decimal

total = Decimal("0")
with open(sys.argv[1], newline="") as f:
    for row in csv.DictReader(f):
        total += Decimal(row["price"])
print(f"{total:.2f}")
EOF

cat > "rho/messages/inbox/$OWNER/req-$UUID/request.yaml" <<EOF
version: 1
request:
  id: req-$UUID
  from: rho://id/github/$COLLAB
  to: rho://id/github/$OWNER
  type: private-analysis
  dataset_uuid: $UUID
  code_path: rho://repo/workspace/sum_prices.py
  note: Please run this price sum on real data.
EOF

rho crypto sign "rho/messages/inbox/$OWNER/req-$UUID/request.yaml" \
  --out "rho/messages/inbox/$OWNER/req-$UUID/request.rhosig.yaml"

git add workspace rho/messages
rho commit -m "Request private prices analysis"
git push origin "$COLLAB/prices-request"
```

Open a PR from `$COLLAB:$COLLAB/prices-request` into `$OWNER:main`.

## 6. Owner: Review And Run

Terminal 1:

```bash
rho-use github/$OWNER
rho gh switch "$OWNER"

cd ~/rho-tutorial
git clone "$OWNER_GH_URL" owner-review-run
cd owner-review-run
rho repo init --yes

git fetch "$COLLAB_FORK_URL" "$COLLAB/prices-request"
git checkout -b "review/$COLLAB/prices-request" FETCH_HEAD

rho crypto verify "rho/messages/inbox/$OWNER/req-$UUID/request.yaml" \
  --signature "rho/messages/inbox/$OWNER/req-$UUID/request.rhosig.yaml" \
  --identity github/$COLLAB

python3 workspace/sum_prices.py "datasets/$UUID/mock/prices-mock.csv"
```

Create the execution workspace:

```bash
cd ~/rho-tutorial
export EXEC_ROOT="$PWD/owner-exec"
export OWNER_DATA_ROOT="$PWD/owner-data/users"
export OWNER_PRIVATE_ROOT="$OWNER_DATA_ROOT/$OWNER/datasets/private"

mkdir -p "$EXEC_ROOT/workspace" "$EXEC_ROOT/control/proposals" "$EXEC_ROOT/control/outbox" "$EXEC_ROOT/control/inbox"
cp owner-review-run/workspace/sum_prices.py "$EXEC_ROOT/workspace/"

rho publish "$OWNER" "$UUID" \
  --source-root "$OWNER_DATA_ROOT" \
  --target-root "$EXEC_ROOT/datasets"

rho tools install-minimal --shared-root "$EXEC_ROOT" --owner github/$OWNER

rho request create-run \
  --shared-root "$EXEC_ROOT" \
  --id "req-$UUID" \
  --from github/$COLLAB \
  --to github/$OWNER \
  --tool-id run_real \
  --dataset-uuid "$UUID" \
  --code-path "$EXEC_ROOT/workspace/sum_prices.py" \
  --command "python3 sum_prices.py DATASET_CSV" \
  --tier real
```

The collaborator sends only the request and code. The owner constructs this
proposal locally on the trusted side after review:

```bash
cat > "$EXEC_ROOT/control/proposals/act-run-real-$UUID.yaml" <<EOF
version: 1
proposed_action:
  action_id: act-run-real-$UUID
  request_id: req-$UUID
  tool_id: run_real
  requested_by: github/$COLLAB
  requested_for: github/$OWNER
  action_type: run_real_data
  script_path: /exec/workspace/sum_prices.py
  output_path: /exec/.rho/runs/run-run-real-$UUID/stdout.txt
  summary: Run reviewed sum_prices.py on real data.
  reason: Request signature and script were reviewed.
EOF

rho run proposal-action \
  --proposal "$EXEC_ROOT/control/proposals/act-run-real-$UUID.yaml" \
  --control-root "$EXEC_ROOT/control" \
  --action-id "act-run-real-$UUID" \
  --map "/exec=$EXEC_ROOT" \
  --script-root "$EXEC_ROOT/workspace" \
  --output-root "$EXEC_ROOT/.rho/runs" \
  --allow-tool run_real \
  --allow-requested-by github/$COLLAB \
  --allow-requested-for github/$OWNER \
  --allow-action-type run_real_data

rho run grant-action \
  --shared-root "$EXEC_ROOT" \
  --control-root "$EXEC_ROOT/control" \
  --action-id "act-run-real-$UUID" \
  --granted-by github/$OWNER \
  --input "code:$EXEC_ROOT/workspace/sum_prices.py"

RHO_SANDBOX_RUNNER=gondolin rho run controlled-action \
  --shared-root "$EXEC_ROOT" \
  --control-root "$EXEC_ROOT/control" \
  --action-id "act-run-real-$UUID" \
  --private-root "$OWNER_PRIVATE_ROOT"
```

Check the run output:

```bash
cat "$EXEC_ROOT/.rho/runs/run-run-real-$UUID/stdout.txt"
```

Expected:

```text
1113.03
```

After the owner has reviewed the request, verified the signature, and completed
the protected run, merge the request PR into `main`. Create the result release
branch from that updated `main` so the committed result follows the committed
request in Git history.

## 7. Owner: Release Result

Terminal 1:

```bash
rho-use github/$OWNER
rho gh switch "$OWNER"

cd ~/rho-tutorial
git clone "$OWNER_GH_URL" owner-release
cd owner-release
rho repo init --yes

git checkout -b "$OWNER/result-$UUID"

RESULT_DIR="rho/messages/inbox/$COLLAB/req-$UUID"
RUN_ID="run-run-real-$UUID"
mkdir -p "$RESULT_DIR"
RESULT_VALUE="$(cat "$EXEC_ROOT/.rho/runs/$RUN_ID/stdout.txt")"

cat > "$RESULT_DIR/result.yaml" <<EOF
version: 1
result:
  request_id: req-$UUID
  from: rho://id/github/$OWNER
  to: rho://id/github/$COLLAB
  output: "$RESULT_VALUE"
  run_id: $RUN_ID
  runner: gondolin
  note: encrypted result released from approved sandbox run
EOF

rho crypto sign "$RESULT_DIR/result.yaml" \
  --identity github/$OWNER \
  --out "$RESULT_DIR/result.rhosig.yaml"

git add "$RESULT_DIR"

# Confirm the staged Git storage is encrypted and does not expose the result.
rho status
git show ":$RESULT_DIR/result.yaml"

rho commit -m "Release encrypted prices result"
git push origin "$OWNER/result-$UUID"
```

Open a PR from `$OWNER:$OWNER/result-$UUID` into `$OWNER:main`. After the
collaborator has verified it, merge the result PR.

## 8. Collaborator: Verify Result

Terminal 2:

```bash
rho-use github/$COLLAB
rho gh switch "$COLLAB"

cd ~/rho-tutorial
git clone "$COLLAB_GH_URL" collab-result-review
cd collab-result-review
rho repo init --yes

git pull origin main

RESULT_DIR="rho/messages/inbox/$COLLAB/req-$UUID"

# A fresh clone checks files out before Rho installs the Git filters. Refresh
# the result files after `rho repo init --yes` so Git applies the smudge filter.
rm "$RESULT_DIR/result.yaml" "$RESULT_DIR/result.rhosig.yaml"
git checkout HEAD -- "$RESULT_DIR/result.yaml" "$RESULT_DIR/result.rhosig.yaml"

cat "$RESULT_DIR/result.yaml"

rho crypto verify "$RESULT_DIR/result.yaml" \
  --signature "$RESULT_DIR/result.rhosig.yaml" \
  --identity github/$OWNER

# Committed storage view remains encrypted.
git show "HEAD:$RESULT_DIR/result.yaml"
```

Expected decrypted result:

```text
output: "1113.03"
```
