# Profile Isolation Issue Body Templates

Use these bodies when creating GitHub issues for the profile-isolation work.
Copy the relevant section into a new issue and fill the placeholders.

---

## Epic 0 — Spec finalization

### Issue: Define System / Principal / Session boundaries

```md
## Goal
Define the separation between system profile, principal profile, and session context.

## Problem
Right now the boundaries between global rules, per-user personalization, and temporary session facts are not explicit enough.

## Scope
- Define what belongs in each layer
- Define what may be written vs read-only
- Define which layer has precedence when layers disagree

## Acceptance Criteria
- [ ] The three layers are documented
- [ ] The precedence order is explicit
- [ ] The document explains what must never be merged across users
- [ ] The implementation can point to this spec from code comments

## References
- `docs/profile-isolation/technical-design.md`
- `docs/profile-isolation/checklist.md`
```

### Issue: Define canonical principal identity rules

```md
## Goal
Make principal_id the canonical identity for all profile routing.

## Problem
Email, display names, and platform labels are not stable enough to use as the primary key.

## Scope
- Define canonical principal_id
- Define verified_email as an attribute, not the key
- Define external identity bindings for Telegram / LINE / OpenWebUI / email

## Acceptance Criteria
- [ ] The identity model is documented
- [ ] Ambiguous identity fails closed
- [ ] Non-canonical identity never becomes the durable owner key by accident

## References
- `docs/profile-isolation/technical-design.md`
- `gateway/user_verification.py`
```

---

## Epic 1 — Data model and mapping

### Issue: Build principal -> profile mapping

```md
## Goal
Create a stable mapping from canonical principal_id to a per-principal profile bucket.

## Problem
We need a durable routing layer so each person gets their own profile storage.

## Scope
- Map principal_id to a profile path/name
- Resolve profile directories deterministically
- Fail closed when the principal cannot be resolved

## Acceptance Criteria
- [ ] Same principal always resolves to the same profile bucket
- [ ] Different principals do not share a profile bucket
- [ ] Missing identity does not create a wrong profile

## References
- `gateway/principal_profiles.py`
- `docs/profile-isolation/README.md`
```

### Issue: Separate system profile storage

```md
## Goal
Create a dedicated system profile layer that only local admins can edit.

## Problem
Global rules must not be mixed with per-user profile data.

## Scope
- Choose the storage location for the global profile layer
- Add admin-only write protection
- Add audit logging for writes

## Acceptance Criteria
- [ ] External users cannot edit the system profile
- [ ] Writes are auditable
- [ ] The system profile remains global and stable

## References
- `docs/profile-isolation/technical-design.md`
- `gateway/user_verification.py`
```

### Issue: Add shared knowledge global layer

```md
## Goal
Create a shared non-private knowledge layer that is global across principals.

## Problem
Reusable FAQ content should be shared, but personal facts must not be.

## Scope
- Define the shared knowledge directory
- Load only non-private content
- Sanitize content before prompt injection
- Enforce a size budget

## Acceptance Criteria
- [ ] Shared knowledge is global, not principal-scoped
- [ ] Sanitization blocks dangerous content
- [ ] Content truncation is deterministic
- [ ] Personal facts are not absorbed into the shared layer

## References
- `agent/shared_knowledge.py`
- `docs/profile-isolation/technical-design.md`
```

---

## Epic 2 — Prompt assembly

### Issue: Enforce prompt layering precedence

```md
## Goal
Make the system prompt layering order explicit and stable.

## Problem
Lower-priority personalization layers must not override stable identity or safety rules.

## Scope
- Load system rules first
- Load principal personalization after that
- Keep session context last
- Keep shared knowledge separate and non-private

## Acceptance Criteria
- [ ] The prompt builder exposes the precedence order
- [ ] The stable identity cannot be overridden by personal preferences
- [ ] The profile layer remains subordinate to system rules

## References
- `agent/prompt_builder.py`
- `agent/system_prompt.py`
- `docs/profile-isolation/technical-design.md`
```

### Issue: Add prompt-builder guide comments

```md
## Goal
Leave readable pointers in code so future edits start from the right docs.

## Problem
This flow has multiple layers and can be misread if the code lacks guidance.

## Scope
- Add code comments pointing to the profile-isolation docs
- Annotate the files that future changes should inspect first
- Keep the comment set short and durable

## Acceptance Criteria
- [ ] Code comments point to the right docs
- [ ] Future maintainers can find the design quickly
- [ ] The comments describe what changed and why

## References
- `agent/prompt_builder.py`
- `docs/profile-isolation/README.md`
- `docs/profile-isolation/technical-design.md`
- `docs/profile-isolation/checklist.md`
```

---

## Epic 3 — Write permissions

### Issue: Block gateway users from editing the system profile

```md
## Goal
Only local admin workflows can edit the system profile.

## Problem
Telegram / LINE / email / OpenWebUI users must not be able to modify global behavior.

## Scope
- Block remote write paths
- Allow only local admin updates
- Add audit logging

## Acceptance Criteria
- [ ] Remote users cannot edit the system profile
- [ ] Local admin changes are logged
- [ ] The block is enforced in code, not just in UI

## References
- `gateway/user_verification.py`
- `gateway/principal_profiles.py`
```

### Issue: Restrict principal profile writes to allowed fields

```md
## Goal
Allow each principal to edit only their own allowed personalization fields.

## Problem
Not every profile field should be writeable through a chat session.

## Scope
- Define a write whitelist
- Gate high-risk fields behind extra verification
- Reject cross-principal writes

## Acceptance Criteria
- [ ] A principal cannot write another principal's data
- [ ] High-risk fields are gated
- [ ] Writes are auditable

## References
- `gateway/principal_profiles.py`
- `gateway/user_verification.py`
```

---

## Epic 4 — Shared knowledge safety

### Issue: Sanitize shared knowledge before injection

```md
## Goal
Make shared knowledge safe to inject into the prompt.

## Problem
Global reusable notes must not carry prompt injection or private data.

## Scope
- Scan content for prompt-injection patterns
- Replace unsafe files with safe placeholders
- Keep failures fail-closed

## Acceptance Criteria
- [ ] Dangerous content is blocked
- [ ] Safe content still loads
- [ ] Sanitization is visible in the code path

## References
- `agent/shared_knowledge.py`
```

### Issue: Enforce shared knowledge size budget

```md
## Goal
Keep the shared layer compact enough for prompt stability.

## Problem
Large shared documents can crowd out more important prompt content.

## Scope
- Enforce a char budget
- Truncate deterministically
- Surface truncation clearly

## Acceptance Criteria
- [ ] Oversized content is truncated safely
- [ ] The truncation behavior is deterministic
- [ ] The prompt remains stable across turns

## References
- `agent/shared_knowledge.py`
- `agent/prompt_builder.py`
```

---

## Epic 5 — Gateway / OpenWebUI / Email isolation

### Issue: Resolve live caller principal on shared surfaces

```md
## Goal
Make OpenWebUI / Telegram / LINE / email use the live caller principal instead of a cached operator default.

## Problem
Shared surfaces can accidentally inherit the wrong identity and leak data across users.

## Scope
- Resolve the live caller to canonical principal
- Fail closed on ambiguity
- Use the resolved principal for routing and profile loading

## Acceptance Criteria
- [ ] A cannot see B's data
- [ ] Ambiguous callers are rejected
- [ ] Operator defaults do not override caller identity

## References
- `gateway/run.py`
- `gateway/principal_profiles.py`
- `gateway/user_verification.py`
```

### Issue: Re-check email recipient before handoff

```md
## Goal
Make email handoff verify the recipient principal again before sending.

## Problem
"寄給我" style requests can misroute if the active caller identity is wrong.

## Scope
- Resolve the live recipient principal
- Confirm the verified email match
- Block unsafe sends

## Acceptance Criteria
- [ ] Email is sent only to the verified recipient principal
- [ ] Ambiguous recipient identity fails closed
- [ ] No cross-principal leakage occurs through email

## References
- `gateway/run.py`
- `hermes_cli/send_cmd.py`
- `tools/send_message_tool.py`
```

---

## Epic 6 — Tests

### Issue: Add precedence regression tests

```md
## Goal
Protect the new prompt layering contract with tests.

## Problem
This is easy to regress if the tests only check snapshots.

## Scope
- Test stable identity precedence
- Test shared knowledge injection
- Test principal vs session precedence

## Acceptance Criteria
- [ ] Tests verify the order of layers
- [ ] The stable identity remains authoritative
- [ ] Shared knowledge stays non-private

## References
- `tests/agent/test_system_prompt_principal_precedence.py`
- `tests/agent/test_system_prompt_shared_knowledge.py`
```

### Issue: Add session ownership and export tests

```md
## Goal
Ensure cross-principal session access is blocked.

## Problem
Search, export, and shared-session handoff are common leak paths.

## Scope
- Test owner gating for sessions
- Test search and export isolation
- Test email / handoff guardrails

## Acceptance Criteria
- [ ] A cannot access B's sessions
- [ ] Export and download paths are owner-gated
- [ ] Existence leaks are blocked

## References
- `tests/gateway/test_api_server_session_ownership.py`
- `tests/hermes_cli/test_web_server_session_access.py`
- `tests/gateway/test_verified_email_handoff_guard_helpers.py`
```

### Issue: Add explicit private-vs-public download link selection hooks

```md
## Goal
Expose a stable API/gateway hook so clients can let the user choose whether a generated download link should stay owner-bound or become public.

## Problem
Managed downloads already enforce owner-bound access, but callers also need a safe way to intentionally request a public link without weakening the private default.

## Scope
- Accept an explicit `download_link_mode` request hint
- Preserve private-by-default behavior for verified callers
- Return response metadata that lets clients prompt the user with `private` vs `public`
- Keep expiry semantics unchanged across both modes

## Acceptance Criteria
- [ ] Omitted mode defaults to private for owner-bound downloads
- [ ] `download_link_mode=public` strips owner metadata and produces a public link
- [ ] Responses expose machine-readable metadata so UI/gateway layers can prompt users before or after generation
- [ ] `previous_response_id` / response-store isolation remains principal-scoped

## References
- `gateway/platforms/api_server.py`
- `hermes_cli/managed_downloads.py`
- `hermes_cli/web_server.py`
- `tests/gateway/test_api_server.py`
- `tests/hermes_cli/test_managed_downloads.py`
```
```
