system map · client context

Everything we know. And everything we do not.

A single page per client showing every source the agency has connected, every source it has not, and exactly how much has actually come through. Three clients here, at three different stages of onboarding.

an illustrative view — the clients below are invented

The shape underneath

Five layers, split by what the data is rather than by which vendor it came from. Every row in every layer carries the client it belongs to, and the database itself refuses a read or a write that crosses that line.

L0registry

Who the business is

Name, address, phone, service area, timezone, brand palette, and where its site and files live. Small, hand-curated, and the row everything else hangs off.

tenantsone row per client
Postgresthe anchor row
L1connections

The keys to their accounts

One credential per thing the client already owns — their listing, their page, their ad account, their CRM. Encrypted at rest and decrypted only at the moment of the call. Never stored in a repo.

connectionsencrypted
Postgresencrypted column
L2facts

Everything that happened

One table for every synced fact — reviews, ad numbers, conversations, form fills, visits. A kind column tells them apart. Plus the identity graph, because the same person is a CRM contact and a Facebook commenter and a Google reviewer, with three different IDs.

factspeopleidentities
Postgresstructured + raw
L3blobs

Every original file

Before-and-afters, phone video, raw uploads, logo files. The bytes never go in the database and never go in git — the row holds a bucket and a key and nothing else.

assetspointers only
Object storage~66 MB a job
free to serve
L4meaning

What a model actually reads

Text chunks with their embeddings sitting in the same database as the rows they describe, so one query can filter by client, filter by rating, and search by meaning at the same time. Plus the compiled brief a person can review before it ships.

chunksvector indexcontext_documents
Postgres + gitvectors beside
their own rows
audit

What was done, and by what

Append-only. Every sync, every generated draft, every write. This is what lets you change your mind about the structure later without losing the history.

eventsappend-only
Postgresnever updated
the rule that lets it evolve Every synced row keeps its untouched payload. Typed columns are a projection of that payload, never a replacement for it.

When a platform adds a field, or you decide six months in that you want sentiment on every conversation, you add a column and backfill from what you already stored. No re-fetch, no rate limits, no data you threw away because it did not fit the schema you had on the day you wrote it.

now — what is actually in it
live — rows present empty — connected, never pulled missing — not connected

bright · tenant

Bright Exteriors

Fort Collins, CO · no phone ·

0%0 of 13
L0

Identity and NAP

empty tenants
Legal
Bright Exteriors LLC
Phone
not captured
Location
Fort Collins, CO
Timezone
America/Denver
Email
not captured
L0

Brand board

missing tenants.brand

No palette captured. Pull it from the site stylesheet or the brand kit.

L1

Website and GitHub

empty connections

Nothing linked.

L2

Google Business Profile

missing facts kind=gbp_snapshot

No connection for this yet.

L2

Google reviews

missing facts kind=review

No connection for this yet.

L2

Facebook page

missing facts kind=post

No connection for this yet.

L2

Ad performance

missing facts kind=ad_metric

No connection for this yet.

L2

Conversations and contacts

missing facts kind=message

No connection for this yet.

L2

Form submissions

missing facts kind=form_submission

No connection for this yet.

L2

Site visits

missing facts kind=site_visit

No connection for this yet.

L2

Keyword research

missing facts kind=keyword

No connection for this yet.

L3

Job photos and video

missing assets

No connection for this yet.

L4

Embeddings

empty chunks

Connected, but this has never been pulled.

handy · tenant

Handy Home Co.

Boise, ID · (555) 0142 · handyhome.example

46%6 of 13
L0

Identity and NAP

live tenants
Legal
Handy Home Co. LLC
Phone
(555) 0142
Location
Boise, ID
Timezone
America/Boise
Email
office@example.com
BoiseMeridianNampaEagle
L0

Brand board

live tenants.brand
primary#1F6FEB
ink#10243E
L1

Website and GitHub

live connections
githubagency/handyactive
websitehttps://handyhome.exampleactive
L2

Google Business Profile

empty facts kind=gbp_snapshot

Connected, but this has never been pulled.

L2

Google reviews

empty facts kind=review

Connected, but this has never been pulled.

L2

Facebook page

empty facts kind=post

Connected, but this has never been pulled.

L2

Ad performance

empty facts kind=ad_metric

Connected, but this has never been pulled.

L2

Conversations and contacts

live facts kind=message
1,962rows
Sources
crm
Newest
2026-08-26
most recentCan someone look at a water heater swap this week? The old one is leaking at the base.
L2

Form submissions

live facts kind=form_submission
199rows
Sources
website
Newest
2026-08-24
L2

Site visits

live facts kind=site_visit
1,358rows
Sources
website
Newest
2026-08-26
L2

Keyword research

empty facts kind=keyword

Connected, but this has never been pulled.

L3

Job photos and video

missing assets

No connection for this yet.

L4

Embeddings

empty chunks

Connected, but this has never been pulled.

summit · tenant

Summit Karting

Reno, NV · (555) 0188 · summitkarting.example

100%13 of 13
L0

Identity and NAP

live tenants
Legal
Summit Karting LLC
Phone
(555) 0188
Location
Reno, NV
Timezone
America/Los_Angeles
Email
hello@example.com
RenoSparksCarson CityTruckeeWashoe County
L0

Brand board

live tenants.brand
primary#E5484D
asphalt#1C1F24
flag#FFB224
trim#0F7C74
L1

Website and GitHub

live connections
githubagency/summitactive
websitehttps://summitkarting.exampleactive
L2

Google Business Profile

live facts kind=gbp_snapshot
96rows
Sources
google
Newest
2026-08-26
L2

Google reviews

live facts kind=review
412rows
Sources
google
Newest
2026-08-25
most recentTook both boys for the nine-minute heats and they have not stopped talking about it. Staff walked us through the flag rules before we got on track.
L2

Facebook page

live facts kind=post
288rows
Sources
facebook
Newest
2026-08-24
most recentFriday night league standings are up. Two spots left in the intermediate bracket before we close entries.
L2

Ad performance

live facts kind=ad_metric
1,840rows
Sources
facebook
Newest
2026-08-25
L2

Conversations and contacts

live facts kind=message
6,217rows
Sources
crm
Newest
2026-08-26
most recentDo you do birthday parties for ten year olds, and is there a minimum height?
L2

Form submissions

live facts kind=form_submission
503rows
Sources
website
Newest
2026-08-25
L2

Site visits

live facts kind=site_visit
9,420rows
Sources
website
Newest
2026-08-26
L2

Keyword research

live facts kind=keyword
240rows
Sources
research
Newest
2026-08-01
L3

Job photos and video

live assets
3,140files in R2
Sources
Newest
L4

Embeddings

live chunks
11,208chunks
Sources
Newest