Action requests¶
A partner never changes your data directly. It asks: a PATCH with an
api:Change becomes a ChangeRequest, a POST /subscriptions a
SubscriptionRequest, a POST /access-delegations an
AccessDelegationRequest, a POST /logistics-objects/{id} with an
api:Verification a VerificationRequest. Each is an api:ActionRequest
with a URI under /action-requests/, a status and a history, and the holder
decides it.
Lifecycle¶
stateDiagram-v2
[*] --> REQUEST_PENDING
REQUEST_PENDING --> REQUEST_ACCEPTED: holder accepts
REQUEST_PENDING --> REQUEST_REJECTED: holder rejects
REQUEST_PENDING --> REQUEST_REVOKED: requestor or holder revokes
REQUEST_ACCEPTED --> REQUEST_FAILED: applying failed
REQUEST_ACCEPTED --> REQUEST_REVOKED: subscription or delegation withdrawn
Verification requests have their own, shorter machine: pending to
REQUEST_ACKNOWLEDGED, REQUEST_REJECTED or REQUEST_REVOKED.
A change request cannot be revoked once accepted: the revision exists. A subscription or access delegation can, and revoking it stops notifications or withdraws the grants.
Deciding¶
Over HTTP, the holder's systems call PATCH /action-requests/{id}?status=REQUEST_ACCEPTED
(or REQUEST_REJECTED, REQUEST_ACKNOWLEDGED, REQUEST_REVOKED), answered
204. The spec marks this endpoint internal, so the access policy must allow
Action::DecideActionRequest for the caller; partners get 403.
In PHP, DataHolder::accept($iri), reject($iri, $errors), acknowledge($iri)
and revoke($iri) do the same. Rejections carry api:Errors the requestor
reads back from the request.
DELETE /action-requests/{id} revokes. The requestor may always revoke its
own request; the holder may revoke anything; a third party gets 403. An
impossible transition answers 422 at API 2.3.0 and 400 at 2.2.0, where the
spec had not yet said.
Who sees a request¶
GET and HEAD /action-requests/{id} answer the requestor, the parties a
request concerns (the delegate of an access delegation, the subscriber of a
subscription) and the holder. Anyone else is told 404, whatever the policy
says, so a request's existence is not leaked.
The body is the request with its payload embedded. At 2.3.0 it also carries
api:hasRequestStatusSince and api:hasRequestStatusHistory (one entry per
status left behind, with who changed it); at 2.2.0 these are omitted because
the ontology of that edition does not have them.
What accepting does¶
- ChangeRequest: the
api:Changeis applied withChangeApplierto the current revision and stored as the next one,LogisticsObjectRevisedis raised, and subscribers are told (LOGISTICS_OBJECT_UPDATEDwith the changed properties). If the change cannot be applied (the object moved on, a deleted value is absent, a property is unknown) the request endsREQUEST_FAILEDwith the errors recorded; nothing is stored. Other pending changes written against the same revision are rejected with a 409 error, as the spec requires. - SubscriptionRequest: the subscription is active from now on.
- AccessDelegationRequest: the permissions become grants for each
delegate on each object, and every delegate is notified
(
LOGISTICS_OBJECT_ACCESS_GRANTED). - VerificationRequest: cannot be accepted, only acknowledged.
Changes by the holder¶
When the holder's own agent sends a PATCH, the request is created and
accepted at once, as the spec says it should be. DataHolder::update() and
publish() go the same way: every revision in the audit trail has a change
request behind it, whoever asked for it.
Validation on receipt¶
A PATCH is refused immediately (400 or 422, with an api:Error) when the
body is not a Change, names another object in api:hasLogisticsObject, or
claims a revision the object has not reached. A change written against an
older revision is accepted as a request and fails on application. The
requester needs PATCH_LOGISTICS_OBJECT on the object
(spec question 16).
Notifying the requestor¶
A request with api:notifyRequestStatusChange: true makes the server queue a
notification to the requestor on every status change, with the event type
matching the request (CHANGE_REQUEST_ACCEPTED, SUBSCRIPTION_REQUEST_REJECTED
and so on) and the request URI in api:isTriggeredBy.