Infrastructure
EU-Only Data Residency
All data is stored and processed within the European Economic Area. Our primary infrastructure runs on AWS eu-west-1 (Dublin, Ireland) via Supabase. No customer data leaves the EEA unless explicitly configured by the subscriber (e.g., outbound webhook endpoints).
Hosting & Delivery
The application is served via Netlify's EU CDN edge nodes with HTTPS enforced on all connections. There is no on-premise infrastructure — the platform is fully managed cloud.
Encryption
| Layer | Standard |
|---|---|
| Data in transit | TLS 1.2+ (HTTPS enforced on all endpoints) |
| Data at rest | AES-256 encryption (Supabase/AWS managed) |
| Sensitive financial data | Field-level encryption using pgcrypto (AES-256). Bank IBAN, BIC, and bank name are encrypted at the column level and decrypted server-side only via authenticated Edge Functions. |
| Property identifiers (Manager Dashboard) | Salted SHA-256 hash of (company_id + normalised eircode) stored in the property_hash column. Used for property deduplication in the Manager Dashboard without retaining the eircode in reversible form. The 32-byte salt lives in a private, service-role-only schema with no public grants, so the hash cannot be reversed from outside the database. |
| Passwords | Bcrypt hashing. Plaintext passwords are never stored or logged. Leaked-password protection via HaveIBeenPwned is available on Supabase Auth and enabled at the project level. |
| Payment card data | Not processed by Caolu. All card handling is performed exclusively by Stripe. Card numbers never touch our systems. |
Access Control
Row-Level Security (RLS)
Every database table has PostgreSQL Row-Level Security policies enforced at the database engine level — not in application code. This means:
- Users can only access their own data, even if application code has a bug
- Managers can view data within their own organisation only
- Company-wide settings (brand, webhook, payment configuration) can only be modified by users with the manager or admin role — enforced at the database level
- Direct API access is subject to the same RLS policies as the application
Role-Based Access Control
Three tiers of access:
| Role | Permissions |
|---|---|
| Rep | Create/read/update/delete own assessments and projects. Generate PDFs. No access to company settings, dashboard, or other reps' data. |
| Manager | All rep permissions, plus: view team members' projects, access Manager Dashboard, edit Company Profile (brand, payment, webhook, T&Cs), manage team invitations, mark projects as won/lost with categorised reasons. |
| Admin | All manager permissions. Reserved for account owners. |
Authentication & Session Management
- Authentication provider: Supabase Auth (JWT-based)
- Session timeout: Automatic logout after 30 minutes of inactivity
- Session warning: Visual warning at 25 minutes with option to extend
- Re-authentication: Required after session expiry — no silent token refresh beyond timeout
- Password reset: Secure email-based reset flow with time-limited tokens
Audit Trail
Security-relevant events are logged to an immutable audit table:
| Event | Logged Data |
|---|---|
| Assessment opened | User ID, timestamp, tier |
| Configuration saved | User ID, project reference, system details |
| PDF generated | User ID, customer reference (if consent given), document type |
| Company profile updated | User ID, company ID, timestamp |
| Session timeout | User ID, reason, timestamp |
| Project status changed | User ID, project ID, new status, loss reason (if applicable) |
| Deal reopened | User ID, project ID, previous status, timestamp |
| Team goal set (Manager Dashboard) | Manager user ID, company ID, period, target values |
| Dashboard identity reveal (Manager Dashboard) | Manager user ID, company ID, rep count, period, timestamp. Visible to the affected Reps via their in-app "My Data & Privacy" screen. |
| Dashboard load / render failure | User ID, error message (truncated), company ID, period — used to alert on-call if the Manager Dashboard is failing for any company |
| Rep self-view | Rep user ID, days requested — logged every time a Rep opens the "My Data & Privacy" screen |
| Rep data export | Rep user ID, format (JSON or CSV), company ID — logged every time a Rep exports their own data via the self-service tool |
| Data export (manager CSV) | Manager user ID, period, row count, company ID |
Audit records are retained for the lifetime of the account plus 90 days post-cancellation. Deal status history and locked commercial values are retained for 7 years as commercial records in line with Irish Revenue requirements.
Manager Dashboard Controls
The Manager Dashboard (available on Teams and Enterprise plans) gives managers team performance visibility while applying privacy-by-design guarantees to every sales rep. The following controls are operated by the platform and cannot be disabled by subscribers:
Pseudonymous by Default
When a manager opens the Manager Dashboard, rep cards render with pseudonymous labels (Rep A, Rep B, Rep C) and matching placeholder avatars. Real names and initials do not appear on screen. The names exist in the server response but the client-side renderer only reveals them when the manager explicitly opts in.
Audit-Logged Identity Reveal
To see real rep names, the manager must click the "Reveal names" button above the team tab, confirm a consent dialog, and accept that the action is being recorded. On confirmation, the platform:
- Writes a
dashboard_identities_revealedevent to the immutable audit log, including the manager's user ID, company ID, number of reps viewed, and the period being reviewed - Displays an amber warning banner across the top of the Team tab stating that identity reveal is active
- Re-renders the rep cards and add-on matrix with real names
- Starts a 30-minute auto-expire timer
Every Rep can see the audit log of identity reveals for their company in the in-app "My Data & Privacy" screen. This is a deliberate transparency mechanism: reps are told (via the Privacy Policy and the employee notification their employer sends) that they can check, at any time, which managers have de-pseudonymised the dashboard and when.
Auto-Expiring Reveal Window
The reveal state is not persistent. It auto-expires after 30 minutes or when the manager closes the dashboard, whichever is sooner. Every new dashboard session starts pseudonymous again. This prevents habitual or background surveillance and forces each review session to be a conscious, auditable act.
No Automated Scoring or Evaluative Labels
The Manager Dashboard displays only raw work-product metrics (assessment count, PDF rate, battery attach rate, average system size, estimated pipeline). It does not compute composite scores, tier classifications, or any evaluative labels about individual reps. This is a deliberate design choice: GDPR Article 22 restricts automated individual decision-making with significant effects on a data subject, and by removing the evaluative layer entirely we ensure Article 22 is not engaged.
No In-App Coaching Notes
Caolú does not provide a free-text coaching notes feature inside the platform. Managers record coaching conversations in their own HR system. This keeps evaluative commentary about individual employees out of Caolú's scope entirely and reduces the data categories we hold on behalf of subscribers.
Team-Level Alerts Only
The Manager Dashboard Alerts tab surfaces only team-level patterns (e.g. "Team battery attach below 40% target", "Team volume dropped more than 20% vs previous period"). The platform does not generate alerts that name or implicitly identify individual reps. This prevents the dashboard from acting as a passive surveillance system.
Automated 90-Day Retention at Individual Resolution
A scheduled nightly job (nightly_anonymise_dashboard_data, running at 03:17 UTC via pg_cron) sets the anonymised_at marker on:
- Every
assessment_eventsrow older than 90 days - Every in-progress
saved_configsrow older than 90 days
Identifying metadata keys are stripped from the row at the same time (customer names, addresses, eircodes, installer names, labels). After anonymisation, the row still contributes to team-aggregate statistics but cannot be linked to a specific Rep. Won and lost commercial deal rows are exempt from this process and retain their locked commercial values for the 7-year Irish Revenue retention period.
Rep Self-Service: My Data & Privacy Screen
Every user, regardless of role, can open the "My Data & Privacy" screen from their profile dropdown. This screen shows:
- The Rep's own profile (name, role, company, account creation date)
- Their own performance metrics for the last 90 days (assessments, configs saved, battery attach rate, average kWp, estimated pipeline, total annual savings quoted)
- Their own deal pipeline status (in progress, won, lost)
- The identity-reveal access log — every time a manager in the same company clicked "Reveal names" in the Manager Dashboard, who did it, and in what period — for the last 90 days
- The retention policy applied to the data
A single "Export my data (JSON)" button downloads the whole dataset as a timestamped JSON file tagged with the GDPR Article 15 legal basis. This is first-class GDPR Article 15 (right of access) and Article 20 (right to data portability) support built directly into the product, so Reps do not need to file a formal request to see their data.
Manager-Initiated Erasure Handler
When a Rep exercises their GDPR Article 17 right to erasure via [email protected], Caolú engineering can invoke a service-role function (erase_user_performance_data) that:
- Sets the
anonymised_atmarker on everyassessment_eventsrow for that user and strips identifying metadata keys - Anonymises the Rep's in-progress saved configurations (nulls out rep_name and cust_name, strips PII keys from config_data)
- Scrubs the Rep's profile (full_name becomes "Former rep (erased)", phone number and SEAI installer number are nulled)
- Preserves locked commercial values on won/lost deal rows for the 7-year Revenue retention — the Rep's name is scrubbed but the commercial figure remains
Server-Side Access Gate
The Manager Dashboard data is returned by a PostgreSQL function (get_manager_dashboard) that runs as SECURITY DEFINER and explicitly verifies, before returning any data, that the calling user is authenticated, has the manager or admin role, and belongs to the company whose dashboard is being requested. A rep-role user cannot receive any team data even if they attempt to call the function directly.
No Cross-Company Leakage
Row-Level Security policies, in addition to the function-level guard above, ensure that a manager of Company A cannot read any row belonging to Company B. This is enforced at the PostgreSQL engine level and has been verified via adversarial testing.
Webhook Security
Enterprise subscribers can configure outbound webhooks to transmit assessment data to CRM systems. Webhook payloads are:
- Signed: Optional HMAC-SHA256 signing using a subscriber-configured secret. The receiving system verifies the
X-Caolu-Signatureheader to confirm payload authenticity. - Subscriber-controlled: Caolu transmits to the endpoint configured by the subscriber. The subscriber is responsible for ensuring the receiving system meets their security and GDPR requirements.
- Non-blocking: Webhook failures do not affect platform operation. Failed deliveries are logged but not retried.
AI-Assisted Development & Tooling
Caolú engineers use AI coding assistants — currently Anthropic's Claude, including the Claude Code command-line tool — to help author, review, and debug the Caolú codebase. Because these tools are external processors, we apply strict data-minimisation controls to ensure no real subscriber or customer Personal Data is ever shared with them.
Scope of Use
- AI assistants are used by Caolú engineering only, during development. They are not embedded into the Caolú product and do not process any subscriber or end-customer data at runtime.
- Permitted inputs are limited to source code, SQL migration files, configuration, technical documentation, commit history, and error traces.
- Prohibited inputs include: real customer names, addresses, eircodes, MPRNs, phone numbers, email addresses, GDPR consent records, saved quotations, exported CSV data, webhook payloads, or screenshots containing real customer identifiers.
Data Minimisation Controls
- Synthetic data only. All development and testing sessions use anonymised or fabricated records. Production rows are never copied into a prompt.
- No direct production access. AI tools are not granted read access to the live production Supabase database. Where the Supabase Model Context Protocol (MCP) integration is used, it is scoped to schema inspection, non-sensitive tables, and read-only operations against development or sanitised snapshots where possible.
- Redact-before-share. Engineers must redact or substitute any potentially identifying value in a bug report, log extract, or file dump before pasting it into an AI prompt.
- Separation of duties. AI tools authenticate against our development environment only. Production deployment still requires a human-reviewed git push to the main branch, so no AI-generated code reaches production without human sign-off.
Article 28 Compliance Posture
GDPR Article 28 requires a written Data Processing Agreement (DPA) with any processor of Personal Data on our behalf. We satisfy this on two fronts, depending on the Anthropic plan in use at the time of development:
- Where Caolú engineering operates under Anthropic's Commercial Terms of Service (Claude Team, Claude Enterprise, or the Claude API), Anthropic's DPA with Standard Contractual Clauses is automatically incorporated — so a processor contract is in place for any data that might be transmitted.
- Where Caolú engineering operates under Anthropic's Consumer Terms of Service (Claude Free, Pro, Max, and Claude Code accessed via those plans), no Anthropic DPA is in force — so we treat the tool as out-of-scope for Personal Data processing entirely, and the "synthetic data only" rule above is enforced as a bright line.
A full written policy covering these controls is maintained internally as the Claude Workflow Data Minimisation Policy. Subscribers with audit rights under our DPA may request to review this policy by contacting [email protected].
Incident Response
Breach Notification
- Internal detection → immediate escalation to Caolu security lead
- Subscriber notification: within 24 hours of confirmed breach
- Data Protection Commission (DPC) notification: within 72 hours as required by GDPR Article 33
- Data Subject notification: without undue delay where required by GDPR Article 34
Breach Notification Includes
- Nature and scope of the breach
- Categories and approximate number of records affected
- Likely consequences
- Measures taken or proposed to mitigate
Availability
Caolu targets 99.5% monthly uptime for the application at app.caolu.ie, excluding:
- Scheduled maintenance (communicated 48 hours in advance)
- Force majeure events
- Third-party service outages (Supabase, Stripe, Google Maps, ESB Networks)
Current infrastructure status is available at our hosting provider's status page. We are evaluating a dedicated status page for future deployment.
Compliance Roadmap
| Standard | Status |
|---|---|
| GDPR | Compliant. DPA available. DPO contactable at [email protected] |
| Data Protection Act 2018 (Ireland) | Compliant |
| Consumer Rights Act 2022 | Reflected in default T&Cs template (14-day cooling-off period) |
| SOC 2 Type II | Under evaluation for 2027 |
| ISO 27001 | Under evaluation for 2027 |
Responsible Disclosure
If you discover a security vulnerability in the Caolu platform, please report it responsibly to [email protected]. We commit to:
- Acknowledging receipt within 2 working days
- Investigating and providing an initial assessment within 7 working days
- Not pursuing legal action against good-faith security researchers
- Crediting researchers (with consent) after remediation
Contact
Data Protection Officer:
Caolu Consultants · CRO No. 783041 · Republic of Ireland
[email protected] · Company details
Caolu Consultants — Security
CRO No. 783041 · Republic of Ireland · Company details