Oshun Platform · Features

Architecture, Platform Foundations, and Security

A focused page within the Oshun Platform Features documentation. The full map and every sibling page live in the Features hub.

16sections17 minread9tables

On this page

This page describes the load-bearing layer beneath every Oshun V1 domain: the canonical contracts, the persistence-alignment harness, the event and queue substrate, the inbound/outbound integration plumbing, the public-API authorization model, and the security controls that gate launch. It serves platform engineers, integrators, operators, and security reviewers — anyone who needs to know how a request travels from the shell to a domain service and back, what guarantees hold along the way, and which pieces are real versus spec-only. It sits "below" the customer-facing domain pages (Tara, Arete, Veritas, Nyx, Nisaba, Metis) and underneath the channel surfaces (Channel Abstraction); nearly all of it is implemented code with tests, not aspiration. Where a piece is contract-level or provider-gated, this page says so explicitly.

Candor note up front. This layer is overwhelmingly implemented: the contracts, the persistence drift/index/migration/tombstone test quartet, the OAuth 2.1/PKCE state machine, the role/scope model, the partial-failure envelope contract, the durable queue + dead-letter + SLA monitor, the topic-registry + outbound delivery + webhook-simulator event bus, the inbound connectors, the data-residency enforcer, the idempotency middleware, and the append-only audit hash chain all exist as real code. Two things are honestly narrower than they sound: the OAuth module is a contracts/state-machine implementation (token types, PKCE validation, refresh, revocation cascade) — not a deployed live authorization server — and the event bus is not built on native Redis Streams (see the correction below). Both are called out where they appear.

Where the foundations live#

The platform-foundations package is the single named home for the cross-cutting subsystems. libs/oshun/platform-foundations/src/index.ts re-exports exactly nine subsystems, and the documentation should treat this package (@oshun/platform-foundations) as their canonical address rather than scattering them:

Subsystem (export) Path Responsibility
service-discovery src/service-discovery/service-discovery.ts Domain → service routing, health/readiness, deprecation
public-api src/public-api/oauth.ts §27 OAuth 2.1/PKCE, scoped tokens, quotas, audit
shared-contracts src/shared-contracts/ Cross-subsystem canonical shapes
role-model src/role-model/role-model.ts §28 canonical roles, scopes, function-level authorization
step-up src/step-up/step-up.ts Step-up authentication for sensitive actions
secrets src/secrets/secrets.ts Standardized secret loading, scoping, rotation
configs src/configs/configs.ts Validated feature flags, policy bundles, env configs
rollback src/rollback/rollback.ts Safe rollback plans for shell/admin/grounding/persona/generation
abuse-controls src/abuse-controls/abuse-controls.ts Rate limits, payload caps, query-cost ceilings

Related building blocks live in sibling shared libraries — the auth primitives (libs/shared/auth-primitives), the HTTP client middleware (libs/shared/http-client), the event bus (libs/shared/event-bus), the queue substrate (libs/shared/queue), the inbound connectors (libs/shared/inbound-integrations), data residency (libs/shared/data-residency), and the audit platform (libs/shared/audit-platform). The auth client package used by services is @oshun/auth-client (libs/oshun/auth).

The canonical domain registry#

Every top-level domain is described once, in code, by the domain registry at libs/oshun/domain-registry/src/registry.ts. OSHUN_DOMAIN_IDS enumerates the six customer domains —

text
['tara', 'veritas', 'nyx', 'arete', 'nisaba', 'metis']

— and OSHUN_SHELL_PRIMARY_DOMAIN is 'tara', the domain the shell opens to. DOMAIN_REGISTRY maps each id to a DomainMetadata record that carries far more than a label: a DomainAuthPolicy (required, a sessionKind of customer | learner | reader | operator, scopes, and stepUpActions), an 'analytics-id', a 'notification-channel', an 'assistant-context-key', a 'deep-link-prefix', a 'bff-base-path', a DomainLaunchContract with typed permissions (notifications | audio | location | calendar | camera | storage), a DomainOfflineFallbackCard, a DomainShellNarrative, and an 'admin-taxonomy' (ownerSubsystem, primaryQueues, auditCategory, contentClasses). The BFF base paths are stable and uniform:

Domain bff-base-path
Tara /api/oshun/domains/tara
Veritas /api/oshun/domains/veritas
Nyx /api/oshun/domains/nyx
Arete /api/oshun/domains/arete
Nisaba /api/oshun/domains/nisaba
Metis /api/oshun/domains/metis

Because a domain's route typing, auth/domain preferences, analytics enums, notification preferences, assistant context key, deep-link prefix, and admin taxonomy all originate from one record, the shell, the BFF, analytics, and the admin console cannot drift out of agreement about what a domain is. Adding a seventh domain means adding one entry, not editing eight subsystems.

Shared contracts as the spine#

V1's request and response shapes are canonical Zod contracts. They cover the breadth the source enumerates — rituals, practices, concepts, passages, claims, sources, notebooks, collections, continuity state, personas, voice profiles, avatar packs, evidence packs, review packages, provenance bundles, consent records, memory scopes, workflow templates, model cards, model versions, support cases, incidents, and policy bundles — and they are the enforcement point for object-property authorization, excessive-data-exposure, and mass-assignment defenses (a request shape that doesn't model a field cannot smuggle it through).

Common contracts shared across domains live under libs/contracts/src/common/. That directory contains 286 files today — 161 implementation .ts modules plus their colocated .spec.ts/.test.ts siblings. (An earlier doc figure of "≈265 files" is stale and low; the exact total drifts up as contracts gain tests, so treat the implementation-module count as the stable figure.)

The contracts index (libs/contracts/src/index.ts) does more namespace re-exporting than the three commonly cited (NisabaContracts, MetisContracts, VeritasContracts). It also namespaces the cross-version and living-scene contract sets, using explicit aliasing where names would otherwise collide:

Namespace export Source
NisabaContracts ./nisaba
MetisContracts ./metis
VeritasContracts ./veritas
IrisContracts ./iris/index
AgentContracts ./agent/index
OyaContracts ./oya/index
V3Contracts ./v3/index
V6Contracts ./v6/index
V9Contracts ./v9/index
LivingSceneContracts ./living-scene/index

The living-scene set is aliased to avoid clashes (for example LivingSceneScoreSchema and deepParseLivingSceneScore), so a consumer can import scene contracts alongside domain contracts without symbol collisions. See Scene Score Schema for what those shapes model.

The partial-failure envelope contract#

When an operation fans out across domains (say, a bulk import touching Veritas claims and Nisaba passages), some items can succeed while others fail. V1 represents that outcome as a first-class Zod contract, not an ad-hoc shape. It lives at libs/contracts/src/common/partial-failure-envelope.ts:

ts
// PartialFailureEnvelopeSchema (shape)
{
  results: unknown[],
  errors: PartialFailureError[],   // { domain, stage?, message }
  partial: boolean,
}

The module exports PartialFailureEnvelopeSchema, the per-error PartialFailureErrorSchema (domain and message required, stage optional), a generic factory createPartialFailureEnvelopeSchema(resultSchema, errorSchema?) for typing the results/errors arrays per call site, a constructor buildPartialFailureEnvelope(...), and the type guard isPartialFailureEnvelope. The contract enforces two cross-field invariants via superRefine, so an envelope cannot lie about its own state:

  • partial === true requires at least one error entry, and
  • partial === false requires the errors array to be empty.

buildPartialFailureEnvelope goes further and infers partial from whether any errors are present, throwing if a caller passes a partial flag that disagrees with the errors it supplied. The result is a uniform "here's what worked, here's what didn't, and yes this was a partial outcome" envelope that every fan-out caller can produce and every consumer can validate identically.

Contract-to-persistence alignment#

Canonical Zod schemas are kept honest against the Prisma data models by a real alignment harness in libs/oshun/persistence/src. The centerpiece is the introspection + rendering pair — zod-prisma-introspection.ts reads the Zod contracts, and prisma-renderer.ts projects them — backed by the drift/index/migration/tombstone test quartet the source promises:

Test What it guards
contract-persistence-registry.test.ts Every persisted contract is registered and mapped
index-requirements.test.ts Required query indexes exist for declared access patterns
migration-plan.test.ts Schema changes produce a coherent, ordered migration plan
tombstone-semantics.test.ts Soft-delete / tombstone behavior is correct and consistent

Privacy deletion is implemented in the same library: dsar-deletion-cascade.ts and dsar-erasure-runtime.ts (each with unit and integration tests) carry the DSAR (data-subject-access-request) cascade through the persistence layer, so an erasure request actually propagates rather than merely flagging a row. Durable stores for memory, snapshots, and the admin audit-events stream (durable-memory-store.ts, durable-snapshot-store.ts, durable-admin-audit-events-store.ts) provide the replayable critical histories the source calls for — review state, approval state, memory, generation/research jobs, persona release state, provenance bundles, audit events, and review/incident timelines.

Inter-service contracts: OpenAPI and proto#

External and internal service contracts are published, not implicit. libs/openapi/src/specs/ holds 15 real spec directories plus the aggregate main.yaml and v3.yaml:

text
arete  bellona  calliope  concordia  hathor  isis  lilith
metis  nisaba   nyx       oshun-bff  sophia  tara  veritas  yemaya

For typed cross-service RPC, gRPC/proto is real and broad. libs/proto carries a buf.work.yaml, a generated/ tree, and roughly two dozen proto domain directories under src/agent, ai, asset, auth, bridge, collaboration, common, concordia, generation3d, health, hathor, isis, loadbalancing, oshun, oya, pipeline, procedural, project, reflection, rendering, shared, sophia, splatting, and user. (The older doc that named only Psyche/Isis/Sophia understated the proto footprint substantially.)

The public-API platform: OAuth 2.1 / PKCE#

libs/oshun/platform-foundations/src/public-api/oauth.ts is the §27 public-API authorization layer. To be precise about scope: this is a contracts and state-machine implementation — token shapes, PKCE validation, refresh rotation, revocation cascade, and quota evaluation — that the BFF and domain services compose. It is not, by itself, a deployed live authorization server; treat the deployment of an authorization-server endpoint as the integration step around this verified core.

The module supports the grant types authorization-code, refresh-token, and client-credentials, with two token types: TOKEN_TYPES = ['access', 'refresh']. An OauthClient declares its clientKind (first-party | third-party), redirectUris, allowedScopes, whether it requiresPkce, separate access and refresh token lifetimes, a perKeyRateLimitPerMinute, and an optional tenantId for tenant-scoped clients.

PKCE is enforced structurally. The code challenge must match PKCE_CODE_CHALLENGE_PATTERN = /^[A-Za-z0-9_-]{43,128}$/ and the verifier its own pattern, and only the S256 challenge method is accepted — verifyPkceChallenge recomputes the SHA-256 of the verifier (via @noble/hashes) and compares. validateAuthorizationCodeRequest returns a typed list of AuthorizationCodeErrors rather than throwing, covering the real failure modes:

Error code Meaning
client-mismatch Request clientId ≠ registered client
invalid-redirect-uri Redirect URI not in the client's allow-list
scope-not-allowed Requested scope outside allowedScopes
pkce-required Client requires PKCE but none supplied
invalid-code-challenge Challenge fails the pattern
unsupported-challenge-method Method ≠ S256
code-expired / code-consumed Authorization code reused or stale
pkce-verification-failed Verifier doesn't hash to the challenge

Refresh and revocation are modeled honestly as a state machine. redeemRefreshToken(...) rotates tokens: it rejects a non-refresh token (wrong-token-type), an already-revoked token (revoked-refresh), or an expired one (expired-refresh); on success it returns a fresh access token, a fresh refresh token, and the now-revoked prior refresh — and it stamps the new access token's mintingRefreshTokenId so lineage is traceable. That lineage is what revocationCascade(...) walks: revoking a refresh token id revokes that token and every access token minted from it. Quotas are evaluated by evaluatePublicApiQuota(...) against per-key windows, and privileged API activity is captured via buildPublicApiAuditEvent(...). Tokens carry scopes, tenantId, and subjectUserId, so authorization stays tenant-aware across the BFF and domain services.

The auth-primitives library (libs/shared/auth-primitives/src) is richer than "JWT and session primitives": it ships api-key.ts, jwt.ts, session.ts, oauth-client.ts, oauth-revoke.ts, token-refresh.ts, token-audit.ts, totp.ts (TOTP for step-up), platform-roles.ts, tenant-isolation.ts, and password.ts, each with colocated specs.

The canonical role and scope model#

Access control is anchored by CANONICAL_ROLES in libs/oshun/platform-foundations/src/role-model/role-model.tsten roles, not the looser ~eight the prose previously implied. The exact set is:

text
customer, creator, support-agent, reviewer, moderator,
privacy-operator, model-operator, persona-operator,
tenant-admin, admin-leadership

Two of these deserve surfacing because the older prose hid them: creator is a distinct canonical role (it adds the creator.studio scope on top of customer.self/customer.shell), and tenant-admin is distinct from admin-leadership (a tenant-admin gets tenant.console and tenant-scoped audit, while only admin-leadership gets the cross-tenant and break-glass scopes). The prose name support should read support-agent to match the code.

Scopes are explicit (SCOPE_KEYS) and mapped per role; scopesFor(role) returns the scope set, and filterFieldsByRole(...) shapes responses by role. The scope-to-role mapping is least-privilege by construction:

Role Granted scopes
customer customer.self, customer.shell
creator customer.self, customer.shell, creator.studio
support-agent support.case.read, support.case.write
reviewer review.queue.read, review.queue.decide
moderator review.queue.read, review.queue.decide, moderation.decide
privacy-operator privacy.dsar.execute, privacy.audit.read, admin.audit.global
model-operator model.registry.read, model.registry.promote
persona-operator persona.registry.read, persona.registry.publish
tenant-admin tenant.console, admin.audit.global
admin-leadership admin.users.act, admin.scope.grant, admin.breakglass.act, admin.audit.global

Function-level authorization runs through authorizeAction(...), which takes the subject's roles, the requiredScope, and the subject/target tenant ids and returns a typed AuthorizationDecision whose denial reason is one of role-not-permitted, tenant-isolation, or object-not-owned. Crucially, cross-tenant authorization (a non-null target tenant that differs from the subject's) requires admin-leadership — so a tenant-admin cannot reach across into another institution's data. Step-up authentication (platform-foundations/src/step-up/step-up.ts, backed by totp.ts) gates the sensitive actions a domain declares in its stepUpActions.

The event bus — and a correction#

Cross-domain integration runs over the event bus at libs/shared/event-bus/src/event-bus.ts: export class EventBus implements IEventBus, instantiated by the factory createEventBus(config: EventBusConfig). It uses import { Redis } from 'ioredis' with separate publisher and subscriber connections, and it ships the topic registry, outbound delivery, and webhook-simulator pieces the source promises (the default registry is DEFAULT_EVENT_TOPIC_REGISTRY in topic-registry.ts, with schema-versioned topics and payload validation via EventPayloadValidationIssue).

Accuracy correction. V1/DEPENDENCIES.md historically labeled this an "Event Bus (Redis Streams)" — the one outright-wrong technical claim in this area, since corrected in the registry. The implementation does not use native Redis Streams — there is no XADD/XREAD/XREADGROUP/XGROUP anywhere. Per the module's own header, it is built from ordinary Redis primitives:

  • publish() serializes the envelope, stores it in a TTL-bounded key (default DEFAULT_EVENT_TTL = 86400 seconds — the replay source), and fans it out to subscribers over Redis pub/sub. A separate HASH tracks which subscriptions/consumer-groups have already processed an event so crash-restarts don't redeliver acked work.
  • ack() marks (eventId, subscriptionId) or (eventId, group) processed; unacked work is redelivered on the next replayUnacked sweep.
  • nack(delay) and delayed publish({ delay }) reschedule through a durable sorted set named scheduled; a scheduler loop (DEFAULT_SCHEDULER_INTERVAL = 250 ms) pulls due entries — setTimeout is never the only copy, so it's crash-safe.
  • Consumer groups are emulated: when a subscription declares a group, members race via SET NX on a per-(eventId, group) claim key, and only the winner runs the handler; without a group, every matching subscription runs (classic broadcast).
  • Dead-letter entries live in a list (for ordered pagination) and a hash (for O(1) id lookup); removeDeadLetter uses LREM on the serialized entry for atomic removal.

So the BFF→…→event-bus→audit composition the architecture diagrams describe is real and matches the building blocks present (idempotency → tenant → residency → Zod → adapter → event-bus → audit). Only the "Redis Streams" substrate label was wrong; the accurate description is "Redis pub/sub with TTL-keyed replay, a scheduled sorted set, and SET NX consumer-group claims." (We have not traced a single production BFF route proving every middleware fires in exactly that order in deployed wiring — the components exist; the end-to-end order is a composition claim.) Outbound webhook delivery rides on top with signing, retry/backoff, dead-letter routing, replay, and delivery audit.

The durable queue substrate#

Cross-domain background jobs (editorial, asset, agentic AI, notification, billing, integration) run on the queue substrate at libs/shared/queue/src, which matches the source's "durable queues, replay, deduplication, priority classes, observability" claim with real modules:

Module Role
durable-queue.ts Persistent, replayable job queue with priority classes
dead-letter-queue.ts DLQ for jobs that exhaust retries
sla-monitor.ts SLA tracking / observability over queued work
memory-queue.ts In-memory queue (tests / single-process)
worker.ts Worker loop that drains and acks jobs

The durable queue, DLQ, and SLA monitor each ship colocated specs.

Inbound integration connectors#

Inbound integration plumbing lives in libs/shared/inbound-integrations/src, whose index.ts exports 15 connector modules — substantially more than the nine-row table older docs showed. These are sizable, real implementations (for example oneroster.ts is ~1081 LOC, lms.ts ~1425 LOC, and identity.ts ~1048 LOC):

Connector module Covers
types Shared connector descriptors and health types
lms LMS integration (LTI 1.3 / LTI Advantage surface)
lti-verification LTI launch/JWT verification
scorm-rte SCORM run-time environment
scorm-2004-rte SCORM 2004 run-time environment (distinct from scorm-rte)
oneroster OneRoster rostering
identity SAML 2.0 / OIDC / SCIM 2.0 identity
calendar Calendar integration (Google / Apple / Outlook)
calendar-google-transport Google-specific calendar transport
payment Payment connector
telemetry Telemetry sinks (xAPI / cmi5 / Caliper)
byom Bring-your-own-model ingestion
byom-model BYOM model descriptor (distinct from byom)
notification Partner notification sinks
health Connector health reporting

Note the modules older tables omitted: the two SCORM RTEs (scorm-rte and scorm-2004-rte), lti-verification, the separate byom-model, and calendar-google-transport. Each connector carries version pinning and health reporting, and the framework supports declarative descriptors, sandbox tenants, certification flows, OpenAPI specs, code samples, and operator-managed enable/disable per tenant. Metis is the primary consumer of LMS/OneRoster/identity (see Metis).

HTTP-client middleware: idempotency, SSRF, breakers#

The outbound HTTP client (libs/shared/http-client/src) carries the request hygiene the architecture leans on:

  • Idempotencyidempotency.ts exports DEFAULT_IDEMPOTENCY_HEADER = 'Idempotency-Key', DEFAULT_IDEMPOTENCY_TTL_MS = 24 * 60 * 60 * 1000 (24h), DEFAULT_IDEMPOTENCY_MAX_ENTRIES = 1000, an IdempotencyMiddleware class, and createIdempotencyMiddleware(...). It defaults to guarding mutating methods (POST/PUT/PATCH/DELETE), fingerprints the request, and throws typed errors — IdempotencyKeyRequiredError, IdempotencyRequestMismatchError, IdempotencyWaitAbortedError, IdempotencyFingerprintError — so a replayed key with a different body is rejected rather than silently re-run.
  • SSRF guardssrf-guard.ts blocks unsafe-upstream consumption for URL ingestion, fetch tools, webhooks, BYOM ingestion, and retrieval connectors.
  • Resiliencecircuit-breaker.ts, retry.ts, timeout.ts, plus tenant-context.ts (tenant-aware request scoping) and tracing.ts.

Data residency#

Residency enforcement is real, in libs/shared/data-residency/src: enforcer.ts decides whether an operation may touch data in a given zone, home-zone.ts models a tenant's home region, traffic-shaping.ts implements residency-aware traffic shaping, dsr-routing.ts routes data-subject requests, and an injected ResidencyAuditPublisher seam (exercised by audit-platform-bridge.spec.ts) ties residency decisions into the audit chain. Together these back the "multi-tenant routing, residency-aware traffic shaping, tenant-aware caching/flags/experiments" guarantees the source lists.

The audit platform — an append-only hash chain#

Audit is not a single sentence; it is a large substrate (libs/shared/audit-platform/src, ~40 implementation modules). The integrity backbone is hash-chain.ts, which implements an append-only, tamper-evident hash chain: hashEvent(...) hashes a CanonicalPlatformAuditEvent, chainHashStep(previousChainHash, eventHash) links each entry to its predecessor, and HashChainedAuditEventStore / createHashChainedAuditEventStore(...) persist the chain. Verification (AuditChainVerificationResult, AuditChainMismatch) detects breaks and raises AuditChainTamperDetectedError, and canonicalSerialize(...) guarantees stable hashing across runs. Around that core sit compliance and provenance modules, including schema-versioning, retention, escalation-rules, support-escalation, compliance-attestation, watermark-verification, synthetic-media-labeling, voice-likeness-consent, provenance-attachment, and provenance-badges. This is what gives the privileged-action workflows (support, review, moderation, privacy, model, persona, copilot override, incident) their immutable, per-flow audit assertions.

Security as a launch gate#

V1 is a multi-tenant, multi-role product handling consented memory, synthetic media, educational records, and institutional rosters, so security is treated as a launch gate, not post-launch hardening. The role/scope model, step-up auth, audit hash chain, idempotency, SSRF guard, residency enforcement, and abuse controls above are the implemented core. The broader program the source enumerates — and the honest status of each control — is:

Control area Status
Canonical role model, least-privilege scopes, step-up, tenant/env separation Implemented (role-model, step-up, tenant-isolation)
Immutable audit + per-flow audit assertions Implemented (audit-platform hash chain)
Standardized secrets handling, rotation, scoping Implemented (platform-foundations/secrets)
Validated configs, policy bundles, experiment guardrails, rollback plans Implemented (configs, rollback)
Object-property auth, excessive-data-exposure, mass-assignment defense Implemented via canonical Zod shapes + filterFieldsByRole
Resource-consumption / abuse controls (rate, payload, pagination, query-cost) Implemented (abuse-controls)
SSRF / egress-policy / unsafe-upstream enforcement Implemented (ssrf-guard)
Permission/authorization regression suites, tenant-isolation/leakage suites Implemented (function-level authorizeAction + isolation specs)
Authenticated DAST against staging web/admin/APIs Process/CI gate — exercised against deployed staging
SAST + CodeQL, dependency-vuln + secret scanning CI/process gate
SBOM + provenance verification + release signing Process gate, "where supported"
Fuzz / malicious-input suites (uploads, markdown, search, tool/webhook surfaces) Process gate
API inventory / shadow-endpoint discovery per release Process gate
Pre-GA pen-test + red-team (auth, RBAC, privacy, Metis, grounded gen, escalation) External engagement, scoped + signed off

The bottom rows are deliberately marked as process/CI/external gates rather than "shipped code": DAST, pen-testing, SBOM signing, and red-team exercises are launch activities run against deployed environments, and the docs keep that candor rather than claiming a green checkmark for work that happens at release time.

Why it works this way#

The recurring pattern across these foundations is one canonical definition, many consumers: a single domain registry, a single contracts spine, a single role/scope model, a single audit chain, a single idempotency policy. Drift is the enemy of a multi-tenant, multi-domain platform — eight subsystems each holding their own copy of "what a domain is" or "what a customer may do" is how cross-account leakage and authorization gaps creep in. By making the source of truth executable (and testing the alignment with the persistence quartet and the authorization suites), the platform turns "did we keep these in sync?" from a review question into a CI failure.