@tideorg/mcp 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/GAP_REGISTER.md +117 -0
  2. package/README.md +38 -0
  3. package/adapters/AGENTS.md +241 -0
  4. package/adapters/CLAUDE.md +146 -0
  5. package/adapters/replit.md +224 -0
  6. package/canon/anti-patterns.md +2133 -0
  7. package/canon/concepts.md +816 -0
  8. package/canon/custom-contracts.md +533 -0
  9. package/canon/delegation.md +195 -0
  10. package/canon/feature-mapping.md +637 -0
  11. package/canon/framework-matrix.md +1125 -0
  12. package/canon/invariants.md +851 -0
  13. package/canon/redirect-handler.md +254 -0
  14. package/canon/tidecloak-bootstrap.md +287 -0
  15. package/canon/tidecloak-endpoints.md +294 -0
  16. package/canon/troubleshooting.md +1494 -0
  17. package/canon/version-policy.md +89 -0
  18. package/mcp-server/dist/index.d.ts +2 -0
  19. package/mcp-server/dist/index.js +605 -0
  20. package/package.json +45 -0
  21. package/playbooks/add-auth-nextjs-existing.md +799 -0
  22. package/playbooks/add-auth-nextjs-fresh.md +654 -0
  23. package/playbooks/add-rbac-nextjs.md +190 -0
  24. package/playbooks/bootstrap-realm-from-template.md +170 -0
  25. package/playbooks/configure-e2ee-roles-and-policies.md +196 -0
  26. package/playbooks/deploy-tidecloak-docker.md +792 -0
  27. package/playbooks/diagnose-broken-login.md +234 -0
  28. package/playbooks/diagnose-missing-roles-or-claims.md +253 -0
  29. package/playbooks/initialize-admin-and-link-account.md +235 -0
  30. package/playbooks/migrate-from-existing-auth.md +198 -0
  31. package/playbooks/protect-api-nextjs.md +451 -0
  32. package/playbooks/protect-routes-nextjs.md +544 -0
  33. package/playbooks/setup-forseti-e2ee.md +756 -0
  34. package/playbooks/setup-iga-admin-panel.md +344 -0
  35. package/playbooks/setup-server-delegation.md +142 -0
  36. package/playbooks/start-tidecloak-dev.md +130 -0
  37. package/playbooks/verify-jwt-server-side.md +439 -0
  38. package/prompts/add-admin-approval-flow.md +50 -0
  39. package/prompts/build-private-customer-portal.md +85 -0
  40. package/prompts/migrate-generic-auth-to-tide.md +67 -0
  41. package/prompts/secure-existing-app.md +80 -0
  42. package/reference-apps/INDEX.md +87 -0
  43. package/reference-apps/encrypted-communication/anti-patterns.md +123 -0
  44. package/reference-apps/encrypted-communication/bootstrap-sequence.md +152 -0
  45. package/reference-apps/encrypted-communication/manifest.yaml +100 -0
  46. package/reference-apps/encrypted-communication/role-policy-matrix.md +80 -0
  47. package/reference-apps/encrypted-communication/scenario.md +134 -0
  48. package/reference-apps/git-pr-signing-service/anti-patterns.md +61 -0
  49. package/reference-apps/git-pr-signing-service/bootstrap-sequence.md +157 -0
  50. package/reference-apps/git-pr-signing-service/manifest.yaml +80 -0
  51. package/reference-apps/git-pr-signing-service/role-policy-matrix.md +99 -0
  52. package/reference-apps/git-pr-signing-service/scenario.md +169 -0
  53. package/reference-apps/iga-admin-governance/anti-patterns.md +133 -0
  54. package/reference-apps/iga-admin-governance/bootstrap-sequence.md +153 -0
  55. package/reference-apps/iga-admin-governance/manifest.yaml +87 -0
  56. package/reference-apps/iga-admin-governance/role-policy-matrix.md +67 -0
  57. package/reference-apps/iga-admin-governance/scenario.md +126 -0
  58. package/reference-apps/organisation-password-manager/anti-patterns.md +71 -0
  59. package/reference-apps/organisation-password-manager/bootstrap-sequence.md +127 -0
  60. package/reference-apps/organisation-password-manager/manifest.yaml +86 -0
  61. package/reference-apps/organisation-password-manager/role-policy-matrix.md +59 -0
  62. package/reference-apps/organisation-password-manager/scenario.md +107 -0
  63. package/reference-apps/policy-governed-signing/anti-patterns.md +67 -0
  64. package/reference-apps/policy-governed-signing/bootstrap-sequence.md +128 -0
  65. package/reference-apps/policy-governed-signing/manifest.yaml +78 -0
  66. package/reference-apps/policy-governed-signing/role-policy-matrix.md +62 -0
  67. package/reference-apps/policy-governed-signing/scenario.md +113 -0
  68. package/skills/tide-diagnostics/SKILL.md +99 -0
  69. package/skills/tide-integration/SKILL.md +136 -0
  70. package/skills/tide-learning-capture/SKILL.md +174 -0
  71. package/skills/tide-rbac-and-e2ee/SKILL.md +214 -0
  72. package/skills/tide-reviewer/SKILL.md +133 -0
  73. package/skills/tide-route-and-api-protection/SKILL.md +201 -0
  74. package/skills/tide-scenario-resolver/SKILL.md +81 -0
  75. package/skills/tide-setup/SKILL.md +218 -0
  76. package/skills/tide-solutions-architect/SKILL.md +130 -0
@@ -0,0 +1,190 @@
1
+ # Add Role-Based Access Control (RBAC) in Next.js
2
+
3
+ Implement role-based access control using Tide JWT roles.
4
+
5
+ ---
6
+
7
+ ## When to Use
8
+
9
+ - Need different access levels (admin, editor, viewer)
10
+ - Need fine-grained permissions
11
+ - Have configured roles in TideCloak realm
12
+ - Roles assigned to users via IGA
13
+
14
+ ---
15
+
16
+ ## Prerequisites
17
+
18
+ **Required — do not skip**:
19
+
20
+ 1. API protection implemented: [protect-api-nextjs.md](protect-api-nextjs.md) **must be completed first**. That playbook creates the files this playbook imports (`lib/auth/tideJWT.ts`, `lib/auth/protect.ts`, `lib/auth/tidecloakConfig.ts`) and installs `jose`.
21
+ 2. Roles created in TideCloak Admin Console
22
+ 3. Users assigned roles (via IGA if enabled)
23
+
24
+ **Verify before proceeding**:
25
+ ```bash
26
+ # These files must exist (created by protect-api-nextjs.md)
27
+ ls lib/auth/tideJWT.ts lib/auth/protect.ts lib/auth/tidecloakConfig.ts
28
+ # jose must be installed
29
+ grep '"jose"' package.json
30
+ ```
31
+
32
+ If any are missing, complete [protect-api-nextjs.md](protect-api-nextjs.md) first.
33
+
34
+ ---
35
+
36
+ ## Role Storage in Tide
37
+
38
+ Roles appear in JWT token:
39
+
40
+ ```json
41
+ {
42
+ "realm_access": {
43
+ "roles": ["admin", "offline_access", "_tide_enabled"]
44
+ },
45
+ "resource_access": {
46
+ "my-client": {
47
+ "roles": ["manage-account"]
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ **Realm roles**: Global to realm (`realm_access.roles`)
54
+ **Client roles**: Specific to client (`resource_access.{client-id}.roles`)
55
+
56
+ ---
57
+
58
+ ## Client-Side RBAC (UI Gating Only)
59
+
60
+ ```typescript
61
+ // components/AdminPanel.tsx
62
+ 'use client';
63
+
64
+ import { useTideCloak } from '@tidecloak/nextjs';
65
+
66
+ export function AdminPanel() {
67
+ const { hasRealmRole } = useTideCloak();
68
+
69
+ if (!hasRealmRole('admin')) {
70
+ return <div>Access denied</div>;
71
+ }
72
+
73
+ return (
74
+ <div>
75
+ <h1>Admin Panel</h1>
76
+ {/* admin content */}
77
+ </div>
78
+ );
79
+ }
80
+ ```
81
+
82
+ **Warning**: This is UI gating. Real enforcement happens server-side.
83
+
84
+ ---
85
+
86
+ ## Server-Side RBAC (Real Authorization)
87
+
88
+ ```typescript
89
+ // app/api/admin/users/route.ts
90
+ import { withRole } from '@/lib/auth/protect';
91
+
92
+ // Requires 'admin' role
93
+ export const GET = withRole('admin', async (req, jwt) => {
94
+ return Response.json({ users: [...] });
95
+ });
96
+ ```
97
+
98
+ **Multiple roles** (any match):
99
+
100
+ ```typescript
101
+ // lib/auth/protect.ts
102
+ export function withAnyRole(roles: string[], handler: AuthenticatedHandler) {
103
+ return withAuth(async (req, jwt) => {
104
+ const hasAnyRole = roles.some(role => hasRole(jwt, role));
105
+ if (!hasAnyRole) {
106
+ return Response.json({ error: 'Forbidden' }, { status: 403 });
107
+ }
108
+ return handler(req, jwt);
109
+ });
110
+ }
111
+
112
+ // Usage
113
+ export const GET = withAnyRole(['admin', 'editor'], async (req, jwt) => {
114
+ return Response.json({ data: [...] });
115
+ });
116
+ ```
117
+
118
+ ---
119
+
120
+ ## Role Hierarchies
121
+
122
+ TideCloak supports composite roles. Define in Admin Console:
123
+
124
+ 1. Create `super-admin` role
125
+ 2. Add `admin`, `editor`, `viewer` as composite roles
126
+ 3. Assign `super-admin` to user → user inherits all roles
127
+
128
+ ---
129
+
130
+ ## E2EE Role-Based Encryption
131
+
132
+ Tide E2EE uses tag-based roles for **self-encryption** (user-bound, private data):
133
+
134
+ **Role pattern**: `_tide_<tag>.selfencrypt`, `_tide_<tag>.selfdecrypt`
135
+
136
+ ```typescript
137
+ // Client-side E2EE with role enforcement (self-encryption)
138
+ const { doEncrypt, doDecrypt } = useTideCloak();
139
+
140
+ // Requires _tide_ssn.selfencrypt role
141
+ const ciphertext = await doEncrypt('ssn', '123-45-6789');
142
+
143
+ // Requires _tide_ssn.selfdecrypt role
144
+ const plaintext = await doDecrypt('ssn', ciphertext);
145
+ ```
146
+
147
+ Fabric enforces roles cryptographically. See [canon/concepts.md#tag-based-e2ee-roles](../canon/concepts.md#tag-based-e2ee-roles).
148
+
149
+ **Self-encryption is user-bound**: only the encrypting user can decrypt. Giving another user the `selfdecrypt` role does NOT let them decrypt your data. For shared data between users, use policy-governed VVK encryption with a Forseti contract instead. See [setup-forseti-e2ee.md](setup-forseti-e2ee.md) and [canon/anti-patterns.md AP-24](../canon/anti-patterns.md#ap-24-using-self-encryption-for-shared-data-between-users).
150
+
151
+ ---
152
+
153
+ ## Verification Checklist
154
+
155
+ - [ ] Admin API rejects non-admin users (403)
156
+ - [ ] Role-based UI elements show/hide correctly
157
+ - [ ] Multiple roles (OR condition) work
158
+ - [ ] Composite roles inherit permissions
159
+ - [ ] E2EE roles enforce encrypt/decrypt access
160
+
161
+ ---
162
+
163
+ ## Common Failures
164
+
165
+ ### Role Always Missing in JWT
166
+
167
+ **Fix**: See [diagnose-missing-roles-or-claims.md](diagnose-missing-roles-or-claims.md).
168
+
169
+ ---
170
+
171
+ ## Do Not Do This
172
+
173
+ ### ❌ Do Not Mix Tide Roles with App Roles
174
+
175
+ ```typescript
176
+ // ❌ WRONG: Confuse E2EE role with app admin role
177
+ if (hasRealmRole('_tide_ssn.selfencrypt')) {
178
+ // Grant admin access (WRONG)
179
+ }
180
+ ```
181
+
182
+ **Why**: `_tide_*` roles are for E2EE, not app permissions. See [canon/anti-patterns.md AP-18](../canon/anti-patterns.md#ap-18-mixing-tide-roles-with-application-roles).
183
+
184
+ ---
185
+
186
+ ## References
187
+
188
+ - [protect-api-nextjs.md](protect-api-nextjs.md) - API protection
189
+ - [canon/concepts.md](../canon/concepts.md#tag-based-e2ee-roles) - E2EE roles
190
+ - [diagnose-missing-roles-or-claims.md](diagnose-missing-roles-or-claims.md) - Debug roles
@@ -0,0 +1,170 @@
1
+ # Bootstrap Realm from Template
2
+
3
+ Create a TideCloak realm from the canonical template, initialize Tide licensing and IGA, and approve the initial client change requests.
4
+
5
+ ---
6
+
7
+ ## When to Use
8
+
9
+ - After `start-tidecloak-dev` — TideCloak is running but has no realm
10
+ - Automating realm setup instead of clicking through Admin Console
11
+ - Rebuilding a realm from scratch
12
+
13
+ **Do not use** if the realm already exists. Check: `curl -sf -H "Authorization: Bearer $TOKEN" http://localhost:8080/admin/realms/$REALM_NAME > /dev/null && echo "Exists"`.
14
+
15
+ ---
16
+
17
+ ## Prerequisites
18
+
19
+ - TideCloak running and reachable (playbook `start-tidecloak-dev` complete)
20
+ - `curl`, `jq` installed
21
+ - `templates/shared/realm.json.template` available (or equivalent realm template)
22
+
23
+ ---
24
+
25
+ ## Steps
26
+
27
+ ### Step 1: Get master admin token
28
+
29
+ ```bash
30
+ TIDECLOAK_URL="${TIDECLOAK_URL:-http://localhost:8080}"
31
+ REALM_NAME="${REALM_NAME:-myapp}"
32
+ CLIENT_NAME="${CLIENT_NAME:-myclient}"
33
+ CLIENT_APP_URL="${CLIENT_APP_URL:-http://localhost:3000}"
34
+
35
+ get_token() {
36
+ curl -s -X POST "$TIDECLOAK_URL/realms/master/protocol/openid-connect/token" \
37
+ -H "Content-Type: application/x-www-form-urlencoded" \
38
+ -d "username=admin&password=password&grant_type=password&client_id=admin-cli" \
39
+ | jq -r '.access_token'
40
+ }
41
+ TOKEN="$(get_token)"
42
+ ```
43
+
44
+ ### Step 2: Prepare realm template
45
+
46
+ ```bash
47
+ TMP_REALM="$(mktemp)"
48
+ cp templates/shared/realm.json.template "$TMP_REALM"
49
+ sed -i "s|REALM_NAME|$REALM_NAME|g" "$TMP_REALM"
50
+ sed -i "s|CLIENT_NAME|$CLIENT_NAME|g" "$TMP_REALM"
51
+ sed -i "s|CLIENT_APP_URL|$CLIENT_APP_URL|g" "$TMP_REALM"
52
+ ```
53
+
54
+ The template contains placeholders: `REALM_NAME`, `CLIENT_NAME`, `CLIENT_APP_URL`. All three must be replaced before import.
55
+
56
+ ### Step 3: Import realm
57
+
58
+ ```bash
59
+ TOKEN="$(get_token)"
60
+ curl -s -X POST "$TIDECLOAK_URL/admin/realms" \
61
+ -H "Authorization: Bearer $TOKEN" \
62
+ -H "Content-Type: application/json" \
63
+ --data-binary @"$TMP_REALM"
64
+ rm -f "$TMP_REALM"
65
+ ```
66
+
67
+ ### Step 4: Initialize Tide realm
68
+
69
+ ```bash
70
+ TOKEN="$(get_token)"
71
+ curl -s -X POST "$TIDECLOAK_URL/admin/realms/$REALM_NAME/vendorResources/setUpTideRealm" \
72
+ -H "Authorization: Bearer $TOKEN" \
73
+ -H "Content-Type: application/x-www-form-urlencoded" \
74
+ --data-urlencode "email=admin@yourorg.com" \
75
+ --data-urlencode "isRagnarokEnabled=true"
76
+ ```
77
+
78
+ This activates the Tide license and generates the VRK. No manual "Manage License" step needed.
79
+
80
+ **Critical:** Content-Type must be `application/x-www-form-urlencoded`. JSON causes failure.
81
+
82
+ ### Step 5: Enable IGA
83
+
84
+ ```bash
85
+ TOKEN="$(get_token)"
86
+ curl -s -X POST "$TIDECLOAK_URL/admin/realms/$REALM_NAME/tide-admin/toggle-iga" \
87
+ -H "Authorization: Bearer $TOKEN" \
88
+ -H "Content-Type: application/x-www-form-urlencoded" \
89
+ --data-urlencode "isIGAEnabled=true"
90
+ ```
91
+
92
+ ### Step 6: Approve and commit client change requests
93
+
94
+ Realm creation + Tide setup generates change requests that must be approved before proceeding.
95
+
96
+ ```bash
97
+ approve_and_commit() {
98
+ local TYPE=$1
99
+ TOKEN="$(get_token)"
100
+ local requests=$(curl -s "$TIDECLOAK_URL/admin/realms/$REALM_NAME/tide-admin/change-set/$TYPE/requests" \
101
+ -H "Authorization: Bearer $TOKEN" 2>/dev/null || echo "[]")
102
+
103
+ echo "$requests" | jq -c '.[]' | while read -r req; do
104
+ local id=$(echo "$req" | jq -r .draftRecordId)
105
+ local cst=$(echo "$req" | jq -r .changeSetType)
106
+ local at=$(echo "$req" | jq -r .actionType)
107
+ local payload="{\"changeSetId\":\"$id\",\"changeSetType\":\"$cst\",\"actionType\":\"$at\"}"
108
+
109
+ TOKEN="$(get_token)"
110
+ curl -s -X POST "$TIDECLOAK_URL/admin/realms/$REALM_NAME/tide-admin/change-set/sign" \
111
+ -H "Authorization: Bearer $TOKEN" \
112
+ -H "Content-Type: application/json" -d "$payload"
113
+
114
+ curl -s -X POST "$TIDECLOAK_URL/admin/realms/$REALM_NAME/tide-admin/change-set/commit" \
115
+ -H "Authorization: Bearer $TOKEN" \
116
+ -H "Content-Type: application/json" -d "$payload"
117
+ done
118
+ }
119
+
120
+ approve_and_commit clients
121
+ ```
122
+
123
+ ---
124
+
125
+ ## Verification
126
+
127
+ - [ ] `curl -H "Authorization: Bearer $TOKEN" "$TIDECLOAK_URL/admin/realms/$REALM_NAME"` returns realm JSON
128
+ - [ ] Realm has Tide IdP listed: `curl -H "Authorization: Bearer $TOKEN" "$TIDECLOAK_URL/admin/realms/$REALM_NAME/identity-provider/instances"` includes `tide`
129
+ - [ ] IGA is enabled
130
+ - [ ] No pending client change requests remain
131
+
132
+ ---
133
+
134
+ ## Common Failures
135
+
136
+ | Symptom | Cause | Fix |
137
+ |---------|-------|-----|
138
+ | "Could not create tide realm" | JSON sent to `setUpTideRealm` instead of form data | Use `application/x-www-form-urlencoded` |
139
+ | "email is null or empty" | Missing email parameter | Add `--data-urlencode "email=..."` |
140
+ | 404 on setUpTideRealm | Wrong path (`/tide-admin/setUpTideRealm`) | Correct path: `/vendorResources/setUpTideRealm` |
141
+ | Realm import fails | Placeholders not replaced in template | Verify `sed` replaced all three: `REALM_NAME`, `CLIENT_NAME`, `CLIENT_APP_URL` |
142
+ | Missing `link-tide-account-action` | Minimal realm template used | Use full template from `templates/shared/realm.json.template` |
143
+
144
+ ---
145
+
146
+ ## Anti-Patterns
147
+
148
+ - **Do not** create a minimal realm.json. Every section in the template is required.
149
+ - **Do not** skip IGA enablement. The `jwk` field in adapter JSON only appears with IGA enabled.
150
+ - **Do not** reorder steps. License before IGA. IGA before change request approval.
151
+ - **Do not** leave license activation as a manual step. `setUpTideRealm` handles it automatically.
152
+ - **Do not** blindly set `registrationAllowed: true`. This is a deployment decision — open registration lets any user sign up via Tide IdP. For invite-only apps, keep it `false` and use admin invite links (`tideAdminResources/get-required-action-link`). The admin invite link bypasses the registration gate. VERIFIED (LEARNINGS-batch-008 L-01).
153
+ - **Do not** use the standard Keycloak adapter export path (`/clients/{id}/installation/providers/...`). Use `vendorResources/get-installations-provider?clientId={uuid}&providerId=keycloak-oidc-keycloak-json`. The standard path returns a minimal adapter missing `jwk`, `vendorId`, `homeOrkUrl`. VERIFIED (LEARNINGS-batch-008 L-04).
154
+
155
+ ### Realm Deletion on Dev Image (H2)
156
+
157
+ If re-running bootstrap against an existing realm, delete the old realm first. On the H2 dev database, `DELETE /admin/realms/{realm}` fails with FK constraint violations on composite roles. Use this sequence:
158
+
159
+ 1. Disable IGA: `POST /tide-admin/toggle-iga` with `isIGAEnabled=false`
160
+ 2. Delete Tide IdP: `DELETE /identity-provider/instances/tide`
161
+ 3. Strip ALL composite roles (realm-level and per-client) — remove composites from `default-roles-*`, `realm-admin`, `manage-account`, etc.
162
+ 4. Delete realm: `DELETE /admin/realms/{realm}`
163
+
164
+ Or: stop the container, delete `data/keycloakdb*`, restart. VERIFIED (LEARNINGS-batch-008 L-06).
165
+
166
+ ---
167
+
168
+ ## Next Step
169
+
170
+ Proceed to playbook `initialize-admin-and-link-account`.
@@ -0,0 +1,196 @@
1
+ # Configure E2EE Roles and Policies
2
+
3
+ Create encryption/decryption realm roles, assign them to default users, and approve the resulting change requests. Optionally deploy a Forseti contract for shared (policy-governed) encryption.
4
+
5
+ This is realm-level preparation. App-level E2EE wiring (`doEncrypt`/`doDecrypt`) is covered in playbook `setup-forseti-e2ee` and skill `tide-rbac-and-e2ee`.
6
+
7
+ ---
8
+
9
+ ## When to Use
10
+
11
+ - After `initialize-admin-and-link-account` — admin linked, adapter exported
12
+ - App needs self-encryption (private data, only the encrypting user can decrypt)
13
+ - App needs shared encryption (policy-governed, multiple users can decrypt)
14
+
15
+ **Do not use** if E2EE roles already exist. Check: `curl -H "Authorization: Bearer $TOKEN" "$TIDECLOAK_URL/admin/realms/$REALM_NAME/roles" | jq '.[].name' | grep '_tide_'`.
16
+
17
+ ---
18
+
19
+ ## Prerequisites
20
+
21
+ - TideCloak running with realm fully bootstrapped (all three prior playbooks complete)
22
+ - Admin has linked Tide account
23
+ - Decision: which data tags to protect (e.g., `secret`, `medical`, `vault`)
24
+
25
+ ---
26
+
27
+ ## Two Encryption Models
28
+
29
+ Choose the right model before creating roles.
30
+
31
+ | Model | SDK method | Who can decrypt | Roles needed |
32
+ |-------|-----------|----------------|--------------|
33
+ | Self-encryption | `doEncrypt`/`doDecrypt` (no policy) | Only the encrypting user | `_tide_{tag}.selfencrypt`, `_tide_{tag}.selfdecrypt` |
34
+ | Policy-governed (VVK) | `IAMService.doEncrypt(data, policyBytes)` | Any user whose doken satisfies the Forseti contract | `_tide_{tag}.selfencrypt`, `_tide_{tag}.selfdecrypt` (voucher gates) + a regular role for the contract |
35
+
36
+ Self-encryption is simpler. Start here unless you explicitly need sharing.
37
+
38
+ **After completing this playbook**: Self-encryption works immediately. Shared encryption does NOT — it additionally requires the admin signing ceremony from `setup-forseti-e2ee`. That ceremony is browser-only (cannot be scripted) and must happen before any user can encrypt shared data. If the app offers a "shared" mode, it must be blocked/gated until the signing ceremony is complete. VERIFIED (session-002).
39
+
40
+ **Anti-pattern:** Using self-encryption (`doEncrypt`/`doDecrypt` without policy) for shared data. Ciphertext is bound to the encrypting user's identity. Another user with the same `selfdecrypt` role cannot decrypt it. VERIFIED.
41
+
42
+ **Anti-pattern:** Renaming `selfencrypt`/`selfdecrypt` to `encrypt`/`decrypt` to "enable sharing". The role suffix does not change the encryption model. The SDK call path determines self vs shared. See AP-26.
43
+
44
+ **If self-encryption fails**, fix the self-encryption setup (roles, IGA approval, re-login). Do not switch models as a "fix". See T-13.
45
+
46
+ ---
47
+
48
+ ## Steps: Self-Encryption Roles
49
+
50
+ ### Step 1: Add roles to realm template
51
+
52
+ Add role entries for each tag to `templates/shared/realm.json.template` under `roles.realm`, and include them in the default composite role:
53
+
54
+ ```json
55
+ { "name": "_tide_secret.selfencrypt", "description": "Encrypt data with tag: secret" },
56
+ { "name": "_tide_secret.selfdecrypt", "description": "Decrypt data with tag: secret" }
57
+ ```
58
+
59
+ Update the default composite:
60
+ ```json
61
+ {
62
+ "name": "default-roles-REALM_NAME",
63
+ "composites": { "realm": ["_tide_enabled", "appUser", "_tide_secret.selfencrypt", "_tide_secret.selfdecrypt"] }
64
+ }
65
+ ```
66
+
67
+ See `templates/shared/role-matrix.md` for the full role reference.
68
+
69
+ ### Step 2: Create roles via API (existing realm)
70
+
71
+ If the realm already exists (no template re-import), create roles via API:
72
+
73
+ ```bash
74
+ TOKEN="$(get_token)"
75
+ for ROLE in "_tide_secret.selfencrypt" "_tide_secret.selfdecrypt"; do
76
+ curl -s -X POST "$TIDECLOAK_URL/admin/realms/$REALM_NAME/roles" \
77
+ -H "Authorization: Bearer $TOKEN" \
78
+ -H "Content-Type: application/json" \
79
+ -d "{\"name\":\"$ROLE\",\"description\":\"E2EE role for tag: secret\"}"
80
+ done
81
+ ```
82
+
83
+ ### Step 3: Assign roles to default composite
84
+
85
+ ```bash
86
+ TOKEN="$(get_token)"
87
+ DEFAULT_ROLE_ID=$(curl -s "$TIDECLOAK_URL/admin/realms/$REALM_NAME/roles/default-roles-$REALM_NAME" \
88
+ -H "Authorization: Bearer $TOKEN" | jq -r '.id')
89
+
90
+ ROLES_JSON=$(curl -s "$TIDECLOAK_URL/admin/realms/$REALM_NAME/roles" \
91
+ -H "Authorization: Bearer $TOKEN" \
92
+ | jq '[.[] | select(.name | startswith("_tide_secret."))]')
93
+
94
+ curl -s -X POST "$TIDECLOAK_URL/admin/realms/$REALM_NAME/roles-by-id/$DEFAULT_ROLE_ID/composites" \
95
+ -H "Authorization: Bearer $TOKEN" \
96
+ -H "Content-Type: application/json" \
97
+ -d "$ROLES_JSON"
98
+ ```
99
+
100
+ ### Step 4: Approve change requests
101
+
102
+ Role assignments produce IGA change requests that must be approved:
103
+
104
+ ```bash
105
+ approve_and_commit roles
106
+ ```
107
+
108
+ (Use the `approve_and_commit` function from prior playbooks.)
109
+
110
+ ### Step 5: Users must re-login
111
+
112
+ After roles are committed, users must log out and back in (or call `forceUpdateToken()`) for the new roles to appear in their doken. Token refresh delay is up to 120 seconds.
113
+
114
+ ---
115
+
116
+ ## Steps: Policy-Governed Encryption (Shared)
117
+
118
+ For shared data where multiple users need to decrypt, you need voucher gate roles AND a regular role checked by a Forseti contract.
119
+
120
+ ### Step 1: Create all three role types
121
+
122
+ ```bash
123
+ TOKEN="$(get_token)"
124
+ for ROLE in "_tide_shared.selfencrypt" "_tide_shared.selfdecrypt" "shared-data-access"; do
125
+ curl -s -X POST "$TIDECLOAK_URL/admin/realms/$REALM_NAME/roles" \
126
+ -H "Authorization: Bearer $TOKEN" \
127
+ -H "Content-Type: application/json" \
128
+ -d "{\"name\":\"$ROLE\"}"
129
+ done
130
+ ```
131
+
132
+ | Role | Purpose |
133
+ |------|---------|
134
+ | `_tide_shared.selfencrypt` | Voucher gate — enables `vendorsign` action at ORK |
135
+ | `_tide_shared.selfdecrypt` | Voucher gate — enables `vendordecrypt` action at ORK |
136
+ | `shared-data-access` | Forseti contract checks this for actual access control |
137
+
138
+ Add all three to the default composite role.
139
+
140
+ ### Step 2: Forseti contract deployment (browser signing flow only)
141
+
142
+ **There is no REST API for deploying Forseti contracts.** The endpoint `PUT /tide-admin/forseti-contracts` does not exist on the dev or staging image (returns 404/405). VERIFIED (learning-batch-003, L-06; session-001, RB-010).
143
+
144
+ The contract is deployed as part of the PolicySignRequest flow in the admin's browser:
145
+ 1. Embed the contract C# source code in the admin page
146
+ 2. Compute the SHA-512 hash client-side as the `contractId`
147
+ 3. Use `policyRequest.addForsetiContractToUpload(contractSource)` during the signing flow
148
+
149
+ The contract source code is included in the signing request and uploaded to the ORKs automatically. No prior deployment step is needed.
150
+
151
+ See `setup-forseti-e2ee.md` for the full browser-based signing flow.
152
+
153
+ ### Step 3: Policy signing (frontend only)
154
+
155
+ Policy signing requires the admin's browser enclave. It cannot be done from a script.
156
+
157
+ The admin must:
158
+ 1. Log in to the app as `tide-realm-admin`
159
+ 2. Sign the role policy (init-cert) via approval popup
160
+ 3. Sign the encryption policy via approval popup
161
+
162
+ Both policies must be signed before the group is usable. See playbook `setup-forseti-e2ee` for the full frontend flow.
163
+
164
+ **Anti-pattern:** Trying to sign policies from the init script or backend. Signing requires `requiresApprovalPopup: true` → browser enclave. The script can prepare (deploy contracts, attach policies), but signing must happen from the frontend. VERIFIED.
165
+
166
+ ---
167
+
168
+ ## Verification
169
+
170
+ - [ ] `_tide_{tag}.selfencrypt` and `_tide_{tag}.selfdecrypt` roles exist in realm
171
+ - [ ] Roles are in the default composite (or assigned to target users)
172
+ - [ ] All role change requests approved and committed
173
+ - [ ] (Self-encryption) `doEncrypt`/`doDecrypt` works in app after user re-login
174
+ - [ ] (Shared encryption) Forseti contract deployed, both policies signed by admin
175
+
176
+ ---
177
+
178
+ ## Common Failures
179
+
180
+ | Symptom | Cause | Fix |
181
+ |---------|-------|-----|
182
+ | `doEncrypt` throws "unauthorized" | User lacks `_tide_{tag}.selfencrypt` role | Create and assign the role, approve via IGA |
183
+ | "vendordecrypt voucher not allowed" | User lacks `_tide_{tag}.selfdecrypt` role | Same — create, assign, approve |
184
+ | "Policy supplied has not been signed" | Admin policy not signed via browser enclave | Admin must sign both policies from frontend |
185
+ | Decrypt fails for another user (self-encryption) | Self-encryption binds to encrypting user | Use policy-governed VVK encryption for sharing |
186
+ | Roles assigned but encrypt still fails | User hasn't refreshed token | Log out/in or call `forceUpdateToken()` |
187
+
188
+ ---
189
+
190
+ ## Anti-Patterns
191
+
192
+ - **Do not** assign decrypt roles to all users by default for shared data. Encrypt roles can be broad; decrypt roles are your access control boundary.
193
+ - **Do not** encrypt everything with the same tag. Different sensitivity levels should use different tags mapped to different roles.
194
+ - **Do not** skip tags on encrypted fields. Tags define who can decrypt.
195
+ - **Do not** use the built-in `GenericRealmAccessThresholdRole` contract for production. Deploy a custom Forseti contract.
196
+ - **Do not** use `_tide_*` roles in your Forseti contract's `ValidateExecutor`. Those are voucher gates only. Use a regular realm role for access control.