# Rho Improvements

These are recommended changes before the current protocol and CLI shape harden
into v1.

## Real GitHub rehearsal findings

This section comes from the live two-identity GitHub rehearsal in:

```text
https://github.com/madhavajay/rho-live-tutorial-test-20260615
```

The real flow completed successfully:

- `madhavajay` initialized the Rho repo and owner inbox policy.
- `madhavajay-test` joined through a fork PR.
- `madhavajay` admitted the collaborator and added encrypted inbox policy.
- `madhavajay` published a mock prices dataset while keeping real data local.
- `madhavajay-test` submitted signed analysis code and an encrypted request PR.
- `madhavajay` reviewed the request, verified the signature, ran the code on
  mock data, then ran the approved real-data action in Gondolin.
- `madhavajay` merged the request PR before releasing the result.
- `madhavajay` released an encrypted signed result PR.
- `madhavajay-test` fetched merged `main`, decrypted the result, verified the
  owner signature, and saw `1113.03`.

Merged PR sequence:

```text
#1 Request Rho access for madhavajay-test
#2 Admit madhavajay-test to Rho project
#3 Request private prices analysis
#4 Release encrypted prices result
```

### 1. Add first-class join and admit commands

The join/admit path currently requires users to compose low-level commands and
remember which files to stage:

```bash
rho add user github/$COLLAB
git add rho/participants/$COLLAB.yaml rho/membership.yaml
rho repo protect-path "rho/messages/inbox/$COLLAB/**" --recipient github/$COLLAB
```

This should become:

```bash
rho repo join github/$COLLAB
rho repo admit github/$COLLAB
```

`rho repo join` should:

- verify or initialize the caller identity
- export the public participant bundle
- update membership as a pending collaborator if needed
- create a branch with a conventional name
- print or optionally open the GitHub PR

`rho repo admit` should:

- verify the participant bundle against the GitHub profile claim
- add or update membership with role `collaborator`
- add the collaborator inbox encryption policy
- run `rho repo doctor`
- commit on an owner review branch

The live run exposed that `rho add user` originally wrote only
`rho/participants/<handle>.yaml`, while `rho repo doctor` required matching
membership. That is now locally patched, but the workflow should still be
higher-level than `rho add user` plus manual policy edits.

Implemented first slice: `rho repo join github/<handle>` now creates a
conventional `<handle>/join-rho` branch, exports the participant bundle through
the existing `add-user` path, updates membership, and prints the stage/commit
next step. `rho repo admit github/<handle>` now creates a
`review/<handle>/admit-rho` branch, ensures the participant is present, adds the
recipient-encrypted inbox policy, installs filters, runs `rho repo doctor`, and
prints the stage/commit next step. Remaining work is GitHub PR creation/opening
and online profile proof verification as part of the high-level command.

### 2. Make GitHub account and commit identity explicit per role

The first owner commit was accidentally authored as `madhavajay-test` because
`gh` had been switched to the collaborator account while accepting the private
repo invite. The fix was to run:

```bash
rho gh switch "$OWNER"
rho commit --amend --no-edit --reset-author
```

Rho should make this harder to get wrong:

- `rho-use github/$OWNER` should warn if active `gh` is a different login.
- `rho commit` should warn when `RHO_IDENTITY` and active `gh` disagree.
- `rho commit --require-identity-match` should fail on mismatch.
- `rho id init github/<handle>` should persist the chosen commit author and
  make future commits independent of the active `gh` account.

The tutorial also needs every role block to call both:

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

Implemented first slice: `rho env switch` / `rho-use` emits a shell warning when
the active `gh` login differs from the selected Rho GitHub identity, and
`rho commit` warns on the same mismatch. `rho commit
--require-identity-match` now strips that Rho-only flag before invoking Git and
fails before committing if `RHO_IDENTITY` and the active `gh` account do not
match or cannot be verified. `rho id init github/<handle>` already persists the
selected commit author when the user accepts the binding prompt.

### 3. Infer repo ids from SSH host aliases

The owner used SSH aliases:

```text
git@github-madhavajay:madhavajay/rho-live-tutorial-test-20260615.git
```

`rho repo init --yes` could not infer the repo id from this remote, so the live
run had to pass:

```bash
rho repo init --repo-id "$OWNER/$REPO" --yes
```

Improve `detect_github_remote` so it handles configured SSH aliases when
`HostName github.com` is present, or add a clearer error that prints the exact
`--repo-id` command to run.

Implemented first slice: repo id inference now delegates GitHub-specific remote
parsing to the provider layer instead of embedding it in `rho repo`. Standard
GitHub remotes and SSH aliases that resolve to `HostName github.com` can infer
`owner/repo`, while unsupported providers still fall back to the explicit
`--repo-id` path. Future providers should add their own provider module rather
than extending the repo command with provider-specific parsing.

### 4. Fix fresh-clone Git filter materialization

Several fresh clones checked out encrypted inbox files before Rho installed the
repo-local Git filters. After `rho repo init --yes`, those files still remained
as `rho_recipient_envelope` YAML in the working tree until they were removed and
checked out again:

```bash
rm "$RESULT_DIR/result.yaml" "$RESULT_DIR/result.rhosig.yaml"
git checkout HEAD -- "$RESULT_DIR/result.yaml" "$RESULT_DIR/result.rhosig.yaml"
```

This happened while verifying the merged result from `main`. It also appeared
during owner review of the encrypted request.

Rho should provide a safe repair command:

```bash
rho repo refresh-protected
```

It should:

- install or verify filters
- find protected files that are still encrypted envelopes in the working tree
- re-materialize only those files through the smudge filter
- avoid touching unrelated dirty files
- print exactly which files changed

`rho repo init --yes` could optionally run this when the repo has no unrelated
dirty changes.

Implemented first slice: `rho repo refresh-protected` now installs the protected
path filters, scans tracked recipient-encrypted files, and smudges encrypted
working-tree envelopes back to readable plaintext when the active `RHO_HOME` has
the recipient key. The committed `HEAD:path` blob remains encrypted.

Remaining follow-up: recipient-envelope clean filters reseal with fresh
randomness, so a refreshed plaintext file may appear modified to Git even though
re-adding it preserves encrypted storage. Rho needs either deterministic
worktree/index reconciliation, a filter protocol that can preserve clean stat
state, or a higher-level workflow that avoids exposing this as accidental user
churn.

### 5. Add first-class request review

Owner review required several manual steps:

```bash
rho crypto verify request.yaml --signature request.rhosig.yaml --identity github/$COLLAB
python3 workspace/sum_prices.py datasets/$UUID/mock/prices-mock.csv
```

Rho should provide:

```bash
rho request review req-$UUID
```

It should:

- locate the request in the owner inbox
- decrypt/refresh it if needed
- verify the collaborator signature
- show request metadata, code path, code digest, dataset id, and requested tier
- run the submitted code against the mock dataset
- emit a review summary suitable for committing or attaching to a PR

Implemented first slice: `rho request review <req-id> --root <repo>` now locates
`rho/messages/inbox/*/<req-id>/request.yaml`, parses the live inbox request
shape, verifies `request.rhosig.yaml` against `request.from` when present,
prints the request metadata, validates the submitted code digest when the
request includes `code_sha256`, and runs the repo-relative Python code path
against either `--mock-dataset` or the conventional
`datasets/<dataset_uuid>/mock/prices-mock.csv`. Remaining work is automatic
refresh/decryption for encrypted working-tree envelopes and richer review
artifact generation.

### 6. Add first-class protected run approval

The owner-side trusted run required manual construction of:

- execution workspace
- request manifest
- proposed action YAML
- action grant
- Gondolin execution command

The command shape should be:

```bash
rho run approve-request req-$UUID \
  --runner gondolin \
  --private-root <private-datasets-root>
```

It should:

- derive the action id and run id
- map repo-portable request paths to host paths
- validate the code digest
- create the proposal/action grant
- execute in Gondolin
- write a stable run manifest
- print the output path and digest

The live run also showed that Gondolin host mounts must be absolute paths. If
relative paths are accepted by `rho run`, they must be canonicalized before
writing the Gondolin config.

Implemented first slice: `rho run approve-request <req-id> --root <repo>
--private-root <path>` now consumes the live signed inbox request shape,
verifies `request.rhosig.yaml`, validates the submitted code digest, derives
conventional `act-run-real-<id>` and `run-run-real-<id>` identifiers, writes
local execution request/action-grant artifacts under `.rho/`, executes the
reviewed script against the private dataset through the selected runner, and
writes `.rho/runs/<run-id>.yaml` plus stdout/stderr artifacts for `rho result
release`. `--runner gondolin` uses the existing Gondolin config path, and
repo/private roots are canonicalized before execution. Remaining work is
owner-key-signing the grant as an in-repo authority artifact and supporting
non-Python/tool-specific command templates without relying on `--command`.

### 7. Add first-class result release

The final release required manually creating:

```text
rho/messages/inbox/<collab>/req-<uuid>/result.yaml
rho/messages/inbox/<collab>/req-<uuid>/result.rhosig.yaml
```

and relying on Git clean filters to encrypt both files on commit.

This should become:

```bash
rho result release req-$UUID \
  --run-id run-run-real-$UUID \
  --to github/$COLLAB
```

It should:

- require the request PR to be merged or explicitly acknowledged
- read the approved run output
- write a result manifest
- sign it with the owner identity
- place it in the collaborator inbox
- verify staged Git storage is encrypted
- create a conventional result branch
- optionally open the GitHub PR

Implemented first slice: `rho result release <req-id> --run-id <run-id> --to
github/<collab>` now requires the request to exist in the repo unless explicitly
acknowledged, reads a run output from `--output`, `--value`, or
`.rho/runs/<run-id>/stdout.txt`, writes the collaborator inbox `result.yaml`,
signs `result.rhosig.yaml` with the owner identity, stages both files by
default, verifies the staged Git blobs are encrypted, and creates a conventional
`<owner>/result-<req-id>` branch. When a run manifest exists, it also writes and
signs a committed run receipt. Remaining work is GitHub PR opening and requiring
that signed receipt throughout the full flow verification path.

### 8. Codify the PR choreography

The real flow should be represented as first-class state transitions:

```text
join requested -> admitted -> request submitted -> request merged
-> run approved -> result released -> result merged -> result verified
```

The key ordering discovered in the live run:

- Merge the request PR before creating or merging the result PR.
- Rebase the result branch on top of the merged request so Git history shows
  causality.
- Verify result decryption and signature as the collaborator before merging
  the result PR.
- After merge, verify again from a fresh collaborator clone of `main`.

Add `rho repo doctor --flow` or a scenario-specific command that can inspect a
repo and say which step is next.

Implemented first slice: `rho repo doctor --flow` now audits committed signed
run receipts and linked signed results. It does not yet infer the next missing
workflow step, but it does enforce the key request -> run receipt -> result
chain once a result has been released.

### 9. Store shared payloads outside YAML

YAML should describe metadata, routing, signatures, hashes, and policy. It
should not become the default container for large payloads, binary payloads, or
files being shared back and forth.

For binary files, files over a configured size threshold, and normal shared
artifacts, prefer this layout:

```text
rho/messages/inbox/<recipient>/<message-id>/
  message.yaml
  attachments/
    original-report.pdf.rhoenc
    prices-output.csv.rhoenc
```

The payload file should preserve the original filename and extension where
possible, with encryption represented by an added suffix or header. The
encrypted file itself should be an envelope:

```text
rho encrypted file header
metadata: algorithm, recipients, original filename, original MIME type, digest
payload: encrypted bytes
```

The YAML message should reference those payloads:

```yaml
version: 1
message:
  id: msg-...
  from: rho://id/github/alice
  to: rho://id/github/bob
  type: result_available
  attachments:
    - path: attachments/prices-output.csv.rhoenc
      original_name: prices-output.csv
      mime_type: text/csv
      sha256: ...
```

This gives Rho better behavior for:

- PDFs, images, archives, model outputs, and other binary files
- large CSV or JSON result artifacts
- Git diffs, because metadata changes stay readable while payloads stay binary
- streaming and partial file handling
- future object storage or Git LFS integration

Messages may also deserve a concise envelope extension, for example:

```text
request.message
result.message
note.message
```

A `.message` file could be the canonical Rho message envelope for small
text-first messages, while larger attachments remain sidecar files. The rule of
thumb should be:

- small structured metadata: YAML or `.message`
- user-visible text message: `.message`
- binary or large payload: original filename plus encryption envelope
- signed metadata should hash referenced payload files, not inline them

### 10. Reduce public profile editing churn

The GitHub profile claim flow worked, but regenerating identities creates new
profile fragments. During the live run, keys were moved into `~/.rho` to avoid
having to edit profiles again.

Improve this experience:

- default identities should live in `~/.rho`
- `rho id init github/<handle>` should reuse existing keys by default
- the command should clearly say when it is reusing versus generating keys
- `rho id rotate-key` should be the explicit command that creates a new claim
- `rho id status github/<handle>` should show whether the profile claim is
  currently verified

### 11. Keep committed storage checks built in

The live run repeatedly checked that plaintext did not leak into Git:

```bash
rho status
rho crypto view <path>
git show HEAD:<path>
```

This should be one command:

```bash
rho repo audit-encryption
```

It should check all protected paths in the index and `HEAD`, fail if plaintext
is detected, and print a concise report.

Implemented first slice: `rho repo audit-encryption` checks policy-protected
recipient-encrypted paths in both the index and `HEAD`, verifies recipient
envelopes contain every expected recipient, and fails on plaintext blobs or
repo-key envelopes where recipient encryption is required.

## 1. Replace the custom X25519 envelope

Do not keep a hand-rolled X25519 recipient envelope as a long-term format.
The current approach makes Rho responsible for envelope structure, KDF choices,
recipient binding, replay semantics, metadata handling, and future
compatibility.

Use a standard format instead:

- Prefer `age` (`age-encryption.org/v1`) for file encryption.
- Consider HPKE (RFC 9180) only if Rho needs lower-level protocol composition.

`age` is a good fit for Rho because it already supports multi-recipient X25519,
identity files, and a mature Rust ecosystem. This should let Rho delete crypto
format code and inherit more security analysis.

## 2. Harden encrypted tar bundle extraction

Encrypted tar files are still attacker-controlled input after decryption.
Any `.request.tar.rhoenc` or `.result.tar.rhoenc` bundle opened into local
staging must be extracted through a safe extractor, not plain `tar -xf`.

The extractor must reject:

- absolute paths
- `..` path components
- symlinks
- hardlinks
- device files
- FIFOs and other special files

Add hostile fixture coverage in scenario tests before treating encrypted tar
bundles as a stable protocol surface.

Implemented first slice: `rho crypto extract-tar <path.tar> --out-dir <dir>`
now performs fail-closed ustar extraction for regular files and directories
only. It rejects absolute paths, parent-directory traversal, symlinks,
hardlinks, device files, FIFOs, and unsupported metadata entry types such as
PAX headers. The branch-PR fixture now uses this extractor after decrypting
request/result bundles instead of `tar -xf`, and hostile tar coverage verifies
the rejection paths.


## 4. Add explicit key revocation

Key rotation by leaving old keys unused is not enough for compromise. Add a
repo-committed revocation policy such as:

```yaml
version: 1
revocations:
  - identity: rho://id/github/example
    key_id: rho://key/github/example/ssh-ed25519-1
    reason: compromised
    revoked_at: 2026-05-01T00:00:00Z
    revoked_by: rho://id/github/admin
```

Verification should reject signatures from revoked keys according to the
revocation policy. Keep v1 simple: an admin-maintained deny list is enough.

Implemented first slice: `rho repo init` now creates
`rho/policy/revocations.yaml`, `rho repo doctor` requires it, and
`rho crypto verify <path> --signature <sig> --identity <id> --repo-root <repo>`
rejects signatures whose `(signer, key_id)` pair appears in that repo policy.
The e2e revocation fixture signs a request, verifies it before revocation, adds
the key to the policy, and confirms verification fails afterward.

## 5. Consolidate membership and admin authority

Membership, roles, admin status, and participant bundle references should not be
duplicated across several independent files.

Keep one authoritative membership file for admitted participants and their
roles/admin rights. Participant public bundles can remain separate files, but
membership entries should reference them by stable identity id and key id.

This avoids consistency bugs such as:

- a member with no participant bundle
- an admin who is not a member
- a participant bundle that is not admitted
- conflicting role or admin state across files

`rho repo doctor` should validate the references rather than reconciling
multiple sources of truth.

## 6. Define deterministic inbox paths

Directory names for identity-specific paths must be specified. Avoid short,
invented names like `rho-user1`; they do not survive provider collisions or
handle renames.

Prefer a deterministic mapping from the identity URL, for example:

```text
rho://id/github/madhavajay
-> rho/messages/inbox/id/github/madhavajay/
```

If identity paths become too awkward for filesystems, use a documented
URL-safe encoding or hash. The mapping should be used everywhere messages,
policy patterns, participants, and scenario fixtures reference identity-specific
directories.

## Findings from auditing the merged live repo

This section comes from auditing the actual merged state of the live rehearsal
repo (cloned `main`, all committed artifacts, and the four merged PRs):

```text
https://github.com/madhavajay/rho-live-tutorial-test-20260615
```

The earlier rehearsal findings (#1-#11) and the protocol-hardening sections
cover workflow ergonomics and crypto-format hygiene. Auditing the committed
result surfaced a deeper class of gaps: the repo is not independently auditable
after the fact, and almost nothing except messages is signed.

### A. Concrete defects in the committed example repo

These inconsistencies are sitting in `main` of the canonical example repo right
now. They are cheap to fix and should be fixed first so the reference repo does
not ship contradictions.

1. **`crypto.yaml` contradicts `.gitattributes`.** `rho/policy/crypto.yaml`
   declares `transparent_git_filters: disabled`, but `.gitattributes` assigns
   `filter=rho-crypt diff=rho-crypt` to the inbox paths. Two sources of truth
   for the same decision, already disagreeing.
2. **`paths.yaml` points at paths that are not used.** `rho/policy/paths.yaml`
   declares `rho://repo/rho/requests/` and `rho://repo/rho/control/outbox/`,
   but the real request lives at
   `rho/messages/inbox/<owner>/req-<uuid>/request.yaml`. Stale aspirational
   config.
3. **`rho.yaml` references a missing file.** It points at
   `rho/resources.yaml`, which does not exist in the repo.
4. **`dataset.yaml` is structurally malformed.** `dataset:` and `variants:`
   are empty keys, while `uuid`, `name`, `mock`, and `real` sit at the top
   level instead of nested underneath. It round-trips only through the
   line-based `yaml_field` helpers in `lib.rs`; it is not valid structured YAML
   and will break any real parser.
5. **`.gitattributes` hardcodes one filter line per handle.** Every new
   collaborator requires a manual `.gitattributes` edit. The filter config is
   not generated from policy.

Fix direction:

- Make crypto policy the single source of truth and **generate `.gitattributes`
  from it** instead of hand-maintaining per-identity lines.
- Generalize `rho repo doctor` to validate **all** manifest cross-references
  (not just membership, see consolidation section) and to flag policy
  contradictions and dangling file references.

Implemented first slice: `rho repo init` now creates the referenced
`rho/resources.yaml` manifest, recipient `rho repo protect-path` and
`rho repo install-filters` now mark `transparent_git_filters: enabled` with
`filter: rho-crypt`, and `rho repo doctor` requires `rho/resources.yaml` and
fails when `.gitattributes` uses `filter=rho-crypt` while crypto policy says
filters are disabled or omits the filter name. Remaining Section A work includes
full cross-reference validation, generated `.gitattributes` as an output of
policy rather than an append-only file, and broader manifest normalization.

Implemented dataset slice: `rho repo doctor` now parses every tracked or
untracked `dataset.yaml`, rejects the deprecated flat top-level dataset fields,
requires the nested `dataset.uuid`, `dataset.owner`, and `dataset.variants`
shape, validates mock payload references, and checks payload paths stay inside
the dataset bundle. The local Git Pi fixture now commits a canonical nested
dataset manifest instead of the old `mock_path` / `real_tier` shape.

Implemented generated-attributes slice: recipient-encrypted Git filter entries
are now rendered as a marked generated block in `.gitattributes` from
`rho/policy/permissions.yaml`. `rho repo protect-path` and
`rho repo install-filters` rewrite that block idempotently and remove legacy
append-only lines for the managed policy patterns. Remaining policy generation
work is to model repo-key transparent protected paths in policy too, so every
`filter=rho-crypt` entry has a single manifest source.

### B. Commit a signed provenance chain

The merged `main` contains the encrypted request, the encrypted result, and
nothing linking them. Every run/approval/grant manifest defined in `lib.rs`
(`RunManifest`, `ApprovalManifest`, `ActionGrant`, `ControlledAction`) lives in
gitignored `.rho/` and `sandbox/`. So the repo cannot prove after the fact
which code digest was approved, which runner executed it, against which input,
producing which output digest.

For a trusted-compute system this is the most important missing artifact.

Rho should commit an **owner-signed run receipt** that binds:

```text
request_id -> approved code_sha256 -> runner -> input commitment
-> output_sha256 -> result path
```

This receipt is what makes the repo independently auditable. It should be
written and signed by `rho run approve-request` / `rho result release` and
verified by `rho repo doctor --flow` and the collaborator-side verify step.

Implemented first slice: when `.rho/runs/<run-id>.yaml` exists,
`rho result release` now writes `rho/run-receipts/<request-id>/<run-id>.yaml`,
signs it with the owner identity, and stages both receipt files. The receipt
binds the request id, run id, runner, status, tier, code path/digest, dataset
input path/digest when available, output digest, and result path. Remaining work
is to make `rho run approve-request` produce the receipt directly and require
the receipt chain from collaborator-side verification.

Implemented flow-audit slice: `rho repo doctor --flow` now requires at least one
`rho/run-receipts/**/<run-id>.yaml`, validates the receipt links back to a
request and result, checks the bound code/input/output/result fields are
present, verifies the receipt signature, and verifies the linked result
signature. Signature verification with `--repo-root` now falls back to
`rho/participants/*.yaml`, so a fresh collaborator clone can verify owner-signed
flow artifacts without importing the owner into local `RHO_HOME` first.

### C. Sign governance files, not just messages

Only messages carry a `.rhosig.yaml`. `rho/membership.yaml`,
`rho/participants/*.yaml`, `rho/tools/*.yaml`, all of `rho/policy/`, and
`dataset.yaml` are plaintext and writable by any `members`-class actor.

A compromised or malicious collaborator could flip `run_real.yaml`'s
`approval_required: true -> false`, rewrite membership, or swap the mock CSV,
and nothing would detect it. The trust root itself is unsigned.

Rho should require these governance files to be **owner-signed**, with
`rho repo doctor` verifying those signatures. This extends the membership
consolidation section: consolidating is not enough if the consolidated file is
still unsigned.

Implemented first slice: `rho repo sign-governance` now signs the repo
governance manifests with the repo owner key by default and stages adjacent
`.rhosig.yaml` files. `rho repo doctor --governance` requires those signatures,
checks that each signer matches `rho/repo.yaml`'s owner, and verifies the
signature using repo participant bundles. The signed set currently covers
`rho.yaml`, `rho/repo.yaml`, `rho/membership.yaml`, `rho/resources.yaml`,
`rho/policy/*.yaml`, `rho/participants/*.yaml`, `rho/tools/*.yaml`, and every
`dataset.yaml`. Remaining work is making governance signatures mandatory in the
default doctor path once existing fixtures and live repos have been migrated,
and deciding whether some participant self-signatures should complement the
owner authorization signature.

### D. Bind approval authority to the owner key, not the GitHub merge

Today the approval step is effectively "the owner merged the PR". That rests on
GitHub's merge permissions: anyone with repo merge rights, not necessarily the
Rho owner identity, can admit a member or approve a run.

Approval should be bound to an **owner-key-signed grant committed in-repo**, so
Rho authorization is independent of GitHub's permission model. The merged PR
becomes the transport, not the authority.

Implemented first slice: `rho run approve-request` now signs the local
`.rho/action-grants/<action-id>.yaml` execution grant and writes an owner-signed
committed approval grant at
`rho/approval-grants/<request-id>/<action-id>.yaml`. The committed grant binds
the request, action id, tool id, action type, decision, owner identity, action
digest, local grant digest, repo commit, reviewed code digest, and private input
digest without committing the private input path. `rho repo doctor --flow` now
requires the conventional approval grant for each run receipt, verifies the
grant signature is from `rho/repo.yaml`'s owner, and checks the grant's request,
action, code digest, tier, and private input digest bindings before accepting
the receipt/result chain. Remaining work is to migrate older live repos and add
collaborator-side commands that present this authority chain directly during
result verification.

### E. Add context binding / domain separation to crypto

Signatures cover `signed_sha256` and a `namespace`, but nothing binds an
envelope or signature to `{repo_id, request_id, recipient_id, purpose}`.
Without that binding, an encrypted blob plus its signature could be transplanted
into another request slot or another repo and still verify.

The protocol-hardening section cites replay as a reason to adopt `age`, but
**binding is the application layer's responsibility regardless of `age` or
HPKE**. Make it an explicit requirement: every signed/encrypted payload must
commit to its repo, request, recipient, and purpose.

### F. Encrypt to self (and to all admins)

The request is encrypted only to the owner. The collaborator who sent it can
never decrypt their own sent message again, and a second admin cannot co-read
it. `age` makes multi-recipient trivial.

Rho should **always include the sender, and all current admins, as recipients**
when encrypting a message or request.

### G. Bind released results to the real input

`dataset.yaml`'s real variant is just `staged_under_user_folder: true` with no
digest and no commitment. The released result (`1113.03` in the live run) is
tied to nothing reproducible.

Record a **real-input commitment** (a digest of the private file actually used)
in the run receipt from section B. This gives provenance and dispute resolution
without revealing the private data.

## Suggested order

1. Fix the concrete example-repo defects (Section A): generate `.gitattributes`
   from crypto policy, remove the stale `paths.yaml` / `rho.yaml` references,
   fix `dataset.yaml` nesting, and extend `rho repo doctor` to validate all
   manifest cross-references and policy contradictions.
2. Define and commit the signed run-receipt and signed-governance schema
   (Sections B, C, D, G) before refactoring crypto, so the new crypto layer has
   a stable thing to sign.
3. Replace the custom crypto envelope with `age`, including multi-recipient
   encrypt-to-self and admins (Section F) and context binding (Section E).
4. Add safe tar extraction and hostile extraction tests.
5. Add key revocation checks to signature verification.
6. Normalize deterministic identity path mapping.
7. Consolidate membership/admin authority (now signed, per Section C).
8. Refactor the CLI into one real `rho` binary.
