@phosra/gatekeeper 0.8.12 to receive Phosra Link, apply controls to real profiles,
read those effects back, and return 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.
This page is the recommended platform quickstart — the one new
integrations start from.
A legacy connect-receiver lane
(
createConnectReceiver({ env, did, seed, authorize, store, onBound })) also
exists and remains supported for existing integrations and HMAC-migration
drain windows — see
Platform · createConnectReceiver (legacy connect receiver).1. Create one platform singleton
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
GET /api/phosra/profiles) routes, plus the readiness report
at GET /api/phosra/status — the one route that answers even while the
platform is unready. 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. If any protocol route returns an opaque 503, read
/api/phosra/status first — see the
readiness contract.
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:Your directory row is a boot precondition
At boot the SDK fetches your own census directory entry (GET {census}/api/v1/providers/{yourDid}/connect) and refuses to serve any
protocol route unless the entry matches your credential exactly. A
mismatch fails closed: every /api/phosra/* route (except
/api/phosra/status) answers the opaque uniform 503, and
GET /api/phosra/status reports the outstanding precondition as
PLATFORM_DIRECTORY_UNAVAILABLE — see the
readiness contract. The checks are
exact-match, not fuzzy:
All six members above are required. Only
scopes, icon_url,
connect_url, provisioning_form, and profile_management_url may
additionally appear; any other member in the row also fails the check. So a
platform with a correct credential, correct standing, and a correct origin can
still be permanently 503 because its display name differs by one character
or its row carries a stray member.
Self-declare the row: PATCH /api/v1/platforms/{did}/connect
You publish and update the OAuth-surface members of your own row with one
signed call — no operator round-trip:
- Method and path:
PATCH /api/v1/platforms/{did}/connect. - Auth: RFC-9421 signed request; self-scoped — the signing caller’s DID
must equal the percent-decoded
{did}in the path. A mismatch answers the same404an unknown platform answers (no existence leak). - Declarable members:
authorize_url,par_url,token_url,profiles_url,profile_management_url,scopes(array of OAuth scope tokens), andprovisioning_form("per_child"or"batch"). - Merge semantics (RFC 7386 discipline): only the members your body names
change; everything else survives verbatim. An explicit JSON
nulldeletes a member. An unrecognized member rejects the whole request with400— a typo can never silently no-op. The merged result must remain serveable (the three OAuth legs non-empty, a validprovisioning_form) or it is rejected whole, never partially applied. - Creates as well as updates: an accredited-but-unconfigured platform (no
row yet, discovery
404) may create its entry with this call. - Never rotates credentials: this operation touches only connect
metadata. Your
endpoint_id_label/ delivery registration are separate (the endpoint-registration operation), and a metadata declare rotates and discloses nothing. Replays of a byte-identical signed declare are idempotent.
name and directory_version
are attached by the census from your accredited Trust List entry. If the
served name does not match your credential’s display name, that is fixed at
issuance/accreditation — contact Phosra rather than PATCHing.
3. Run the worker separately
await phosra.runWorkerOnce() instead; do not run both modes together.
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:
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:Read privacy-safe topology
Server-side operations and support tooling can inspect shared-target progress: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
IfhumanRecovery 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.
Production checklist
- Confirm
GET /api/phosra/statusreportsPLATFORM_READYfrom the deployed host. - 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.
@phosra/gatekeeper/platform for advanced conformance infrastructure. New
applications should use createPlatform(...) from the package root.