Repository navigation
Description
Motivation
Before maintenance, an application should be able to publish one notice, notify selected peers, extend its end time and cancel it when maintenance finishes. Doing that manually requires stable notice/activity URLs, immutable identity, revision races, signing and restart-safe notification progress. A database commit alone does not mean peers received the announcement.
Provide an online publisher that follows FEP-f799 publication and cancellation, independently of the receiving feature.
Proposed API
Add a registration method to Federatable<TData>:
setDeliverySuspensionPublisher(
noticePath: `${string}{noticeId}${string}`,
options: DeliverySuspensionPublisherOptions<TData>,
): DeliverySuspensionPublisher<TData>;Options include an activity path, local actor identifiers for shared/origin controllers, optional personal/external inbox mappings, an optional bounded paginated peer source, and optional store: AtomicKvStore/keyPrefix. Omitted store means the federation's KV. The sharedInbox option chooses a controller for the shared inbox; the origins option maps each origin to a controller. Resolve actor URLs and keys through existing actor/key dispatchers.
The returned handle exposes create(), update(), cancel(), get(), list(), notify(), getNotificationReport(), retryNotification(), runPending(), getCommittedDocument(), getDiscoveryLinks() and getExportSource(). Each takes the owning Context<TData> first. The core mutations are:
create(ctx: Context<TData>, input: {
operationId: string; target: DeliverySuspensionTarget;
endTime: Temporal.Instant; startTime?: Temporal.Instant;
noticeId?: string; summary?: string; url?: string;
announce?: boolean; notify?: NotificationPlan;
}): Promise<PublisherMutationResult>;
update(ctx: Context<TData>, noticeId: string, input:
DeliverySuspensionChanges & {
operationId: string; expectedRevision: number; notify?: NotificationPlan;
}): Promise<PublisherMutationResult>;
cancel(ctx: Context<TData>, noticeId: string, input: {
operationId: string; expectedRevision?: number;
summary?: string; notify?: NotificationPlan;
}): Promise<PublisherMutationResult>;A result contains the committed notice, a committed/replayed/unchanged/already-cancelled disposition and a separate notification report. Plans choose none/background/immediate delivery and explicit recipients or the configured peer source.
Usage
const publisher = federation.setDeliverySuspensionPublisher(
"/maintenance/{noticeId}", {
activityPath: "/maintenance/{noticeId}/activities/{activityId}",
controllers: { origins: [
{ origin: "https://social.example", controller: "server" },
] },
},
);
const created = await publisher.create(ctx, {
operationId: "maintenance-create",
target: { kind: "origin", origin: "https://social.example" },
endTime,
announce: true,
notify: { delivery: "background", recipients: peers },
});
// Run when maintenance has actually finished.
await publisher.cancel(ctx, created.notice.noticeId, {
operationId: "maintenance-cancel",
expectedRevision: created.notice.facts.revision,
notify: { delivery: "background", recipients: peers },
});This proposed usage assumes normal actor/key/inbox routes and a validated persistent atomic store. Keep the request, including time values, stable when retrying its operation ID. For a new peer, notify(ctx, id, {operationId, plan}) reuses committed activities instead of incrementing the notice revision.
Persistence and lifecycle decisions
Reserve operation identity, generated ID, first publication time and signing inputs before a lost response can cause a second publication. Commit the head, receipt, artifact and notification ownership/index consistently. expectedRevision detects competing updates; cancellation is terminal and no-op/replayed mutations do not re-sign. A fetched-only notice cannot acquire an announcement or start push publication under the same ID.
Serve the current complete notice at its stable URL and committed immutable activities at activity URLs. Register routes at the final router setup, since builder construction clones/replaces the router. Reject collisions and preserve unrelated WebFinger links. Minimal inbox metadata and authentication-policy changes are explicit opt-ins; conflicting controller assertions must be diagnosed rather than overwritten.
Notification plans own all selected destinations or the remaining peer cursor durably, including cancellation's prior recipients. Background sweeping must recover without new traffic. Immediate notification returns caller-owned remaining progress instead of secretly queueing it. Preserve known-sent outcomes while documenting duplicates after unknown HTTP acknowledgments.
Use existing KV adapters and shared static signing/export tools. A separate backend-specific publisher store and a guessed worldwide peer inventory were rejected. An export head version identifies notice state only, not notification progress or artifact-slot filling; already-recorded bytes are reused even if a new signer is supplied. Proof refresh must be an explicit operation.
Registration performs no storage I/O and needs no public initialize(). The first asynchronous operation prepares its runtime internally. Builder handles use the supplied ctx to find their built runtime; reject foreign contexts. Integrate pending work with existing queue execution and bounded runPending(ctx, {limit, signal}).
Shipping criteria and docs
Test operation replay, update/cancel races, terminal/no-change cases, final route collisions, repeated builds, multi-page peers, partial notifications, restart recovery and same-head export races. A usable publisher includes notification execution, not just saved plans. At least one validated persistent atomic adapter is required; completion of every adapter is not.
Revise docs/manual/delivery-suspension.md, federation.md, actor.md, webfinger.md, tasks.md, deployment guidance, public JSDoc, actor/framework skills and package fragments. Use examples using package imports with selected peers. Record only the implemented publishing scope in FEDERATION.md. Receiving and optional durable backlog are not prerequisites; if already present, connect existing control exemptions and queue behavior in this implementation.
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Effort
Activity
AtomicKvStore#1363