A focused supplement to Architecture-v3.1.html. Covers the table schemas (per pool), the API surface owned by sdk-identity · sdk-persona · sdk-engagement · sdk-rebac, the lifecycle states of every layer, and the compliance mapping (HIPAA · DPDP · GDPR · PCI · SOC 2 · RBI/SEBI) of where each PII / PHI / payment field lives.
A single human can be — simultaneously — a patient at three hospitals (with multiple doctors and admissions per hospital), a buyer at five eCommerce stores, an investor in two funds, a donor at one ashram, a homeowner working with a contractor, and an employee at her own clinic. We need this to work with:
No single-layer identity model solves all of this. The six-layer stack does, by giving each requirement a dedicated entity with its own lifecycle, audit, and key tier.
person_idOne row per human. Lives in the admin pool of the person's home region. Carries identity alias graph (emails · phones · biometric refs · gov-IDs), home-region pointer, master encryption key handle, and a compliance summary. Encrypted under the Person Key.
app_identity_idThe person's projection into one app. One row per (person_id × app_id). Carries the app-scoped display profile, app preferences, app-scoped notification routing (push tokens, WhatsApp number used in this app).
tenant_membership_idThe app identity's membership in one tenant. One row per (app_identity_id × tenant_id). Carries the tenant-scoped role, the membership status (active · suspended · departed), tenant-scoped attributes, and joining metadata.
persona_idThe "hat" the person wears in this tenant membership. Carries a kind (Patient · Doctor · Buyer · Donor · Investor · …), persona-scoped status, and join keys into the persona-extension tables (donor giving history, patient chart root, investor stake records).
encounter_idA bounded interaction between one persona and one or more counterpart personas inside a tenant context. Has time bounds, a location, a list of participants with roles, encounter-scoped consent, and an encounter-level encryption key.
relationship_idA long-running bond between two personas. Spans many encounters. Carries kind (primary-care · sales-rep · loyalty · advisor), grant scope, status, and an attestation cadence.
The six-layer stack handles per-person identity. Real enterprises also need hierarchies above the tenant (resellers, sub-tenants, org charts), orthogonal to identity (geographic, fiscal-period), and refinements of role identity (role templates with inheritance). v3.1 makes each one a first-class typed entity so verticals stop inventing flat-string substitutes.
reseller_idA partner organization that owns and bills a portfolio of customer tenants under a contract with Projexlight. White-labels the platform under their own brand and domain; sees consolidated billing across their portfolio; gets scoped admin access (read-mostly) into their customer tenants.
reseller.tenant.attached.v1.tenant.parent_tenant_idA tenant whose parent is also a tenant. Healthcare HIE (50 hospitals under one network), franchise chain (500 locations under one brand), property-management company (200 buildings under one operator), conglomerate (subsidiaries under one parent).
tenant_id and root_tenant_id for showback.bu.parent_bu_idv3 introduced a flat bu_id. v3.1 makes BU a tree so enterprise org charts (Global VP → Regional VP → Country Manager → Site Director → Department Lead) cascade ABAC policy correctly. Matches Analyze.txt's mandated scope hierarchy (Org → BU → Region → Property → Dept → User).
subject.bu.ancestors() as a queryable set.geo_node_idA typed tree of geographic units, separate from pool_index. Pool indexes encode where data lives; geo nodes encode what real-world locality something refers to. Levels: Region → Country → State/Province → City → Locality.
role_template_idv3 stored persona roles as flat strings (['patient'], ['doctor']). v3.1 promotes roles to typed templates with inheritance. junior_doctor < senior_doctor < chief_of_medicine — junior is a subset of senior is a subset of chief. ABAC policies bind to templates, not strings, so a "senior_doctor can write Rx" rule automatically grants chief.
fiscal_period_idPer-tenant fiscal calendar so billing, reporting, and contract renewals align with the tenant's actual fiscal year — not the platform's calendar year. Decomposes into Year → Quarter → Month → Week. Defaults to calendar year for tenants who haven't customized.
agent_id Analyze.txt #5AI agents acting in the system get their own typed identity — not a special-case bypass of the human identity stack but a parallel structure. An agent has an agent_id, runs on behalf of an agent_persona (the human persona it represents), has a typed agent_scope (allow-list of methods it can invoke), and an agent_chain (provenance: human → meta-agent → executing agent).
actor.kind = 'agent' with the full chain recorded on every action.sdk-approval for human sign-off.The six layers plus the extended hierarchies make identity expressive. They also make identity expensive to resolve on demand — at hyperscale, walking person → app identity → tenant membership → persona → encounter → relationships → role-templates → consent → policy for every authenticated call is the platform's worst hot-path. v3.1's answer is the Identity Projection System: precomputed flattened read models, refreshed by change events, consulted in sub-ms.
../../Analyze2.txt #1) called runtime identity resolution at PB scale "the platform's first bottleneck": it must become projected/cached. This section is the canonical model — the SDK that implements it (sdk-identity-resolver) lives in P2 of the build plan.
For every active (person_id, app_id, tenant_id) triple, the projection store holds one materialized subject_view row that flattens everything authz / audit / personalization needs:
CREATE TABLE subject_view ( -- Composite key person_id uuid not null, app_id text not null, tenant_id uuid not null, -- Flattened identity layers (read-only snapshots of L1-L4) app_identity_id uuid not null, tenant_membership_id uuid not null, primary_persona_id uuid not null, all_persona_ids uuid[] not null, -- the person may hold multiple personas at this tenant -- Extended hierarchies (read-only snapshots) bu_id uuid, -- current BU bu_ancestors uuid[], -- full ancestry up to org parent_tenant_id uuid, -- if sub-tenant root_tenant_id uuid not null, -- top of the tenant chain reseller_id uuid, -- if attached to a reseller geo_node_id text, -- current locality role_template_ids text[] not null, -- effective roles after inheritance effective_role_closure text[] not null, -- transitive role closure (junior < senior < chief) -- Authorization summary abac_attributes jsonb not null, -- snapshot of attributes the ABAC engine reads rebac_edges jsonb not null, -- relationships keyed by kind (subset, depth-bounded) active_consents jsonb not null, -- consent receipts by (purpose, processor) effective_scopes text[] not null, -- IQL-evaluated effective access scopes -- Pool routing admin_pool_index text not null, app_pool_index text not null, evidence_pool_index text, -- Snapshot metadata projection_version bigint not null, -- monotonic, used by audit and policy decisions projected_at timestamptz not null, source_event_id uuid not null, -- the event that triggered this refresh ttl_expires_at timestamptz not null, -- safety: force re-projection beyond this primary key (person_id, app_id, tenant_id) ); -- Indexes for the common access patterns CREATE INDEX subject_view_by_persona ON subject_view USING gin (all_persona_ids); CREATE INDEX subject_view_by_role ON subject_view USING gin (effective_role_closure); CREATE INDEX subject_view_by_geo ON subject_view (geo_node_id); CREATE INDEX subject_view_by_reseller ON subject_view (reseller_id) WHERE reseller_id IS NOT NULL;
Storage: Redis for live hot reads (sub-ms); Postgres in the person's home Admin Pool for durability and replay. The projector worker writes both atomically (Postgres → Redis pub/sub invalidate). See Architecture §8B Polyglot Persistence and §8A Pool Placement Matrix for the doctrine behind these placement choices.
The projector worker subscribes to a small set of change events. When any of these fires, the affected subject_view rows are recomputed within 1s and pushed to Redis:
| Triggering event | Which subject_views to refresh |
|---|---|
identity.persona.created.v1 · identity.persona.shred.v1 | All views for the person |
identity.membership.suspended.v1 · identity.membership.reactivated.v1 | The (person, app, tenant) view |
identity.role.assigned.v1 · identity.role.revoked.v1 | The (person, app, tenant) view |
rebac.relationship.created.v1 · rebac.relationship.terminated.v1 | Both endpoint persona's views |
consent.granted.v1 · consent.revoked.v1 | All views for the consenting person |
tenant.bu.moved.v1 | All views for personas under the BU subtree |
tenant.role-template.updated.v1 | All views whose effective_role_closure references the template |
| TTL expiry (default 1h) | The view (safety re-projection) |
resolveIdentityContext(token) from sdk-identity-resolver is the only sanctioned entry point for reading identity. It executes in this order:
// pseudocode — actual implementation in sdk-identity-resolver
async function resolveIdentityContext(token: JWT): Promise<IdentityContext> {
const claims = verifyJwt(token); // (person_id, app_id, tenant_id, ...)
const key = `${claims.person_id}:${claims.app_id}:${claims.tenant_id}`;
// 1) Hot path — Redis projection store, p99 ≤ 0.5ms warm
const view = await redis.get(`subject_view:${key}`);
if (view) return materializeContext(claims, view);
// 2) Cold path — Postgres projection store, p99 ≤ 5ms
const view = await pg.query('SELECT * FROM subject_view WHERE ...', key);
if (view) {
await redis.set(`subject_view:${key}`, view, { EX: 60 });
return materializeContext(claims, view);
}
// 3) Fallback — live six-layer resolve (rare; emits an alert)
emitMetric('identity.projection.miss', { reason: 'cold-not-found' });
const view = await liveSixLayerResolve(claims); // expensive — typically > 50ms
await projector.materializeOutOfBand(view); // schedule a backfill
return materializeContext(claims, view);
}
The fallback exists so the platform degrades to live resolution under projector failure rather than hard-failing. CI chaos-tests killing the projector verify the fallback works without errors.
resolveIdentityContext().Conceptual Postgres DDL. Every table's primary key is non-sequential (ULID); every PII column is envelope-encrypted at the column level under the appropriate key.
CREATE TABLE person ( person_id text PRIMARY KEY, -- pers_01HXYZ... home_admin_pool_index text NOT NULL, -- admin-014 home_region text NOT NULL, -- ap-south-1 person_key_arn text NOT NULL, -- HSM key handle status text NOT NULL, -- ACTIVE | SUSPENDED | ERASED alias_graph_version int NOT NULL DEFAULT 0, created_at timestamptz NOT NULL, erased_at timestamptz -- set on cryptographic erasure ); CREATE TABLE person_alias ( person_id text REFERENCES person, alias_kind text NOT NULL, -- email | phone | govid | biometric_ref alias_value_enc bytea NOT NULL, -- envelope-encrypted alias_fingerprint bytea NOT NULL, -- HMAC(value) for lookup verified_at timestamptz, PRIMARY KEY (person_id, alias_kind, alias_fingerprint) ); CREATE TABLE person_secure_data ( person_id text REFERENCES person, band text NOT NULL, -- 'dl' | 'pan' | 'aadhaar' | 'ssn' | 'passport' | 'pci' field_label text NOT NULL, field_value_enc bytea NOT NULL, -- per-field envelope field_last4_clear text, -- masked, for display jurisdiction text, -- 'IN' | 'US' | ... consent_ref text NOT NULL, PRIMARY KEY (person_id, band, field_label) );
CREATE TABLE app_identity ( app_identity_id text PRIMARY KEY, -- apid_01HXYZ... person_id text NOT NULL, -- references person in home pool app_id text NOT NULL, -- 'healthcare' | 'ecommerce' | ... display_name_enc bytea, locale text, preferences_enc jsonb, -- envelope-encrypted JSON notification_routing_enc bytea, -- channel routing (push tokens, etc.) created_at timestamptz NOT NULL, status text NOT NULL, -- ACTIVE | SUSPENDED | DEPRECATED UNIQUE (person_id, app_id) ); CREATE INDEX ix_app_identity_person ON app_identity (person_id);
CREATE TABLE tenant_membership (
tenant_membership_id text PRIMARY KEY, -- tmb_01HXYZ...
app_identity_id text NOT NULL,
tenant_id text NOT NULL,
roles text[] NOT NULL, -- ['patient'] or ['doctor', 'admin']
status text NOT NULL, -- ACTIVE | SUSPENDED | DEPARTED | INVITED
joined_at timestamptz NOT NULL,
departed_at timestamptz,
attributes_enc jsonb, -- tenant-scoped, ABAC inputs
UNIQUE (app_identity_id, tenant_id)
);
-- Row-level security still applied within a pool
ALTER TABLE tenant_membership ENABLE ROW LEVEL SECURITY;
CREATE POLICY pol_membership ON tenant_membership
USING (tenant_id = current_setting('app.tenant_id')::text);
-- Identity facet (admin pool of the tenant) CREATE TABLE persona ( persona_id text PRIMARY KEY, -- pers_01HXYZ_patient tenant_membership_id text NOT NULL, kind text NOT NULL, -- 'patient' | 'doctor' | 'buyer' | ... status text NOT NULL, -- ACTIVE | SUSPENDED | ARCHIVED display_label_enc bytea, extension_table_ref text, -- 'healthcare_chart_root' | 'donor_giving_root' | ... extension_pool_index text, -- pool that holds extension rows created_at timestamptz NOT NULL ); -- Extension data (tenant's app pool — example: healthcare chart root) CREATE TABLE healthcare_chart_root ( chart_root_id text PRIMARY KEY, persona_id text NOT NULL, -- back-reference; resolved at runtime mrn text, -- medical record number (per-tenant) blood_type text, allergies_enc bytea, primary_care_provider text, -- relationship_id reference encounter_count int DEFAULT 0, status text DEFAULT 'ACTIVE', created_at timestamptz NOT NULL );
CREATE TABLE encounter ( encounter_id text PRIMARY KEY, -- enc_01HXYZ_visit_42 tenant_id text NOT NULL, app_id text NOT NULL, kind text NOT NULL, -- 'visit' | 'admission' | 'order' | 'appointment' | ... state text NOT NULL, -- OPEN | IN_PROGRESS | CLOSED | SEALED scheduled_at timestamptz, opened_at timestamptz NOT NULL, closed_at timestamptz, sealed_at timestamptz, -- on retention-driven seal address_id text, -- L5 location anchor encounter_key_arn text NOT NULL, -- payload key payload_enc bytea, -- encrypted payload (visit notes, order body) metadata jsonb -- non-PII (counts, refs) ); CREATE TABLE encounter_participant ( encounter_id text REFERENCES encounter, persona_id text NOT NULL, role text NOT NULL, -- 'patient' | 'doctor' | 'nurse' | 'buyer' | ... joined_at timestamptz NOT NULL, left_at timestamptz, PRIMARY KEY (encounter_id, persona_id, role) ); CREATE TABLE encounter_grant ( grant_id text PRIMARY KEY, encounter_id text REFERENCES encounter, grantee_persona_id text NOT NULL, scope text[] NOT NULL, -- ['vitals.read', 'labs.read'] granted_by text NOT NULL, -- persona_id of issuer expires_at timestamptz NOT NULL, revoked_at timestamptz, reason text -- 'shift_assignment' | 'consult_request' );
CREATE TABLE relationship ( relationship_id text PRIMARY KEY, kind text NOT NULL, -- 'primary_care' | 'loyalty' | 'advisor' | ... persona_a_id text NOT NULL, persona_b_id text NOT NULL, scope text[] NOT NULL, -- which actions persona_a may take on persona_b's data status text NOT NULL, -- ACTIVE | SUSPENDED | TERMINATED consent_ref text NOT NULL, created_at timestamptz NOT NULL, attested_at timestamptz, -- last annual attestation terminated_at timestamptz, cross_tenant boolean DEFAULT false, source_tenant_id text, -- when cross_tenant=true target_tenant_id text -- when cross_tenant=true ); CREATE INDEX ix_rel_a ON relationship (persona_a_id, status); CREATE INDEX ix_rel_b ON relationship (persona_b_id, status);
resolveIdentityContext(token) from sdk-identity-resolver. Every other layer-attribute read in any non-resolver SDK is forbidden by lint (Opinionated Constraint OC-4, Architecture §3A). The resolver reads from the Identity Projection store (§2B) on the hot path with p99 ≤ 0.5ms warm; falls back to live six-layer resolve only on miss. The endpoints below are the mutation surfaces — reads happen through the resolver.
// The one read endpoint that matters
import { resolveIdentityContext } from '@projexlight/sdk-identity-resolver';
const ctx = await resolveIdentityContext(token);
// ctx.person_id · ctx.persona_id · ctx.tenant_id · ctx.bu_ancestors ·
// ctx.effective_role_closure · ctx.active_consents · ctx.rebac_edges ·
// ctx.admin_pool_index · ctx.projection_version · ...
// — snapshot is stable for the request lifetime; audit + policy reference projection_version
// First login per app — mints app_identity if missing
POST /v1/identity/login
{ credential, app_id }
→ { jwt, person_id, app_identity_id }
// JWT carries the full tuple
GET /v1/identity/me
→ { person_id, app_identity_id, memberships: [...], personas: [...] }
// Alias graph stitch (admin only)
POST /v1/identity/merge
{ primary_person_id, alias_person_id }
→ { primary_person_id, merged_alias_count }
// Create a tenant membership (e.g., patient joins hospital)
POST /v1/persona/membership
{ app_identity_id, tenant_id, roles: ['patient'] }
→ { tenant_membership_id, persona_id }
// Add a persona to an existing membership
POST /v1/persona/{tenant_membership_id}/persona
{ kind: 'doctor' }
→ { persona_id }
// List a person's complete stack (across apps and tenants)
GET /v1/persona/stack?person_id=pers_001
→ {
app_identities: [...],
memberships: [...],
personas: [...]
}
// Open an encounter (issues encounter key)
POST /v1/engagement/encounters
{
tenant_id, app_id, kind: 'visit',
participants: [
{ persona_id: 'pers_001_patient', role: 'patient' },
{ persona_id: 'pers_002_doctor', role: 'attending' }
],
address_id, scheduled_at
}
→ { encounter_id, encounter_key_arn, state: 'OPEN' }
// Time-bounded grant for non-participants
POST /v1/engagement/encounters/{encounter_id}/grants
{
grantee_persona_id, scope: ['vitals.read'],
expires_at, reason: 'shift_assignment'
}
→ { grant_id }
// Close an encounter (payload retained, encounter still readable until seal)
POST /v1/engagement/encounters/{encounter_id}/close
→ { state: 'CLOSED', closed_at }
// Seal (retention expiry — shreds the encounter key)
POST /v1/engagement/encounters/{encounter_id}/seal
→ { state: 'SEALED', sealed_at, key_shredded: true }
// Open a relationship (e.g., PCP assignment)
POST /v1/rebac/relationships
{
kind: 'primary_care',
persona_a_id: 'pers_dr_smith',
persona_b_id: 'pers_ravi_patient',
scope: ['chart.read', 'rx.write'],
consent_ref: 'cnst_01HXYZ'
}
→ { relationship_id, status: 'ACTIVE' }
// Check if a relationship allows an action (used by Policy SDK)
POST /v1/rebac/check
{
actor_persona_id, target_persona_id, action: 'chart.read'
}
→ { allow: true, via_relationship: 'rel_01HXYZ' }
// Terminate a relationship (e.g., patient changes PCP)
POST /v1/rebac/relationships/{relationship_id}/terminate
{ reason: 'patient_request' }
→ { status: 'TERMINATED', terminated_at }
stateDiagram-v2
[*] --> OPEN: encounter created
OPEN --> IN_PROGRESS: first activity logged
IN_PROGRESS --> CLOSED: closed by participant
CLOSED --> SEALED: retention expires
CLOSED --> IN_PROGRESS: reopened (within window)
SEALED --> [*]
note right of SEALED
Encounter Key shredded
Payload mathematically irrecoverable
Metadata retained for analytics counts
end note
stateDiagram-v2
[*] --> ACTIVE: relationship created with consent
ACTIVE --> SUSPENDED: anomaly · attestation overdue
SUSPENDED --> ACTIVE: re-attested
ACTIVE --> TERMINATED: either party terminates
SUSPENDED --> TERMINATED: timeout
TERMINATED --> [*]
stateDiagram-v2
[*] --> INVITED: invitation issued
INVITED --> ACTIVE: accepted
INVITED --> [*]: declined
ACTIVE --> SUSPENDED: admin action
SUSPENDED --> ACTIVE: reinstated
ACTIVE --> DEPARTED: person leaves tenant
SUSPENDED --> DEPARTED: timeout
DEPARTED --> [*]
stateDiagram-v2
[*] --> ACTIVE: created
ACTIVE --> SUSPENDED: admin · fraud
SUSPENDED --> ACTIVE: reinstated
ACTIVE --> ERASED: erasure request honored
SUSPENDED --> ERASED: erasure request honored
ERASED --> [*]
note right of ERASED
Person Key shredded
All downstream layers undecryptable
Tombstone retained for audit
end note
Where each regulated field type lives — by layer, pool, and key.
| Regulation | Field class | Layer | Pool | Key | Erasure mechanism |
|---|---|---|---|---|---|
| HIPAA (US PHI) | Clinical notes, vitals, labs, dx codes | L5 Encounter payload | App pool (Healthcare) | Encounter Key | Seal encounter → key shred |
| HIPAA | MRN, allergies, blood type | L4 Persona extension | App pool | Tenant Key | Tenant offboarding |
| DPDP (India) | Name, phone, email aliases | L1 alias graph | Admin pool (home region) | Person Key | Right-to-erasure shreds Person Key |
| DPDP | Aadhaar, PAN, DL | L1 Secure Data band | Admin pool (IN region pinned) | Person Key + per-field envelope | Field-level shred |
| GDPR (EU) | Personal data + processing record | L1 + L2 + L4 + consent | EU-region admin + app pools | Person + Tenant keys | Portability export from MDM endpoint; erasure shreds Person Key |
| PCI DSS | Card tokens (no raw PAN) | L1 Secure Data band, PCI | Admin pool | Person Key + tokenizer | Token revoke via processor; field shred locally |
| SOC 2 | Audit ledger | Per-pool audit chain | Every pool | Pool KEK (immutable) | Retention-policy driven (typically 7y) |
| RBI / SEBI | Investor stake, K-1s | L4 Persona extension + L5 Encounter (cap call, distribution) | App pool (OneEstate) | Tenant Key + Encounter Key | 8y retention, then seal |
| Data residency | All PII at-rest | L1–L6 | Region-pinned pools only | Per-region root CMK | Region-bounded by pool placement |
org_id · app_id · tenant_id · bu_id · persona_id · encounter_id — from the JWT. That lets sdk-billing split a tenant's bill per app, per business unit, per persona-kind (e.g., "your clinicians cost X, your patients cost Y"), and per encounter ("Hospital A's Q3 visits cost Z in platform fees"). AWS, GCP, Azure, and Stripe all bill against a flat customer/account identity and rely on user-defined tags for any further split. We get this for free because the six-layer stack is the identity, not a metadata afterthought.
// 1. Ravi (already a person) joins Hospital A
POST /v1/persona/membership
{ app_identity_id: 'apid_ravi_hc', tenant_id: 'ten_hospA', roles: ['patient'] }
→ { tenant_membership_id: 'tmb_ravi_hA', persona_id: 'pers_ravi_hA_patient' }
// 2. Dr. Smith is a doctor at Hospital A
// (persona already exists: pers_drsmith_hA_doctor)
// 3. PCP relationship is established
POST /v1/rebac/relationships
{
kind: 'primary_care',
persona_a_id: 'pers_drsmith_hA_doctor',
persona_b_id: 'pers_ravi_hA_patient',
scope: ['chart.read', 'rx.write'],
consent_ref: 'cnst_pcp_ravi'
}
→ { relationship_id: 'rel_pcp_ravi_smith' }
// 4. Visit is scheduled
POST /v1/engagement/encounters
{
tenant_id: 'ten_hospA', app_id: 'healthcare', kind: 'visit',
participants: [
{ persona_id: 'pers_ravi_hA_patient', role: 'patient' },
{ persona_id: 'pers_drsmith_hA_doctor', role: 'attending' }
],
scheduled_at: '2026-04-12T10:30Z'
}
→ { encounter_id: 'enc_visit_42', state: 'OPEN' }
// 5. Dr. Smith reads Ravi's chart (Pool Router → app-healthcare-007)
GET /v1/healthcare/chart/pers_ravi_hA_patient
Authorization: Bearer
→ Policy: ABAC ✓
→ ReBAC: rel_pcp_ravi_smith ACTIVE ✓
→ Encounter: enc_visit_42 IN_PROGRESS ✓
→ chart returned
// 6. Visit closes
POST /v1/engagement/encounters/enc_visit_42/close
→ state: 'CLOSED'
// 7. 6 years later, retention triggers seal
POST /v1/engagement/encounters/enc_visit_42/seal
→ state: 'SEALED', encounter key shredded
// 1. Anita joins BookStore tenant (e-com app)
POST /v1/persona/membership
{ app_identity_id: 'apid_anita_ec', tenant_id: 'ten_bookstore', roles: ['buyer'] }
→ { tenant_membership_id: 'tmb_anita_bs', persona_id: 'pers_anita_bs_buyer' }
// 2. Order placed (one encounter)
POST /v1/engagement/encounters
{
tenant_id: 'ten_bookstore', app_id: 'ecommerce', kind: 'order',
participants: [
{ persona_id: 'pers_anita_bs_buyer', role: 'buyer' }
],
metadata: { order_no: 'BS-2026-04-998' }
}
→ { encounter_id: 'enc_order_BS998' }
// 3. Payment + line items hang off encounter
// Payment SDK uses Anita's PCI token from L1 Secure Data
// Order body is encrypted under encounter key
// 4. Later, Anita orders from ElectroMart
POST /v1/persona/membership
{ app_identity_id: 'apid_anita_ec', tenant_id: 'ten_electromart', roles: ['buyer'] }
→ { tenant_membership_id: 'tmb_anita_em', persona_id: 'pers_anita_em_buyer' }
// 5. ElectroMart staff cannot see BookStore orders.
// Pool Router refuses cross-tenant query.
// Anita aggregating her own data needs an analytics-export consent.
Storm-damage repair marketplace. Sam (Homeowner persona at ten_marketplace) posts a job; Bob's Roofing (Contractor persona at the same tenant) bids; platform holds funds in escrow; dispute resolution is an encounter the platform staff joins. Exercises: two personas at one tenant with opposing interests, escrow via sdk-payment, reputation across encounters, dispute encounter with a third-party participant.
// 1. Sam and Bob's Roofing both hold personas at the marketplace tenant
// Sam: pers_sam_mp_homeowner Bob: pers_bob_mp_contractor
// 2. Sam posts a job — a Listing encounter
POST /v1/engagement/encounters
{ kind: 'listing', tenant_id: 'ten_marketplace',
participants: [{ persona_id: 'pers_sam_mp_homeowner', role: 'requester' }] }
→ { encounter_id: 'enc_listing_223' }
// 3. Bob bids — bids are sub-encounters referencing the listing
POST /v1/engagement/encounters
{ kind: 'bid', parent_encounter_id: 'enc_listing_223',
participants: [{ persona_id: 'pers_bob_mp_contractor', role: 'bidder' }] }
→ { encounter_id: 'enc_bid_BR_99' }
// 4. Sam accepts → Job encounter opened; sdk-payment escrows funds
POST /v1/engagement/encounters
{ kind: 'job', parent_encounter_id: 'enc_listing_223',
participants: [
{ persona_id: 'pers_sam_mp_homeowner', role: 'payer' },
{ persona_id: 'pers_bob_mp_contractor', role: 'provider' }
],
payment_ref: 'pay_escrow_998' }
→ { encounter_id: 'enc_job_998' }
// 5. Bob captures evidence (sdk-evidence) — each capture stamped with encounter_id
// 6. Dispute? sdk-approval routes to platform support; a 3rd persona joins enc_job_998
// as 'arbitrator'; ReBAC grants temporary read on evidence chain
// 7. Resolution closes the job encounter; escrow releases; ratings written to
// persona-extension on both sides; encounter key sealed at the end of the
// dispute window for retention
A SaaS tenant (FitStudio) uses our platform to run their fitness app; their end users subscribe to FitStudio at $20/month. Platform bills FitStudio for SDK usage; FitStudio bills its end users. End users are personas at FitStudio's tenant, not direct platform customers.
// 1. End user signs up via FitStudio's app
// Identity SDK mints person + app identity + tenant membership + persona
// Persona kind: 'member' at tenant_id='ten_fitstudio'
// 2. FitStudio's app calls sdk-payment for end-user's monthly $20 charge
// actor: { kind: 'tenant_admin', tenant_id: 'ten_fitstudio' }
// payee: tenant's own Stripe Connect account (not ours)
// 3. sdk-meter records that call as 'payment.charge.subscription' SKU
// against ten_fitstudio (the platform-tenant)
// 4. End of month: sdk-billing invoices FitStudio for all SDK usage
// FitStudio receives one invoice line: '847k API calls · $1,245'
// FitStudio's revenue from end-user subscriptions is theirs, not ours
// 5. The end user appears in our system only as a persona;
// they never see a Projexlight invoice
GlobalCorp operates in US, EU, IN with revenue in USD, EUR, INR. Their fiscal year ends March 31. They want one consolidated quarterly P&L in USD. Exercises: fiscal-period entity, multi-region pool placement, geo-node + currency dimensions, warehouse aggregation with FX normalization.
// 1. GlobalCorp configures its fiscal calendar
PUT /v1/tenant/fiscal-period
{ tenant_id: 'ten_globalcorp', fiscal_year_start: '04-01',
base_currency: 'USD',
fx_source: 'platform_daily_close' }
// 2. Each region's encounters carry geo_node_id + transaction currency
// US sales encounter: geo_node_id='geo_us', currency='USD'
// EU sales encounter: geo_node_id='geo_eu', currency='EUR'
// IN sales encounter: geo_node_id='geo_in', currency='INR'
// Each lives in its region's app pool (residency)
// 3. Quarterly close (cron, T+1 day after fiscal quarter end)
// sdk-analytics rollup job (warehouse only — no live cross-pool join):
// SELECT geo_node_id, SUM(amount * fx_to_usd_at_txn_date) AS usd_revenue
// FROM warehouse.encounters_by_tenant
// WHERE tenant_id = 'ten_globalcorp'
// AND fiscal_period_id = 'fp_globalcorp_FY24_Q1'
// GROUP BY geo_node_id;
// 4. Output: consolidated P&L in USD, broken down by geo_node
// Audit trail: which FX rates applied per transaction; reproducible
// Showback: per-BU and per-geo splits available via sdk-billing
Ravi's PCP at Hospital A refers him to a specialist at Hospital B. Without explicit cross-tenant relationship, B has no access to A's chart. Exercises: cross-tenant Relationship (L6 with cross-tenant key), Encounter Grant scoped to specific records, consent-driven hand-off, commission-tracking if the referral is paid.
// 1. Dr. Smith at Hospital A initiates the referral
POST /v1/rebac/cross-tenant-relationship
{ kind: 'referral',
source: { persona_id: 'pers_drsmith_hA_doctor', tenant_id: 'ten_hospA' },
target: { tenant_id: 'ten_hospB', persona_kind: 'specialist' },
subject: { persona_id: 'pers_ravi_hA_patient' },
scope: ['chart.read.subset', 'imaging.read'],
consent_ref: 'cnst_ravi_refer_hB',
expires_at: '2026-09-01' }
→ { relationship_id: 'rel_referral_ravi_hAhB', cross_tenant_key_id: 'ck_998' }
// 2. Hospital B's intake team accepts the referral
// A specialist persona at B is matched: pers_drjones_hB_specialist
PUT /v1/rebac/cross-tenant-relationship/rel_referral_ravi_hAhB
{ accepted_by: 'pers_drjones_hB_specialist' }
// 3. Dr. Jones can now read Ravi's chart subset
// On read: ReBAC check finds the cross-tenant relationship + active consent
// Decryption uses cross-tenant relationship key (re-encrypted from A's tenant key)
// Every read audited in BOTH ten_hospA and ten_hospB audit chains
// 4. Visit at Hospital B opens a new encounter under ten_hospB
// pers_ravi_hB_patient created (separate persona at Hospital B)
// Persona-extension link tags rel_referral_ravi_hAhB for provenance
// 5. If A and B have a commercial referral agreement
// sdk-billing applies commission rule: % of B's revenue → A's reseller-style payout
// Tracked via sdk-meter actor.relationship_ref
Support engineer Priya needs to debug an issue in Tenant X's account. Direct DB access is forbidden. She uses scoped, time-bounded, consent-required impersonation; every action under impersonation is loudly audited and visible to the customer.
// 1. Tenant X granted "support impersonation" consent during contract signing
// Stored as a tenant-level standing consent: cnst_support_impersonation
// Tenant admin can revoke at any time from their console
// 2. Priya opens a support ticket and requests impersonation
POST /v1/identity/impersonation/request
{ support_engineer: 'usr_priya',
target_tenant_id: 'ten_X',
reason: 'investigating ticket SUP-998',
requested_scope: ['read:audit', 'read:billing', 'read:user.list'],
duration_minutes: 30 }
→ { request_id: 'imp_req_223' }
// 3. Approval routing via sdk-approval
// Manager approves → if scope includes write, also requires customer approval
// Customer X's primary contact gets a notification + must approve in-app
// 4. Approved → sdk-identity mints an impersonation JWT
// Claims: actor.kind='support_impersonator',
// actor.real_user='usr_priya',
// actor.target_tenant='ten_X',
// expires_at=now+30min
// JWT cannot be silently refreshed; expires hard
// 5. Every call under this JWT
// - tagged in audit with both actor.real_user AND impersonation flag
// - surfaced LOUDLY in customer's audit-read API (red banner)
// - sdk-meter records usage under platform overhead, not customer's bill
// 6. End of session: certificate of impersonation generated
// Lists every action, every record touched, every reason
// Stored permanently in tenant's audit chain