Experiments
A governed B2B-SaaS foundry — Elixir · Ash · Oban · one Postgres

Support your tenants without ever seeing their customers. On one substrate.

Samen presets the boring 80% of every B2B SaaS — identity, CRM, billing, support, the operator control plane, and now the entire product UI — as six governed idioms applied uniformly to every object, on one Postgres per product. The product your tenants use and the control plane you run the business with are the same resources on the same substrate. The difference is the plane: on the tenant plane an owner reads their own people in the clear; on the operator plane the very same rows render •••• — because masking is the field's normal value, not a view you remembered to add. The flagship makes it literal: realtime cross-plane chat where pasting any object id renders a live card that is masking-aware per viewer — the same message shows a clear contact card to the owning tenant and a •••• card to the SaaS operator, with the non-PII fields still clear so support can actually help. This document describes what was built, gated GO, and tested — then hardened past the demo by four post-build workstreams (real CRUD, an operator cockpit, generator catch-up, and table-stakes UX), each adversarially gated GO, with every masking guarantee held by a standing 15-patch sabotage harness. 1,179 kernel tests · 598 framework · 454 demo · 110 freight · 49 vet — all warnings-as-errors, over a navigable 5-tenant demo, with a destruction oracle you can run.

one BEAM release — web · workers · cron one Postgres — truth · queue · history · audit · vault one message, two truths — clear to the tenant, •••• to the operator 15 sabotages — CI proves each guarantee's test fails when it's broken
The tension nobody resolved

Every SaaS ships the same 80% — and re-leaks PII through all of it

If you have shipped a second or third B2B SaaS, you have rebuilt the same eighty percent — identity, billing, CRM, email, support — a little worse each time, then rebuilt it again as the internal admin tooling nobody scoped. That stack gives you the 80% as six integration surfaces and six failure domains, with personal data smeared across all of them. And the two disciplines that actually keep customers — an operator who can support a tenant without seeing their PII, and an erasure you can prove — are exactly the ones a from-scratch stack gets last, if ever.

The web-framework path — a bag of vendors
"Assemble the 80% from six SaaS vendors and glue. PII lands in each of them; the admin tool comes six months later."
The metadata-platform path — a config engine
"Model everything as generic records. Now nothing is typed, nothing is verified, and the PII discipline is a convention."
Samen presets the 80% as typed, catalogued, PII-safe, malleable idioms on one Postgres — vault, audit, and the operator plane come with it, not after it. Engineering writes only the B2B problem.
The mechanism

Six idioms, applied uniformly to every object in every scope

Consistency is the whole product: it is what makes the substrate uniform enough for a human — or a model — to build on. Every resource in every scope (identity, CRM, billing, marketing, CMS, support, primitives) obeys the same six rules, injected by one base macro. Break one and the build fails closed. These six are not aspirations; each is a Spark transformer or verifier with a red-path test that must fail on violation.

idiom 01

Self-qualifying storage

You write attribute :name; a compile-time transformer projects it to com_name in the DB, CDC, logs, and catalog. One stable column identity everywhere, no rename drift. Abbrevs are permanent, like a ticker.

idiom 02

A machine catalog

Every table and field lands in tam_table / fld_field — written in the migration transaction. A committed schema.dict.json is the ground an agent builds on: a reference to an uncatalogued column doesn't compile.

idiom 03

The PII vault

A pii_attribute routes to a per-subject-keyed vault; the row carries a token. Masking is the field's normal value — %Masked{}•••• across UI, JSON, CSV, logs, by omission. Erasure is crypto-shred.

idiom 04

Postgres as the engine

Truth, the Oban queue, cron, the append-only audit tier, rollups, and the vault are one database. LISTEN/NOTIFY, SKIP LOCKED, partitioning, transactional DDL — the infrastructure is queries, not services.

idiom 05

A malleability ladder

Tier-0 config rows, Tier-1 jsonb custom fields validated at write, Tier-2 tenant-defined objects, Tier-3 builder code-composition. Customization with the guarantees intact, degrading honestly as it climbs.

idiom 06

Two planes, one core

The operator plane is built from the same objects the tenant uses. Masked impersonation, second-party reveal grants, and a token-blind aggregate actor with no pii_ columns at all — support and analytics without disclosure.

Unique feature № 1 — composition

Compose a core object in code. Inherit every guarantee.

Here is one core object — person — shown as the shipped CRM resource, then re-composed by a freight SaaS and a vet SaaS. This is Ash resource composition: a base macro folds a tableless fragment (Samen.Fragments.CorePerson) into one resource that compiles to one physical table, with normal PK/UNIQUE/FK. It is emphatically not Postgres INHERITS — that footgun (a parent UNIQUE that skips child rows, an FK that can't target the set) is never emitted. FKs target the composed table, never the fragment.

Everything not highlighted is injected by the base macro — and so are the guarantees. The storage convention, vault routing, masking, org-scope, audit, catalog parity, and crypto-shred come free on every column, including the new ones. One pii_attribute line buys the entire vault-plus-mask-plus-shred-plus-KMS chain.

Be precise about which 80% is free: the cross-cutting infrastructure — the idioms every column obeys — not the domain meaning of the noun. Driftwood's Driver and PawChart's Patient both compose CorePerson with zero vertical PII code; what a driver is stays authored. That is the calibrated deal, proven twice.

kernel · shipped
personcomposes CorePerson
per_id core · pk
per_full_name → pii_name
per_emails → pii_email
per_phones → pii_phone
per_company_id FK
per_custom Tier-1
per_*_at audit
base macro folds it in
Driftwood · freight
drivercomposes person
pii_drv_cdl_number → vault
drv_cdl_state core
drv_medical_card_expiry core
drv_carrier_id FK → company
drv_eld_provider Tier-0
PawChart · vet
patientcomposes person
pat_species core
pat_weight_kg core
pii_pet_microchip → vault
pat_owner_id FK → owner
pat_temperament Tier-0
Free on every row, no code: a catalog row the LLM grounds on · masking by default (%Masked{} → ••••) · org_id policy scope · audit trail · crypto-shred via the per-subject vault key, held in an external KMS outside the WAL/PITR surface.
Unique feature № 2 — the vault & the destruction oracle

Audit everything. Forget anyone. And prove it left every tier.

A pii_attribute value never lands as plaintext in a domain row. It lives in a mutable, per-subject-keyed vault; the row carries a vt_ token, and every tier downstream — replicas, PITR history, CDC mirror, rollups, the audit chain — carries only that token or ciphertext. The per-subject key is not a Postgres row: it lives in an external KMS outside the WAL and backup surface, so a PITR restore resurrects ciphertext, never the key. Erasure is crypto-shredding: destroy one subject's key and their PII becomes permanently undecryptable across every tier at once — a key-destruction, not a copy-chase — while the history still replays perfectly.

The load-bearing artifact is the destruction oracle: mix samen.verify.no_plaintext_pii --subject <uuid> --tiers all, run as a separate OS process against a freshly-shredded driver. In the Driftwood crypto-shred game-day it exits 0 with 15 positive attestations — the reveal returns {:error, :shredded}, no vault row decrypts, the driver-keyed rollup was rebuilt driver-free (count → 0, no resurrection), and the redacted non_pii! columns carry sentinels. The oracle is the proof that erasure actually reached every place the data could hide.

One deliberate exception to "nothing hand-rolled": the vault's cipher is an authenticated envelope construction (a reviewed library, per-subject DEKs under a KMS master key), never a bespoke one — because a broken cipher still round-trips and a vault is exactly where "from scratch" stops being a virtue.

# the declaration — one line buys the whole chain
pii_attribute :cdl_number, :string,
  vault: :pii_cdl  # → vault · mask · shred · KMS
 
# right-to-erasure — destroy the key, not the copies
Samen.Erasure.shred(subject_id)
# reveal → {:error, :shredded} on every tier
 
# the oracle — a separate OS process, exits 0
$ mix samen.verify.no_plaintext_pii \
  --subject <uuid> --tiers all
# → 15 attestations · key absent from every DB tier
no_plaintext_pii the destruction oracle · tiers it scans EXIT 0 · 15✓
live
domain rows carry tokens only — no key column on the vault
replica
physical replica inherits ciphertext + tokens
rollup
driver-keyed rollups rebuilt driver-free, or suppressed
audit
hash-chained aud_event / aud_chain — tokens, never plaintext
pitr-history
a restore brings back ciphertext — the key was never in the WAL
kms-store
external key store, PITR disabled — the system of record for shred
◆ = external to Postgres · the load-bearing exclusion · replica + real KMS/Neon = operator TODO, identical in the local sim
What it actually looks like — the operator plane

Masked impersonation: the operator sees the real UI, the PII renders ••••

This is the product's thesis, on screen. An operator opens a tenant to work a support ticket and sees the tenant's real driver roster — real FMCSA badges, real settlement bars, real dispatch state — but every name and CDL renders ••••, because the impersonation session carries no reveal grant and masking is the field type's normal value. There is no CSV, API, or log path that leaks by omission; the mask is the default. Unmasking one subject (bottom row) is second-party: the operator requests, a distinct party approves — enforced in policy and by a DB CHECK (granted_by <> requestor_id) — and the reveal is time-boxed, auto-revoked by an Oban job scheduled in the same transaction, and written to a hash-chained, tenant-readable log the operator cannot edit.

Operator plane / Impersonation / Blue Ridge Logistics / Drivers

Driver roster

Masked impersonation. You're viewing Blue Ridge Logistics as an operator — personal data renders •••• by default. Unmasking a subject needs a second-party reveal grant, is time-boxed, and is written to the tenant-readable audit log.
session 27m left · reason: ticket #7781

Midwest lane

5· Chicago, IL → Columbus, OH corridor
DriverCarrierCDL #Med cardDispatchSettlement · wk
••••••••••
Lakeside Carriers ••• •• •••• Valid Eligible
$3,905
••••••••••••
Lakeside Carriers ••• •• •••• Expires 30d Eligible
$2,870
•••••••
Prairie Line LLC ••• •• •••• Valid En route
$1,740
•••••••••••
Prairie Line LLC ••• •• •••• On file Off duty
$0
Marcus Vale
Prairie Line LLC D4471-8820 Valid Eligible
$4,980
Revealed · 4m left
Operator plane · Driftwood — masked impersonation over the real freight tenantproof: web_red_paths_test.exs RP1/RP2 · DB CHECK rvg_distinct_party
The thesis, side by side — same rows, opposite planes

The tenant reads its own people in the clear. The operator never can.

The two screens below are the whole argument. On the left, the tenant plane: Blue Ridge's own dispatcher sees driver names in the clear, because a tenant key reads its own org's PII with no operator grant. On the right, the token-blind aggregate plane the operator uses for portfolio MRR — an actor whose resources have no pii_ column by construction, reading a vault-excluded projection. The two paths are mutually exclusive: the reveal function refuses the aggregate actor before any grant or vault check runs. Same substrate, same driver rows underneath — the plane decides what is knowable. Note the settlement card: the kernel's Invoice reshaped into a two-sided carrier settlement by a bounded-context calculation, the vertical's money model over the substrate's vault and audit.

Blue Ridge Logistics / Operations / Dispatch board

Dispatch board

Tenant plane · own-org PII in the clear. Rosa reads her own drivers' names because a tenant key resolves its own org's PII with no operator grant — the same resolver, the opposite plane from the operator view above.
plane: tenant · own-org

Active loads

5· your org — driver names in the clear
Load #LaneDriverRateStatusMargin
BR-4471 Carthage, NY → Boston
MV
Marcus Vale
$5,200 En route $500
BR-4472 Boston → Providence
DW
Dana Whitfield
$1,850 Delivered $210
BR-4468 Chicago → Columbus
PA
Priya Anand
$3,905 En route $430
BR-4470 Chicago → Detroit
CB
Cole Barrett
$2,740 Booked $305

Settlement · BR-4471

· kernel Invoice, reshaped
Carrier settlementreshaped
Linehaul$5,200.00
Advances fuel−$500.00
Factoring 3.0%−$156.00
Claims−$50.00
Net payable$4,494.00
Kernel Invoice reshaped to a two-sided settlement via a bounded-context calculation — proven to the cent + a 200-run property in settlement_math_test.exs.
Tenant plane — Blue Ridge reads its own drivers in the clear (plane: tenant)proof: RED PATH 6 · PiiResolution.resolve/4 · anti-tautology flipped
Operator plane / Aggregate / Portfolio

Portfolio

Token-blind aggregate plane. This actor has no pii_ column in its domain by construction — it reads a vault-excluded projection. Cohorts below the k-anonymity floor (k ≥ 5) are suppressed; an enforcing query budget bounds cross-cohort differencing.
no reveal path · k ≥ 5 · l-diversity
Portfolio MRR
$284,900+6.2%
Active tenants
42+3
Loads / wk
1,418+11%
Net settlements
$1.92M−1.4%

Tenants by MRR

42· org-level aggregates only — no personal data
Tenant orgPlanMRRLoads · wkDriversHealth
Blue Ridge Logistics
Scale$18,40012631
Healthy
Cascade Freight Co
Scale$16,05010927
Healthy
Sunbelt Carriers
Growth$11,7208419
At risk
Northwind Transit
Growth$9,3007116
Healthy
3 tenants below the k-anonymity floor — suppressed from this cohort to prevent re-identification.
Operator plane · aggregate — a token-blind actor, no pii_ columns, k-anon suppressionproof: C7 no_pii_columns · aggregate_actor_denied before any grant · aggregate_privacy floor
The flagship — cross-plane chat with catalog-driven object unfurl

One message, two truths. The same card, clear to the tenant and •••• to the operator.

This is the feature that is only possible on one substrate. Because the SaaS company and every tenant live on the same Postgres with shared, catalogued objects and Phoenix PubSub, realtime chat that spans the operator↔tenant boundary is near-free. And the crown jewel: pasting any Samen object id — samen:<resource>:<id> — into a message renders a live card that is masking-aware per viewer. The card is resolved through the viewer's OrgScope and PiiResolution for the viewer's plane, so the same message renders a clear card to the owning tenant and a •••• card to the SaaS operator — with the non-PII fields still clear (title, CDL state, ELD provider, status) so support can actually help without seeing identity.

Realtime is safe by construction: the PubSub envelope is id-only — the body never transits PubSub. Each subscriber re-reads the message through its own scope, so a masked operator session cannot receive plaintext even by listening on the topic. The unfurl resolver is a composition of two unchanged kernel gates over the host's own resource — no code path reads a column directly, unwraps a %Masked{}, or calls Vault.reveal. And it is a genuine framework capability, not a chat trick: a catalog-driven default card renders any catalogued resource — a new vertical's new object unfurls the day it is catalogued, with zero card code. Identity is masked-by-default with a per-conversation opt-in and a tenant-wide disclosure override. Gated GO (ADR-012): the live operator DOM scanned 0 leaks, 7× ••••.

Blue Ridge (tenant) — /chat/<id> · plane: tenant · own-org clear
Rate confirmation for load BR-4471cross-plane thread · owned by tenant
tenant
CK
SaaS supportdriftwood-desk· 10:24
Confirming the rate on samen:crm.person:0f00…aa and the assigned driver samen:freight.driver:0f00…cc — both look good on our side.
RM
Rosa Medinablueridge-dispatch· 10:26
Confirmed. Here's the contact + driver for BR-4471:
Dana Whitfieldsamen:crm.person
TitleShipping dispatcher
Emaildana.whitfield@brl.example
LifecycleCustomer
your org · resolved clear (tenant plane)
Dana Compliantsamen:freight.driver
CDL #D4471-8820
CDL stateTX
ELDsamsara
StatusAvailable
vertical override card · clear (tenant plane)
2 online · driftwood-desk is typing…
Message… paste a samen: ref to unfurl an object
Driftwood Ops (operator) — /operator/desk-chat/<id> · plane: operator · masked
Rate confirmation for load BR-4471same thread · reached via impersonation
operator · masked
CK
You (SaaS support)driftwood-desk· 10:24
Confirming the rate on samen:crm.person:0f00…aa and the assigned driver samen:freight.driver:0f00…cc — both look good on our side.
RM
••••blueridge-dispatchidentity ••••· 10:26
•••• •••••••• •••• •• ••• •••••• ••• •••••• ••• •••••••
••••••••••••samen:crm.person
TitleShipping dispatcher
Email••••••••••••••
LifecycleCustomer
tenant's customer · identity •••• · non-PII clear
••••••••••••samen:freight.driver
CDL #•••••••••
CDL stateTX
ELDsamsara
StatusAvailable
same card · name/CDL •••• · state/ELD/status help support
2 online · blueridge-dispatch shown as handle only
Message… (you never see the tenant's customer PII)
The money shot — one thread, one message, one set of refs · rendered per viewer's plane, live in Driftwood on port 4041proof: chat_unfurl_masking_test.exs · id-only PubSub envelope · operator DOM: 0 leaks · 7× ••••

Why this levels up the framework, not just chat. Samen.Web.ObjectRef is a standalone module usable anywhere — a CRM detail page can unfurl a related object, an audit log can unfurl its subject, a notification can unfurl its target. Chat is the first consumer, not the owner. The card registry plus catalog-driven default is a reusable "render any catalogued object, masked-per-viewer" primitive — and cross-plane visibility itself is just the ADR-010 impersonation bridge reused: the thread is owned by the tenant org, and the SaaS participant reaches it carrying the target org_id, so OrgScope is satisfied for both parties with no new policy and no upward escalation. An operator whose impersonation session targets a different org sees the thread as zero rows — cross-plane access is a grant that expires, not a backdoor.

Unique feature № 3 — the framework

You inherit the UI too, not just the schema.

The reuse thesis had a hole: the inherited-80% data was framework-level, but the product surface that renders it was forked into one vertical. That has been closed. A new library, samen_web, now holds the entire product-UI layer — a 13-component kit plus the CRM, Billing, Support, Operator, Marketing, and Chat LiveViews — behind a Mount seam that derives each host's resources from the catalog's Module.concat(namespace, Resource) convention. A host supplies three facts (namespace · repo · plane); the framework derives the rest. No LiveView names a host module; no host copies a page.

The payoff is visual. PawChart, the vet vertical, had no web layer at all. It inherited the entire product UI — 3,284 lines of LiveViews, reads, and the component kit — through ~3 router lines. The same Samen.Web.CRM.ContactsLive module renders freight contacts for Driftwood and clinic contacts for PawChart, byte-identical, each over its own data. And the two-plane masking travels with it by construction: the framework LiveViews resolve every PII field through the single PiiResolution chokepoint, so a component fix or a new inherited page ships to every vertical on a dep bump. Throughout, samen_core stayed web-dep-free — now 1,179 tests green; the web dependency lives only in samen_web (598 green).

samen_web — the framework product-UI library
One canonical definition of the inherited product surface, mounted thin by every host. The Mount struct carries the host's namespace/repo/plane; the router macro turns them into live pages. The masking invariant is tested in the framework, not in one vertical.
Samen.UI · 13 components Web.CRM.* Web.Billing.* Web.Support.* Web.Marketing.* Web.Operator.* Web.Chat.* + ObjectRef Web.{Mount,Plane,Router}
D
Driftwoodfreight · mounts thin
# router.ex
samen_module_routes :crm, Driftwood.Crm
samen_module_routes :billing, Driftwood.Billing
samen_module_routes :support, Driftwood.Support
Renders freight contacts/invoices/tickets over Driftwood.* resources. The freight dispatch board is the vertical 20%, still local.
V
PawChartvet clinic · had no web layer
# router.ex — the whole UI, inherited
samen_module_routes :crm, PawChart.Crm
samen_module_routes :billing, PawChart.Billing
samen_module_routes :support, PawChart.Support
Renders clinic contacts/invoices/tickets over PawChart.* — the same modules, zero UI code. Branded "Happy Paws Clinic", live at HTTP 200.
The same module renders both verticals. PawChart inherited 3,284 lines of product UI via 3 router lines · samen_core web-dep-free, 1,179 green · masking proven in the framework, not the host · gated GO (ADR-009).
Unique feature № 3b — the reuse thesis, measured

Build the 20%. Inherit the 80%. On real numbers, honestly.

The bet is that the boring infrastructure is inherited near-total and only the domain stays authored — and the second vertical is where it stops being a claim. PawChart, a vet-clinic SaaS, was built as the reuse probe. It authored 191 lines of real domain logic and sits on 33,640 lines of substrate. Every one of the 15 verifiers returned green on its first invocation — no verifier fix, ever. Against the four families a clinic touches, it inherited ~96% of the code that makes them work.

The starkest leverage is at the point declarations: pii_attribute :microchipone line — buys the ~1,900-line vault-mask-shred-KMS chain; use Samen.Scopes.Billing17 lines — buys eight fully-policed, vaulted, catalogued billing resources; a base: option buys the folded core-person shape. Four of the six idioms applied with literally zero vertical code.

The honest calibration, which the doc names and PawChart confirms: you inherit infrastructure, not a domain model. The 104 lines of clinic logic are real work — what a Pet is, that a microchip is a vaulted secret, that the owner is the messaged party. And PawChart was the easy additive case: it wrote zero Samen.Context, where Driftwood had to author an anti-corruption layer of renames plus a ~90-line settlement reshape. The 80% is inherited; the 20% is yours; the reshape tax is real when the vertical's language is genuinely new.

PII vaultvault · mask · shred · KMS
1 line
CorePerson / basefolded person shape
0 lines
Billing scope8 resources, mounted
17 lines
Tier-2 objectsVaccineLot, self-defined
0 lines
Operator planetoken-blind projection
42 lines
Domain (the 20%)Patient + Pet — authored
104 lines
~96% inherited against the 4 families a clinic touches · 15/15 verifiers green on first run · the domain 20% stays authored real work — that is the calibrated deal, not the oversell
Unique feature № 4 — the SaaS control plane

The SaaS company is itself an org — and its accounts are the tenant orgs.

Phase 2 turned the thesis on itself. The SaaS company runs the same universal scopes as any tenant — except its rows describe its book of business as a vendor. An operator dashboard lands over all tenants: Accounts (each account IS a tenant org, with its admins, plan, health, and MRR), Platform billing (each tenant's subscription to the SaaS, dunning, total platform MRR), a SaaS support desk (tickets tenants filed with the SaaS), and the token-blind aggregate portfolio. In the shipped 5-tenant demo you land on the operator dashboard and drill into any tenant — no typed UUIDs, no dead ends.

The identity line, stated precisely: a SaaS rep sees a tenant-admin's identity in the clear — that admin is the SaaS's own customer, its signup contact — while the tenant's end-customers stay masked, reachable only through impersonation plus a second-party reveal. And this is not new machinery: it is the pure composition of two kernel gates already testedOrgScope (which rows) × PiiResolution (clear vs masked). Population (1) is clear because it is the operator org reading its own data on the tenant plane; population (2) is masked because reaching a downstream customer flips to the operator plane. Two populations, two ownership planes, separated by both a mount boundary and a plane boundary — belt and suspenders, both by construction.

population 1 · the SaaS's own book
tenant-adminoperator org · tenant plane
operator_org_id OrgScope → own rows
plane: :tenant own-org, no grant
admin name/email CLEAR ✓
account = tenant Org Identity.Org
subscription to SaaS Billing
desk ticket (tenant→SaaS) Support
× two kernel gates ×
population 2 · the tenant's downstream
end-customerimpersonation · operator plane
tenant_org_id OrgScope → target rows
plane: :operator impersonated
contact name/email •••• MASKED
reachable via reveal 2nd-party grant
vertical namespace separate mount
back-ref bridges once custom.tenant_org_id
No new masking code. Live in Driftwood (ADR-010, gated GO): the operator's own surfaces show tenant-admins Marlene Okafor in the clear with zero ••••; drilling into that tenant's driver roster renders every CDL/name •••• — same session, opposite plane. Pinned by operator_identity_line_test.exs incl. a cross-mount refusal (operator actor reading the vertical namespace returns zero rows).
Unique feature № 5 — the enriched CRM

Rolodex → real CRM. All inherited, framework-level.

The CRM was three list pages — companies, contacts, pipeline. It is now a real CRM, and every enrichment landed in samen_web so both verticals inherit it: contact and company detail pages with tabs, an activity timeline over the existing Activity resource with a working log-activity composer, a lifecycle pill and social handles, and outreach — the Marketing scope mounted at last (campaigns · segments · leads) with consent and suppression enforced on every send. The realization that made it cheap: almost every resource already existed in the kernel, already PII-safe; the work was surfacing, not authoring. The one new PII surface — the contact detail header — ships with a masking test: tenant clear, operator ••••, the log-activity composer hidden on the operator plane, no vault token in the DOM. The consent/suppression red path is fail-closed at the framework layer, wrapped around a known kernel send_checked residue — corrected without touching the kernel. Proven live on both Driftwood and the clinic (ADR-011, re-gated GO after a cold-start fix).

Unique feature № 6 — from demo-deep to shippable

The demo became a SaaS you can run. Four gated workstreams, all GO.

A four-lens gap audit (2026-07-09) reached one verdict: the governance kernel was as deep as the gates claimed, but the product surfaces on top were demo-deep — read-only LiveViews, unwired buttons, stub adapters. Four framework-first workstreams closed it, each landing in samen_web or the kernel so every vertical inherits it on a dep bump, each adversarially gated GO against a mapped set of acceptance criteria, none touching the two-plane masking that was already proven.

GO · 36 AC
WS-A — Product Reality

Real CRUD on every mounted LiveView (kit-level form/table primitives), pagination/sort/filter/bulk as kit defaults + bounded JSON:API reads, an operator Pii.WriteGuard, a fail-honest email adapter (never fake-:delivered), and a notifications inbox fed by a real delivery engine. The kernel msp_suppression hardcode and the CDC "non-PII" heuristic were fixed here too (default-deny).

GO · 32 AC
WS-B — Operator Cockpit v1

An MRR movement ledger (waterfall · churn · cohorts), an explainable per-tenant health score with account drill-down, a feature-flag evaluation engine with deterministic targeting/rollout, and token-blind product-event analytics over the vault-excluded CDC projection — privacy-correct by construction, mounted at 0 operator-surface LOC in both verticals.

GO · 22 AC
WS-D — Builder Joy

mix samen.gen.app now emits a running product — web + JSON:API + Factory-backed vault-aware seeds + observability, not a headless data layer. A flagship generative proof (generate → ci.sh green → boot → 200) is permanent in root CI, docs are command-verified, and deploy templates fail-honest on missing secrets.

GO · 28 AC
WS-E — Table Stakes UX

A files engine with fail-closed quarantine + plane-gated byte-serve, CSV import/export with per-plane masked export, Search + global ⌘K, self-serve Settings (profile · sessions · digest-only API keys), and a responsive kit. The last of the six flagged mask-by-omission PII surfaces, all closed with per-plane red-paths.

The sabotage harness keeps every guarantee refutable: 15 committed sabotage patches (scripts/sabotages/) that CI applies one at a time — each proves its named guarantee's test actually FAILS when the guarantee is broken, then restores the tree byte-exact (SHA-256). A green test is only trusted once its red twin is proven un-fakeable. Run SAMEN_SABOTAGE=1 ./ci.sh.

Every masking/vault surface ships a green (plane resolves clear) · red (operator-without-grant renders ••••, never a vt_ token) · sabotage (the mask is proven refutable) triple. Suites, all --warnings-as-errors: samen_core 1,179 · samen_web 598 · demo 454 · driftwood 110 · pawchart 49 — the final CI line is ROOT CI: ALL PASSED.

How it runs — and how the build stays honest

One release, one Postgres — and a gate that fails closed

One BEAM release runs web, workers, and cron; one Postgres is truth, queue, cron, history, audit, and the vault. A request mounts an Ash.Scope carrying actor + org_id; every read emits WHERE com_org_id = $1 and runs its policy check — cross-org isolation is proven adversarially (org-A sees zero org-B rows). But the load-bearing discipline is the verifier gate: seventeen AST/Spark-checked verifiers, each shipped with a red-path test that must fail on violation, run in the same CI that runs the suites, the sabotage harness, and both game-days. Nothing merges that a verifier rejects.

admit

The build fails closed

catalog_parity, prefixes, pii_reads, pii_classify, no_plaintext_pii, no_pii_columns, api_contract — AST-checked, not grepped. Each exits non-zero on a violation. A hallucinated column, net-new plaintext PII, a leaked vault value: each fails the build, proven by the agent-authoring eval.

17 verifiers · red-path each · CI exit 0 = obeyed
enqueue

Postgres is the engine

Oban gives durable jobs, cron, and same-transaction enqueue over SKIP LOCKED with per-queue concurrency limits. The auto-revoke for a reveal grant is scheduled in the same transaction that writes it. The append-only aud_event tier is time-partitioned with BRIN on time.

queue · cron · audit · rollups — one DB
migrate

Expand / contract, drilled

Migrations expand-then-contract, never edit-in-place; every expand ships a tested down/0; lock_timeout/statement_timeout bound the blast. The bad-migration incident — the highest-consequence one — is a PITR game-day run on a production-sized dataset, both recovery arms.

reversible · timeout-bounded · PITR-drilled
One BEAM release — web · workers · cron
SAMEN RELEASE
Phoenix/LiveView · Ash resources · Oban workers · the reveal-grant + vault + mask stack · the 16-verifier gate baked into CI
two planes, one core
the operator plane reads the SAME resources the tenant does — masking is the field's value, not a separate admin app
the seam — the vault key
One Postgres — the whole stateful surface
DOMAIN + INFRA
org-scoped rows (com_/drv_/per_) · oban_jobs · aud_event + aud_chain · rollups · the catalog (tam_table/fld_field)
pii_vault
ciphertext + tokens · the per-subject key lives OUTSIDE, in an external KMS — a restore brings back ciphertext, never the key
plaintext PII crosses the seam only through the one decrypt chokepoint — under a live grantone substrate is one blast radius — engineered down, drilled, recoverable
Simplicity, measured in deletions

Infrastructure you will never run

Every box below is a thing the from-scratch stack deploys, versions, secures, monitors, and pays for. In Samen each is a table, a query, a verifier, or the base macro's normal behavior — inside the one release and the one Postgres you already have. The last rows are the unusual ones: the compliance work most stacks bolt on at the end is the storage format here.

Redis · broker · cron

→ postgres — queues, pubsub, and schedules are Oban rows and NOTIFY, transactional with your data

Six SaaS vendors

→ the scopes — identity, CRM, billing, marketing, CMS, support are inherited Ash domains, one object model

Internal admin tooling

→ the operator plane — built from the same objects; accounts ARE tenant orgs, masked impersonation, not a second app six months later

A forked product UI per app

→ samen_web — the component kit + every LiveView is inherited; PawChart got the whole UI in 3 router lines

A per-app object-preview service

→ ObjectRef — the catalog renders any resource as a masking-aware card; new objects unfurl with zero card code

A PII-masking layer

→ the field type — %Masked{} is the normal value; no CSV, API, or log path leaks by omission

An erasure runbook

→ crypto-shred — destroy one key; the oracle proves the PII left every tier at once

A code-search / IDE indexer

→ the catalog — schema.dict.json is ground an agent can't hallucinate off of

A WORM audit service

→ aud_chain — a DB append-only trigger refuses UPDATE/DELETE; the hash chain detects tamper

A data-dictionary doc

→ in the migration — tam_table/fld_field written in the same transaction as the DDL

An analytics warehouse

→ rollups (+ opt-in CDC) — dashboards read the summary; ClickHouse is a power-up, default off

The host stack

Boring on purpose — Elixir where it's proven, Samen where it's new

exists
Ash 3.x + Spark

The resource-derivation engine and its compile-time DSL. The abbrev storage transformer, catalog extension, and PII vault are Spark transformers — the compiler is the first verifier. Pinned (Ash 3.29.3, spark 2.7.2).

exists
Postgres + Oban

LISTEN/NOTIFY, SKIP LOCKED queues, partitioned history, transactional DDL. Queue, cron, audit, rollups, and the vault are one database. The distribution layer is a table.

exists
Phoenix · LiveView · AshOban

The web plane and durable jobs. Both planes are LiveViews over the same Ash resources; the reveal auto-revoke is an AshOban job enqueued in the grant's transaction.

prior art
Twenty · Ash · the Rule of Three

The composite-PII spec (re-implemented, not depended on), resource derivation, and the extract-the-third-product discipline. Credits and a spec, not runtime dependencies.

samen
The base macro + fragments

use Samen.Resource: injects the abbrev transformer, catalog, vault, org-scope, and audit; base: folds CorePerson into one table. One line per new column, every guarantee free.

samen
The 17-verifier gate + sabotage harness

catalog_parity, prefixes, pii_reads, pii_classify, no_plaintext_pii, no_pii_columns, api_contract, aggregate_privacy — each fail-closed, each with a red path. 15 committed sabotage patches prove each named guarantee's test actually fails when broken. The type system the dynamic idioms must earn.

samen
The vault + destruction oracle

Per-subject external-KMS keys, authenticated envelope crypto, %Masked{} by default, expiring second-party reveal grants, crypto-shred, and the oracle that attests erasure across every tier.

samen
The two-plane control plane + generators

Masked impersonation, the token-blind aggregate actor, hash-chained tenant-readable audit + WORM anchor + break-glass, and mix samen.gen.app — one command to a gate-green vertical.

samen
samen_web — the product-UI framework

The 13-component kit + the CRM/Billing/Support/Operator/Marketing/Chat LiveViews behind a Mount seam. samen_module_routes mounts a whole scope in ~3 lines; masking is tested in the framework. samen_core stays web-dep-free.

samen
Cross-plane chat + ObjectRef unfurl

Realtime chat over an id-only PubSub envelope; Samen.Web.ObjectRef.resolve/3 turns any samen: ref into a card masked per the viewer's plane — a framework primitive, not a chat trick.

What we won't pretend

The honest edges

Every claim above maps to a passing test, a game-day artifact, or a substrate-inherited proof in docs/claim-evidence.md. These are the places where the build corrected the vision, where a mechanism is proven in a local simulation and the real-cloud wiring is an operator TODO, or where a posture is deliberately named as under-construction. None is a breach; all are labeled.

Named, not waved away
  • You inherit infrastructure, not a domain model — and the third product is where that bites. The vision oversold "inherit the 80%" if you read it as inheriting nouns. Measured on PawChart, the infrastructure 80% is near-totally inherited (0–1 lines per idiom), but every non-trivial vertical re-identifies the core nouns and reshapes billing — bounded-context translations, not additive extensions. The Rule-of-Three payoff is inherited plumbing plus a faster context to build, not a pre-shaped domain. And the reuse is measured on two self-built hosts, not three independently-motivated ones — the honest counting rule.
  • The external KMS is real in mechanism, simulated in cloud. The load-bearing exclusion — the per-subject key lives outside the WAL/PITR surface — is proven identically by a FileBacked KMS adapter and the PITR drill (a pg_dump restore resurrects ciphertext; reveal returns {:error, :unavailable} against an empty key dir; the dump grepped clean of key material). Real AWS-KMS / Neon-PITR / S3-Object-Lock wiring and a physical read replica are operator TODOs — identical exclusion in the sim, but the real drill numbers aren't earned yet.
  • The drilled RTO/RPO are local-sim floors. The bad-migration PITR game-day ran on a production-sized dataset with both recovery arms and hit its wall-clock targets — in the local simulation. The ≤30-min forward-fix / ≤2-h full-PITR numbers are targets pending the real Neon drill; detection_ms is a harness proxy for monitoring-driven detection latency, stated as such.
  • Token-blind is not inference-blind. k-anonymity + l-diversity floors are enforced today, fail-closed. The cross-query differencing defense is now an enforcing per-cohort read budget (two colluding actors share one budget — "per-actor is the wrong unit," made real). But it is a deterministic read-count budget, not a formal ε-budget: a distribution-tested Laplace DP mechanism ships opt-in, yet formal DP composition and t-closeness stay posture-under-construction. There is deliberately no flag that claims a guarantee the math doesn't have.
  • "Can't log it ⇒ can't see it" is a real availability cost. A routine reveal fails closed if the audit sink or KMS is unreachable — a KMS partition denies decrypts, never resurrects a key or exposes plaintext. Break-glass is a locally-durable, deferred-anchor hash chain on the operator node, reconciled into the WORM chain on reconnect; the mechanism is proven in the substrate, but a Fly persistent-volume posture and a freight-specific break-glass drill are carry items.
  • One substrate is one blast radius. Truth, queue, cron, audit, and vault on one Postgres is a correlated failure six vendors couldn't have — the CFO objection, owned not dodged. It is engineered down (BEAM process isolation, Oban SKIP LOCKED + per-queue limits, a read replica for analytics, expand/contract + timeouts + PITR) and drilled — not removed. The read replica isn't provisioned locally yet.
  • The abbrev registry gained a sanctioned allocator and a host-namespaced schema (WS-D / ADR-023). Allocation now runs only through mix samen.abbrev.reserve (driven by the mix samen.gen.* generators) against a host-partitioned registry — no more hand-editing rows, and a generated app reserves into its own host namespace, fail-closed and idempotent. The permanence ledger stays global by design (a ticker, not a read surface, no cross-vertical bleed); the one remaining deferral is the compile-time verifier's host-partition (ADR-025), which still reads the flattened global view — correct today, triggered only by the first legitimate cross-host prefix reuse.
  • Tenant customization is best-effort above Tier-0. System resources are provable; Tier-1 jsonb fields are validated-at-write but not compile-checked, and Tier-2 custom objects are a deliberately minimal metadata model with a one-way boundary — not a workflow builder or a UI designer. The ladder degrades honestly as it climbs; we didn't let the metadata model become a second product.
  • The webhook storage-name guard is over-strict. A defense-in-depth regex drops some legitimate freight catalog names (cdl_number) from webhook bodies — absent by omission, never a leak, but the JSON:API surface renders them correctly and the guard needs to key on the declared abbrev, not a blanket pattern. A named, carried fix (roadmap G27), not a hole.
  • The kernel's Send suppression check — once hardcoded — is now abbrev-derived, fixed in the kernel (WS-A / ADR-014). The old send_checked path hardcoded the msp_suppression table, so a host mount's differently-named suppression table wouldn't be consulted. WS-A replaced the literal SQL with an Ash read on the mount's declared Suppression resource (abbrev-derived, inheriting OrgScope) — proven by a red-path test that mounts Marketing under a non-msp abbrev, adds an active suppression row, and confirms the send is REFUSED (no crash, no silent bypass). The framework's fail-closed consent/suppression wrapper remains as belt-and-suspenders. This residue, named in the original doc, is closed.
  • The identity-disclosure path synthesizes a tenant-plane actor. The 3-state chat identity model implements disclosure as "resolve this one participant on the tenant plane instead of the operator plane," which means the disclose branch builds a tenant-plane actor map rather than deriving it from the live scope. This is legitimate and org-scope-confined — the branch only fires when stored disclosure_mode/identity_shared say so, never on operator request — but it is a synthesized-actor pattern flagged for future-reader awareness, not a bypass.
  • Cosmetic label / nav residue in the demo. The "acting as" banner rendered an empty <b></b> because an attr default pre-empted its assign_new fallback — cosmetic only (the topbar/breadcrumb show the org name correctly); filed as a fix-forward. The operator "health" pill (derived from subscription status) and the billing "dunning" list (derived from invoice due dates) can read as inconsistent on the seed until "has a past-due invoice" folds into the at-risk signal. Neither affects scoping or masking; both are named polish items from the demo-coherence gate.
  • The doc was a sales artifact; the code is the arbiter. Positioning claims from the vision (socket counts, CFO math) were never lowered into code. Where the doc asserted more than the build proves, this page tells the build's version — docs/claim-evidence.md maps every load-bearing claim to a test, a game-day, or a named residue, and no claim is left un-evidenced and un-labeled.
The pitch, in one sentence

Your identity, CRM, billing, support, PII vault, audit, the operator plane, the product UI, and realtime cross-plane chat are one governed object model on one Postgres — so engineering writes only the B2B problem, the compliance-grade parts come with it, and you can support your tenants without ever seeing their customers.