npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@phosra/link

v0.7.80

Published

Phosra Link SDK for connecting a parent application to an OCSS (Open Child Safety Specification) platform. It provides the mandatory-branded parent experience, the same-origin provider route, durable delivery, and the lower-level writer-plane APIs.

Downloads

2,757

Readme

@phosra/link

Phosra Link SDK for connecting a parent application to an OCSS (Open Child Safety Specification) platform. It provides the mandatory-branded parent experience, the same-origin provider route, durable delivery, and the lower-level writer-plane APIs.

Install

npm install @phosra/link

pg (node-postgres) is a runtime dependency — Link's durable authority and delivery stores are Postgres-backed. React is an optional peer dependency used by @phosra/link/react.

Database migration (run before the web and worker processes)

Apply the complete provider schema once per deployment, before either runtime process starts. The migration needs only database authority: DATABASE_URL is its only input. It does not need PHOSRA_CREDENTIAL, census or Trust List settings, writer keys, or delivery secrets.

// scripts/migrate-phosra-link.ts
import { Pool } from "pg"
import { migrateLinkServerSchema } from "@phosra/link/server"

const databaseUrl = process.env.DATABASE_URL
if (!databaseUrl) throw new Error("DATABASE_URL is required")

const pool = new Pool({ connectionString: databaseUrl })
try {
  await migrateLinkServerSchema(pool)
} finally {
  await pool.end()
}

Run this entry point as the single pre-deploy migration owner. Start the web process only after it succeeds, then start one worker against the same database. Do not run migrations concurrently from the web and worker processes.

Provider application quickstart (recommended for new v3 integrations)

A v3 PHOSRA_CREDENTIAL contains the pinned environment authority. The SDK derives the correct environment, trust roots, platform endpoints, public rule copy, granted scope, durable stores, and same-origin protocol from that credential. Do not copy those values into browser code or accept them from a form.

// lib/phosra-link-server.ts — server-only composition shared by two processes.
import { Pool } from "pg"
import { createGoldenLinkServer } from "@phosra/link/server"

export const phosraLinkServer = await createGoldenLinkServer({
  credential: process.env.PHOSRA_CREDENTIAL!,
  database: new Pool({ connectionString: process.env.DATABASE_URL }),

  // The only app-owned business seams:
  authenticate: appAuth.authenticatedParent,
  children: { resolve: appChildren.resolveForParent },
  policy: { resolve: appPolicy.rulesForChildAndPlatform },
})
// app/api/phosra/link/[...phosra]/route.ts — web process only.
import { phosraLinkServer as server } from "@/lib/phosra-link-server"

// This catch-all file routes the credential's callback and every sibling route:
//   /api/phosra/link/callback
//   /api/phosra/link/sessions
//   /api/phosra/link/sessions/:ceremonyId
//   /api/phosra/link/sessions/:ceremonyId/retry
//   /api/phosra/link/sessions/:linkId/events
export const GET = server.handler
export const POST = server.handler
export const DELETE = server.handler

Keep this code in the catch-all route shown above, not only in app/api/phosra/link/callback/route.ts. The credential-configured v3 callback, session collection, ceremony status/retry, and Link event receiver must all resolve through the same handler or the sibling paths will return 404.

The POST /api/phosra/link/sessions/:linkId/events route is the platform-to-provider machine-event receiver. Here :linkId is the signed UUIDv8 Link ID from the platform event authority, not the parent's UUIDv4 ceremony ID used by GET .../sessions/:ceremonyId. Keep it mounted: after the delivery ACK, platforms use this same handler to submit signed profile materialization, rule confirmation, observation, and release events. Omitting it can leave a successfully linked session at rule_recorded because later evidence has nowhere to arrive. Platform SDKs call it with a UUIDv4 Idempotency-Key and the exact Link event or release-event content and accept media types; app developers mount the handler and must not parse or rewrite those machine messages.

The factory returns only after the package-owned schema is ready and the pinned environment has passed its checks. The runtime readiness guard is useful for local development, but it does not replace the single-owner pre-deploy migration above. Run one SDK-owned worker loop in the provider worker process; abort it during graceful shutdown. The loop never overlaps delivery attempts.

// scripts/phosra-link-worker.ts — separate long-running process/service.
import { phosraLinkServer as server } from "../lib/phosra-link-server"

const shutdown = new AbortController()
process.once("SIGTERM", () => shutdown.abort())
process.once("SIGINT", () => shutdown.abort())
await server.runWorker({ signal: shutdown.signal })

Run that entry point as its own worker service against the same database and credential as the web service. Never await runWorker() from the web server, the catch-all route module, a request handler, or a build step. A web-only deployment is incomplete even when session creation and the parent UI appear healthy.

Projecting an active connection into your application

A completed browser callback, a saved grant, a successful delivery attempt, or a verified receiver ACK is not enough to mark a host-owned connection active. Those facts prove progress through Link, not that the receiver has signed an applied report for every current required rule.

Reconcile host state from the exact grant authority. Bind all three identifiers to the row you intend to update, and project active only when platformReport is exactly complete:

async function reconcileConnection(connection: {
  grantId: string
  platformDid: string
  targetRef: string
}) {
  const projection = await server.getSnapshotForGrant({
    grantId: connection.grantId,
    platformDid: connection.platformDid,
    targetRef: connection.targetRef,
  }).catch((error) => {
    log.warn("Phosra Link projection unavailable", { error })
    return null
  })

  if (projection?.snapshot.stage === "revoked") {
    if (projection.releaseStatus === "complete") {
      await removeExactActiveConnection(connection)
    }
    // An incomplete release is not authority to erase the last known state.
    return
  }

  if (projection?.platformReport !== "complete") {
    // null, incomplete, failed, or a lookup error remains pending/attention.
    // Do not create, repair, or upgrade an active connection here.
    return
  }

  await upsertExactActiveConnection({
    ...connection,
    // An absent presentation is not authority to erase a prior verified label.
    ...(projection.selectedProfile === null
      ? {}
      : { selectedProfile: projection.selectedProfile }),
  })
}

Handle lookup errors like an unavailable projection: retain the last known host state and retry later. Do not convert uncertainty into either a new active row or a disconnect. Removal requires both the exact signed revoked snapshot and releaseStatus === "complete" for that same grant, platform, and target.

platformReport: "complete" means the receiver supplied valid signed applied reports for every required rule in the current profile generation. It is the minimum authority for the host's active-connection projection; it is not an independent observation and does not by itself earn the stronger E4 applied stage. Use the full Link snapshot for parent-facing enforcement detail instead of reducing every state to a boolean.

In the parent UI, render the mandatory-branded component from the React subpath only after the parent chooses a child and platform and opens the Link flow. Its session response contains SDK-generated display copy and platform identity; the component owns handoff, restart-safe recovery, polling, cancel, and evidence-based status. When the current verified stage is degraded, it offers Try again for the same connection. The SDK schedules an exact durable platform retry, waits until the server-authoritative next_check_at, and keeps the degraded result visible unless new signed evidence earns a better state. The stale stage remains distinct and recoverable across reloads, but does not offer retry until a verified freshness authority is available.

Link 0.7.40 preserves the provider's last signed revoked observation across a generation change. The platform may accept it only when its independent current authority explicitly allows revoked recovery and every delivery and authority binding is exact. This lets a parent finish an interrupted disconnect without weakening the rule that older non-revoked retry requests are rejected. Link 0.7.41 lets an explicit parent retry mint one new signed epoch after a prior authorization failure, so a repaired platform can be reached without rewriting or deleting the immutable failed request. Link 0.7.42 also permits one new signed epoch after an accepted requeue when the platform still reports degraded or stale. Completed, active, and recovered results remain idempotent.

import { PhosraLink } from "@phosra/link/react"

{linkOpen ? <PhosraLink
  createSession={async ({ signal }) => {
    const response = await fetch("/api/phosra/link/sessions", {
      method: "POST",
      signal,
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        platform_did: selectedPlatformDid,
        child_ref: selectedChildId,
        return_to: window.location.pathname,
      }),
    })
    if (!response.ok) throw new Error("Unable to start Phosra Link")
    return response.json()
  }}
  onEvent={recordLinkEvent}
  onExit={closeLink}
/> : null}

Always forward the supplied signal to the request that creates the session. Phosra Link aborts it when the 15-second creation fence expires, the component unmounts or closes, or the parent starts over. Each retry receives a new signal; an adapter must never reuse a prior controller or continue an abandoned request. This keeps browser cancellation aligned with the SDK's ceremony authority and prevents a late response from adopting or cancelling the wrong connection.

Shared profile confirmation needs no host UI

If a parent chooses a platform profile already linked inside the same verified family, the platform's Golden Gatekeeper authorize handler presents the mandatory Phosra Link confirmation. It recommends a separate profile, explains that sharing applies the strictest combined protections and limits per-child activity attribution, and offers Choose another profile, Confirm sharing, and Cancel and return in that order.

Provider applications keep the same PhosraLink component, createSession, onEvent, and onExit integration. Do not recreate the confirmation in the host application or pass family, child, profile, target, or policy authority through browser props. Gatekeeper 0.8.1 displays the authenticated platform catalog label recovered from its server-side sealed catalog and the same-family member count. The signed selected-profile presentation follows token exchange; it is not the source of the confirmation label. Member names and numeric ages are intentionally deferred until a future provider-signed member-display resolver exists.

No recoveryKey is required. After authenticating the parent and resolving the authoritative child and platform, the server SDK returns a versioned opaque recovery scope bound to that provider, parent, child, platform, application origin, and environment. The React SDK hashes that scope again for its sessionStorage key and stores only the bounded ceremony locator; it never stores a raw identifier, authorization URL, credential, policy, code, state, or platform secret.

Reopening the same Link flow creates one unhanded probe session to learn the current verified scope. If an unexpired ceremony already owns that exact scope, Link retires the unused pending intent after re-verifying the authenticated parent-session and child commitments, then resumes the prior ceremony. A different parent, child, platform, origin, or environment cannot inherit it. A scope is intentionally one recovery slot: the latest successful handoff write wins, and an older simultaneous instance cannot clear the newer claim. Legacy/custom session endpoints may supply the advanced recoveryKey override; an invalid override or unavailable browser storage fails closed and Link does not offer a same-tab handoff it cannot recover.

See examples/server-junior.ts for the packed, type-checked server composition core. V1/v2 credentials are compatibility-only and use the advanced compatibility APIs; the five-input golden server is v3-only and rejects host environment, transport, lifecycle, receiver, network, and clock overrides.

vNext family setup

The additive family-plan contract freezes one parent-reviewed platform-account plan above the existing scalar child/profile grants. It does not widen or replace the v1 session wire shape. The package now includes PostgreSQL session, accepted-plan, operation, destination-graph, and signed-event persistence; task-oriented vNext routes; a restart-safe worker; a Notflix qualification adapter; and an accessible, responsive family mapping and review surface.

import {
  acceptFamilyPlanV1,
  encodeAcceptedFamilyPlanV1,
  makeInMemoryFamilyPlanStoreV1,
  materializeAcceptedFamilyPlanOperationsV1,
  parseAcceptedFamilyPlanV1,
  type FamilyPlanDraftV1,
} from "@phosra/link"

const accepted = acceptFamilyPlanV1(draft satisfies FamilyPlanDraftV1, {
  expected_revision: draft.revision,
  display_plan_digest: reviewedDisplayDigest,
  accepted_by: authenticatedParentRef,
  accepted_at: "2026-08-04T16:30:00Z",
})

const acceptedBytes = encodeAcceptedFamilyPlanV1(accepted)
const restored = parseAcceptedFamilyPlanV1(acceptedBytes, accepted)

// Reference/conformance implementation only; it is not process-durable.
const plans = makeInMemoryFamilyPlanStoreV1()
const stored = await plans.putAccepted(restored)
const reopened = await plans.getAccepted(accepted.plan_id, accepted.accepted_revision)
const queuedOperations = materializeAcceptedFamilyPlanOperationsV1(reopened!.plan)

Acceptance fails closed when the reviewed revision is stale, a choice remains unresolved, a material exception is unacknowledged, an operation crosses the provider-authorized child set, or the operation graph is duplicate, incomplete, cyclic, or ambiguously bound. The returned plan and every dependency list are detached and frozen so later draft mutation cannot change the accepted operation set. FamilyPlanAcceptanceError extends the package's stable LinkError surface. Every operation must also carry an explicit execution_mode (automatic or guided) and retry_safety (idempotent, reconcile_before_retry, or manual_only); Link never supplies a retry default. connect_account and guided_step operations are necessarily guided plus manual_only. Profile, mapping, and policy operations are automatic, but their retry safety remains an adapter-capability decision bound to the reviewed capability_revision.

classifyFamilyPlanOperationSafetyV1 turns that exact versioned adapter declaration into the fields the draft builder must present for review. A declared same-key replay guarantee produces idempotent. Without replay safety, authoritative post-attempt observation produces reconcile_before_retry; without either guarantee, an automatic operation becomes manual_only. Connection and guided-step declarations remain guided plus manual_only, even when their completion can be authoritatively observed. The classifier rejects stale capability revisions, missing or unknown fields, widened objects, automatic connection/guided operations, guided platform mutations, and replay guarantees on human-guided work. FamilyPlanCapabilityError uses the stable family_plan_capability_revision_conflict code for stale authority and family_plan_capability_invalid for malformed declarations through the package's LinkError surface.

Normal draft construction should use buildFamilyPlanOperationV1 and buildFamilyPlanDraftV1, not hand-written safety fields. The operation builder accepts references, readiness, exceptions, dependencies, and one exact capability declaration; callers cannot provide execution_mode or retry_safety overrides. The draft builder applies the plan's single capability_revision to every operation, validates the authorized child set and complete acyclic dependency graph, derives whether unresolved choices remain, and returns a deeply frozen FamilyPlanDraftV1. Cross-kind capabilities, stale capability revisions, self/duplicate dependencies, missing dependency targets, crossed child scope, and false ready_for_review or needs_choices projections fail closed through typed builder errors.

The byte codec emits only canonical JSON and the parser rejects malformed UTF-8, noncanonical JSON, duplicate members, unknown fields, invalid operation authority, and any mismatch with the optional exact expected plan. Parsed plans are detached and deeply frozen, making the artifact safe to hand to a later durable-store slice.

FamilyPlanStoreV1 defines that durable-store seam. Its accepted-plan coordinate is the exact (plan_id, accepted_revision) pair: replaying identical canonical authority is idempotent, while different authority at that coordinate throws FamilyPlanStoreConflictError and preserves the original acceptance. Stored records include the canonical bytes and their base64url SHA-256 digest. Every returned plan and byte array is detached from store-owned state. A valid missing coordinate returns null; malformed locators fail closed. The exported in-memory implementation is intended for conformance, tests, and integration development, not production durability.

Repository-owned store adapters must run the shared test/helpers/family-plan-store-contract.ts suite through their own isolated factory and cleanup fixture. The suite is adapter-neutral and covers retrieval, exact replay, immutable-coordinate conflicts, caller-mutation isolation, independent revisions, concurrent exact replay, and concurrent conflicting acceptance. The PostgreSQL adapter runs the same contract and adds transactional, restart, and compare-and-set coverage. Additional adapters should register with the shared contract rather than copying it.

materializeAcceptedFamilyPlanOperationsV1 deterministically expands valid accepted authority into one immutable, independently addressable work record per operation. Every record carries the provider, platform account, inventory and capability revisions, child/destination authority, dependency IDs, accepted-plan artifact digest, operation-authority digest, accepted execution mode and retry safety, and a bounded content-derived idempotency key. Materialization preserves review order with a zero-based operation_index, starts every operation as queued with zero attempts, and does not perform any destination mutation. Re-materializing identical accepted authority returns equal records; changing any accepted-plan authority changes the operation keys. The operation worker executes these records under expiring leases, reconciles uncertain results before any unsafe retry, preserves partial success, and publishes reconstructible signed provider events.

For production PostgreSQL composition, use the server subpath:

import { makePostgresFamilyLinkRuntimeV1 } from "@phosra/link/server"

const familyLink = makePostgresFamilyLinkRuntimeV1({
  database: pool,
  adapter: destinationAdapter,
  application_origin: "https://app.bark.example",
  provider_did: "did:ocss:bark",
  client_token_key: familyLinkClientTokenKey,
  authenticate: authenticateBarkParent,
  children: barkChildAuthority,
  platforms: phosraPlatformDirectory,
  accounts: destinationAccountAuthority,
  provider_events: {
    signing_key: familyEventSigningKey,
    destination_url: "https://app.bark.example/webhooks/phosra-link",
  },
})

await familyLink.migrate()
// Mount familyLink.handler at its same-origin /api/phosra/link/vnext paths.
const shutdown = new AbortController()
process.once("SIGTERM", () => shutdown.abort())
await familyLink.runWorker({ signal: shutdown.signal })

The parent application creates sessions server-side for child, family, platform, repair, or zero-child preconnection entries. The browser receives only a one-time pfl1 token and a safe projection. Bootstrap consumes that token and issues a Secure, HttpOnly, SameSite=Strict continuation cookie that is cryptographically bound to the one family session and expires with it; raw parent authority is never placed in the cookie. Account inventory, adapter capabilities, child/household authority, review labels, and operation safety are resolved again on the server; the browser submits choices, not operations.

import { createFamilyLink, FamilyPhosraLinkFlow } from "@phosra/link/react"

const link = createFamilyLink({
  token: oneTimeFamilyLinkToken,
  onLoad: renderLink,
  onEvent: recordNonSensitiveMilestone,
  onPlanAccepted: () => refreshBarkServerProjection(),
  onExit: () => restoreBarkReturnContext(),
})

await link.preload()
link.open()

The component and its JSON routes must share the parental-controls application's origin. Browser requests use credentials: "same-origin"; do not proxy the UI from an unrelated Phosra domain or widen the continuation cookie for cross-site use. A native iOS or Android app can present a provider-owned web route containing FamilyPhosraLinkFlow in its existing authenticated web view. Pass the one-time token to that page through a native-to-page message or initial in-memory page state, immediately remove any transient URL fragment if one is used, and route terminal callbacks back into the provider app. The parent still starts and ends inside the provider, while the server runtime, account handoff, review authority, and durable worker remain identical across web and native shells.

FamilyPhosraLinkFlow is the turn-key React controller: it drives preload, destination authentication return, the server-authored masked account checkpoint, profile choices, explicit sharing and translation acknowledgements, exact review, acceptance, and safe exit. FamilyPhosraLink is the lower-level controlled renderer for hosts that intentionally own that controller. Both render the projection with Phosra branding and a controlled partner appearance profile. Shared destinations require explicit acknowledgement, unresolved choices block approval, and final approval submits the exact server-rendered review digest. Closing before approval preserves the draft; closing after durable acceptance does not cancel background execution. Ongoing settings, age-change approval, notifications, and support remain in the parental- control product.

The account authority adapter may expose discover() after the destination handoff; Link projects only an opaque account reference, a masked differentiator, and a profile count, then re-resolves the reference before confirmation. For policy writes, return one policy_authorities entry per authorized child. Its content-addressed policy_ref, factual display summary (for example Content rating: TV-Y7), translation outcome, and approval requirement become part of the reviewed plan. Changing that authority invalidates review instead of applying newly looked-up rules.

Formal OCSS status and Phosra runtime admission are deliberately separate. Legacy OAuth resolution still fails closed on a missing or inactive OCSS entry. A server may configure LinkConfig.runtimeAdmission to consult a separately reviewed capability record with rollout, expiry, and kill-switch policy; this can admit a Phosra-provisional integration without claiming OCSS verification. Never surface developer governance labels as parent-facing trust claims.

For the signed operational registry behind that resolver, use signIntegrationCapabilityAdmissionV1 to produce canonical Ed25519 artifacts and makeIntegrationRuntimeAdmissionRegistryV1 to load them atomically. Pass registry.resolve as runtimeAdmission. Every lookup re-binds the signed record to the current root-verified OCSS fact and enforces its capability, rollout cohort, expiry, disagreement flag, and kill switch.

Local whole-product qualification (testing only)

Prerequisite: install OpenSSL 1.1.1 or newer, or a compatible LibreSSL build that supports req -addext and x509 -extfile, and make openssl available on PATH. Confirm the local tool before starting with openssl version. If TLS generation cannot run, the helper fails closed with FRESH_AUTHORITY_TLS_GENERATION_FAILED and never exposes raw command or environment details.

Use @phosra/link/testing to walk a real parent app and platform locally with real signed credentials, consent encryption, RFC 9421 requests, replay authority, endpoint minting, and a router-signed profile. The policy is the source of the age ceiling; do not duplicate the number in a second fixture.

import {
  cleanupLocalPairResourcesV1,
  createLocalLinkPairAuthorityV1,
  installLocalPairNetworkV1,
  startLocalPairAuthorityServerV1,
  writeLocalPairProcessBootstrapV1,
} from "@phosra/link/testing"

const pair = createLocalLinkPairAuthorityV1({
  contentRatingMaxAllowed: policy.config.max_allowed,
  // Optional for an isolated local provider app; omitted stays localhost:3000.
  providerApplicationOrigin: "http://localhost:3019",
})
const network = installLocalPairNetworkV1(pair)
let bootstrap
let authority
try {
  bootstrap = await writeLocalPairProcessBootstrapV1("./tmp/link-pair", pair)
  authority = await startLocalPairAuthorityServerV1({
    pair,
    extension: { staticGetRoutes: [jwksRoute, policyRoute, confirmation404Route] },
  })
  await walkParentAndPlatform({
    providerCredential: pair.provider.credential,
    platformCredential: pair.platform.credential,
    childProcessEnv: {
      NODE_OPTIONS: bootstrap.nodeOptions,
      NODE_EXTRA_CA_CERTS: bootstrap.nodeExtraCaCerts,
    },
  })
  console.log(authority.snapshot()) // safe counts + request digests only
} finally {
  await cleanupLocalPairResourcesV1({ authority, bootstrap, network })
}

Static host routes are cloned and bounded before the listener starts; they cannot override Link-owned paths. A profile URL returns 404 until the exact consent-bound mint succeeds, then serves only that minted label. Each call creates a new private run secret, signed provider/platform credentials, trusted localhost chain, and separate wrong-chain fixture; no fixed private key is published. Temporary key files are mode-restricted and removed immediately. The helper denies external origins. It is for local automated/human qualification only—never import the testing subpath into staging or production. A default pair captures the current clock once and signs one coherent 30-day manifest, Trust List, and profile window. For a multi-process test, capture one canonical whole-second pairNow, persist that non-secret value in the fresh run manifest, and pass the same explicit now: pairNow and policy ceiling to every process; explicit clocks are fixed hermetic test fixtures, while default-clock pairs are rechecked against wall time when the local authority starts. Never rebuild one side from a newly sampled clock or reuse credentials from an older run. The complete executable version is examples/local-pair-junior.ts, which is compiled by npm run typecheck:examples.

Legacy compatibility writer-plane API: createLink

createLink remains available for direct OCSS writer clients and existing integrations. New provider applications should start with the v3 server and React facades above. Its schema helpers provision the legacy writer-plane tables only; they are not the Golden server migration path.

createLink collapses the 8-field LinkConfig to three and returns a signed-by-default compatibility client. One key (the writer key you already hold), one census URL, one database — no shared connect secret.

Which census URL. There is ONE production census and https://prodapi.phosra.com is its name; https://phosra-api-prod-bootstrap-production.up.railway.app is the same service under its Railway origin name (verified 2026-07-26 — byte-identical /health, trust-list, succession and environment-manifest responses, both verifying under root root-prod-bootstrap-2026-07 / 824vsCATBxyUiA-znpGx01N48NNs_3gPE3M7f7vIEaI). Do not configure census.phosra.com: it is the intended canonical alias but no service is attached yet, so every path — /health included — returns Railway's {"code":404,"message":"Application not found"}.

Two caveats while this lands. (1) The prodapi.phosra.com pin is in source but not yet published — on npm latest (0.7.56) that host is unpinned, so createLink throws no bundled Trust-List root for census host. Until a release ships, either pass trustRoot: "824vsCATBxyUiA-znpGx01N48NNs_3gPE3M7f7vIEaI" explicitly or use the Railway origin name, which 0.7.56 already pins. (2) Railway's edge intermittently answers prodapi.phosra.com with its *.up.railway.app wildcard certificate (~1 handshake in 4, measured 2026-07-26) — a TLS altname error that a retry usually clears; the Railway origin name was 60/60 clean.

import { createLink } from "@phosra/link"
import pg from "pg"

const link = createLink({
  census:     "https://prodapi.phosra.com",  // THE production census (root-prod-bootstrap-2026-07)
  writerSeed:  process.env.OCSS_WRITER_SEED!, // your Ed25519 accreditation seed (base64url)
  writerKeyId: "did:ocss:your-org#2026-07",   // the DID+kid published on your Trust-List entry
  db:          new pg.Pool({ connectionString: process.env.LINK_DB_URL }),
})

// Provision the product tables + run the accreditation self-check (once, at boot).
await link.ready()

// Connect a child (Plaid-style 3-leg ceremony) — signed delivery by default:
const { authorizeUrl, state, sessionId } =
  await link.connect.start({ platformDid: "did:ocss:notflix", redirectUri, parentSessionRef })
// …redirect the parent, then on the OAuth callback:
const { childProfiles } = await link.connect.resume({ code, state, parentSessionRef })
const result = await link.connect.finish({
  sessionId, platformChildProfileId: childProfiles[0].id, childId: "child:<uuid>",
  granted_scope: ["addictive_pattern_block"], state,
})

// Write an enforcement rule under the granted scope:
await link.enforce(result.grant_id, "addictive_pattern_block", "child:<uuid>", { decision: "block" })

// Batch-provision N age-banded profiles in one action (signed by default):
await link.provision("did:ocss:notflix", { state: "acct", children: [ /* … */ ] })

// Withdraw consent:
await link.revoke(result.grant_id)

What is derived (so nothing downstream changes): writerDid from writerKeyId; the router DID (constant did:ocss:phosra-router); the router payload key (pulled from the Trust List at use time); the family-hash householdSecret = HKDF(writerSeed, "phosra-link/household-hash/v1"); and the household/parent persona = HKDF(writerSeed, "phosra-link/parent-persona/v1") → a deterministic did:ocss:household-<hash>.

  • Pinned roots, no TOFU. For a canonical census host the Trust-List root ships with the SDK. For any other host you MUST pass trustRoot (the base64url root X) — the SDK never trusts a root it just met.
  • Signed by default. connect.finish and provision deliver the sender-DID-signed writer envelope (verified to the pinned root); there is no connect secret to mint or rotate. An undocumented __legacyConnectSecret escape hatch remains for receivers that have not migrated off HMAC.
  • Ships its legacy writer-plane schema. link.migrate() (or migrateLinkSchema(pool) / printLinkSchema()) provisions only the compatibility tables used by this API. It does not provision the complete Golden provider schema. Golden integrations must use migrateLinkServerSchema(pool) from @phosra/link/server as the single pre-deploy migration owner.
  • Accreditation self-check. link.verifyAccreditation() (run by link.ready() and before every write) root-verifies the Trust List, matches your writer public key, and requires an active enforcement-agent entry — else NotAccreditedError. Pass verifyAccreditation: false to disable.
  • Census registration of the derived household DID. The derived did:ocss:household-<hash> persona signs the consent attestation; for the census to verify that signature the persona must be admitted on the census (or pass your own already-registered parentKey). This is an operator step, not a code change.

Typed errors

Every failure is a LinkError subclass with a stable code: NotAccreditedError, PlatformNotConnectableError, LaneInactiveError, StateExpiredError, ParentSessionMismatchError, DeliveryFailedError (with .status + a .hint — a 401 delivery says "add your DID to the platform allowlist"), SchemaNotReadyError, BandMismatchError. Branch on .code or instanceof; the human-readable messages are unchanged from prior versions.

Legacy low-level API reference

This manual LinkConfig surface is retained for existing writer-plane integrations. It is not the recommended provider quickstart and its individual census, Trust List, writer, parent, and household values must not be copied into a new Golden integration. New applications use one PHOSRA_CREDENTIAL, the Golden server quickstart above, and migrateLinkServerSchema(pool) for deployment.

import { makeLinkStore, createLinkSession, completeLink, directive } from "@phosra/link"
import type { LinkConfig, LinkSession } from "@phosra/link"
import pg from "pg"

// 1. Construct your config (real keys, live census URL).
//    Obtain a key from the Phosra developer console or via
//    POST /api/v1/developers/orgs/{orgID}/keys (see docs.phosra.com).
const config: LinkConfig = {
  censusBaseUrl:    "https://phosra-api-sandbox-production.up.railway.app", // the ONE canonical partner sandbox host (see the manifest caveat below)
  trustRootXB64Url: process.env.OCSS_TRUST_ROOT_X!,                         // sandbox root X (public): CMHWy3vUAiEcYDdE_bDvkRuEqwxkklS0tV-TYHJTlWU
  parentKey:        { seed: new Uint8Array(32) /* supply real Ed25519 seed */, keyID: "did:ocss:household-acme#parent-key-2026" },
  writerKey:        { seed: new Uint8Array(32) /* supply real Ed25519 seed */, keyID: "did:ocss:your-org#writer-key-2026" },
  writerDid:        "did:ocss:your-org",
  routerDid:        "did:ocss:phosra-router",
  householdSecret:  process.env.HOUSEHOLD_SECRET!,  // high-entropy per-family shared secret
  pool: new pg.Pool({ connectionString: process.env.LINK_DB_URL }),
}

// 2. Obtain the Postgres-backed grant store (synchronous — takes cfg.pool directly).
//    NOTE (manifest caveat, verified 2026-07-26): the partner sandbox census does
//    NOT currently serve /.well-known/phosra/environment-manifest-v1 — it returns
//    plain-text "404 page not found", i.e. the route is absent from its deployed
//    build. Anything that resolves its environment, trust roots or platform
//    endpoints FROM the manifest (the v3 createGoldenLinkServer/PHOSRA_CREDENTIAL
//    path) therefore cannot bootstrap against this host yet. This explicit
//    LinkConfig path, which carries censusBaseUrl and trustRootXB64Url directly,
//    is unaffected. The production census (prodapi.phosra.com) and the
//    Phosra-internal staging sandbox both serve the manifest normally.
//    completeLink/directive also use the store internally; use this reference for
//    direct grant queries (listGrants, getGrant, etc.).
const store = makeLinkStore(config.pool)

// 3. Open a link session for a parent authorizing access for a platform.
const session: LinkSession = createLinkSession(
  "did:ocss:your-platform",    // audience_did — the platform being granted write access
  ["addictive_pattern_block"], // granted_scope
  "child:a11ce0fa-0000-4000-8000-0000000000a1", // target_ref — the child's full census UUID (normalizeChildRef rejects a truncated id)
  { ageHint: "13_15" },
)

// 4. Complete the link: posts a consent_attestation to the census.
const { grant_id } = await completeLink(config, session)

// 5. Write a rule directive under the granted scope.
const res = await directive(config, grant_id, "addictive_pattern_block", session.target_ref, {
  decision: "block",
})

Create-and-link (no existing profile, no prior grant)

completeLink binds an EXISTING platform profile via the connect ceremony. When the child has no profile on the platform yet (one parent action → create the profile AND bind it — EXT-01 batch provisioning), the chain is three public calls, per child:

import { ingestConsentAttestation, mintEnforcementEndpoint, provisionProfiles } from "@phosra/link"

// 1. Consent FIRST (the census enforcement-endpoint mint is consent-gated):
//    signed as the AUTHORITY HOLDER (config.parentKey — the household persona).
const consent = await ingestConsentAttestation(config, {
  platformDid: "did:ocss:notflix",
  childRef: "child:<census-child-uuid>",   // a census subject (POST /api/v1/subjects)
  band: "13_15",                            // §8.3.2 AGE band, never a capability band
})

// 2. Mint the §9.3(b) resolver-bound endpoint (signed as config.writerKey),
//    naming the consent standing on the binding:
const { endpointIdLabel } = await mintEnforcementEndpoint(config, {
  platformDid: "did:ocss:notflix",
  childRef: "child:<census-child-uuid>",
  standingRef: consent.standingRef,        // "consent:attestation:<key>"
})

// 3. Deliver the batch to the platform's connect receiver, sender-DID-signed
//    (EXT-01 §3.6 — zero shared secret; `state` is the platform-minted principal
//    ref, e.g. its OAuth access token from the parent's authorize-approve):
await provisionProfiles(config, "did:ocss:notflix", {
  state: platformAccessToken,
  children: [{ endpoint_id_label: endpointIdLabel, age_band: "13_15", display_hint: "Nia" }],
  auth: { mode: "signed" },
})

Rules then live on the child's policy (the census compiles the served enforcement profile from the policy's rules; the binding only routes the platform's pull): write them with directive(...) under a grant, or directly via the rule-write API citing consent.standingRef as standing_ref.

Parent sessions (the ceremony's auth binding)

The parent authenticates with your app's own auth — Phosra never sees parent credentials. What @phosra/link needs is a trustworthy binding between your authenticated parent session and the OAuth ceremony:

  • Verified mode (recommended): set parentSessionSecret in LinkConfig. After your login succeeds, issue a token with issueParentSession(secret, { parentId }) (e.g. as an HttpOnly cookie) and pass it as parentSessionRef to initPlatformOAuth / completePlatformOAuth / bindProfile. The SDK verifies it (HMAC + expiry, fail closed) and binds the ceremony to the verified session id — an expired login cannot start or finish a ceremony, and one parent's state can never be completed by another parent's session.
  • BYO mode: leave parentSessionSecret unset and pass your own server-side session identifier as parentSessionRef. Contract: derive it server-side from your authenticated session; NEVER accept it from the client.

For v2/v3 PHOSRA_CREDENTIAL servers, the golden callback handler performs the parent authorization, exact child/profile selection, platform-session bind, and ceremony-authority write in one transaction. The callback is exact-replay safe, and a failed authority write cannot leave the session partially bound. The v1 credential path remains available only as the explicit legacy fallback.

See examples/reference-bff/ for the full working wiring.

Receiving sealed harm signals (EXT-03, the F6 lane)

The parent app pulls its sealed harm excerpts from the census over the documented §8.3.9 recipient-pull (GET /api/v1/harm-context — receiver-scoped, sealed_payload served byte-verbatim) and decrypts them client-side with its own EC P-256 payload key. The census/router never holds that key (the §3A.3 router-blind invariant).

import { harmContextReadiness, receiveHarmContext } from "@phosra/link"

// payload_jwk prerequisite checklist + the harm_context_ready flag:
const { harm_context_ready, checklist } = harmContextReadiness(config) // LinkConfig w/ payloadJwk
if (!harm_context_ready) throw new Error(checklist.filter(c => !c.ok).map(c => c.item).join(", "))

const { signals, rejected } = await receiveHarmContext({ client, payloadJwk: config.payloadJwk! })
// signals[i].signal is one of two TYPED shapes (the bespoke sealed shape is retired):
//   kind: "harm_context" — the §3A.2 post-confirmation excerpt body
//   kind: "verdict"      — the EXT-03 harm-signal-verdict.v1, severity + harm_class
//                          validated against the §4.4 closed enums BY CITATION
// rejected[] rows failed a vocabulary rule (H7/H8/H9/H11) and were rejected WHOLE —
// out-of-vocabulary is never coerced to a near match (§4.4 rule 2, §7.1).

Post-decrypt conduct is the recipient's §4.6 duty: retention clock, purpose limitation, disposition receipt. Apparent CSAM never rides this lane in either direction — the census diverts it at the route step, and the parent-facing enum deliberately excludes it (report-and-preserve is the only path for that class).

Pricing & keys

This SDK is free and open (MIT). It performs all signing/verification locally and calls the hosted Phosra census over RFC 9421. Metered census usage requires a Phosra API keybilling is enforced server-side, never in this package. Provision a key via the Phosra developer console (docs.phosra.com) or the REST endpoint POST /api/v1/developers/orgs/{orgID}/keys.

The /protocol subpath

import { verifyDocument, verifyReceipt } from "@phosra/link/protocol"

@phosra/link/protocol is a verbatim re-export of @openchildsafety/ocss — identical object identities. It lets platform-side code verify signed documents and receipts without a separate @openchildsafety/ocss install when you already depend on this package.

Stability

This package is 0.x — live-proven but still co-evolving with the OCSS spec. Under 0.x semver, minor bumps (0.1 → 0.2) may include breaking changes with a one-minor deprecation window. Exports marked @experimental are excluded from that window.

The following test/sandbox utilities are intentionally not exported from the public barrel and are not part of the stable API: makeConfigFromEnv, deterministicTestSeed, scopeDigest, resolveProviderEntry, startExampleProvider.