Files
resolutionflow/legal/implementation-verification.md
Michael Chihlas 41f5519916
All checks were successful
Mirror to GitHub / mirror (push) Successful in 6s
docs(legal): add baseline legal documents (privacy, ToS, DPA, subprocessors, cookies)
Generated by the resolutionflow-legal skill from a code scan of the FastAPI
backend + React frontend on commit 0564646. Each document is a starting
point for attorney review, not legal advice.

Includes:
- privacy-policy.md, terms-of-service.md, cookie-policy.md (public-facing)
- dpa.md (contractual; signed with MSP customers)
- subprocessor-list.md (Railway, Anthropic, Voyage, Stripe, Resend, Sentry,
  PostHog, Google Fonts — confirmed live as of scan)
- data-inventory.md + classification.md (Phase 1/2 working files)
- attorney-review-checklist.md (consolidated [LEGAL REVIEW] punch list)
- implementation-verification.md (claim-by-claim audit vs. actual code)

Three blocking issues filed before public publication:
- #175 deletion-on-offboarding (or rewrite retention claims)
- #176 narrow Sentry send_default_pii + Session Replay config
- #177 EU/UK consent for PostHog + Google Fonts

Public-facing documents intentionally route physical-mail requests through
support@ rather than publishing the LLC's registered address.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:51:19 -04:00

120 lines
11 KiB
Markdown

# Implementation Verification
Generated: 2026-05-14
Scanned commit: `0564646` on `feat/public-landing-routing-refactor`
This document checks every concrete claim in the generated legal documents against what the code actually does. Each row is marked:
-**Confirmed** — code clearly supports the claim
- ⚠️ **Partial** — the code supports a narrower or related claim; the language is acceptable but tighten if possible
-**Not implemented** — the claim is aspirational; either build it or rewrite the claim
-**Cannot verify in scan** — depends on a runtime config, deployment posture, or external attestation the scan can't reach
> A claim that overpromises is worse than one that underpromises. Anything ❌ must be resolved (built or rewritten) before publication.
---
## Privacy Policy
| Claim | Source in docs | Reality | Verdict |
|---|---|---|---|
| Passwords are bcrypt-hashed with 12 rounds; plaintext never stored | §3.1, §9 | `BCRYPT_ROUNDS=12` ([config.py:86](../backend/app/core/config.py#L86)); `User.password_hash` ([user.py:36](../backend/app/models/user.py#L36)) | ✅ |
| PSA integration credentials encrypted at the application layer using Fernet (AES-128-CBC + HMAC), key derived via HKDF from `SECRET_KEY` | §3.1, §9; DPA Annex B.1 | [encryption.py](../backend/app/services/psa/encryption.py) | ✅ |
| TLS for production traffic | §9; DPA Annex B.1 | Hosted at `api.resolutionflow.com` / `resolutionflow.com` via Railway with HTTPS | ❓ (depends on Railway domain config; verify) |
| Tenant isolation enforced by PostgreSQL row-level security | §9; DPA Annex B.2 / B.5 | RLS referenced in [PROJECT_CONTEXT.md:206](../.ai/PROJECT_CONTEXT.md#L206) as "Phase 4 RLS"; `account_id` scoping pervasive | ✅ |
| Access tokens stored in `localStorage` rather than HTTP-only cookies | §9 | Confirmed in [authStore.ts:47-48](../frontend/src/store/authStore.ts#L47-L48), [OAuthCallbackPage.tsx:100-101](../frontend/src/pages/OAuthCallbackPage.tsx#L100-L101) | ✅ |
| 5-minute access tokens, idle 3d / absolute 14d refresh defaults | §6 retention table; Cookie Policy §2.1 | [config.py:69-79](../backend/app/core/config.py#L69-L79) | ✅ |
| Account deletion soft-deletes the user and revokes refresh tokens; account-scoped content **not** automatically purged | §6 (drafted as a `[LEGAL REVIEW]` flag) | [accounts.py:524-567](../backend/app/api/endpoints/accounts.py#L524-L567) — confirms the soft-delete + token revoke; no purge of `audit_logs`, `ai_sessions`, etc. | ⚠️ disclosed accurately as a flagged gap; ❌ if you intend to claim "we delete your data" |
| AI flow-builder wizard conversations purged 24h after creation | §6 retention | [scheduler.py:118-136](../backend/app/core/scheduler.py#L118-L136), hourly job | ✅ |
| Assistant chat threads retained 90 days OR 100-chat cap (account-configurable), pinned exempt | §6 retention | [retention_cleanup.py](../backend/app/services/retention_cleanup.py); defaults in [account.py:40-45](../backend/app/models/account.py#L40-L45) | ✅ |
| AI chat sessions auto-archived after 30 days idle | §6 retention | [main.py:45-63](../backend/app/main.py#L45-L63) | ✅ (note: archived, not deleted — disclosed accurately) |
| Audit logs retention | §6 (flagged) | No purge job — indefinite | ❌ — fix or rewrite |
| Refresh-token row cleanup | §6 retention | Rows persist after expiry/revoke | ❌ — fix or rewrite (data-inventory open item) |
| Email-verification / password-reset token cleanup | §6 retention | Rows persist after expiry/use | ❌ — fix or rewrite |
| File-upload deletion on account deletion | §6 retention | `file_uploads` rows + Railway Object Storage objects retained | ❌ — fix or rewrite |
| Stripe never sees full card data; we hold only Stripe customer/subscription IDs | §3.4; Subprocessor List Stripe row | `@stripe/stripe-js` on frontend (Elements pattern); backend stores `stripe_customer_id`, `stripe_subscription_id` only ([account.py:28](../backend/app/models/account.py#L28), [subscription.py](../backend/app/models/subscription.py)) | ✅ |
| PostHog initialized with `persistence: 'localStorage+cookie'`; identified by `user.id`, grouped by `account_id`; US instance | §3.2; Cookie Policy §2.3 | [main.tsx:17-23](../frontend/src/main.tsx#L17-L23); [analytics.ts:34-40](../frontend/src/lib/analytics.ts#L34-L40) | ✅ |
| Sentry: backend `send_default_pii=True`; replay 1%/100% with text + media unmasked | §3.2 (disclosed); Subprocessor List | [main.py:14-26](../backend/app/main.py#L14-L26); [instrument.ts:9-12](../frontend/src/instrument.ts#L9-L12) | ✅ (disclosed accurately; ⚠️ recommend narrowing — see Attorney Checklist A2) |
| Anthropic is the sole live LLM provider | §5.1; Subprocessor List | `AI_PROVIDER='anthropic'` ([config.py:159](../backend/app/core/config.py#L159)); user-confirmed Gemini not provisioned | ✅ |
| Voyage AI is the live embedding provider | Subprocessor List | `VOYAGE_API_KEY`, `EMBEDDING_MODEL='voyage-3.5'` ([config.py:219-221](../backend/app/core/config.py#L219-L221)); user-confirmed key set | ✅ |
| No model training on Customer Data (Anthropic, Voyage) | ToS §3.4; Subprocessor List | Public terms commitment of each subprocessor; not enforceable from our side | ❓ — re-verify subprocessor terms before each publish |
| Resend is the transactional email provider; address `invites@resolutionflow.com` | Subprocessor List | [config.py:97-99](../backend/app/core/config.py#L97-L99) | ✅ |
| Google Fonts loaded over CDN → IP exposed to Google | §5.1; Subprocessor List; Cookie Policy §2.5 | [index.html:11-13](../frontend/index.html#L11-L13) | ✅ |
| Microsoft Learn MCP retrieves public docs only; no Customer Data egress | Subprocessor List "What is NOT" | `ENABLE_MCP_MICROSOFT_LEARN=True` ([config.py:216](../backend/app/core/config.py#L216)); the MCP search query string is the only outbound payload | ⚠️ partial — the query string itself can include AI-session context. Disclosed at a high level; if Customer Data text could be substantively included in a query, consider listing MS Learn as a subprocessor. |
| Backup retention 90 days | §9 backup language; DPA §6.3 | User-stated target; depends on Railway PITR window configuration | ❓ — verify Railway PITR configuration matches |
---
## Terms of Service
| Claim | Source | Reality | Verdict |
|---|---|---|---|
| Owner, admin, engineer, viewer role hierarchy; team-admin gate separately | §2.3 | `permissions.py`, `User.account_role` ([user.py:25-52](../backend/app/models/user.py#L25-L52)) | ✅ |
| Only owner can delete the account; deletion blocked if other members remain | §9.2 | [accounts.py:524-548](../backend/app/api/endpoints/accounts.py#L524-L548) | ✅ |
| Removed members are moved to a personal account on the free tier | §2.3 | [accounts.py:231-254](../backend/app/api/endpoints/accounts.py#L231-L254) | ✅ |
| ConnectWise PSA integration available | §1, §3.1, §8 | `services/psa/connectwise/`; only live PSA provider per user | ✅ |
| AI features integrate Anthropic; outputs may include errors | §4.2 | Code confirms Anthropic integration; honest disclosure | ✅ |
| 30-day export window post-termination | §9.4 | No automated export-window enforcement in code | ❌ — needs implementation or rewrite |
| Stripe handles payment processing | §5.3 | `@stripe/stripe-js` + `STRIPE_*` env vars | ✅ |
| Auto-renewal of subscriptions | §5.2 | Stripe Subscriptions semantics | ✅ |
| 30-day notice for price changes | §5.5 | Operational commitment; not code-enforced | ❓ — operational |
| MFA disclosure (not required) | (Privacy Policy §9 — accurate omission) | No MFA code path detected | ✅ |
---
## DPA
| Claim | Source | Reality | Verdict |
|---|---|---|---|
| Application-layer encryption for PSA credentials | Annex B.1 | Confirmed (above) | ✅ |
| RLS for tenant isolation | Annex B.2/B.5 | Confirmed (above) | ✅ |
| Authorized sub-processors list matches reality | Annex C | Matches Subprocessor List (Anthropic, Voyage, Stripe, Resend, Sentry, PostHog, Railway, Google Fonts) | ✅ |
| 72-hour breach notification SLA | §3.7 | Operational commitment | ❓ — define an internal detection-to-notify procedure to make this credible |
| Audit reports (SOC 2) available | §3.8.1 | No SOC 2 today | ⚠️ language says "when available," which is honest |
| Customer Data deleted after 30-day export window | §6.2 | Not implemented — see Privacy Policy table above | ❌ — flagged in Attorney Checklist A1 |
| 90-day backup retention | §6.3 | User-stated; depends on Railway PITR config | ❓ |
| SCC Module 2 / Module 3 incorporation | §5.1 + Annex D | Drafting only — no Customer signed instance yet | ❓ — operational |
---
## Subprocessor List
| Subprocessor | Listed correctly? | Notes |
|---|---|---|
| Railway | ✅ | Hosting + DB + Object Storage all in one entry |
| Anthropic | ✅ | LLM API for FlowPilot and AI features |
| Voyage AI | ✅ | Embedding provider; confirm DPA URL with attorney |
| Stripe | ✅ | Payment processor |
| Resend | ✅ | Transactional email |
| Sentry | ✅ | Error + Session Replay; see A2 about config |
| PostHog | ✅ | Product analytics; US instance |
| Google Fonts | ✅ | Disclosed; consider self-hosting (A3) |
| Gemini / Google AI | Omitted (correct) | Not provisioned in prod |
| OpenAI | Omitted (correct) | Not detected |
| Autotask, HaloPSA | Omitted (correct) | Not live |
| ConnectWise | Disclosed as non-subprocessor (correct) | Customer-controlled data source |
| Microsoft Learn MCP | Disclosed as non-subprocessor | Verified: doc-retrieval only |
---
## Cookie Policy
| Item | Reality | Verdict |
|---|---|---|
| `access_token` and `refresh_token` in localStorage | [authStore.ts:47-48, 86-87](../frontend/src/store/authStore.ts) and others | ✅ |
| `theme-storage`, `rf-editor-fullscreen`, `rf-intended-plan`, `recentFlows`, step-feedback flag, rated-sessions, escalation-queue seen | All confirmed by grep | ✅ |
| `ph_*` cookie set by PostHog due to `persistence: 'localStorage+cookie'` | [main.tsx:17-23](../frontend/src/main.tsx#L17-L23) | ✅ |
| Sentry described as telemetry-only, not cookie-setting | Default Sentry browser SDK behavior matches description | ✅ |
| Google Fonts disclosed | [index.html:11-13](../frontend/index.html#L11-L13) | ✅ |
| Consent mechanism for EU/UK | **Not implemented** | ❌ — see Attorney Checklist A3 |
---
## Net verdict
**Safe to share with an attorney as a starting draft.** Do not publish to the public website until the items marked ❌ are resolved by either:
1. Building the missing behavior (recommended path for A1 deletion-on-offboarding, A3 consent banner, A2 Sentry config tightening), OR
2. Rewriting the relevant paragraph to describe the actual behavior with no overclaim.
The factual scaffolding (subprocessors, encryption posture, retention reality, cookie inventory) is accurate. The remaining work is commercial-risk calibration and a small number of high-priority implementation gaps.