Repository navigation
Description
Motivation
An authenticated Undo can arrive before its referenced Create is available. The baseline receiver can fetch within a bound or decline, but then cannot promise that a later Create, Update-first delivery or Link fetch will respect an unknown cancellation it could not resolve.
Offer an explicit stronger guarantee: after committing an admitted exact controller/announcement pair, the receiver never activates a notice claiming that pair. The guarantee begins at durable admission, not when a queued inbox request returns 202 Accepted. The pair alone does not create or extend a hold. This extends the baseline described by FEP-f799 cancellation.
Proposed API and use
Extend the receiver registration with optional pending-cancellation policy and a read-only management handle:
interface PendingCancellationOptions<TData> {
admission?: boolean;
admissionScope?: "any-authenticated" | "known-controllers";
budgets?: {
maxPerController?: number; maxPerOrigin?: number;
maxTotal?: number; maxIdentifierBytes?: number;
};
onEvent?: (event: PendingCancellationEvent,
context: Context<TData> | null) => void | Promise<void>;
}
interface PendingCancellationAdmin<TData> {
getStatus(ctx: Context<TData>): Promise<PendingCancellationStatus>;
getUsage(ctx: Context<TData>, scope:
{ controller: string } | { controllerOrigin: string }): Promise<BudgetUsage>;
get(ctx: Context<TData>, key:
{ controller: string; announcement: string }): Promise<PendingCancellation | undefined>;
list(ctx: Context<TData>, options?: PageOptions & {
controller?: string; state?: "unmatched" | "matched";
}): Promise<Page<PendingCancellation>>;
}Add pendingCancellations?: false | PendingCancellationOptions<TData> to receiver options and expose receiver.pendingCancellations for reading retained state even when new admission is closed. A pair records exact controller, referenced Create ID, first Undo ID/time and bounded match metadata. Status includes matched/unmatched usage and admission state. Events distinguish admission outcomes from activation blocked; event kinds are closed and refusal detail is an open reason string, where an unknown reason is still refusal.
const receiver = federation.setDeliverySuspension({
mode: "apply", policy: { profile: "baseline-v1" },
pendingCancellations: {
budgets: { maxPerController: 16, maxPerOrigin: 256, maxTotal: 4096 },
onEvent(event, ctx) {
if (event.type === "admission" && event.result.kind === "refused") {
ctx?.data.log(event.result.reason);
}
},
},
});
const status = await receiver.pendingCancellations.getStatus(ctx);
const page = await receiver.pendingCancellations.list(ctx, { state: "unmatched" });These proposed limits are example settings, not selected defaults. The operator can expand budgets or close new admission on redeployment without erasing existing guarantees.
Admission, storage and alternatives
Authenticate the Undo and enforce wrapper freshness/origin/identifier byte limits. Attempt bounded referenced-Create lookup outside the storage transaction. A verified Create from a different actor is not a valid cancellation claim; verified ordinary non-FEP-f799 activity does not enter permanent suspension state. A known notice uses the baseline receiver's cancellation path.
Commit pair admission, activation matching and controller/origin/global budgets within the receiver's coherent atomic storage domain. Serialize absent rows and recheck visibility/version after acquiring the chosen protection. Avoid introducing a lock order conflicting with replay GC. Refused admission must not leave unlimited sentinel/counter rows. Hashes may aid locking/indexing, but exact identifiers establish equality.
Check the same pair on Create, Update-first, fetched notice, revalidation and Link admission. Keep matched as well as unmatched pairs counted permanently. Otherwise a different future notice ID could reuse the announcement after the pair was deleted. Setting pendingCancellations: false closes new pair admission, just like admission: false, but keeps matching retained pairs while the receiver is active. Use admission: false when retaining explicit budget and event settings. This differs from setDeliverySuspension(false), which disables the whole receiver; its stored pairs are retained and enforced again on re-enable. Backup restore or a new store does not automatically preserve the same historical guarantee.
Cleanup by time to live (TTL) or controller cutoff was rejected because the original notice publication time is unknown. Consume-on-first-match was rejected because it permits announcement reuse. Global quarantine on saturation was rejected because it would hold unrelated delivery. Refuse new pairs explicitly instead. No release/forget/purge method is included in this first guarantee; an explicit future abandonment feature would need to state that it ends the guarantee.
known-controllers may narrow admission based on retained controller evidence without an extra fetch. Requiring that evidence to be currently fresh was considered but is unnecessary for this local admission restriction: the pair blocks activation and grants no suspension authority. Do not silently make this restriction the default unknown-first behavior.
Shipping criteria and docs
Test Undo/Create and admission/GC races, counter limits/rollback, restart, matched reuse under a new notice ID, Update/Link-first paths, closed admission and observer failure. Store errors precede app listener/idempotency marking; observers run after commit and cannot undo it. Use the internal pre-dispatch path rather than registering another competing app Undo listener.
Revise docs/manual/delivery-suspension.md, inbox.md, KV state-retention/deployment guidance, public JSDoc, inbox/framework skills and the core changelog. Show saturation and read-only budget inspection. This child is complete when its admitted-pair guarantee works across all existing admission paths, not just activity delivery.
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Effort
Activity