@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,851 @@
1
+ # Tide Security and Operational Invariants
2
+
3
+ Rules that must never be violated in Tide implementations. Each invariant includes verification steps and consequences of violation.
4
+
5
+ **Critical**: These are not recommendations. They are requirements for Tide's security properties to hold.
6
+
7
+ ---
8
+
9
+ ## I-01: Never-Whole-Key Invariant
10
+
11
+ **Rule**: Cryptographic keys never exist in complete form at any point in their lifecycle, except during Ragnarok (Fabric offboarding). **VERIFIED** (whitepaper tier1-article3, Architecture.md)
12
+
13
+ **What this means**:
14
+ - Do not generate keys locally
15
+ - Do not request complete keys from Fabric
16
+ - Do not assemble key shards
17
+ - Do not store complete keys
18
+ - Do not export complete keys
19
+
20
+ **Sole exception**: Ragnarok deliberately reconstructs keys for offboarding. This is a one-way, quorum-approved, irreversible operation.
21
+
22
+ **Verification**:
23
+ ```bash
24
+ # Check codebase for key generation
25
+ grep -r "generateKey\|crypto.subtle.generateKey\|keygen" src/
26
+ # Should find ZERO local key generation for Tide operations
27
+
28
+ # Check for key assembly
29
+ grep -r "assembleShards\|combineKeys\|reconstructKey" src/
30
+ # Should find ZERO except in Ragnarok-specific code
31
+ ```
32
+
33
+ **Consequence of violation**: Single point of compromise. Attacker gaining access to assembled key can forge tokens, decrypt data, bypass all threshold enforcement.
34
+
35
+ **Related invariants**: I-02 (Threshold), I-09 (No Single Bypass)
36
+
37
+ ---
38
+
39
+ ## I-02: Threshold Enforcement
40
+
41
+ **Rule**: All cryptographic operations require T or more ORK participation. No operation succeeds with fewer than threshold ORKs. **VERIFIED** (tier1-article2, Threat-model.md)
42
+
43
+ **What this means**:
44
+ - Authentication (PRISM) requires T+ ORKs to verify password
45
+ - JWT signing (VVK) requires T+ ORK partial signatures
46
+ - E2EE decryption requires T+ ORKs to threshold-decrypt session key
47
+ - IGA approval sealing requires T+ ORK partial signatures
48
+ - Forseti policy enforcement requires majority of ORKs to approve
49
+
50
+ **Threshold is deployment-configurable** **VERIFIED** (keylessh `start.sh`):
51
+ - Mainnet: T=14 of N=20
52
+ - Test: T=3 of N=5
53
+ - Custom: Set via `TIDE_VENDOR_THRESHOLD_SIGNING` / `TIDE_VENDOR_THRESHOLD_TOTAL`
54
+
55
+ **Do not hardcode 14/20**.
56
+
57
+ **Verification**:
58
+ ```bash
59
+ # Check environment configuration
60
+ echo $TIDE_VENDOR_THRESHOLD_SIGNING
61
+ echo $TIDE_VENDOR_THRESHOLD_TOTAL
62
+
63
+ # Check adapter JSON does not hardcode threshold
64
+ jq '.threshold // "not hardcoded"' data/tidecloak.json
65
+ ```
66
+
67
+ **Consequence of violation**: Compromising fewer than T ORKs allows attacker to forge tokens, decrypt data, or bypass policy.
68
+
69
+ **Related invariants**: I-01 (Never-Whole-Key), I-09 (No Single Bypass)
70
+
71
+ ---
72
+
73
+ ## I-03: Server-Side JWT Verification Required
74
+
75
+ **Rule**: Protected APIs must verify JWT signature and claims server-side. Client-side role checks are UI gating only, not authorization. **VERIFIED** (keylessh `tideJWT.ts`)
76
+
77
+ **What this means**:
78
+ - Do not rely on client-side `hasRealmRole()` / `hasClientRole()` for API authorization
79
+ - Do not trust client-side auth state
80
+ - Do not skip JWT verification because "the client already checked"
81
+ - Do verify signature, issuer, audience, expiration, issued-at, roles server-side
82
+
83
+ **Server-side verification pattern** **VERIFIED** (keylessh `server/lib/auth/tideJWT.ts`):
84
+ ```typescript
85
+ import { jwtVerify, createLocalJWKSet } from 'jose';
86
+ import { loadTideConfig } from './tidecloakConfig';
87
+
88
+ const config = loadTideConfig();
89
+ const JWKS = createLocalJWKSet(config.jwk); // Embedded JWKS
90
+
91
+ export async function verifyTideJWT(token: string) {
92
+ const { payload } = await jwtVerify(token, JWKS, {
93
+ issuer: `${config['auth-server-url'].replace(/\/+$/, '')}/realms/${config.realm}`,
94
+ });
95
+
96
+ // TideCloak access tokens use azp (authorized party) for the client ID.
97
+ // The aud claim contains "account", not the client ID.
98
+ if (payload.azp !== config.resource) throw new Error('Token azp mismatch');
99
+
100
+ const now = Math.floor(Date.now() / 1000);
101
+ if (payload.exp < now) throw new Error('Token expired');
102
+ if (payload.iat > now + 60) throw new Error('Token issued in future');
103
+
104
+ return payload;
105
+ }
106
+
107
+ // Usage in API route
108
+ app.get('/api/admin/users', async (req, res) => {
109
+ const jwt = await verifyTideJWT(extractToken(req));
110
+ if (!jwt.realm_access.roles.includes('admin')) {
111
+ return res.status(403).json({ error: 'Forbidden' });
112
+ }
113
+ // Proceed
114
+ });
115
+ ```
116
+
117
+ **Verification**:
118
+ ```bash
119
+ # Check API routes verify JWT
120
+ grep -A 10 "app\.get\|app\.post\|export async function GET" server/ | grep "verifyJWT\|jwtVerify"
121
+ # Every protected route should include verification
122
+
123
+ # Check routes do NOT rely solely on middleware auth
124
+ grep -r "hasRole\|checkRole" server/routes/ | grep -v "after verifyJWT"
125
+ # Role checks must come AFTER JWT verification, not instead of
126
+ ```
127
+
128
+ **Consequence of violation**: Attacker can call protected APIs by bypassing client-side checks. No server-side enforcement = no authorization.
129
+
130
+ **Related invariants**: I-04 (Embedded JWKS), I-08 (UI Gating Not Authorization)
131
+
132
+ ---
133
+
134
+ ## I-04: Embedded JWKS Verification (Local-Only)
135
+
136
+ **Rule**: JWT verification must use embedded JWKS from adapter JSON. Do not use `createRemoteJWKSet`. Do not fetch keys from the remote OIDC certs endpoint. If the `jwk` field is missing from `tidecloak.json`, this is a setup/bootstrap failure — stop and fix the export, do not fall back to remote key fetching.
137
+
138
+ **What this means**:
139
+ - Adapter JSON contains `jwk: { keys: JWK[] }` field (EdDSA public key, only when IGA enabled)
140
+ - Use `createLocalJWKSet(config.jwk)` from `jose`
141
+ - If `config.jwk` is missing, throw an error. The fix is to re-export the adapter from TideCloak with IGA enabled, not to fetch keys remotely.
142
+ - Also validate `azp` matches `config.resource`
143
+
144
+ **Why**: Embedded JWKS avoids network dependency. Remote JWKS fetching weakens the security model by introducing a network path that could be intercepted or spoofed. Missing `jwk` means IGA is not enabled or the adapter was exported incorrectly — both are configuration problems that must be fixed at the source. DPoP proof verification is separate — per RFC 9449, the proof is verified against the ephemeral `jwk` in its own header (no JWKS endpoint involved).
145
+
146
+ **Forbidden**: `createRemoteJWKSet` must not be used in Tide verification flows. Do not add it as a fallback. Do not import it. If verification fails with `ERR_JWKS_NO_MATCHING_KEY`, the adapter JSON is stale — re-export it from TideCloak.
147
+
148
+ **Required pattern**:
149
+ ```typescript
150
+ import { createLocalJWKSet, jwtVerify } from 'jose';
151
+ import { loadTideConfig } from './tidecloakConfig';
152
+
153
+ const config = loadTideConfig();
154
+ if (!config.jwk) {
155
+ throw new Error(
156
+ 'Missing jwk in tidecloak.json. Re-export adapter from TideCloak with IGA enabled.'
157
+ );
158
+ }
159
+ const JWKS = createLocalJWKSet(config.jwk);
160
+
161
+ export async function verifyTideJWT(token: string) {
162
+ const { payload } = await jwtVerify(token, JWKS, {
163
+ issuer: `${config['auth-server-url'].replace(/\/+$/, '')}/realms/${config.realm}`,
164
+ });
165
+ if (payload.azp !== config.resource) throw new Error('Token azp mismatch');
166
+ return payload;
167
+ }
168
+ ```
169
+
170
+ **Verification**:
171
+ ```bash
172
+ # Check adapter JSON has jwk field
173
+ jq '.jwk.keys | length' data/tidecloak.json
174
+ # Should output number of keys (>= 1)
175
+
176
+ # Check code uses local JWKS only (no remote)
177
+ grep -r "createLocalJWKSet\|config.jwk" lib/auth/ src/lib/auth/
178
+ # Should find local JWKS usage
179
+
180
+ # Check code does NOT use remote JWKS
181
+ grep -r "createRemoteJWKSet" lib/auth/ src/lib/auth/
182
+ # Should find ZERO matches
183
+ ```
184
+
185
+ **Consequence of violation**: `createRemoteJWKSet` introduces a network dependency and a potential interception point. Missing `jwk` is a setup problem — remote fetching masks it instead of fixing it.
186
+
187
+ **Related invariants**: I-03 (Server-Side Verification), I-05 (Adapter JSON Required)
188
+
189
+ **Note**: The `jwk` field is only present when IGA is enabled on the realm. Validate at startup. If missing, the fix is TideCloak bootstrap (enable IGA, re-export adapter), not remote JWKS.
190
+
191
+ ---
192
+
193
+ ## I-05: Adapter JSON Configuration Required
194
+
195
+ **Rule**: Adapter JSON with Tide extensions (`jwk`, `vendorId`, `homeOrkUrl`) must be present and loaded at the correct location. **VERIFIED** (keylessh `tidecloakConfig.ts`)
196
+
197
+ **Canonical filename**: `tidecloak.json`. Do not use `adapter.json`, `keycloak.json`, or other names.
198
+
199
+ **What this means**:
200
+ - Export adapter JSON from TideCloak admin console via `vendorResources/get-installations-provider?providerId=keycloak-oidc-keycloak-json` (this is the only valid provider ID — `tidecloak-oidc-keycloak-json` does not exist in the codebase)
201
+ - Store at the correct location for the framework (see below)
202
+ - Load at startup
203
+ - Validate presence of Tide extensions
204
+
205
+ **Framework-specific locations**:
206
+
207
+ | Framework | Location | Why |
208
+ |-----------|----------|-----|
209
+ | Next.js (server-side) | `data/tidecloak.json` | Read from filesystem by `loadTideConfig()` at server startup. Not publicly exposed. Client-side SDK gets config via `configUrl="/api/config"` API route. |
210
+ | React/Vite SPA | `public/tidecloak.json` | Served as static asset. SDK loads via `configUrl="/tidecloak.json"`. |
211
+ | Vanilla JS | `public/tidecloak.json` | Same as React/Vite — served as static asset via dev server. |
212
+ | Any (env var) | `CLIENT_ADAPTER` or `TIDECLOAK_CONFIG_B64` | Override for containerized or CI deployment. Takes priority over file. |
213
+
214
+ **Loading priority for server-side** (Next.js, Express) **VERIFIED** (keylessh `tidecloakConfig.ts`):
215
+ 1. `process.env.CLIENT_ADAPTER` (JSON string)
216
+ 2. `process.env.TIDECLOAK_CONFIG_B64` (base64-encoded JSON)
217
+ 3. `data/tidecloak.json` (file)
218
+
219
+ **Loading for client-side SDK** (`TideCloakContextProvider`):
220
+ - The SDK fetches adapter JSON from the `configUrl` prop. **Default is `/adapter.json`** — this will 404 unless you set `configUrl` explicitly.
221
+ - The prop is `configUrl`, NOT `configFilePath`. Wrong prop falls through to the default `/adapter.json`.
222
+ - **Next.js**: Config is in `data/tidecloak.json` (server-side, not in `public/`). Create an API route at `app/api/config/route.ts` that calls `loadTideConfig()` and returns the JSON. Set `configUrl="/api/config"` on the provider.
223
+ - **React/Vite**: Config is in `public/tidecloak.json`. Set `configUrl="/tidecloak.json"` on the provider.
224
+
225
+ **Required fields**:
226
+ - Standard Keycloak: `realm`, `auth-server-url`, `resource`, `ssl-required`, `public-client`, `confidential-port`
227
+ - Tide extensions: `jwk`, `vendorId`, `homeOrkUrl`
228
+
229
+ **Verification**:
230
+ ```bash
231
+ # Check adapter JSON exists (check both possible locations)
232
+ test -f data/tidecloak.json && echo "Found at data/" || echo "Not in data/"
233
+ test -f public/tidecloak.json && echo "Found at public/" || echo "Not in public/"
234
+
235
+ # Check for wrong filenames
236
+ ls data/adapter.json data/keycloak.json public/adapter.json public/keycloak.json 2>/dev/null && echo "WRONG FILENAME — rename to tidecloak.json"
237
+
238
+ # Validate Tide extensions present (adjust path as needed)
239
+ jq 'has("jwk") and has("vendorId") and has("homeOrkUrl")' data/tidecloak.json
240
+ # Should output: true
241
+ ```
242
+
243
+ **Consequence of violation**: Server cannot verify JWTs (missing `jwk`). Client cannot connect to Fabric (missing `homeOrkUrl`). Licensing may fail (missing `vendorId`). Wrong filename or wrong location causes silent load failure.
244
+
245
+ **Related invariants**: I-04 (Embedded JWKS)
246
+
247
+ ---
248
+
249
+ ## I-06: CSP Whitelist for SWE Iframe
250
+
251
+ **Rule**: Content-Security-Policy must include `frame-src '*'` for SWE iframe. Without it, SWE iframe silently fails. **VERIFIED** (vendor confirmation, GAP-028 resolved)
252
+
253
+ **Required CSP directive**:
254
+ ```
255
+ frame-src 'self' *
256
+ ```
257
+
258
+ **Why `frame-src '*'`**: Users can re-home their SWE session to any ORK they trust. There is no fixed domain list. The vendor confirmed `frame-src '*'` is required for ORK re-homing.
259
+
260
+ **Additional CSP for E2EE / ORK enclaves**: When using E2EE operations (`doEncrypt`/`doDecrypt`), the ORK enclave iframe also requires `connect-src` (for ORK fetch) and `script-src 'unsafe-inline'` (for ORK enclave scripts). For environments with strict CSP (e.g., Electron apps, custom CSP headers), ensure all three directives allow ORK domains:
261
+ - `frame-src: 'self' *` (or `https://*.tideprotocol.com`)
262
+ - `connect-src: 'self' https://*.tideprotocol.com` (if restricting connect-src)
263
+ - `script-src: 'unsafe-inline'` (on the enclave route only, via `next.config.ts` `headers()`)
264
+
265
+ For standard Next.js apps with the default permissive CSP (`frame-src 'self' *`), the ORK iframe works without additional changes. This matters primarily for apps with custom restrictive CSP policies. VERIFIED (TIDE_LEARNINGS-001 L-17).
266
+
267
+ **Failure symptom**: Login hangs, E2EE operations timeout, no visible errors. Check browser console for CSP violations: `Refused to frame 'https://...' because it violates the following Content Security Policy directive: "frame-src 'self'"`.
268
+
269
+ **Implementation examples**:
270
+
271
+ Next.js (`next.config.js`):
272
+ ```javascript
273
+ module.exports = {
274
+ async headers() {
275
+ return [{
276
+ source: '/:path*',
277
+ headers: [{
278
+ key: 'Content-Security-Policy',
279
+ value: "frame-src 'self' *"
280
+ }]
281
+ }]
282
+ }
283
+ }
284
+ ```
285
+
286
+ Express (via helmet):
287
+ ```typescript
288
+ import helmet from 'helmet';
289
+
290
+ app.use(helmet({
291
+ contentSecurityPolicy: {
292
+ directives: {
293
+ frameSrc: ["'self'", '*']
294
+ }
295
+ }
296
+ }));
297
+ ```
298
+
299
+ **Verification**:
300
+ ```bash
301
+ # Check response headers
302
+ curl -I https://app.example.com | grep -i content-security-policy
303
+
304
+ # Should include frame-src 'self' *
305
+ ```
306
+
307
+ **Consequence of violation**: SWE iframe cannot load. All Tide cryptographic operations fail. Login hangs indefinitely. E2EE encrypt/decrypt timeout.
308
+
309
+ **Related invariants**: I-07 (Silent SSO File Required)
310
+
311
+ ---
312
+
313
+ ## I-07: Silent SSO File Required
314
+
315
+ **Rule**: Static file `public/silent-check-sso.html` must exist for silent token refresh. **VERIFIED** (SDK how-to guides, keylessh)
316
+
317
+ **File content** **VERIFIED**:
318
+ ```html
319
+ <html>
320
+ <body>
321
+ <script>
322
+ parent.postMessage(location.href, location.origin);
323
+ </script>
324
+ </body>
325
+ </html>
326
+ ```
327
+
328
+ **Why**: OIDC silent refresh uses hidden iframe. TideCloak redirects to this file, which posts message back to parent window with updated auth state.
329
+
330
+ **Failure symptom**: Silent refresh fails. User forced to full re-login on token expiration. Session appears to expire prematurely.
331
+
332
+ **Verification**:
333
+ ```bash
334
+ # Check file exists
335
+ test -f public/silent-check-sso.html && echo "Exists" || echo "Missing"
336
+
337
+ # Check file is publicly accessible
338
+ curl https://app.example.com/silent-check-sso.html
339
+ # Should return HTML with postMessage script
340
+ ```
341
+
342
+ **Consequence of violation**: Silent token refresh fails. User experience degraded; frequent forced re-logins. Not a security issue, but operational failure.
343
+
344
+ **Related invariants**: I-06 (CSP Whitelist)
345
+
346
+ ---
347
+
348
+ ## I-08: UI Gating Is Not Authorization
349
+
350
+ **Rule**: Client-side role checks (`hasRealmRole()`, `hasClientRole()`, route guards, conditional rendering) are UI convenience only. They are NOT authorization. **VERIFIED** (AGENTS.md, CLAUDE.md)
351
+
352
+ **What this means**:
353
+ - The SDK hook exports `hasRealmRole(role)` and `hasClientRole(role, client?)`. There is no generic `hasRole()` on the hook.
354
+ - `hasRealmRole()` checks `realm_access.roles`. `hasClientRole()` checks `resource_access.{client}.roles`.
355
+ - These are for show/hide buttons, render different components — not authorization.
356
+ - Route middleware is for redirecting unauthenticated users to login.
357
+ - Neither protects APIs; both are easily bypassed.
358
+ - Real authorization happens server-side via JWT verification (I-03).
359
+
360
+ **Example of violation**:
361
+ ```typescript
362
+ // WRONG: API relies on client-side role check
363
+ app.get('/api/admin/users', (req, res) => {
364
+ // Assumes client already checked role
365
+ return res.json({ users: [...] });
366
+ });
367
+ ```
368
+
369
+ **Correct pattern**:
370
+ ```typescript
371
+ // CORRECT: Server verifies JWT and role
372
+ app.get('/api/admin/users', async (req, res) => {
373
+ const jwt = await verifyTideJWT(extractToken(req));
374
+ if (!jwt.realm_access.roles.includes('admin')) {
375
+ return res.status(403).json({ error: 'Forbidden' });
376
+ }
377
+ return res.json({ users: [...] });
378
+ });
379
+ ```
380
+
381
+ **Verification**:
382
+ ```bash
383
+ # Check API routes do NOT trust client
384
+ grep -r "hasRole\|checkRole" server/routes/ | grep -v "verifyJWT"
385
+ # Should find ZERO API routes that check roles without verifying JWT first
386
+
387
+ # Check client-side role checks are UI-only
388
+ grep -r "hasRole" src/components/ | grep -v "button\|render\|display"
389
+ # Client-side role checks should only affect UI, not security decisions
390
+ ```
391
+
392
+ **Consequence of violation**: Attacker can bypass client-side checks and call protected APIs directly. No real authorization = no security.
393
+
394
+ **Related invariants**: I-03 (Server-Side Verification)
395
+
396
+ ---
397
+
398
+ ## I-09: No Single Point of Bypass
399
+
400
+ **Rule**: No single admin, server, ORK node, or process can bypass Tide's threshold enforcement. **VERIFIED** (tier1-article2, Threat-model.md, IGA.mdx)
401
+
402
+ **What this means**:
403
+ - Compromising TideCloak server does not allow token forgery (VVK signing is threshold across ORKs)
404
+ - Compromising one admin does not allow unauthorized changes (IGA requires quorum)
405
+ - Compromising one ORK does not allow key extraction or policy bypass (threshold T required)
406
+ - Compromising application server does not allow data decryption (E2EE session keys are threshold-decrypted by Fabric)
407
+
408
+ **Security assumption**: System remains secure if fewer than 30% of ORKs compromised (honest minority). On mainnet: <7 of 20 ORKs. **VERIFIED** (Threat-model.md)
409
+
410
+ **Verification**:
411
+ ```bash
412
+ # Check no local key generation (I-01)
413
+ grep -r "generateKey\|keygen" src/ server/
414
+
415
+ # Check no admin bypass in IGA workflow
416
+ grep -r "bypassIGA\|skipApproval\|adminOverride" server/
417
+
418
+ # Check JWT verification does not have admin bypass
419
+ grep -r "if.*admin.*skip.*verify" server/
420
+ ```
421
+
422
+ **Consequence of violation**: Single point of compromise defeats all Tide security properties. Threshold enforcement collapses to single-party trust.
423
+
424
+ **Related invariants**: I-01 (Never-Whole-Key), I-02 (Threshold), I-10 (IGA Quorum)
425
+
426
+ ---
427
+
428
+ ## I-10: IGA Quorum Enforcement
429
+
430
+ **Rule**: Admin changes to roles, users, clients require quorum approval sealed by VVK threshold signatures. No single admin can act unilaterally. **VERIFIED** (IGA.mdx, SetupIGA.md, keylessh `init-tidecloak.sh`)
431
+
432
+ **Quorum formula**: `max(1, floor(TotalAdmins * 0.7))` **VERIFIED** (SetupIGA.md)
433
+
434
+ **Change-set workflow** **VERIFIED** (CHANGE_REQUEST_API.md):
435
+ 1. Create: Admin action triggers draft creation automatically
436
+ 2. Approve: `POST /admin/realms/{realm}/tide-admin/change-set/sign/batch` (MultiAdmin mode returns enclave challenge)
437
+ 3. Commit: `POST /admin/realms/{realm}/tide-admin/change-set/commit/batch`
438
+
439
+ **What this means**:
440
+ - Single admin cannot create, modify, or delete users
441
+ - Single admin cannot assign or revoke roles
442
+ - Single admin cannot modify clients or realm settings
443
+ - All changes enter approval queue (DRAFT → PENDING → APPROVED → ACTIVE)
444
+ - Quorum of admins must approve
445
+ - Approved changes sealed by threshold signatures
446
+ - After commit, token refresh required for roles to propagate (up to 120s)
447
+
448
+ **Enable IGA** **VERIFIED** (all exemplar init scripts):
449
+ ```bash
450
+ curl -X POST "${TIDECLOAK_URL}/admin/realms/${REALM}/tide-admin/toggle-iga" \
451
+ -H "Authorization: Bearer ${ADMIN_TOKEN}"
452
+ ```
453
+
454
+ **Verification**:
455
+ ```bash
456
+ # Check IGA is enabled
457
+ curl "${TIDECLOAK_URL}/admin/realms/${REALM}" | jq '.igaEnabled'
458
+ # Should output: true
459
+
460
+ # Attempt admin change without approval
461
+ curl -X POST "${TIDECLOAK_URL}/admin/realms/${REALM}/users" -d '{...}'
462
+ # Should create change-set, not immediate user creation
463
+
464
+ # Check pending change-sets
465
+ curl "${TIDECLOAK_URL}/admin/realms/${REALM}/tide-admin/change-set/users/requests" \
466
+ -H "Authorization: Bearer ${ADMIN_TOKEN}"
467
+ # Should list pending change
468
+ ```
469
+
470
+ **Consequence of violation**: Single compromised admin can create backdoor accounts, grant unauthorized roles, bypass all governance. IGA enforcement collapses to single-admin trust.
471
+
472
+ **Related invariants**: I-09 (No Single Bypass)
473
+
474
+ Change requests have a hardcoded 1-month (2628000s) cryptographic expiry enforced by the Tide enclave. After this window, signing is rejected. No automatic DB cleanup — stale drafts persist until cancelled. Not configurable. **VERIFIED** (vendor confirmation, batch-02 Q-08, A-25 resolved).
475
+
476
+ ---
477
+
478
+ ## I-11: E2EE Requires Online Fabric Access
479
+
480
+ **Rule**: Hermetic E2EE decryption requires live Fabric threshold participation. No offline decryption. No server-side decryption. **VERIFIED** (hermetic-e2ee.md, tier2-protocol-hermetic-e2ee.mdx)
481
+
482
+ **What this means**:
483
+ - `doDecrypt()` sends request to Fabric ORKs
484
+ - Fabric threshold-decrypts session key
485
+ - Plaintext recovered client-side only
486
+ - No caching of decrypted session keys
487
+ - No offline fallback
488
+
489
+ **Why**: Session keys are encrypted via CVK threshold. Decryption requires T+ ORKs to participate. No single party (client, server, or ORK) can decrypt alone.
490
+
491
+ **Verification**:
492
+ ```bash
493
+ # Check no local session key cache
494
+ grep -r "cacheSessionKey\|storeSessionKey" src/ client/
495
+ # Should find ZERO session key caching
496
+
497
+ # Check E2EE calls do not have offline fallback
498
+ grep -r "doDecrypt" src/ | grep -i "offline\|fallback\|cache"
499
+ # Should find ZERO offline decrypt fallback
500
+ ```
501
+
502
+ **Consequence of violation**: Offline decryption requires storing session keys locally, defeating threshold enforcement. Server-side decryption requires exposing plaintext to server, defeating E2EE.
503
+
504
+ **Related invariants**: I-02 (Threshold), I-09 (No Single Bypass)
505
+
506
+ No application-level max payload size **VERIFIED** (vendor confirmation, GAP-013 resolved). Hybrid scheme: <32 bytes ElGamal, >=32 bytes AES-256-GCM + ElGamal. Practical ceiling ~28.6 MB (Kestrel default). Ciphertext is a TideMemory-serialized envelope (SerializedField v1), base64 when string input, raw `Uint8Array` when binary input. Overhead ~157B (small) / ~217B (large) before base64. Format versioned (v1), stable. **VERIFIED** (vendor confirmation, batch-02 Q-05, A-28 resolved).
507
+
508
+ ---
509
+
510
+ ## I-12: DPoP Token Binding (Bidirectional Lockstep)
511
+
512
+ **Rule**: DPoP requires simultaneous configuration on both server and client. If either side is enabled without the other, authentication fails. **VERIFIED** (session-002)
513
+
514
+ **Bidirectional lockstep**:
515
+ - **Server-side** (realm template): `"dpop.bound.access.tokens": "true"` on the OIDC client. This tells TideCloak to bind tokens with DPoP.
516
+ - **Client-side** (provider config): `useDPoP: { mode: 'strict', alg: 'ES256' }` inside the config object. This tells the SDK to generate DPoP proofs.
517
+ - **Both must be set simultaneously.**
518
+ - Server on, client off → token endpoint returns 400 "DPoP proof is missing" (no hint the fix is client-side). VERIFIED (session-002).
519
+ - Client on, server off → proofs generated but ignored by TideCloak.
520
+ - `useDPoP` goes inside the config object, NOT as a JSX prop on `TideCloakProvider`. VERIFIED (session-002).
521
+
522
+ **`tide_dpop_auth.html` required** (when DPoP is enabled):
523
+ - Copy `tide_dpop_auth.html` to `public/`. This file is loaded by the Tide enclave during login to prove DPoP key possession to the ORKs. **Do not modify it** — its content is integrity-checked.
524
+ - The SDK requests `/tide_dpop/iss/<hex-issuer>/aud/<hex-client>/tide_dpop_auth.html`. This path does not map to the static file, so you must rewrite it.
525
+ - **Use `next.config.ts` `rewrites()` to map `/tide_dpop/:path*` → `/tide_dpop_auth.html`**. Set CSP via `headers()` config targeting the `/tide_dpop/:path*` source: `script-src 'unsafe-inline'` and `Allow-CSP-From: *`.
526
+ - **Do NOT use a route handler** (`app/tide_dpop/[...path]/route.ts`). Next.js 16 dev server injects its own hash-based CSP on route handler responses, overriding the `script-src 'unsafe-inline'` header. Static files served via rewrites are not processed through this pipeline. VERIFIED (LEARNINGS-batch-005 L-04).
527
+ - **Do NOT validate issuer/client hex params in the handler**. The enclave already integrity-checks the HTML. Server-side issuer/client validation is defense-in-depth but when it fails (config loading timing, path resolution), it prevents login entirely by returning 400 before the HTML loads. VERIFIED (LEARNINGS-batch-005 L-03).
528
+ - **The HTML must match the SDK version**. The only source is the pack template — the file is NOT shipped inside `@tidecloak/*` npm packages. If the pack template is stale and the enclave rejects it, contact the Tide team for the updated file. VERIFIED (LEARNINGS-batch-005 L-05, LEARNINGS-batch-007 L-03).
529
+ - Without this: DPoP login fails with `Tide user did not provided a dpop bound token` or `Popup DPoP verification failed to load`. VERIFIED (learning-batch-004, L-07).
530
+
531
+ **`secureFetch` requirements** (when DPoP is enabled):
532
+ - Use `IAMService.secureFetch` with `await IAMService.getToken()` for the managed token. **`getToken()` is async** — must be awaited. `secureFetch` attaches DPoP proofs when it sees `Authorization: Bearer <managed-token>`. VERIFIED (LEARNINGS-batch-005 L-07).
533
+ - URLs must be **absolute** — relative paths throw. VERIFIED (session-002).
534
+ - **Recommended**: Use an `appFetch` wrapper. See `canon/anti-patterns.md`.
535
+
536
+ **What this means** **VERIFIED** (vendor confirmation, GAP-032 resolved):
537
+ - Client-side: SDK generates ephemeral key pair, includes `DPoP` header with each request
538
+ - Server-side: Verify DPoP proof signature, method, URL, timestamp, nonce, thumbprint match
539
+ - Supported algorithms: ES256 (default) and EdDSA. ES384/ES512 declared in types but not implemented.
540
+
541
+ **Server-side verification pattern** **VERIFIED** (keylessh `server/auth.ts`):
542
+ 1. Extract `DPoP` header
543
+ 2. Verify JWT: `typ: "dpop+jwt"`, `alg` matches expected (ES256 or EdDSA)
544
+ 3. Verify signature against `jwk` in proof
545
+ 4. Check `htm` (HTTP method) matches request method
546
+ 5. Check `htu` (HTTP URI) matches request URL (ignore query string)
547
+ 6. Check `iat` timestamp (120s freshness window)
548
+ 7. Check `jti` not replayed (2-min TTL in-memory cache)
549
+ 8. Extract access token, verify `cnf.jkt` matches DPoP proof thumbprint
550
+
551
+ **Why**: DPoP prevents token replay attacks. Stolen access token is useless without corresponding private key to generate fresh proofs.
552
+
553
+ **Verification**:
554
+ ```bash
555
+ # Check DPoP is configured client-side
556
+ grep -r "useDPoP.*strict" src/ client/
557
+ # Should find DPoP configuration if enabled
558
+
559
+ # Check server verifies DPoP if header present
560
+ grep -r "verifyDPoP\|DPoP.*header" server/
561
+ # Should find DPoP verification in auth middleware
562
+ ```
563
+
564
+ **Consequence of violation**: Attacker can replay stolen access tokens without DPoP proof. Token binding defeats replay attacks.
565
+
566
+ DPoP is required for Tide's full security guarantees **VERIFIED** (vendor confirmation, GAP-032 resolved). ES256 is the default algorithm; EdDSA is also supported.
567
+
568
+ ---
569
+
570
+ ## I-13: Adapter JSON Tide Extensions Required
571
+
572
+ **Rule**: Adapter JSON must include Tide extensions (`jwk`, `vendorId`, `homeOrkUrl`). Generic Keycloak adapter is insufficient. **VERIFIED** (keylessh `tidecloakConfig.ts`)
573
+
574
+ **What this means**:
575
+ - Export adapter from TideCloak via `vendorResources/get-installations-provider?providerId=keycloak-oidc-keycloak-json` (the only valid provider ID)
576
+ - Do not use generic Keycloak adapter export
577
+ - Do not manually construct adapter JSON without Tide extensions
578
+ - `jwk` field is only present when IGA is enabled on the realm
579
+
580
+ **Required Tide extensions**:
581
+ - `jwk: { keys: JWK[] }` - Embedded JWKS for local JWT verification
582
+ - `vendorId: string` - Tide vendor identifier
583
+ - `homeOrkUrl: string` - Home ORK endpoint
584
+
585
+ **Verification**:
586
+ ```bash
587
+ # Check Tide extensions present
588
+ jq 'has("jwk") and has("vendorId") and has("homeOrkUrl")' data/tidecloak.json
589
+ # Should output: true
590
+
591
+ # Check jwk contains keys
592
+ jq '.jwk.keys | length' data/tidecloak.json
593
+ # Should output: number >= 1
594
+ ```
595
+
596
+ **Consequence of violation**: JWT verification fails (missing `jwk`). Client cannot connect to Fabric (missing `homeOrkUrl`). Licensing may fail (missing `vendorId`).
597
+
598
+ **Related invariants**: I-04 (Embedded JWKS), I-05 (Adapter JSON Required)
599
+
600
+ **Note**: The `jwk` field is only present when IGA is enabled. Validate at startup.
601
+
602
+ ---
603
+
604
+ ## I-14: Realm Requires `_tide_enabled` Role
605
+
606
+ **Rule**: All Tide users must have `_tide_enabled` role in their default composite role set. Without it, Tide cryptographic operations fail. **VERIFIED** (vendor confirmation, GAP-031 resolved)
607
+
608
+ **What this means**:
609
+ - `setUpTideRealm` runs after realm import and does not auto-create `_tide_enabled`
610
+ - Declare `_tide_enabled` in the realm template so the role already exists when setup continues
611
+ - Default roles must include `_tide_enabled`
612
+ - Users created inherit `_tide_enabled` automatically
613
+
614
+ **Realm setup** **VERIFIED** (keylessh `realm.json`):
615
+ ```json
616
+ {
617
+ "defaultRoles": ["_tide_enabled", "offline_access", "uma_authorization"],
618
+ "roles": {
619
+ "realm": [
620
+ { "name": "_tide_enabled", "composite": false }
621
+ ]
622
+ }
623
+ }
624
+ ```
625
+
626
+ **Verification**:
627
+ ```bash
628
+ # Check realm default roles
629
+ curl "${TIDECLOAK_URL}/admin/realms/${REALM}" | jq '.defaultRoles'
630
+ # Should include "_tide_enabled"
631
+
632
+ # Check user has role
633
+ curl "${TIDECLOAK_URL}/admin/realms/${REALM}/users/${USER_ID}/role-mappings/realm" | jq '.[] | select(.name=="_tide_enabled")'
634
+ # Should output role object
635
+ ```
636
+
637
+ **Consequence of violation**: Tide operations fail with unclear errors. Login may succeed but E2EE, Forseti, and threshold operations fail.
638
+
639
+ Vendor guidance: universally required. Declare in realm.json template.
640
+
641
+ ---
642
+
643
+ ## I-15: Forseti Contracts Execute in Every ORK
644
+
645
+ **Rule**: Forseti policy contracts execute in distributed ORK sandboxes. Majority of ORKs must approve for operation to proceed. No single ORK can bypass. **VERIFIED** (Forseti.mdx, tier2-protocol-forseti.mdx, keylessh `sshPolicy.ts`)
646
+
647
+ **What this means**:
648
+ - Contract is C# code compiled and executed on each ORK
649
+ - Each ORK independently evaluates policy
650
+ - Majority approval required (not threshold T; simple majority)
651
+ - Compromised ORK cannot override policy
652
+ - No client-side or server-side policy bypass
653
+
654
+ **Contract submission** **VERIFIED** (keylessh `tideSsh.ts`):
655
+ ```typescript
656
+ const signRequest = await tc.createTideRequest({
657
+ contract: "using System; ...", // C# code
658
+ modelName: "BasicCustom<APP>:BasicCustom<1>",
659
+ authFlow: "Policy:1",
660
+ authorizer: tc.doken,
661
+ challengeData: { ... }
662
+ });
663
+
664
+ const result = await tc.executeSignRequest(signRequest);
665
+ ```
666
+
667
+ **Verification**:
668
+ ```bash
669
+ # Check contract is real C# code, not config
670
+ grep -r "createTideRequest" src/ | grep "contract:"
671
+ # Contract field should contain C# source, not JSON config
672
+
673
+ # Check no local policy evaluation bypass
674
+ grep -r "bypassPolicy\|skipPolicy" src/ server/
675
+ # Should find ZERO policy bypass logic
676
+ ```
677
+
678
+ **Consequence of violation**: Policy enforcement collapses to single-party trust. Compromised client or server can bypass policy. No distributed enforcement.
679
+
680
+ **Related invariants**: I-02 (Threshold), I-09 (No Single Bypass)
681
+
682
+ **Sandbox model** **VERIFIED** (vendor confirmation, GAP-008 resolved): Five-layer security (Roslyn compilation, IL vetting with namespace block-list, AssemblyLoadContext isolation, VmHost process isolation, gas metering at 50,000 gas default). Block-list: `System.IO`, `System.Net`, `System.Diagnostics`, `System.Threading`, `System.Reflection`, `System.Runtime.InteropServices`, `System.Reflection.Emit`, `Microsoft.Win32`, plus `System.Console` and `System.Runtime.CompilerServices.Unsafe`. Non-deterministic calls blocked by default. Gas metering: `Claim(key)` = 5 gas, `Log(message)` = 25-30 gas, `OutOfGasException` when exhausted.
683
+
684
+ **Remaining open questions**: Contract debugging opaque **PARTIALLY_RESOLVED** (GAP-018).
685
+
686
+ ---
687
+
688
+ ## I-16: Post-Auth Redirect Handler Required
689
+
690
+ **Rule**: A real page or route must exist at the configured `redirectUri` path, and the Tide SDK provider must be active on that page. Without it, login completes at TideCloak but the user lands on a 404. **ASSUMED** (operational requirement of OIDC authorization code flow)
691
+
692
+ **Default redirect path**: `/auth/redirect`
693
+
694
+ **What this means**:
695
+ - The OIDC flow redirects the browser to `redirectUri?code=...&state=...` after authentication
696
+ - The Tide SDK on the target page reads URL parameters and completes token exchange
697
+ - If no page exists at that path, the browser shows a 404 and login silently fails
698
+ - If the page exists but lacks the SDK provider, the auth code is never processed
699
+
700
+ **Framework-specific handlers**:
701
+ - Vanilla JS / Vite: `public/auth/redirect.html` (must include SDK script)
702
+ - React SPA: route at `/auth/redirect` (or SPA fallback to index.html)
703
+ - Next.js App Router: `app/auth/redirect/page.tsx` (or `src/app/auth/redirect/page.tsx`)
704
+ - Next.js Pages Router: `pages/auth/redirect.tsx` (or `src/pages/auth/redirect.tsx`)
705
+
706
+ **Verification**:
707
+ ```bash
708
+ # Check the redirect path returns HTML, not 404
709
+ curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/auth/redirect
710
+ # Should return 200, not 404
711
+ ```
712
+
713
+ **Consequence of violation**: Login completes at TideCloak but the user sees a 404 or blank page. The SDK never processes the auth code. Authentication appears broken despite correct credentials.
714
+
715
+ **Full guidance**: See [canon/redirect-handler.md](redirect-handler.md) for framework-specific examples, diagnostics, and anti-patterns.
716
+
717
+ **Related invariants**: I-06 (CSP), I-07 (Silent SSO)
718
+
719
+ ---
720
+
721
+ ## Summary of Invariants
722
+
723
+ | ID | Invariant | Verification Frequency | Failure Severity |
724
+ |----|-----------|----------------------|------------------|
725
+ | I-01 | Never-Whole-Key | Code review | **CRITICAL** |
726
+ | I-02 | Threshold Enforcement | Deployment config check | **CRITICAL** |
727
+ | I-03 | Server-Side JWT Verification | Every API route | **CRITICAL** |
728
+ | I-04 | Embedded JWKS | Server startup | **HIGH** |
729
+ | I-05 | Adapter JSON Required | Server startup | **CRITICAL** |
730
+ | I-06 | CSP Whitelist | Deployment check | **HIGH** |
731
+ | I-07 | Silent SSO File | Deployment check | **MEDIUM** |
732
+ | I-08 | UI Gating Not Authorization | Code review | **CRITICAL** |
733
+ | I-09 | No Single Bypass | Architecture review | **CRITICAL** |
734
+ | I-10 | IGA Quorum | Realm config check | **HIGH** |
735
+ | I-11 | E2EE Online Access | Code review | **HIGH** |
736
+ | I-12 | DPoP Verification | Every API request (if enabled) | **HIGH** |
737
+ | I-13 | Tide Extensions in Adapter | Server startup | **CRITICAL** |
738
+ | I-14 | `_tide_enabled` Role | Realm setup | **MEDIUM** |
739
+ | I-15 | Forseti Distributed Execution | Architecture review | **HIGH** |
740
+ | I-16 | Post-Auth Redirect Handler | Deployment check | **MEDIUM** |
741
+
742
+ **Failure severity**:
743
+ - **CRITICAL**: Violation defeats Tide's core security properties
744
+ - **HIGH**: Violation creates exploitable security weakness
745
+ - **MEDIUM**: Violation degrades user experience or operational reliability
746
+
747
+ ---
748
+
749
+ ## Invariant Violation Detection
750
+
751
+ ### At Development Time
752
+ - Code review checklist against invariants I-01, I-08, I-09, I-11, I-15
753
+ - Static analysis for local key generation, policy bypass, single-admin paths
754
+
755
+ ### At Deployment Time
756
+ - Validate adapter JSON has Tide extensions (I-04, I-05, I-13)
757
+ - Check CSP includes Tide domains (I-06)
758
+ - Verify `silent-check-sso.html` exists (I-07)
759
+ - Verify post-auth redirect handler exists at `redirectUri` path (I-16)
760
+ - Confirm IGA enabled if using governance (I-10)
761
+ - Check `_tide_enabled` role in default roles (I-14)
762
+ - Verify threshold configuration (I-02)
763
+
764
+ ### At Runtime
765
+ - Monitor JWT verification failures (I-03)
766
+ - Monitor DPoP verification failures (I-12)
767
+ - Monitor Fabric connectivity (I-02, I-11)
768
+ - Monitor quorum enforcement (I-10)
769
+
770
+ ---
771
+
772
+ ## I-17: Scenario-Disambiguation Gate
773
+
774
+ **Rule**: If a request can map to more than one valid scenario, skill, or playbook path, the agent must resolve the ambiguity before writing code, creating roles, running bootstrap, or selecting a playbook. **VERIFIED** (session-001: agent silently chose self-encryption when request required policy-governed encryption).
775
+
776
+ **What this means**:
777
+ - Before actioning, identify whether multiple scenario paths are plausible
778
+ - If yes, resolve via explicit evidence from the repo or request, or ask the user
779
+ - Only then select the playbook/skill/template path
780
+ - If the scenario remains unresolved, surface the ambiguity — do not guess
781
+
782
+ **Known multi-scenario branch points**:
783
+
784
+ | Branch | Option A | Option B | Discriminating question |
785
+ |--------|----------|----------|------------------------|
786
+ | App state | Fresh app (no auth) | Existing app (has auth) | Does the project have an existing auth system? |
787
+ | Infrastructure | Generated app (has init script) | Manual / BYO TideCloak | Does `package.json` have an `"init"` script? Is TideCloak already running? |
788
+ | Encryption model | Self-encryption (private, user-bound) | Policy-governed VVK (shared, cross-user) | Do other users need to decrypt the same ciphertext? |
789
+ | App scope | Single-user private app | Organisation / team / shared app | Are there multiple users who share data or resources? |
790
+ | Task type | Diagnosis (something is broken) | Setup (something needs building) | Is the app partially working, or not started? |
791
+ | Auth complexity | Simple RBAC (role checks) | Policy/governance flow (IGA, Forseti) | Does the app need multi-admin approval, policy signing, or contract-governed access? |
792
+ | Signing vs encryption | Threshold signing (Forseti contracts authorize signatures) | Encryption (self or shared) | Does the app produce cryptographic signatures, or encrypt/decrypt data? |
793
+ | "Use staging" | Docker image only (`tidecloak-stg-dev`) | Docker image + npm tags (WRONG) | "Staging" means the ORK staging network (Docker image). npm packages always use stable versions. VERIFIED (LEARNINGS-batch-005 L-02). |
794
+
795
+ **This is a pre-action gate.** It runs before code generation, role setup, bootstrap steps, or playbook execution. It is not a warning in a safety-check section after the path is already chosen.
796
+
797
+ **Anti-pattern**: Agent receives "add encryption" request, silently chooses self-encryption, builds the feature, then discovers the user wanted sharing. The entire encryption integration must be redone because self-encryption cannot be upgraded to shared encryption. (session-001, AP-24, AP-26)
798
+
799
+ **Consequence of violation**: Wrong architecture. Wrong roles. Wrong SDK calls. Wrong bootstrap steps. Rework is not incremental — it is a full redo of the affected layer.
800
+
801
+ **Related invariants**: I-03 (server-side JWT required), I-08 (UI gating is not auth)
802
+
803
+ ---
804
+
805
+ ## I-18: Development Team Execution Model
806
+
807
+ **Rule**: The agent operates as a virtual development team with named roles. The Tech Lead routes. Specialists do scoped work. The Reviewer checks before handoff. No role does work that belongs to another role.
808
+
809
+ **Team**:
810
+
811
+ | Role | Skill | Owns |
812
+ |------|-------|------|
813
+ | Tech Lead | (adapter front-door) | Inspection, sequencing, stopping on ambiguity |
814
+ | Scenario Resolver | `tide-scenario-resolver` | Determining problem type before build work begins |
815
+ | Setup / Platform Engineer | `tide-setup` | TideCloak bootstrap, realm, licensing, IGA, adapter export |
816
+ | Application Engineer | `tide-integration` | SDK, provider, config, redirect handler, CSP, DPoP auth page, webpack |
817
+ | Security Engineer | `tide-route-and-api-protection` | Route guards, API protection, JWT/DPoP verification |
818
+ | IAM / Policy Engineer | `tide-rbac-and-e2ee` | Roles, encryption, policy signing, Forseti contracts |
819
+ | Solutions Architect | `tide-solutions-architect` | Pattern exploration within pack constraints |
820
+ | Reviewer / QA Engineer | `tide-reviewer` | Compliance checking, invariant validation, anti-pattern detection |
821
+ | Learnings / Pack Curator | `tide-diagnostics` | Troubleshooting, findings, learnings, regression proposals |
822
+
823
+ **Ordering**: Scenario Resolver → Setup → Application → Security → IAM/Policy → Reviewer. Pack Curator at any point. Solutions Architect when no existing path fits.
824
+
825
+ **Handoff rules**:
826
+ - Scenario Resolver runs before any specialist. No implementation without a resolved scenario.
827
+ - Setup must complete before Application (TideCloak must be running).
828
+ - Application must complete before Security (SDK must be wired).
829
+ - Security must complete before IAM/Policy (JWT verification must exist).
830
+ - Reviewer is **mandatory** before handing the app to the user. No implementation is delivered without a review pass. If rejected, the named specialist fixes the issue, then Reviewer runs again.
831
+ - Solutions Architect proposes within constraints. Does not override doctrine.
832
+ - Pack Curator extracts lessons after builds. Proposes regressions and pack updates.
833
+
834
+ **Anti-pattern**: Skipping the Scenario Resolver. Implementation before scenario resolution leads to wrong paths.
835
+
836
+ **Anti-pattern**: One role doing all work in a single pass.
837
+
838
+ **Anti-pattern**: Reviewer acting as a second builder.
839
+
840
+ **Anti-pattern**: Solutions Architect silently promoting a new pattern into default pack behavior.
841
+
842
+ ---
843
+
844
+ ## Status Legend
845
+
846
+ - **VERIFIED** - Directly sourced from documentation or keylessh exemplar
847
+ - **INFERRED** - Strongly implied by source material
848
+ - **ASSUMED** - Operator guidance where sources are silent
849
+ - **REQUIRES_RUNTIME_VALIDATION** - Single-app evidence; needs confirmation
850
+ - **STILL_UNRESOLVED** - Open gap
851
+ - **PARTIALLY_RESOLVED** - Partial evidence; gaps remain