# User Story Walkthroughs

> **Phase 2+ -- Traces the fully-deployed system.** These walkthroughs cover the complete system across all phases. In Phase 1, steps 5, 6, 12, 14, and 15 are feature-flagged no-ops.
>
> **TODO:** Update these walkthroughs as each phase is implemented to reflect actual behavior vs. stubbed steps.

## Purpose & Audience

This document traces three end-to-end user stories through the permission pipeline, rate limits, credits, quotas, and sharing model. It is intended for engineers validating integration coverage and writing integration test suites. For the pipeline architecture, see [platform architecture](../../architecture/platform.md). For pricing details, see [pricing](platform-pricing.md).

---

## Walkthrough 1: Create and Share a Dashboard

**Actors:**

- **Alice** -- `analyst` role at tenant "Acme Records" on the Professional plan
- **Bob** -- `executive` role at the same tenant

### Step 1: Alice creates a dashboard

```
POST /api/v1/dashboards
Authorization: Bearer <Alice's JWT>
Body: { "name": "Q1 Streaming Revenue", "description": "..." }
```

**Permission pipeline (steps 1-16):**

| Step                    | Check                                              | Result |
| ----------------------- | -------------------------------------------------- | ------ |
| 1. Authenticate         | JWT valid, identity resolved                       | Pass   |
| 2. Resolve tenant       | Grass JWT -> Acme Records tenant                   | Pass   |
| 3. Tenant status        | Acme Records is `active`                           | Pass   |
| 4. User status          | Alice is `active` TenantUser                       | Pass   |
| 5. Consent              | No outstanding consent requirements                | Pass   |
| 6. IP allowlist         | No IP allowlist configured                         | Skip   |
| 7. Plan feature gating  | Professional includes `dashboards`? **Yes**        | Pass   |
| 8. Deny overrides       | No deny overrides for Alice on `dashboards.create` | Pass   |
| 9. Super admin          | Alice is not a super admin                         | Skip   |
| 10. Direct grants       | No direct grants                                   | Skip   |
| 11. Role permissions    | `analyst` includes `dashboards.create`? **Yes**    | Pass   |
| 12. Resource scope      | Creating, not accessing -- skip                    | Skip   |
| 13. Policy conditions   | No conditions on `dashboards.create`               | Pass   |
| 14. Data classification | Not applicable                                     | Skip   |
| 15. Step-up auth        | Not in step-up list                                | Skip   |
| 16. **GRANTED**         |                                                    |        |

**Post-permission checks:**

| Check        | Detail                                    | Result |
| ------------ | ----------------------------------------- | ------ |
| Rate limit   | `api`: 3/120 this minute (Professional)   | Pass   |
| Credit check | Dashboard creation costs 0 credits (free) | Pass   |
| Quota check  | `dashboards`: 8/25 (Professional)         | Pass   |

**Execution:** Dashboard `dash-001` created. ResourceUsage `dashboards` count: 8 -> 9. Alice is the **owner** with implicit `manage` access.

### Step 2: Alice shares with Bob (view access)

```
POST /api/v1/dashboards/dash-001/shares
Body: { "shareTarget": "user", "targetId": "bob", "accessLevel": "view" }
```

Pipeline: Alice is owner -> implicit `manage` -> can share. `analyst` includes `dashboards.share`. **GRANTED.**

Execution: ResourceShare created `{ resourceType: "dashboard", resourceId: "dash-001", shareTarget: "user", targetId: "bob", accessLevel: "view" }`.

### Step 3: Bob views the shared dashboard

```
GET /api/v1/dashboards/dash-001
Authorization: Bearer <Bob's JWT>
```

Pipeline step 11: `executive` includes `dashboards.view`. Step 12: ResourceShare exists for Bob with `accessLevel=view`. Required access: `view`. **GRANTED.**

### Step 4: Bob tries to edit (denied)

```
PATCH /api/v1/dashboards/dash-001
Authorization: Bearer <Bob's JWT>
```

Step 11: `executive` does **not** include `dashboards.edit`. **DENIED** with `403: Missing permission: dashboards.edit`.

Even if Bob had `dashboards.edit` in his role, his ResourceShare is `accessLevel=view`, not `edit` -- step 12 would also fail. Both the role permission **and** the share access level must be sufficient.

### Step 5: Alice shares with an entire role

```
POST /api/v1/dashboards/dash-001/shares
Body: { "shareTarget": "role", "targetId": "analyst-role-id", "accessLevel": "edit" }
```

All users with the `analyst` role can now view AND edit `dash-001` -- they have both the role permission (`dashboards.edit`) and the share access (`edit`).

### What-If Scenarios

| Scenario                            | Result                                                                                              |
| ----------------------------------- | --------------------------------------------------------------------------------------------------- |
| Alice is on Starter plan            | Step 7 fails -- `dashboards` not in Starter. 403 "Feature not available. Upgrade to Professional."  |
| Acme has 25 dashboards (quota full) | Permission passes, quota fails -- 409 "Dashboard limit reached (25/25)."                            |
| Cross-tenant share attempted        | Rejected -- `targetId` must resolve to active TenantUser in same tenant.                            |
| Alice's account deleted             | Dashboards persist. Admin with `dashboards.manage` can reassign. Shares remain valid.               |
| Bob's `executive` role revoked      | Bob loses `dashboards.view`. Share exists but step 11 fails. Share is necessary but not sufficient. |
| Tenant suspended                    | Step 3 fails for all users -- 403 "Tenant suspended."                                               |

### Dashboard Listing

| User                            | Sees                                                                 |
| ------------------------------- | -------------------------------------------------------------------- |
| **Alice** (analyst)             | Dashboards she owns + shared with her (by user or role)              |
| **Bob** (executive)             | Only dashboards shared with him                                      |
| **Admin** (`dashboards.manage`) | All dashboards in the tenant                                         |
| **Viewer**                      | Only dashboards shared with them (view-only, cannot create or share) |

---

## Walkthrough 2: Create and Share a Data Source

**Actors:**

- **Alice** -- `analyst` role at tenant "Acme Records" on Professional plan
- **Carol** -- `contributor` role at the same tenant

### Step 1: Alice creates a data source

```
POST /api/v1/data-sources
Body: { "name": "Royalties DW", "type": "snowflake", "config": { "warehouse": "..." } }
```

Pipeline: `data.sources` in Professional plan (step 7). `analyst` includes `data.sources.create` (step 11). **GRANTED.** Quota: `data_sources` 3/10. Data source `ds-001` created. Alice is owner.

### Step 2: Alice queries the data source (costs credits)

```
POST /api/v1/data-sources/ds-001/query
Body: { "sql": "SELECT artist, SUM(revenue) FROM streams GROUP BY artist" }
```

Pipeline: Plan features `data.sources` + `tools.snowflake` pass (step 7). `analyst` includes `data.sources.query` (step 11). Alice is owner (step 12). **GRANTED.**

Post-permission: Rate limit `tools.snowflake` 2/10 this minute. Credit check: `tools.snowflake` costs **5 credits** (Professional). Tenant has 847/1,000 remaining. Deduct 5. CreditEntry written.

### Step 3: Alice shares with Carol (view access)

```
POST /api/v1/data-sources/ds-001/shares
Body: { "shareTarget": "user", "targetId": "carol", "accessLevel": "view" }
```

Alice is owner -> implicit `manage` -> can share. **GRANTED.**

### Step 4: Carol queries via the shared data source

```
POST /api/v1/data-sources/ds-001/query
Authorization: Bearer <Carol's JWT>
```

Step 11: `contributor` includes `data.sources.query`. Step 12: ResourceShare exists with `accessLevel=view`. Querying is a read operation -- `view` is sufficient. **GRANTED.**

Credits are **tenant-wide**, not per-user. Carol's query consumes from the same pool as Alice's. CreditEntry records `userId: "carol"` for per-user breakdown.

### Step 5: Carol tries to edit config (denied)

Step 11: `contributor` does **not** include `data.sources.edit`. **DENIED.** Even if she had the permission, her share is `view` -- step 12 would also fail.

### What-If Scenarios

| Scenario                       | Result                                                                                                     |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------- |
| Data source quota full (10/10) | 409 "Data source limit reached. Add Extra Data Sources add-on ($25/mo)."                                   |
| Cache storage quota exceeded   | Query succeeds but result not cached. Warning logged.                                                      |
| Alice's `analyst` role revoked | Loses all `data.sources.*` permissions. Owned data sources remain. Admin can reassign ownership.           |
| Alice deletes the data source  | All ResourceShares cascade-deleted. Carol loses access immediately. Cache purged. Usage count decremented. |

### Key Differences from Dashboards

| Aspect                 | Dashboards                | Data Sources                                             |
| ---------------------- | ------------------------- | -------------------------------------------------------- |
| Credit cost            | Free (0 credits for CRUD) | Queries cost credits (5/query on Professional)           |
| Storage quota          | Count only                | Count + cache size (MB)                                  |
| Cascading access       | Self-contained            | Querying also requires `tools.snowflake` plan feature    |
| Credential sensitivity | None                      | Connection credentials visible only to owner/edit/manage |

---

## Walkthrough 3: Share, Continue, and Duplicate a Chat

**Actors:**

- **Alice** -- `analyst` role at tenant "Acme Records" on Professional plan
- **Dave** -- `analyst` role at the same tenant
- **Bob** -- `executive` role at the same tenant

Chats use the same three access levels as all other resource types (`view`, `edit`, `manage`), where `edit` means "append messages" (chats are append-only by design).

### Step 1: Alice has a conversation

Alice has been chatting about streaming revenue. Chat `chat-042` includes Snowflake queries, results, and AI analysis. The chat is **private** -- only Alice can see it (default for all chats).

### Step 2: Alice shares with Bob for review (view)

```
POST /api/v1/chats/chat-042/shares
Body: { "shareTarget": "user", "targetId": "bob", "accessLevel": "view" }
```

Alice is owner -> `chat.share` in `analyst` role -> **GRANTED.**

### Step 3: Bob reads the shared conversation

```
GET /api/v1/chats/chat-042
Authorization: Bearer <Bob's JWT>
```

Step 11: `executive` includes `chat.view`. Step 12: ResourceShare `accessLevel=view`. **GRANTED.**

Bob sees: full conversation history, embedded query results (rendered inline, not re-executed -- 0 credits), AI analysis. **Read-only** -- no input box.

### Step 4: Bob tries to reply (denied)

```
POST /api/v1/chats/chat-042/messages
Authorization: Bearer <Bob's JWT>
```

Step 12: Bob's share is `accessLevel=view`. Sending a message requires `edit` or `manage`. **DENIED** with `403: Insufficient share access: view. Required: edit`.

Bob has `chat.stream` in his role (for his own chats), but his share access level is `view`.

### Step 5: Alice shares with Dave for handoff (edit)

```
POST /api/v1/chats/chat-042/shares
Body: { "shareTarget": "user", "targetId": "dave", "accessLevel": "edit" }
```

Alice is going on vacation. Dave will continue the investigation.

### Step 6: Dave continues the conversation

```
POST /api/v1/chats/chat-042/messages
Authorization: Bearer <Dave's JWT>
Body: { "content": "Break down the Pop genre by sub-genre" }
```

Step 11: `analyst` includes `chat.stream`. Step 12: ResourceShare `accessLevel=edit`. Required: `edit` or `manage`. **GRANTED.**

Credits: `chat.sonnet` costs 5 credits (Professional). Charged to Dave (CreditEntry `userId: "dave"`). AI responds using full conversation context (Alice's messages + Coda's prior responses). Dave's messages are attributed to Dave in the UI.

### Step 7: Bob copies the conversation

Bob wants to explore independently without affecting the ongoing investigation.

```
POST /api/v1/chats/chat-042/copy
Authorization: Bearer <Bob's JWT>
```

Step 11: `executive` includes `chat.copy`. Step 12: Bob's share is `view`. Copying requires any share access (including `view`) plus the `chat.copy` permission. **GRANTED.**

Post-permission: Copy costs 0 credits. Quota: Bob's `chats` count 12/500. Attachment storage check: 3.2MB of attachments, tenant at 45/500MB.

Execution: New chat `chat-099` created, owned by Bob. Messages 1-12 deep-copied (snapshot). Attachments **deep-copied** to new S3 keys (independent copies). Future messages in `chat-042` do not appear in `chat-099`. If Alice deletes `chat-042`, Bob's copy is unaffected.

### Step 8: Alice shares with all analysts via role

```
POST /api/v1/chats/chat-042/shares
Body: { "shareTarget": "role", "targetId": "analyst-role-id", "accessLevel": "view" }
```

All analysts can read `chat-042` as reference. They have `chat.copy` so any can duplicate for independent exploration.

### What-If Scenarios

| Scenario                                | Result                                                                                                                              |
| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| Dave triggers a Snowflake query in chat | Tool executes (analysts have `tools.snowflake.query`). Credits: `chat.sonnet` (5) + `tools.snowflake` (5) = 10, attributed to Dave. |
| Alice revokes Dave's share              | Dave loses access immediately. His messages remain in history (append-only).                                                        |
| Chat quota exceeded on Bob's copy       | 409 "Chat limit reached." Bob can still view the original.                                                                          |
| Alice deletes `chat-042`                | All shares cascade-deleted. Bob's copy (`chat-099`) unaffected.                                                                     |
| Chat contains restricted data (step 14) | Share creation itself is denied if recipient lacks the corresponding classification permission.                                     |
| Bob wants edit, not just view           | Alice updates Bob's share from `view` to `edit`. Bob already has `chat.stream` -- the share level was the missing piece.            |

### Chat Listing

| User                       | Sees                                                                               |
| -------------------------- | ---------------------------------------------------------------------------------- |
| **Alice** (owner)          | All chats she owns + shared with her. Marked "owned" vs "shared."                  |
| **Dave** (share recipient) | His own chats + shared. `chat-042` shows "shared by Alice -- continue access."     |
| **Bob** (executive)        | His own chats + shared. `chat-042` shows "view only." `chat-099` shows as his own. |
| **Admin** (`chat.manage`)  | All chats in the tenant.                                                           |
| **Viewer**                 | Only shared chats. View-only. Cannot copy (no `chat.copy`).                        |

### Chat Privacy Invariants

| Invariant              | Enforcement                                                              |
| ---------------------- | ------------------------------------------------------------------------ |
| Private by default     | No ResourceShare rows at creation. Owner is the only user with access.   |
| Explicit sharing only  | No auto-sharing, no "share with tenant" default.                         |
| Revocable shares       | Owner or admin can revoke any time. Access removed immediately.          |
| Append-only messages   | Cannot edit or delete individual messages.                               |
| Copy preserves privacy | Independent deep copy. No data leakage between original and copy.        |
| Tool outputs embedded  | Shared recipients see results inline -- no re-execution, no credit cost. |
| Full audit trail       | Every share, revocation, view, continue, and duplicate is audit logged.  |

---

## Comparison: Sharing Across Resource Types

| Aspect                     | Dashboard                | Data Source                        | Chat                                                                    |
| -------------------------- | ------------------------ | ---------------------------------- | ----------------------------------------------------------------------- |
| Default visibility         | Private (owner only)     | Private (owner only)               | Private (owner only)                                                    |
| Access levels              | view, edit, manage       | view, edit, manage                 | view, edit, manage                                                      |
| Copy permission            | `dashboards.copy`        | `data.sources.copy`                | `chat.copy`                                                             |
| Copy behavior              | Deep copy widgets/layout | Deep copy config (not credentials) | Deep copy messages + attachments at a point in time                     |
| Credit cost of access      | Free (view/edit)         | Queries cost credits               | View: free. Edit (append): credits per message. Copy: free.             |
| Quota impact on copy       | Tenant `dashboards` +1   | Tenant `data_sources` +1           | Per-user `chats` +1, tenant attachments +size                           |
| Can modify original?       | Yes (with edit)          | Yes (with edit)                    | Only by appending (with edit). Existing messages immutable.             |
| Independence after sharing | Changes visible to all   | Config changes visible to all      | Edit: shared state. Copy: independent.                                  |
| Data sensitivity           | Low (layout/widgets)     | Medium (connection config)         | High (conversation content, tool outputs, attachments, potentially PII) |
| Admin override             | `dashboards.manage`      | `data.sources.manage`              | `chat.manage`                                                           |
