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 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 — a clear contact card to the owning tenant, a •••• card to the SaaS operator, non-PII fields still clear so support can actually help. And the newest layer proves the thesis holds for AI too: a first-party, durable, multi-step AI agent loop (ADR-047, accepted) whose tool results are re-masked hop-by-hop and whose every write executes only on a human's approval, as that human, never on the agent's authority. This document describes what was built, gated GO, and tested on an open pre-merge branch — hardened past the demo by post-build workstreams (real CRUD, an operator cockpit, generator catch-up, table-stakes UX, the AI agent loop), each adversarially gated GO, with every guarantee held by a standing, ever-growing committed sabotage harness (285 patches as of this pass — count it live: ls scripts/sabotages/*.patch). 2,786 kernel tests · 1,773 framework · 631 demo · 251 freight · 125 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 propose-then-approve AI — an agent's write executes as the human who approved it, never the agent self-serve identity spine — signup · verify · reset · sessions · invites · OIDC + TOTP step-up · 2FA · onboarding, generator-emitted 285 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. Fields are rich declared types — Money, Percent, Score, Duration, Priority, URL, Email, Phone, Address — and the vault write path re-runs each type's cast_input, so a value is validated and normalized before it is tokenized, not after.

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 same floor, turned inward (ADR-045 / P17). The k-anonymity discipline the operator's portfolio uses is not operator-only — a tenant can hand its own lower-privilege staff analytics without letting a masked role reconstruct a ••••'d individual. This surface reads as the tenant's own org actor over the vault-excluded rollup (Samen.Aggregate.read_all_for_org/3, org bound from the authenticated scope — never a ?org= parameter), through the same shipped Privacy.apply/3 floor: k ≥ 5, l ≥ 2, and any cohort below the floor is emitted as ⊘ suppressed on both planes. A support member who sees driver names as •••• still gets the operational read — throughput, on-time, cohort health — but a count-of-one never leaks back as "exactly one subject." It is a separate org-scoped read path, not a relaxation of the operator-only analytics gate (T144, byte-intact).

Blue Ridge Logistics / Insights / Team analytics

Team analytics

Tenant plane · masked member. Rosa's role reads its own org's aggregates over a vault-excluded rollup — driver identity stays ••••, but the operational numbers are hers. Cohorts under the k-anonymity floor render , so a count-of-one can't be reconstructed by a role that can't see the name.
plane: tenant · k ≥ 5 · l ≥ 2
Loads / wk
126+8%
On-time %
94.2+1.1
Active drivers
31
Rev / load
$3.1k+4%

Drivers by lane cohort

6· org-level aggregates only — no personal data, no reveal path
Lane cohortDriversLoads · wkOn-timeMed-card valid
Midwest corridor114895%100%
Northeast metro93793%89%
Southeast regional62994%100%
Pacific NW · n = 3
Pacific NW holds 3 drivers — below the k ≥ 5 floor, so every cell is suppressed to ⊘. Insight for the role, not a lever to re-identify one person.
Tenant plane · masked member — the tenant's own k-anon analytics over a vault-excluded rollupproof: aggregate_privacy org-scoped arm · MaskingCase twin (masked member cannot reconstruct ••••) · T144 byte-intact · sab 276–278
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 2,786 tests green; the web dependency lives only in samen_web (1,773 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, 2,786 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 ~104 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 newest datapoint is the sharpest: Driftwood's support-triage AI agent — a durable, multi-step, propose-then-approve loop with EG2 egress scrubbing — mounts at ~5 authored lines of tool-definition (13 counting the heredoc, plus an 8-line router line), the figure the leverage guard holds under a ceiling.

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
AI agent loopDriftwood triage · multi-step
~5 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 · a first-party AI agent at ~5 authored lines · the domain 20% stays authored real work — 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: every committed sabotage patch (285 today and growing — 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 2,786 · samen_web 1,773 · demo 631 · driftwood 251 · pawchart 125 — the final CI line is ROOT CI: ALL PASSED.

Unique feature № 7 — the first-party AI agent loop

An AI agent that proposes. A human that approves. The write runs as the human — never the agent.

The newest layer (ADR-047, accepted 2026-08-17) closes the last gap the governance model had left open: multi-step AI tool use. It is built first-party — zero new dependencies. Adopting an agent framework (ash_ai, Jido — both evaluated, both rejected) would have moved prompt assembly outside the two chokepoints that make every AI claim on this page defensible; so the loop is a durable state machine over the substrate that already exists. A run is a checkpoint-per-turn cursor row (Samen.AI.Agent.Run) driven by Oban and recovered by a watchdog: a crash mid-turn replays the turn and finds its own :proposed checkpoint, so a tool fires at most once — at-least-once delivery, the turn row as the idempotency key, never a claimed exactly-once. The first vertical to mount it, Driftwood support-triage, is the leverage proof: the whole agent is ~5 authored lines of tool-definition (13 total incl. heredoc, plus an 8-line router mount) — the framework's first samen_ai_routes vertical mount, held under the leverage guard.

Two properties carry the section. Every read is re-masked hop-by-hop (the EG2 egress class). A tool result is resolved in egress mode%Masked{}••••, nil••••, anything unrecognized dropped with a bounded [unrenderable:<field>] marker, never inspect-ed — so the transcript at rest holds no vault plaintext and no vt_ token, and if the renderer ever regressed the assembled payload refuses {:error, :pii_egress_refused}, fail-closed. Crucially, an agent run resolves masked on every plane regardless of any live reveal grant — because the history is persisted, and a grant never unlocks persisted egress. And writes do not exist for the agent. An effect: :write tool doesn't mutate — it proposes through the E3 approvals engine and parks the run :awaiting_approval (unbilled, 24-hour deadline). It executes only when a human approves, inside the decision transaction — re-resolved, re-validated, field-by-field digest-bound (else :proposal_mismatch) — and it runs as the approver's actor, never the agent's. Requester ≠ approver is enforced twice: in policy and by a DB CHECK (<abbrev>_distinct_party). The approver sees token-only provenance — tool kind, turn index, the argument key names, and an args digest — never the raw argument values, which live only in the vault-routed transcript.

Driftwood · support-triagerun 6f2a…d1 · owner: R. Medina (member)
awaiting approval
1
search_records read
query status:open lane:midwest3 tickets matched · driver identity resolved ••••••, non-PII clear
done
2
fetch_record read
driver ••••••••• · CDL ••••••••• · status Available · ELD samsara · med-card valid — identity masked, ops fields clear
done
3
assign_record_owner write
proposes reassigning ticket DW-7781 to an on-duty driver — parked, unbilled, no mutation until a human decides →
proposed
write · awaiting approval Approve this agent write? The agent proposed a mutation. It runs only if you approve — and it runs as you.
Toolassign_record_owner
Turn#3
Arg keysrecord_id · owner_id
Arg valuesnot shown — vault transcript only
Args digestsha256:9f2a…c71b
Requesteragent · support-triage
Approveryou · C. Kluis (support)
On approve, the write executes inside this decision transaction — re-resolved, re-validated, and digest-bound (a changed arg ⇒ :proposal_mismatch) — under your actor. The agent never holds write authority.
requester ≠ approver — enforced in policy and by DB CHECK (apv_distinct_party) · executes at most once (the turn row is the idempotency key) · fail-honest budget: exhaustion returns an honest error, never a dressed-up partial
Tenant plane · /ai/agents — a support-triage run, reads re-masked, the one write parked for a humanproof: agent_write_test.exs (real E3 approvals · executes as approver) · EG2 tool-result re-mask · sab 250/252/253/256

The operator plane sees the fleet of runs — never the transcript. Where the tenant surface renders the run and its (masked) transcript, the operator's /operator/agents/:org_id health plane is token-only by construction: per-definition run counts, states, and budgets, plus a per-definition durable kill — and no transcript at all. The kill switch closed a real blast-radius: a rate-trip writes an org-scoped kill row, never the host-wide switch, so one tenant tripping a definition can never silence it for another. And the plane is fail-honest about wiring: a host that never configured the agent repos renders an explicit "agent plane not wired" card rather than a false "no runs," and an unreadable kill row reads :unknown, never active. Every one of these was landed behind its own adversarial gate (A1–A7), each with a red-path sabotage, and the whole arc is locked by mix samen.verify.agent_coverage — an AST check that no tool-exporting module can start an agent run, so the recursion guard can't be bypassed by a raw spawn.

SaaS-readiness — the BATON burn-down (2026-07)

The two adapters every B2B SaaS actually bills and mails through — added fail-honest, with the kernel still vendor-free.

A separate orchestrated burn-down took the foundry from demo-deep governance to SaaS-readiness: the self-serve identity spine (Phase 1) and the two integration surfaces no B2B product escapes — Stripe billing and transactional/marketing email (Phase 2) — each landed as a fail-honest adapter behind a vendor-free kernel. The proof that the kernel stayed clean is mechanical: delete all four adapter packages (samen_stripe · samen_postmark · samen_ses · samen_resend) and samen_core + samen_web still pass, because the kernel names zero vendor strings — dispatch is via host config, never a compile-time alias.

Phase 1 · Gate GO
Self-serve identity spine + rich types

Registration (atomic Org+User+Membership, credential PII vaulted), email verification, password reset with no timing oracle, sessions, team invites, OIDC that honors TOTP step-up, 2FA + vaulted recovery codes, an onboarding wizard — emitted by the generator with zero hand-edits — plus the rich declared-type menu (Money · Percent · URL · Email · Phone · Address) whose cast_input re-runs on the vault write path. Claim-evidence section J.

Phase 2 · WS-B
Stripe billing — vendor-free, fail-honest

Hosted checkout, subscription lifecycle sync (fetch-on-event · idempotent · out-of-order-safe), invoice + tax mirroring (never a fabricated $0), hosted-only payment methods (a PAN-shaped column can't even compile), dunning, metered usage, a signature-fail-closed webhook ingress with a DLQ, and a settings page with an honest :not_configured empty state. Claim-evidence section K, B1–B10.

Phase 2 · WS-C
ESP email — one behaviour, three vendors

A shared, non-vacuous conformance harness satisfied by Postmark, SES, and Resend (Basic-Auth / SNS-RSA+SigV4 / Svix-HMAC) — every forgery/replay attack rejected. A single send chokepoint; PII-safe rendering that resolves through the vault plane behind a fail-closed, non-skippable no-leak gate; deliverability (bounce/complaint → suppression) and masked notification digests. Claim-evidence section K, C1–C4/C8.

Phase 2 · hardening
Auth rate-limiting + bounded audit

The Phase-1 deferral, closed: sign-in / 2FA / registration / reset limited via one shared seam, keyed on HMAC-bidx / credential / IP — never plaintext email — with a bounded login_failed audit edge row (O(windows), not O(N)). Two P1 money/leak bugs (dunning out-of-order · deliver-seam) were caught by adversarial verification and fixed in-phase with RED-on-revert proofs.

Honest lane. Everything above runs keyless in CI — dispatch proven against hermetic fakes + injected-transport cassettes/fixtures. No host wires a live provider: every generated app boots with billing/ESP unconfigured and says so. Production persistence is fake-backed; the Ash-backed billing mirrors' host-wiring is deferred (T108), and the live lanes (STRIPE_TEST_KEY · SAMEN_POSTMARK_SMOKE · SAMEN_ESP_LIVE) are documented-but-not-CI. Full mapping + named deferrals: docs/claim-evidence.md section K.

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: 22 AST/Spark-checked verifiers (and growing — each new invariant ships its own), 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, aggregate_privacy, erasure_completeness, agent_coverage — AST-checked, not grepped. Each exits non-zero on a violation. A hallucinated column, net-new plaintext PII, a leaked vault value, a tool-module that could start an agent run: each fails the build, proven by the agent-authoring eval.

22 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 22-verifier gate + sabotage harness 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
The technical substrate — what's proven, by name

The load-bearing invariants. Each one has a name, a mechanism, and a red twin.

This is the technical-document half in one screen: the invariants everything else on this page rests on, stated by their real names in the codebase. None is a slogan — each is enforced by construction and paired with a sabotage that proves its test fails when the invariant is broken. Spot-check any of them against docs/claim-evidence.md and _orch/verify/.

INV-7 · no-PII-egress (incl. EG2)

Every AI egress is masked-by-default through one chokepoint. Tool definitions and tool results are re-scrubbed hop-by-hop; a leaked vt_ token or a struct on the wire refuses {:error, :pii_egress_refused}, fail-closed — never a partial leak.

proof: ai_prompt_masking · red-team EG2 arm · adr047-a3-tools-eg2-verdict
Two-plane masking

%Masked{} is the field's normal value, not a view. Tenant-plane (or operator-with-grant) resolves clear; operator-without-grant renders •••• across UI, JSON, CSV, and logs — by omission, with no vt_ token in the DOM.

proof: Samen.MaskingCase three-proofs · RED PATH 1/2/6
Propose-then-approve

An AI-proposed write executes only on a human's approval, inside the decision transaction, digest-bound, and as the approver's actor. Requester ≠ approver is enforced in policy and by a DB CHECK. The agent never holds write authority.

proof: agent_write_test · apv_distinct_party · sab 250/252/253/256
Erasure-completeness

Crypto-shred reaches every tier at once — live rows, replica, rollups, audit, PITR history, and the agent transcript + custom-object bags. The destruction oracle attests it left every place the data could hide.

proof: erasure_completeness · no_plaintext_pii oracle · ADR-046
Per-definition durable kill

A rate-trip writes an org-scoped kill row, never the host-wide switch — one tenant can't silence a definition for another. An unreadable row reads :unknown, never active; the gate fails closed.

proof: adr047-a5-surfaces-verdict · sab 257–262
k-anonymity aggregate floor

Aggregates enforce k ≥ 5, l ≥ 2; sub-floor cohorts suppress to on both planes. The tenant's own org-scoped analytics path is separate from the operator-only gate (T144), not a relaxation of it.

proof: aggregate_privacy (+ org-scoped arm) · p17-tenant-analytics-verdict
Compliance & trust posture

The compliance work most stacks bolt on at the end is the storage format here.

These are GDPR-relevant capabilities and controls that support your SOC 2 journey — born into the object model, each enforced by construction and paired with a proof. This is a control posture, described honestly for a pre-merge foundry. The full GDPR / SOC 2 story splits what the substrate gives you on day one from what stays your responsibility as the operator.

The honesty boundary — read first
  • Samen is not SOC 2 certified — there is no Type I/II report, no auditor, and no control period under observation.
  • Samen has not completed a GDPR audit, a DPIA, or a DPA process, and ships no Data Processing Agreement.
  • Samen operates no production deployment. It is a foundry / substrate on an open pre-merge branch — not a hosted service and not a package on Hex. Nothing here asserts that a system built on it is compliant; that depends on your deployment, DPO, policies, and processes. "Born-compliant substrate" means the controls are native to the object model, never that anything is certified.
Masked by default, both planes

Data-minimisation at rest: every 🔒 field is a vt_ vault token; %Masked{} is the field's normal value, resolved per-plane by one resolver — operator-without-grant renders •••• across UI, JSON, CSV, and logs by omission. (GDPR Art. 5/25)

proof: claim-evidence §B C2 · PiiResolution · MaskingCase three-proofs
Right to erasure — crypto-shred

Destroy one subject's external KMS key and their vaulted PII is undecryptable across live, replica, rollup, audit, and PITR-history at once — key destruction, not row-chasing. (GDPR Art. 17)

proof: claim-evidence §C D3/D4 · erasure_completeness · driftwood shred game-day
Second-party, time-boxed reveal

Least privilege: a reveal needs a distinct approver — policy and a DB CHECK (granted_by <> requestor_id). Grants expire, cannot renew in place, and enqueue auto-revoke in the same transaction. (SOC 2 CC6)

proof: claim-evidence §B C3/C5 · README hero
Tamper-evident audit

Records-of-processing substrate: aud_chain is hash-chained and append-only at the DB level — a raw UPDATE/DELETE is refused by trigger; the chain detects any gap or forgery, is tenant-readable, and is crypto-shreddable. (SOC 2 CC7 · GDPR Art. 30)

proof: claim-evidence §B C7 · audit_chain_test
Encryption & key handling

Per-subject data-encryption keys in an external KMS outside the WAL/PITR surface; authenticated envelope crypto; a PITR restore brings back ciphertext, never the key; no PAN column can compile. (SOC 2 CC6 · GDPR Art. 32)

proof: claim-evidence §C D4/§E H5 · Samen.Kms.*
Consent / suppression ledger

Lawful basis for outreach: marketing send runs through a suppression chokepoint — a suppressed or unconsented address is refused at the single send seam; the kernel check is abbrev-derived, not a hardcoded table. (GDPR Art. 6/7 · ePrivacy)

proof: claim-evidence §L/§M · ADR-014 · sequence_send_test
Backup verification

Availability & recoverability: the backup-verify job really pg_restores into a scratch DB and checksums it; unconfigured returns {:error, :not_configured} — never a fake :ok; corrupt / missing / drifted fail loudly. (SOC 2 A1)

proof: phase7-l4-l6-verdict (L6, 13/13) · sab 285
Multi-node availability

A real two-BEAM-node proof over one Postgres: exactly-once job fetch (no double-grab), refutable dedup, and reveal auto-revoke failover — kill the enqueuing node, the survivor runs the revoke exactly once. (SOC 2 A1)

proof: phase7-l4-l6-verdict (L4, 3/3)
Change management, verified

Every guarantee ships a green proof, a red-path proof, and a committed sabotage that proves the test fails when broken (285 patches). Migrations are expand/contract with a tested down/0 and a bake gate. (SOC 2 CC8)

proof: samen.verify.migrations · ls scripts/sabotages/*.patch
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 surfaces

→ the scopes — CRM, marketing, CMS, and support are inherited Ash domains; identity and billing are governed object models over the auth provider and Stripe you bring — one object model, not six integrations

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

An AI-agent orchestration service

→ a cursor row — the multi-step loop is a durable Run row over Oban; tool egress is re-masked hop-by-hop and every write goes through the approvals engine you already have, executed as the human

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.31.2, 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 22-verifier gate + sabotage harness

catalog_parity, prefixes, pii_reads, pii_classify, no_plaintext_pii, no_pii_columns, api_contract, aggregate_privacy, erasure_completeness, agent_coverage — each fail-closed, each with a red path. Every committed sabotage patch (285 today, growing every phase) proves 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 AI agent loop is governed, durable, and deliberately un-streamed in v1. The crown properties — reads re-masked hop-by-hop, writes via human approval executed as the approver, per-definition kill — are all shipped and gated (ADR-047 A1–A7, each with a red-path sabotage). What v1 does not have is live token streaming: the tenant and operator surfaces re-read on navigation, and the id-only-PubSub live-progress affordance is a named residual (§11), not a governance gap. PawChart and demo inherit the /operator/agents health route deliberately unwired and render an honest "agent plane not wired" card rather than a false empty state. The raw-spawn/1 recursion escape is closed by an AST gate (agent_coverage), not a dynamic mechanism, and was confirmed not tenant-reachable — defence-in-depth, named as such.
  • Tenant analytics enforces a k-anonymity floor, not a formal DP budget. The P17 tenant-facing surface reuses the shipped Privacy.apply/3 floor (k ≥ 5 / l ≥ 2, sub-floor cohorts ⊘) over a vault-excluded rollup, org-bound from the authenticated scope. But cross-query differencing over time is guarded by a WARN-level read budget, not an enforced ε-budget — an honestly-disclosed accepted v1 residual, consistent with "token-blind is not inference-blind" above. It is a separate org-scoped path, and T144 (the operator-only analytics gate) is byte-intact, not relaxed.
  • This is a foundry on an open pre-merge branch, not a deployed product. Everything here is built and gated GREEN on PR #1 (the saas-readiness-phase-1 branch), not merged and not hosted. Every "live," "port 4041," or "HTTP 200" on this page means the app boots and serves locally — the navigable 5-tenant demo and the two verticals on their ports, proven by ci.sh and the game-days — never a claim of a running production instance.
  • 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, realtime cross-plane chat, and a propose-then-approve AI agent loop 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.