Skip to content

Let interaction-controls callers tell transient failures from rejections #1206

Description

@dahlia

While moving Hollo's FEP-044f quote handling onto @fedify/interaction-controls (fedify-dev/hollo#635, fedify-dev/hollo#641), most of the code Hollo had to add was not policy logic. It was code that works around the helper hiding why a check failed. An inbox handler has to choose between retrying later and giving up for good, and the 2.4.0 helpers often don't give it enough information to make that choice. Hollo ended up with about 100 lines of workarounds, listed below. Each of them would be better solved once in the package.

The package's code on main is the same as in 2.4.0, so all of the line references below still apply.

Request verification swallows fetch errors

verifyRequest() dereferences the target and the instrument with suppressError: true (control.ts L326–L330). A failed fetch therefore comes back as missingObject or missingInstrument, the same result as a request that never had one. A caller can't tell a timeout apart from a malformed request. If it treats the failure as final, it drops a valid request. If it retries, it retries garbage.

Hollo now resolves the instrument itself before calling the helper (inbox.ts L770–L794). It has to work on a copy re-parsed from JSON-LD so the request it echoes back is left untouched, and it uses its own transient-error check. That defeats much of the point of calling verifyRequest().

Suggestion: when the target or instrument is referenced by IRI and fetching it fails, report the existing unverifiable/notDereferenceable failure with the URL and the cause. Keep missingObject and missingInstrument for requests that really lack them.

Failures don't say whether retrying could help

verifyAuthorization() reports every fetch problem as unverifiable, but callers still need to classify the cause themselves. A 503, a DNS failure, a 404, and a URL blocked by SSRF protection all look alike. On top of that, materialize() passes the same loader as the context loader, so a failed JSON-LD context fetch surfaces as invalidJsonLd (control.ts L781). That is indistinguishable from a document that is actually malformed.

Hollo wraps the document loader to record every error it throws, then classifies those errors: network errors, UrlError with reason: "dns", 5xx, 408, and 429 count as transient (quote.ts L82–L150). Every Fedify application that verifies authorizations from an inbox needs this same logic.

Suggestion: add a transient: boolean (or retryable) flag to unverifiable failures in both verifyRequest() and verifyAuthorization(), computed from the loader error. Report a failed context fetch as notDereferenceable with the context URL instead of invalidJsonLd. The classification could live in @fedify/vocab-runtime next to FetchError and UrlError, so other packages can reuse it.

Errors from matchesApprovalCollection are swallowed

evaluatePolicy() catches anything the matchesApprovalCollection callback throws and turns it into a denied decision with an unverifiableCollection reason (control.ts L984–L990). The callback is usually a database query. If that query fails for a moment, an application that just follows the decision sends a permanent Reject for a request it would have approved.

Hollo checks for that reason and rethrows (inbox.ts L745–L755), but by then the original error is gone.

Suggestion: put the caught error in the reason as cause, and add an option to let callback errors propagate instead of being converted into a denial.

Constructors are stricter than the wire formats they replace

createRevocation() always reduces the authorization to its IRI (control.ts L182–L188). Hollo has always sent the Delete with the QuoteAuthorization embedded, so it can't use the helper without changing its wire format, and it still builds the Delete by hand.

The constructors also require id and to for Accept, Reject, and Delete. For activities passed straight to sendActivity(), Fedify already assigns an ID when it is missing, and the recipients are given separately. Hollo had to copy Fedify's /#Accept/<uuid> ID shape and add a to it didn't send before.

Suggestion: embed the authorization in createRevocation() when the caller passes an object rather than a URL, as createAccept() already does for result. Make id, to, and cc optional in these constructors, matching createRequest().

Compatibility

All four changes can stay backward compatible. The new failure fields and options are additive. The only output that changes is createRevocation() given an object, and an option can gate that if it seems risky. Once these land, Hollo can drop its own instrument resolution, its loader wrapper and error classification, and its rethrow after evaluatePolicy().

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Fields

Priority

None yet

Effort

None yet

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions