Recovery
What the application keeps ready for a project's recovery, as a connector may read it — the coverage object behind the readiness checklist — and the two places a connector files, a standby and a replacement, through the actions the dashboard runs.
Subscriby\Connector\Core\Recovery. The readiness checklist is the connector's to word (a "standby bot", a "mirrored channel") but the facts behind it are the application's: which installation stands by, which spaces have a standby and whether it is healthy, whether posts are mirrored, whether failover is automatic. A connector reads them here and answers RecoverySupport::readinessChecks() without importing an application class. The two writes are the answers to a Standby and a Replacement link request: the creator picked a place on the platform, and the connector files it here.
coverage()
$coverage = $recovery->coverage($project, 'example');
$coverage->standbyInstallation; // bool: a standby installation is registered
$coverage->autoFailover; // bool: the creator switched automatic failover on
foreach ($coverage->spaces as $space) {
$space->resourceId; // the gated resource
$space->kind; // its stored kind, connector:kind
$space->hasStandby; // a standby place is linked
$space->standbyHealthy; // the last probe of the standby was ready
$space->mirrored; // posts are copied into it
}RecoveryCoverage names the project's standing on your connector, one SpaceCoverage per gated place.
Wording a checklist from it
public function readinessChecks(InstallationRef $installation, ProjectRef $project): array
{
$coverage = $this->recovery->coverage($project, 'example');
return [
new ReadinessItem(
key: 'standby-bot',
label: __('A standby bot is ready'),
description: __('If Example bans your bot, the standby takes over in minutes.'),
icon: 'shield-check',
satisfied: $coverage->standbyInstallation,
prevention: true,
fixRoute: 'recovery.prevention',
),
...array_map(fn (SpaceCoverage $space): ReadinessItem => new ReadinessItem(
key: 'standby-room-'.$space->resourceId,
label: __('Every room has a standby'),
description: __('A standby room is where members are moved when a room is lost.'),
icon: 'home',
satisfied: $space->hasStandby && $space->standbyHealthy,
prevention: true,
fixRoute: 'recovery.prevention',
detail: $space->hasStandby ? null : __('No standby yet.'),
), $coverage->spaces),
];
}Keys must be unique within the connector (recovery.vocabulary_and_readiness); fixRoute is a named route of the core's, and the core builds the link. Items with prevention: true are shown locked to creators without the prevention tier.
registerStandby()
$recovery->registerStandby($resource, $reserve, mirror: false);File a place as a resource's standby: the reserve the core switches to, by the creator's click or automatic failover, when the resource's place is lost. $resource is the ResourceRef the request named (Resources::find() or findBySpace() gives you one), $reserve the place the creator picked, recorded through Spaces::record() first. With mirror: true every post in the resource's place is copied into the reserve from now on, for a connector whose manifest declares recovery_mirror.
replaceSpace()
$recovery->replaceSpace($resource, $replacement);Point a resource at the place that replaces the one it lost. The core opens or continues the recovery operation, re-points the resource, revokes the links into the old place and re-admits everyone with active access into the new one; the creator follows the roll call on the Disaster Recovery page. It is the dashboard's own swap, so the owner guard, the allowance and the notices are the same whichever road the creator took.
mirrorPairs()
foreach ($recovery->mirrorPairs($installation) as $pair) {
$pair->resource; // the ResourceRef sold in the mirrored place
$pair->source; // the SpaceRef whose posts are copied
$pair->standby; // the SpaceRef the copies land in
}The places the core mirrors for an installation, each with the standby its posts are copied into. The core reads no connector's posts: your gateway or webhook hears a post, asks here whether its place is mirrored, and copies it into the standby through your own RecoverySupport::mirror(). The list is empty for a standby whose mirror switch is off, whose place the last probe found unhealthy, or whose project switched the mirror facet off for your connector, so a flipped switch stops the copies at once; ask once a minute per installation rather than once a post, and keep the copy's id against the original if your platform edits and deletes posts, so an edit replaces the copy and a deletion removes it.
recordMirrored()
$recovery->recordMirrored($pair->resource);Note that a post was just copied into a resource's standby, for the prevention page's "last mirrored" line. It throws ResourceRefused for a resource the core does not know.
openRelink()
$handshake = $recovery->openRelink($creator, backup: false);
$start = $yourRecoverySupport->beginIdentityHandshake($platform, $creator, $handshake);Open a recovery handshake for a creator from your creator surface: a replacement for the account they sign in with, or a backup one with backup: true. The dashboard's wizards open the same handshake; you hand the creator the start link your RecoverySupport::beginIdentityHandshake() builds, to open from the other account, and the core records what was detected when that account comes back through your door (completeRelink() below). The core's own gates apply and arrive as RecoveryRefused: the creator must be the account holder, the platform installation must keep the relink facet on, a replacement spends the account allowance, and a backup needs the prevention tier. ResourceRefused for a creator the core does not know.
incidentsFor()
foreach ($recovery->incidentsFor($creator) as $incident) {
$incident->issue; // 'account', 'bot' or 'resources'
$incident->detectedAt; // when the core detected it
$incident->reason; // your HealthReasonText for the code, when you word it
}The creator's open incidents, newest first, as IncidentSummary values: what was lost, when, which project, resource or installation it concerns, and your own label for the reason code when your RecoverySupport::healthReasonText() words it. For a status read on your platform; the Disaster Recovery page stays the place to act on them.
registerStandbyAs() and replaceSpaceAs()
$recovery->registerStandbyAs($creator, $resource, $reserve, mirror: true);
$recovery->replaceSpaceAs($creator, $resource, $replacement);The two writes above as the creator a surface names, for a surface answering from a job where nobody is bound: the Discord connector's /subscriby-support recovery standby and replace run them after the creator picks the place from the server's own menu. The owner guard, the tier gate, the allowance and the re-admission are the dashboard's, and the refusals are the same.
completeRelink()
$detection = $recovery->completeRelink($token, $identityRecord);Complete a recovery handshake with the account that answered it. The core mints the handshake for the dashboard's relink and backup-account wizards and hands your port the token through RecoverySupport::beginIdentityHandshake(); when the start link you built brings a person back through your door with the core's recover_ payload, pass the token (the prefix stripped) and the account as you describe it. The core records what was detected and the creator confirms the change on the Disaster Recovery page afterwards, so your door only tells the person what was detected and where to go next: RelinkDetection::$backup says whether it was a backup account or a replacement, $displayName the name to show.
Refusals
completeRelink() throws Subscriby\Connector\Exceptions\HandshakeRefused: expired when the token names no pending handshake, identity_held when another creator already signs in with the account, same_identity when it is the creator's own and there is nothing to change.
Both writes throw Subscriby\Connector\Exceptions\ResourceRefused for a resource the core does not know, and Subscriby\Connector\Exceptions\RecoveryRefused for anything the core's recovery rules refuse: the actor does not own the project, the plan lacks prevention, the project switched standby places off for your connector, the place is another kind, already sold or already a standby, or the self-service allowance for the window is spent. RecoveryRefused carries the core's translated sentence in userMessage(), which is what you relay to the creator on the platform, and a stable reason you may branch on to say it in your own nouns instead.
What is not here
Incidents, operations, undo, quotas and the notices to members are the core's whole subsystem and never read by a connector; the connector only witnesses (probes) and acts (standby, failover, mirror, relink) when asked through RecoverySupport, and files the two places above when the creator answers a request.
Read it fresh
Coverage is computed when you ask; do not cache it across requests. The Readiness page asks once per render.
How is this guide?
Last updated on
Alerts
Where a creator's alerts go, as far as a connector may ask or change it — the one switch for the account in front of the connector, and what the core keeps to itself.
Support
The support inbox as a connector feeds it and answers from it — filing a member's message, sending the acknowledgement the core says is due, handing the core a creator's answer written on the platform, finding the thread a relay message or a relay-space thread belongs to, and recording the space a project relays into.