Platform Quickstart

The private @phosra/gatekeeper 0.8.12 candidate can be evaluated against synthetic streaming, gaming, and social fixtures. It explores applying controls to test profiles, reading effects back, and returning sample signed evidence.

The Golden integration has one Phosra input—PHOSRA_CREDENTIAL—plus your real account and enforcement adapters. Gatekeeper owns protocol routes, trust, directory discovery, signing, storage, migrations, retries, and workers.

From 0.8.7, the SDK validates browser consent POSTs against the public application origin already bound into the verified platform credential. Hosted apps behind Railway, Cloudflare, or another reverse proxy do not need to trust or rewrite forwarded headers, and an internal proxy URL cannot weaken the same-origin boundary.

Private evaluation candidate. Public npm currently serves 0.7.0; the 0.8.12 code below is not evidence of real-platform adoption or a generally available production integration. Hosted routing, production credentials, and a production receipt rail remain roadmap.

bash
npm install @phosra/[email protected]

1. Create one platform singleton

ts
// src/lib/phosra.ts
import { createPlatform } from "@phosra/gatekeeper"
import { pool } from "@/db"
 
export const phosra = createPlatform({
  credential: process.env.PHOSRA_CREDENTIAL!,
  humanRecovery: {
    signInPath: "/sign-in",
    createProfilePath: "/profiles/new",
  },
  adapter: {
    database: pool,
    authorizedProviders: ["did:ocss:custo"],
 
    resolveAccount: async (request) => {
      const session = await sessionFrom(request)
      if (!session) return null
      return {
        accountId: session.accountId,
        profiles: await childProfilesFor(session.accountId),
        // Each profile: { id, displayName, displayHint? }
      }
    },
 
    apply: async ({ selectedProfileId, profile, idempotencyKey }) => {
      await applyParentalControls({ selectedProfileId, profile, idempotencyKey })
    },
 
    observe: async ({ selectedProfileId, profile }, evidence) => {
      const actual = await readPersistedControls(selectedProfileId)
      return profile.rules.map((rule) => {
        const control = actual.get(rule.category)
        if (!control) return evidence.refused(rule, "unsupported")
        if (!control.enabled) return evidence.degraded(rule, "platform-conflict")
        return evidence.applied(rule, {
          method: "gate",
          sideEffectId: control.id,
        })
      })
    },
 
    release: async ({ selectedProfileId, idempotencyKey }) => {
      await removePhosraControls({ selectedProfileId, idempotencyKey })
    },
 
    observeRelease: async ({ selectedProfileId }, evidence) => {
      const removal = await readRemoval(selectedProfileId)
      return evidence.released({ sideEffectId: removal.id })
    },
  },
})

apply and release are commands. Their return values are never evidence. observe and observeRelease must independently read your real persisted state; the SDK reporter converts those facts into the exact non-leaking evidence shape.

2. Mount one catch-all

ts
// app/api/phosra/[...phosra]/route.ts
import { phosra } from "@/lib/phosra"
 
export const { GET, POST } = phosra.next.handlers()

The catch-all owns PAR, authorize, token, delivery, and retry routes. Do not mount a hand-written OAuth surface beside it. Construction performs no network or database work, so the route remains safe during a Next.js build.

Verify the published delivery endpoint

Mounting the catch-all does not update the Phosra directory. Before opening the parent flow, verify that your platform registration publishes the facade's exact public delivery receiver:

ts
const expected = `${applicationOrigin}/api/phosra/delivery`
const discovery = await fetch(
  `${census}/api/v1/providers/${encodeURIComponent(platformDid)}/connect`,
).then((response) => {
  if (!response.ok) throw new Error(`Platform discovery failed (${response.status})`)
  return response.json()
})
 
if (discovery.connect_url !== expected) {
  throw new Error(`Expected ${expected}, received ${discovery.connect_url ?? "no connect_url"}`)
}

Register the complete /api/phosra/delivery URL, not the application's bare origin. OAuth can still succeed with a bare-origin entry, but durable delivery and connection saving will fail. Retry the parent flow only after discovery returns the exact receiver.

3. Run the worker separately

ts
// phosra-worker.ts
import { phosra } from "@/lib/phosra"
 
await phosra.ready()
const shutdown = new AbortController()
process.once("SIGTERM", () => shutdown.abort())
process.once("SIGINT", () => shutdown.abort())
 
const worker = phosra.worker.start({
  signal: shutdown.signal,
  onError: (error) => console.error("Phosra worker pass failed", error),
})
await worker.drain()

The worker serializes passes, waits while idle, backs off after failures, and does not overlap operations. If you already have a durable scheduler, call await phosra.runWorkerOnce() instead; do not run both modes together.

Mounting the catch-all route without running either worker mode is incomplete. The platform can return a valid signed 202 acknowledgement while the parent app remains at Connection needs finishing, because materialization, independent read-back, and the signed applied-event dispatch happen in worker passes. On Railway, deploy a worker process from the same release with the same PHOSRA_CREDENTIAL and database, or invoke runWorkerOnce() from an existing durable scheduler. Verify both the receiver acknowledgement and the final applied event before qualifying the integration.

Shared profiles: one platform target, many children

A platform profile can be shared by multiple children. For example, Ramsay and Coldy can each have a separate Custo policy while both are linked to one Notflix Kids profile. Gatekeeper treats the shared profile as one target with multiple independently authorized members and computes one effective aggregate profile.

The lifecycle is generation-based:

GenerationExamplePlatform action
G1Ramsay links the Kids profileApply and independently observe the effective profile.
G2Coldy links the same profileApply and observe the newly merged effective profile once.
G3Ramsay disconnects; Coldy remainsPartial removal: apply and observe the remaining member's profile. Do not restore native settings.
G4Coldy disconnects; no members remainFinal release: remove Phosra-managed effects, observe release, and restore native settings.

For aggregate operations, the SDK supplies an opaque targetOperation on the adapter input. Preserve the complete input and its idempotencyKey when calling your command/read-back code. Do not construct, clone, persist, or infer authority from targetOperation; Gatekeeper validates it and owns replay/fencing.

Treat every aggregate apply as a complete desired-state reconciliation, never as an additive patch. Atomically install the supplied effective profile and retire any obsolete Phosra-managed effects from the prior generation. This is especially important at G3: the removed child's effects must disappear while the remaining child's effective controls stay active. Preserve the native baseline captured before G1 throughout partial removals; restore it only during the final G4 release after no authorized members remain.

This distinction is critical: removing one child from a shared Notflix profile must not disable controls still required by another child.

Gatekeeper owns the sharing decision

When the selected platform profile already has members from the same verified family, the SDK-owned authorize handler recommends choosing or creating a separate profile. Separate profiles preserve individual viewing history, recommendations, screen time, and controls. The actions appear in the fixed order Choose another profile, Confirm sharing, and Cancel and return.

Before confirmation, Gatekeeper explains that the shared profile receives the strictest protections required by any linked child and that activity on the profile cannot be reliably attributed to one child. A changing target offers a retry or a return to profile choice. Missing or different family authority never offers sharing. Rules outside the built-in merge registry fail closed during server-side aggregate processing; 0.8.7 does not expose a dedicated public incompatibility page or capability manifest.

Do not create a parallel modal, confirmation endpoint, or custom action order. Before token exchange, the handler renders the authenticated platform catalog label recovered from its server-side sealed catalog and the same-family member count. A signed selected-profile presentation follows during token exchange; it is not the source of the confirmation label. Raw account/profile IDs, family or child authority, aggregate identifiers, and policy values stay server-side; forms carry only bounded opaque tokens. Existing member names and numeric ages are not part of this release. They require a future provider-signed member-display resolver and must not be inferred locally.

The 0.8.7 handler uses these fixed states:

StateParent copyActions, in order
ConfirmationUse {profile} for another child? and Recommended: choose a separate profileChoose another profile, Confirm sharing, Cancel and return to {provider}
Target changingThis profile is finishing another link and Wait a moment and try again, or choose another profile now.Choose another profile, Try again, Cancel and return
Family sharing unavailableChoose a separate profile and This profile can’t be shared for this family link. Choose another profile, or create a separate one in this platform.Choose another profile, Cancel and return

Read privacy-safe topology

Server-side operations and support tooling can inspect shared-target progress:

ts
const topology = await phosra.readTopology()

The feed contains opaque target/member aliases, lifecycle state, generation counters, member counts, freshness, failures, and timestamps. It intentionally does not expose platform account IDs, profile IDs, child names, family references, raw member IDs, or internal key material. Treat aliases as opaque diagnostics, not as product identifiers.

Typical states are queued, working, retry_wait, active, and released. stale: true means desired state is ahead of current observed state; it is not a success signal.

Parent recovery

If humanRecovery is configured, only the authorize GET route may redirect a signed-out parent to signInPath or a parent with no profiles to createProfilePath. Preserve and revalidate the SDK-provided phosra_return_to; never redirect an arbitrary query value.

Design-partner pilot checklist

  • Keep the credential and database server-only.
  • Resolve only profiles owned by the authenticated account.
  • Apply idempotently and read effects back independently.
  • Keep the complete catch-all route mounted.
  • Run exactly one worker mode.
  • Preserve partial-removal versus final-release behavior for shared profiles.
  • Keep shared-profile confirmation inside the SDK-owned authorize handler.
  • Use readTopology() only as the privacy-projected operational view.
  • Show ongoing provenance honestly; see Branding.

The explicit low-level kernel remains available from @phosra/gatekeeper/platform for advanced conformance infrastructure. New applications should use createPlatform(...) from the package root.