Skip to content
Menu
Case StudiesTechnology & SaaS

Case Study

How a B2B SaaS Company Unified Its Data Across HubSpot, Stripe, and Everything Else, and Put an AI Analyst in Slack.

At a B2B SaaS company, every team pulled its own numbers from its own tool, and the numbers never matched. We unified HubSpot, Stripe, and the rest into one governed warehouse and added an AI data intelligence layer for Slack and the AI tools their teams already use, so there is one answer and everyone can ask for it.

To answer a business question in Slack, down from hours
<5 Sec
Governed data layer across HubSpot, Stripe, and the rest of the stack
1
Metrics defined once and read by every dashboard and answer
200

The Challenge

Every Tool Had Its Own Numbers, and Nobody Could Say Which Were Right.

Bring the data together once, give every customer one record and every metric one definition, and write the rules down where they can be checked. Then let people ask where they already work, in Slack, with the guardrails built into the system rather than a memo.

The Business Context

The business is a venture-backed B2B SaaS company that grew faster than its reporting. Sales ran in HubSpot, billing in Stripe, and product usage, sales calls, and marketing attribution in three more tools, with the product’s own usage data in the application itself. Every team depended on numbers put together from these systems by hand.

None of the tools agreed on who a customer was or what they paid. Sales could not match what it had closed to what billing had invoiced, marketing could not follow a lead from first touch to paid, and customer success had no dependable list of renewals coming up. Finance kept the real revenue numbers in a monthly workbook, so every question meant exporting files and joining them in a spreadsheet. Leadership read three sets of numbers that disagreed, and last quarter’s board figures could not be produced twice. Personal data sat unprotected, and there was no record of who had looked at it.

  • Leadership read three sets of numbers that disagreed, and last quarter’s board figures could not be produced twice.
  • Sales could not match what it had closed in HubSpot to what Stripe had invoiced, and pipeline coverage was rebuilt in a spreadsheet every quarter.
  • Marketing could not follow a lead from first touch through sign-up to paid, and nobody trusted the attribution.
  • Customer success had no dependable list of renewals coming up, and churn meant something different on every dashboard.
  • Finance typed the monthly revenue workbook in by hand, so the close took days and could not be reproduced.
  • Personal data sat unprotected, and there was no record of who had looked at it.

What We Delivered

We were the external engineering team. We built one governed warehouse on the company’s own Google Cloud account, defined every metric once, moved reporting to Hex, and put an AI data intelligence layer in Slack so anyone can ask a question and trust the answer. Personal data is protected by default, and the whole setup is written down as code. The company owns all of it.

The Solution

Six Problems, and What We Built for Each.

  1. 01 / 06

    Every tool, its own numbers

    • HubSpot & Stripe
    • Gong & Dreamdata
    • PostHog
    • Product usage data
    Each tool knew part of the customer, and nothing joined them up

    The Problem

    Every Tool Knew Part of the Customer

    Deals in HubSpot, payments in Stripe, usage, calls, and marketing in three more tools. Nobody could follow a customer from first touch to renewal, and what sales closed never matched what billing invoiced.

    One warehouse

    • HubSpot & Stripedeals & payments
    • Gong & Dreamdatacalls & attribution
    • PostHogproduct analytics
    • Product usage dataas it changes

    What We Built

    One Warehouse, Where Every Customer Has One Record

    Everything lands in one warehouse, and one record ties a company’s deals, invoices, calls, and usage together. A customer can be followed from first touch to renewal, and the numbers line up.

    How We Built It

    1. Deals, Contacts, and CompaniesHubSpotLanded through a managed Fivetran connector

    2. Subscriptions, Invoices, and PaymentsStripeLanded the same way, and tied to the HubSpot company through a documented join order

    3. Sales CallsGongCalls, participants, and transcripts, through a purpose-built dlt pipeline

    4. Product Analytics and AttributionPostHogPostHog through its native export, and Dreamdata for marketing attribution

    5. Product Usage Data, as It ChangesPostgreSQLUsers, organisations, and trials, replicated by change data capture into a buffer no person reads

    Google BigQueryApache AirflowApache Airflow

    Everything lands in BigQuery. Airflow runs every load on a schedule and alerts the team in Slack when a run is missed or late.

  2. 02 / 06

    Spreadsheets everywhere, most figures typed in by hand each month

    The Problem

    The Board Numbers Could Not Be Produced Twice

    Finance kept the real revenue figures in a monthly workbook, typed in by hand. The dashboard app showed something different, closed quarters could change after the fact, and nobody could reproduce what the board had seen last quarter.

    • Stripe export
    • HubSpot export

    Monthly revenue, built in the warehouse

    • New
    • Expansion
    • Churn

    Checked against Finance’s close each month

    What We Built

    Revenue Built in the Warehouse, Checked against Finance, and Locked

    Every rule Finance applied by hand is written down and rebuilt in the warehouse. The result is checked against Finance’s close each month, history is kept, and a closed quarter stays closed, so the board reads the same number twice.

    How We Built It

    1. 01

      Every Rule, Written DownMore than 110 numbered decisions, each with an owner, such as “a late renewal is not churn”

    2. Rebuilt as Tested ModelsdbtAround 500 dbt models, built and tested on every run

    3. 03

      Calibrated to Finance’s CloseWarehouse revenue is compared with Finance’s figure each month, and the differences are documented

    4. 04

      Targets from Finance’s Own SheetThe budget sheet Finance already maintains is read in place as a BigQuery external table, so targets are never retyped

    GitHubGitHub

    Every change is a reviewed pull request with its development build and a query that shows the change does what it says. Merging is the release.

  3. 03 / 06

    “Churn” means…

    • Finance
    • Sales
    • Marketing

    The Problem

    The Same Word Meant a Different Number in Each Team

    Churn, active user, and a qualified lead each meant something different in the dashboard app, Finance’s workbook, and a definitions document. When they disagreed, there was no agreed way to settle it.

    “Churn” means…

    • Finance
    • Sales
    • Marketing

    What We Built

    One Definition, Written Once and Read Everywhere

    Each of 200 metrics is defined once, by a named owner. Every dashboard, every Slack answer, and every self-built view reads that one definition, so they cannot drift apart.

    How We Built It

    1. A Semantic LayerMetricFlowEach metric is defined once: certified MRR and ARR, new, expansion, and churn ARR, pipeline, bookings, retention, and product usage

    2. 02

      A Metric RegisterEvery metric carries an owner, a citation, and a status: certified, divergent, unconfirmed, or not implemented. An unimplemented metric is never served

    3. 03

      Guardrails in the DefinitionBalances such as ARR are read at period end, never summed; a table that needs a filter refuses to run without it

    GitHubGitHub

    CI fails the build if a metric has no status, a column does not exist, or a citation does not resolve. The manifest reaches every consumer within seconds of a green build.

  4. 04 / 06

    What was net new ARR last quarter, by segment?

    Someone writes a query, checks it, and sends the result back

    One of around 70 recurring questions

    The Problem

    Every Question Waited for Someone to Pull the Numbers

    Around 70 recurring questions from revenue operations alone, each waiting for a person to write and check a query. A simple question could take hours, and the people who could answer it had a queue.

    What was net new ARR last quarter, by segment?

    Here is net new ARR for last quarter, by segment.Certified

    From the governed metricsAsked by a verified member

    What We Built

    Ask in Slack, and Get an Answer You Can Trust

    Slack is where this team works, so the analyst works there too. Anyone can ask in plain words, and each answer carries a label, Certified, Provisional, Directional, or Declined, worked out from where the figures came from, not by the AI’s own say-so.

    How We Built It

    1. A Read-Only AI AnalystClaudeBuilt on Claude Managed Agents, answering in approved channels and direct messages

    2. 02

      Trusted Sources, in a Fixed OrderThe governed metrics first, then definitions and context, then guarded SQL only when the first two cannot answer

    3. 03

      Every Figure Traced to a Tool CallA checker catches any number not backed by a result in the same turn, and the agent gets one turn to repair it

    4. One MCP Gateway on Cloud RunMCPGoverned metrics and guarded SQL behind one gate, also switched on as a company connector in Claude

    5. Every Asker Verified, Every Query GuardedGoogle BigQueryOnly full workspace members, each with a signed token per conversation. SQL is read-only, capped, and refuses PII columns; BigQuery IAM is the real boundary

    SlackNext.jsNext.js

    The same agent is available in Claude, and a monitoring dashboard reports usage, reliability, speed, cost, and feedback from the agent’s own telemetry.

  5. 05 / 06

    Customer records

    • Name

    • Email

    • Plan

    • Usage

    Anyone with access saw everything, and nothing recorded who read what

    The Problem

    Personal Data, and No Record of Who Had Seen It

    Names, emails, and other personal details sat unprotected across the data, the product held customer content under contract, and we, as the outside engineering team, needed to work on live data. There was no record of who had looked at what.

    Customer records

    • Name

      masked

    • Email

      masked

    • Plan

    • Usage

    Access window: 24 hours at mostWritten reasonOne approvalAlert on start

    What We Built

    Protected by Default, with Short, Approved Access That Leaves a Trail

    Personal details are hidden from everyone by default, our own engineers included. Seeing them needs a written reason and one approval, lasts at most 24 hours, and sends an alert the moment it starts. Customer content never enters the warehouse.

    How We Built It

    1. Policy Tags from BirthGoogle BigQueryA personal-data column is tagged in the same reviewed change that creates it, and a build without the tags fails

    2. Time-Boxed AccessGoogle CloudPrivileged Access Manager grants of at most 24 hours, with a reason, one approval, and an alert if data is copied out

    3. Every Grant Defined as CodeTerraformDatasets, access groups, and entitlements live in Terraform and are applied only from CI. Nobody is granted access as an individual

    Google Cloud

    Customer content never enters the warehouse: no message bodies, prompts, or document text. Customers whose contracts exclude analytics stay excluded on every build.

  6. 06 / 06

    “What did we report to the board last quarter?”

    “Which renewals are due next month?”

    “Can I get a pipeline view for my team?”

    The Problem

    Reports Were Rebuilt by Hand, and Every New Chart Was a Request

    The board summary, pipeline coverage, and the renewal list were put together again each period. A manager who wanted a view for their own team asked for it and waited.

    One set of definitions, three ways in

    • Reporting · Hex dashboards
    • Analytics · the AI analyst in Slack
    • Your own dashboards · Claude, through MCP

    What We Built

    Reports in Hex, Answers in Slack, and Dashboards Anyone Can Build

    Three ways in, all reading the same definitions. The standing reports live in Hex. Questions a report cannot answer go to the AI analyst in Slack. And anyone can build their own dashboard in Claude on the same metrics, without asking.

    How We Built It

    1. The Reporting LayerHexHex reads governed metrics from MetricFlow and data from the warehouse, so a chart and a Slack answer agree

    2. The Analytics LayerSlackThe AI analyst answers the questions a dashboard cannot, in the channels the team already reads

    3. Dashboards through MCPMCPThe MCP gateway is a company connector in Claude, so a sales manager builds a pipeline tracker and customer success builds a renewals board

    MetricFlow

    All three ask the semantic layer for the same named metric, so an official dashboard and a self-built one cannot drift.

The illustrations are schematic. The question, the answer, and the table are examples, not the client’s data.

All the Data, in One Place.

Before: Six Platforms, Each Holding Part of the Customer

  • HubSpotHubSpotContacts, companies, and deals
  • StripeStripeSubscriptions, invoices, and payments
  • PostHogPostHogPostHogProduct analytics events
  • GongGongSales calls and transcripts
  • DreamdataDreamdataMarketing attribution
  • PostgreSQLProduct Usage DataUsers, organisations, and trials

Nothing joined them. Following one customer meant an export from each

After: One Place Holds All of It

Google BigQuery

Everything About a Customer, in One Place

Deals, payments, product usage, sales calls, and marketing attribution for the same company, side by side in one warehouse

  • HubSpotHubSpotContacts, companies, and deals
  • StripeStripeSubscriptions, invoices, and payments
  • PostHogPostHogPostHogProduct analytics events
  • GongGongSales calls and transcripts
  • DreamdataDreamdataMarketing attribution
  • PostgreSQLProduct Usage DataUsers, organisations, and trials
One Customer, One Record
Follow a company from first touch to renewal without joining anything by hand
One Number per Question
Churn, pipeline, and revenue mean the same thing on every dashboard and in every answer
Bookings Match Billing
What sales closed and what finance invoiced reconcile in one place
Last Quarter Still Adds Up
History is kept, so what was reported before can be reproduced

Every platform lands here on a schedule. Nobody exports or pastes a file again

Simplified from the project account. Finance’s budget sheet and the agent’s own usage logs also land in the warehouse and are left out here for clarity.

How the Pieces Fit Together

Before: Every Tool Had Its Own Numbers

  1. HubSpot & Stripe
  2. Files exported and pasted into Finance’s workbook, joined by hand

  3. A dashboard app reading the same two tools, rebuilt from scratch on every run

  4. Two answers to the same question, and no agreed way to settle it

Connected to nothing

  • PostHog
  • Gong
  • Dreamdata
  • Product usage data

Only the CRM and billing reached anyone, and they reached two places that disagreed. Product usage, sales calls, and attribution needed a custom export every time, so nobody could follow a customer from first touch to renewal or match what sales closed to what billing invoiced.

Sets of Numbers3

After: One Set of Numbers, from Source to Answer

  1. 01

    Every Source, Loaded on a Schedule

    HubSpot, Stripe, PostHog, Gong, Dreamdata, and product usage data, each through its own pipeline

  2. 02

    Loaded and Orchestrated

    Fivetran for HubSpot and Stripe, dlt and native exports for the rest, Datastream for product usage data, with Airflow running every pipeline on a schedule and alerting in Slack

  3. 03

    One Warehouse, One Record per Customer

    Cleaned, matched, and joined in BigQuery, with personal details hidden by default

  4. 04

    One Definition for Every Metric

    200 governed metrics in MetricFlow, each with an owner and a status

  5. 05

    One MCP Gateway, Two Tools

    Governed metrics and guarded SQL, served over the Model Context Protocol behind one gate on Cloud Run. Every call is authenticated per person, audited, and capped, with PII columns refused

  6. 06

    The AI Data Agent

    Built on Claude Managed Agents. Reads the governed metrics first and guarded SQL last, and labels every answer Certified, Provisional, Directional, or Declined

  7. 07

    Three Ways In

    Reporting in Hex, reading governed metrics from MetricFlow and data from the warehouse; analytics through the AI analyst in Slack and Claude; and self-built dashboards in Claude through the MCP gateway

  8. 08

    The People Asking

    Finance, sales, marketing, customer success, and leadership, each answer attributed and audited

Production is built only by the scheduler, on infrastructure defined in Terraform. People read from it; nobody writes to it, and nobody reads the raw application data.

Sets of Numbers1

Simplified from the project account. Every number on a dashboard or in a Slack answer comes from the same governed definitions.

How the Data Is Governed, Protected, and Retained.

Governance on this platform covers three things. Protection means personal data is hidden from everyone by default, our own engineers included, access is granted to groups rather than to individuals, and customer content never enters the warehouse at all. Retention means nothing is kept longer than it is needed. Raw product usage data and developer sandboxes expire after 7 days, access grants and agent sessions end within 24 hours, the agent keeps usage counts but never the text of a question, and a person who asks to be erased is removed before the next write, with legal holds taking priority. Control means every dataset, group, grant, and entitlement is written in Terraform and applied only from CI, so a change to who can see what is a reviewed pull request, never a click in a console. The three lanes below show how a read is governed in practice.

Where Data Can Be ReadGoogle BigQueryMasked by default at every tier a person can see

  1. Raw Product Usage Data

    Replicated from the application into a machine-only buffer that expires after 7 days. No person reads it

  2. Landing

    Each source as it was sent. Readable only inside an approved access window

  3. Lake

    Typed and deduplicated, with personal data masked for everyone

  4. Warehouse

    Business models, still masked. A model that is not signed off builds in a candidate dataset nobody reads

  5. Dashboards, Slack & Claude

    Read the governed models only, with every query attributed to the person who asked

PII MaskingGoogle BigQueryBigQuery policy tags with always-null masking; tags come first, before any group is bound to a dataset

  1. Tagged at Birth

    A personal-data column is tagged in the same reviewed change that creates it. A production build without the tag configuration fails

  2. Masked by Default

    Anyone without a grant reads a tagged column as empty: names, addresses, phones, IP addresses, and raw email

  3. The Tag Follows the Column

    A tagged column can appear downstream only with the same tag. Renaming, hashing, or dropping the tag fails CI

  4. Keys Stay Joinable

    Join keys are never masked. People are matched across sources with a keyed email token, encrypted under a managed key, never a plain hash

  5. Unmasked Only in a Window

    Seeing the real value needs an approved access grant, below, and a dev build cannot run while one is held

Reading Sensitive DataGoogle CloudGoogle Cloud Privileged Access Manager

  1. Request

    A written reason, for the shortest time that covers the task

  2. One Approval

    Recorded with the request, so the grant has an owner

  3. Window Opens

    At most 24 hours. An email goes out the moment it activates

  4. Read in Place

    An alert fires if the data is copied out, extracted, or written anywhere else

  5. Expires

    Access ends on its own, with who, why, and when on record

Customers whose contracts exclude their data from analytics are excluded on every build, and that exclusion fails closed.

Simplified from the delivered configuration. The 7-day expiry and the 24-hour limit are configured limits, not measured values.

Technologies Used

  • HubSpot

    CRM: Contacts, Companies & Deals

  • Stripe

    Payments: Subscriptions & Invoices

  • PostHog

    Product Analytics

  • Gong

    Sales Call Recordings

  • Dreamdata

    Marketing Attribution

  • PostgreSQL

    Product Usage Data, Replicated as It Changes

  • Google BigQuery

    Cloud Data Warehouse

  • dbt

    Data Modeling, Tests & Documentation

  • MetricFlow

    Semantic Layer & Governed Metrics

  • Fivetran

    HubSpot & Stripe Ingestion

  • dlt

    Custom Ingestion Pipelines

  • Apache Airflow

    Pipeline Orchestration on Cloud Composer

  • Google Cloud

    Cloud Platform: Datastream, Cloud Run & Privileged Access Manager

  • Terraform

    Infrastructure as Code

  • MCP

    Model Context Protocol Gateway: Governed Metrics & Guarded SQL

  • Claude

    AI Analyst, Claude in Slack & Cloud Coding Agents

  • Slack

    Where the Team Lives: the Analyst, Alerts & Maintenance

  • Linear

    Issue Tracking for Fixes

  • Hex

    Reporting Layer: Dashboards

  • GitHub

    Pull Requests, CI & Releases

  • Python

    Pipelines & the MCP Gateway

  • Next.js

    Agent Monitoring Dashboard

How the System Was Built

  1. 01

    Bring Every Source into One Place

    Sales, billing, product usage, sales calls, marketing attribution, and Finance’s own budget sheet all land in one warehouse on a schedule. Nothing is exported or retyped, and the team hears in Slack if a load is missed or late.

    Technical Detail: Bring Every Source into One Place

    HubSpot and Stripe land through Fivetran. Gong’s calls and Dreamdata’s attribution come through purpose-built pipelines. PostHog uses its native export, and Finance’s budget sheet is read in place as a BigQuery external table. The application Postgres database is replicated by Datastream change data capture into a machine-only buffer tier with 7-day partition expiry. Gong is loaded by a dlt pipeline with a deliberate transcript lag and reconciliation after each load. Every loader goes through one operational contract library: a run lock, a run ledger as the state of record, per-resource reconciliation, schema quarantine for unknown columns, a bounded retry policy, and tombstones for erased subjects. Airflow 3 on Cloud Composer 3 runs 19 production DAGs, with Slack job alerts and a watchdog for missed, queued, overdue, and recovered runs. Every DAG that moves data ships paused, and a person switches it on.

  2. 02

    Write the Rules Down, Then Rebuild Them

    The old dashboard app and the spreadsheets held years of judgment calls that lived in people’s heads. Each one was written down as a numbered decision with a named owner, then rebuilt where it can be tested on every run. Only then was the old app retired, after its hand-entered data had been saved.

    Technical Detail: Write the Rules Down, Then Rebuild Them

    Three dbt Core projects in one repository hold around 500 models: landing, generated from a per-column policy file; a typed and deduplicated lake, one dataset per source; and the warehouse, from staging through intermediate to domain marts for finance, sales, marketing, customer success, product, reference, and a customer data platform. More than 110 decision records cover rules such as “one-off invoices are not MRR” and “the finance month boundary is Pacific.” A model that has not been signed off builds in a candidate dataset that no dashboard or agent reads; promotion is one pull request that records the sign-off.

  3. 03

    Give Every Customer One Record

    HubSpot, Stripe, and the product each knew a customer by a different name. The warehouse ties them together into one record, following a documented order of precedence, so a company’s deals, invoices, calls, and usage finally line up.

    Technical Detail: Give Every Customer One Record

    The HubSpot-to-Stripe join ladder starts with the primary association and falls back in a documented order. People are joined across sources with a keyed email token produced by deterministic encryption under a KMS-wrapped keyset, so raw email never leaves staging. Join keys are never masked, and a check fails any tagged key. Quota comes from the commission tool’s own export as a versioned seed, refreshed by pull request, so it can finally be lined up with pipeline.

  4. 04

    Define Each Metric Once, and Let Finance Keep the Final Say on Revenue

    Two hundred metrics are defined once, each with a named owner, and every dashboard and every answer reads the same definition. Revenue is checked against Finance’s monthly close, and once a quarter is closed it stays closed.

    Technical Detail: Define Each Metric Once, and Let Finance Keep the Final Say on Revenue

    MetricFlow semantic models define certified MRR and ARR, new, expansion, downgrade, and churn ARR, net new ARR, logo counts, ARPC, pipeline created, bookings, won deals, gross and net revenue retention, and product usage measures. A metric register gives each metric an owner, a definition, a citation, and a reconciliation status. Additivity is declared, so flows sum over a quarter while balances are read at the period’s last month. Tables that need a filter refuse to run without it. CI fails if a metric has no status, a column is missing, or a citation does not resolve. The manifest is published after every green production build and hot-reloaded by the MCP gateway, so the agent and Claude read the new definition within seconds, and Hex reads the same MetricFlow definitions directly.

  5. 05

    Put the AI Analyst Where People Already Ask

    Slack is this team’s home. They move fast and decisions happen there, so the analyst had to be there too, not in another tab. Anyone can ask a question in plain words and get an answer in seconds, with a label that says how far it can be trusted. The label comes from where the figures came from, not from the AI’s own confidence.

    Technical Detail: Put the AI Analyst Where People Already Ask

    A read-only data agent answers in Slack and in Claude. The Slack bot runs on Claude Managed Agents as a Cloud Run worker pool. It calls the MCP gateway’s governed metrics tool first, knowledge vaults of definitions second, and its guarded SQL tool last, and labels every answer Certified, Provisional, Directional, or Declined. Every figure needs a tool call in the same turn; a checker catches numbers that are not on the answer card or in a tool result, and the agent gets one turn to repair them. The SQL guard allows a single SELECT over allowlisted datasets, refuses PII columns and candidate datasets, and caps billed bytes and rows. BigQuery IAM on the service account is the real boundary. Each asker is verified as a full workspace member, and a signed token per thread and user, scoped to one server and expiring within 24 hours, puts the verified asker on every audit line. Evals include must-refuse cases, and a Next.js dashboard reports usage, reliability, speed, cost, and feedback from the agent’s own telemetry.

  6. 06

    Three Ways In: Reports, Answers, and Dashboards Anyone Can Build

    The standing reports live in Hex. Questions a report cannot answer go to the AI analyst in Slack. And anyone can build their own dashboard in Claude on the same metrics, so a sales manager has a pipeline tracker and customer success has a renewals board without asking the data team. All three read the same definitions, so they cannot disagree.

    Technical Detail: Three Ways In: Reports, Answers, and Dashboards Anyone Can Build

    Hex is the reporting layer: a revenue dashboard that replaced the old app and a Finance OKR dashboard, built to a written house style. Hex reads governed metrics from MetricFlow and data from the warehouse, not through MCP. The AI analyst in Slack is the analytics layer. The third layer is self-serve: the MCP gateway behind the Slack agent is switched on as a company connector in Claude, so far used to build Claude dashboards. The guardrails live in the gateway and in IAM, not in the prompt, so a user-built dashboard inherits the same PII refusals, row and byte caps, and candidate exclusions as the agent. Every connector call is authenticated per user and audited. Because both the official Hex dashboards and the self-built ones ask the semantic layer for the same named metric, they cannot drift. The data platform owns definitions, quality, and access; the business builds its own views.

  7. 07

    Protect Personal Data by Default, and Make Any Exception Short and Visible

    Names, emails, and other personal details are hidden from everyone by default, our own engineers included. Seeing them needs a written reason and one approval, lasts at most 24 hours, and raises an alert if anything is copied out. Customer content never enters the warehouse at all.

    Technical Detail: Protect Personal Data by Default, and Make Any Exception Short and Visible

    BigQuery policy tags with always-null masking cover three classes: redacted PII, the restricted email token, and restricted customer content. A column is tagged in the same reviewed diff that creates it, a deny-by-default check keeps the tag on every downstream column, and a build without tag configuration fails. Two Privileged Access Manager entitlements cover landing reads and PII unmasking, with an activation email and a separate copy-out alert. A per-column landing policy gives every application column one action, from keep to drop, and JSON fields are readable only through an allowlist of paths. Organisations whose contracts exclude analytics have every content-derived value nulled on every build, with the exclusion list hash-pinned in both repositories.

  8. 08

    Write the Whole Setup Down, So It Can Be Rebuilt and Nothing Goes Live Unreviewed

    Every part of the platform, including who can see what, is written down as code and applied only after review. A change ships by merging it, and there are no passwords or key files sitting anywhere.

    Technical Detail: Write the Whole Setup Down, So It Can Be Rebuilt and Nothing Goes Live Unreviewed

    Terraform defines the four Google Cloud projects, every BigQuery dataset and grant, the Privileged Access Manager entitlements, Cloud Run services, and Cloud Composer, and is applied only from CI. Trunk-based development with CI and one approval; merging moves the dbt image’s production tag and the next scheduled run builds it. Personal credentials in development and Workload Identity Federation in CI, with no service-account keys. Per-developer sandboxes read the production lake and write only to their own datasets, with a query cap. Offline checks cover SQL style, naming, policy tags, masked keys, the semantic contract, and declared freshness, with no cloud access. Every repository carries an AGENTS.md that Claude, Cursor, and Codex read, so coding agents follow the same rules people do.

  9. 09

    Keep It Running from Where the Team Already Works

    The platform is looked after from a Slack channel, because that is where this team works and how fast it moves. A late load, a definition that needs a change, or a new field that needs protecting is raised there, tracked in Linear, and handled by Claude or a cloud coding agent for the small fixes, with a person reviewing every change before it goes live.

    Technical Detail: Keep It Running from Where the Team Already Works

    Airflow job alerts and the watchdog post to the same channel, so a failure is seen where the fix is discussed. Linear holds the issue. Claude in Slack answers questions about the platform from the repositories’ own documentation, and cloud coding agents open the pull request for small changes such as a new metric status, a tag, or a seed refresh. Every change still goes through CI and one approval; merging is the release.

The Results

One Set of Numbers. Answers Anyone Can Ask For.

What Was Delivered

Sales, billing, product usage, sales calls, and marketing attribution are joined in one warehouse, with one record per customer and one definition per metric. Revenue is checked against Finance’s close every month. The standing reports run in Hex, anyone can ask the AI analyst in Slack or Claude and get a labelled answer, and anyone can build their own dashboard on the same metrics. Personal data is protected by default, and the whole setup is written down as code.

What Changed in the Working Month

The old dashboard app was retired after its rules were written down, rebuilt, and checked against it, and after its hand-entered data was saved. The renewal list, Finance’s monthly revenue movements, pipeline coverage, and the board summary are repeatable reports instead of spreadsheets rebuilt by hand. The Slack analyst has been live since autumn 2026, and the project owner reports that a business question that used to wait hours for someone to pull the numbers is now answered in under five seconds.

This account describes the engineering, not the business. The move from hours to under five seconds for an answer is reported by the project owner; no measurement period or independent validation was supplied. No revenue figures or other business results are claimed. The platform figures, 19 production pipelines, around 500 models, 200 governed metrics, and more than 110 decision records, come from the delivered repositories.

What Changed for the People Doing the Work

Finance
Keeps the final say on reported revenue, and checks the warehouse figure against the close each month instead of retyping it. Monthly movements come from the platform, and a closed quarter stays closed.
Revenue Operations & Sales
Ask the recurring questions in Slack instead of waiting for someone to pull the numbers. What sales closed and what billing invoiced now match.
Marketing & Growth
Follow an account from first touch through sign-up to paid in one place, with one definition of a qualified lead and an active user.
Customer Success
Has a dependable list of renewals coming up, and kept its renewal forecasts when the old app was retired.
Leadership & the Board
Read the same numbers on every dashboard and in every Slack answer, with history kept, so last quarter can be looked up rather than reconstructed.
Engineers
Work on live data with personal details hidden by default, and short, approved windows for anything sensitive. Small fixes are raised in Slack, tracked in Linear, drafted by a cloud coding agent, and reviewed before they go live.

What This Project Reinforced

Write the Rules Down before You Build

The old app and the spreadsheets held years of judgment calls. Getting each one written down with a named owner was most of the real work, and it is what made the numbers trustworthy.

Decide Whose Number Wins When Two Disagree

Finance’s close decides reported revenue, and the metric definitions decide what a word means. When they disagree, the owner decides, and the gap is written down rather than quietly patched.

Put the Guardrails in the System, Not in a Memo

A rule that lives only in a document eventually gets broken. Labels worked out by the system, access that expires on its own, and a build that refuses to run without its protections held up where instructions would not.

Let People Ask Where They Already Work

This team runs on Slack and moves fast. An analyst, alerts, and upkeep inside Slack get used every day; a separate portal would not have been.

Self-Serve Only Works Once the Data Is Governed

The Slack analyst and the self-built dashboards were only safe to switch on because the definitions, the protection of personal data, and the access rules were already in place.

Working Through a Similar Problem?

Tell us about your situation and the change your business needs.