Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion charts/cx-ve/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ core-platform-distribution:

# ---- DECADE-X onboarding API -------------------------------------------------------------------
# The TSP onboarding intake. There is no TSP operator yet: requests are approved automatically —
# those of participants hosted here, and for now those of external ones too
# those of participants hosted here, and for now those of external ones too — after being asked
# for changes once, which the applicant answers by submitting the request again
# (dx-onboarding-api.config.dx-onboarding.review). On approval it registers the participant as a
# credential holder and has the platform's issuer offer it the DX_LegalEntity_VC and DX_Site_VC (see
# decadex-profile below).
Expand Down
7 changes: 7 additions & 0 deletions charts/dx-onboarding-api/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,16 @@ config:
# No TSP operator in the VE yet: participants hosted here (DID under hosted-did-prefix, the
# hub's participant.did.template — host-derived, the install scripts override it) are approved
# automatically; external ones too, for now. Switch external off to have them wait in SUBMITTED.
# Before it approves, the automatic review asks for changes once (request-changes): the request
# becomes CHANGES_REQUESTED and is approved once the applicant submitted it again. The hub does
# that for every member it registers, hosted here or not; a participant calling this API on its
# own has to do it itself.
review:
hosted-did-prefix: "did:web:identity.cxve.localhost:"
auto-approve:
hosted: true
external: true
request-changes:
hosted: true
external: true
delay: 0s
34 changes: 29 additions & 5 deletions dx-onboarding-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

The VE's stand-in for the DECADE-X TSP's onboarding intake, implementing
[`dataspace-api-docs.yaml`](dataspace-api-docs.yaml): an applicant creates a **draft**, adds its
documents one by one, and **submits** it for review. Once the TSP approves the participant, this
documents one by one, and **submits** it for review. The review may hand it back
(`CHANGES_REQUESTED`): the applicant then corrects it like a draft and submits it again. Once the
TSP approves the participant, this
service registers it as a credential holder with the IssuerService and has the issuer offer it the
`DX_LegalEntity_VC` and `DX_Site_VC` — nothing else: the participant's EDC resources (its participant
context on IdentityHub, its control plane, its DID) exist before it onboards.
Expand Down Expand Up @@ -53,13 +55,25 @@ sequenceDiagram
CSP-xTSP: PUT /{id} or any document change
TSP-->>CSP: 422 request already submitted
OP->>TSP: review, may edit the request and its documents
OP->>TSP: approve or reject
OP->>TSP: approve, reject or request changes
end

loop Follow-up
CSP->>GW: GET /{id}
GW->>TSP: relay
TSP-->>CSP: 200 { status: UNDER_REVIEW / ON_HOLD / APPROVED / REJECTED }
TSP-->>CSP: 200 { status: UNDER_REVIEW / CHANGES_REQUESTED / APPROVED / REJECTED }
end

rect rgba(150,110,220,0.12)
Note over CSP,OP: Only when CHANGES_REQUESTED: the request is the CSP's again
CSP->>GW: GET /{id}
GW->>TSP: relay
TSP-->>CSP: 200 { status: CHANGES_REQUESTED, changeRequest: { comment, items[] } }
CSP->>GW: PUT /{id} and document changes, as on a draft (no DELETE /{id})
GW->>TSP: relay
CSP->>GW: POST /{id}/submit (If-Match: version)
GW->>TSP: relay
TSP-->>CSP: 200 { status: UNDER_REVIEW, changeRequest.resubmittedAt }
end
```

Expand All @@ -72,8 +86,18 @@ the VE's OSP IdP.
and an `http-pull` transfer. The caller's identity — which the spec leaves unsettled — is taken
from the `X-Connector-Id` header (`dx-onboarding.connector-id-header`), UNVERIFIED; it is the
participant's DID.
- **No operator.** A submitted request is approved automatically (`dx-onboarding.review`), or
waits in `SUBMITTED` for a review that does not exist.
- **No operator.** A submitted request is reviewed automatically (`dx-onboarding.review`), or
waits in `SUBMITTED` for a review that does not exist. The automatic review takes the request
on (`UNDER_REVIEW`) and approves it.
- **Every request is asked for changes once.** Before it approves, the automatic review hands the
request back (`CHANGES_REQUESTED`), with a `changeRequest` that asks for nothing but to submit
the request again; it approves once that happened. This way every participant goes through the
round a real review may ask of it. `dx-onboarding.review.request-changes.hosted` and
`.external` switch it off. The Membership Hub submits the requests it filed again by itself.
- **Replays while changes are requested.** A submit sent again is answered with the request as
it is. Once changes are requested, a submit is the answer to them — unless it carries the
`Idempotency-Key` of the first submit and an outdated version: that is the first submit sent
again, and changes nothing.
- **Hosted participants keep an out-of-band DECADE-X-ID.** The TSP assigns every participant its
DECADE-X-ID on approval. A participant this VE hosts (a connector identity under
`dx-onboarding.review.hosted-did-prefix` — in practice the verification participant) is
Expand Down
34 changes: 33 additions & 1 deletion dx-onboarding-api/dataspace-api-docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,36 @@ components:
updatingDate:
type: string
format: date-time
ChangeRequest:
type: object
properties:
requestedAt:
type: string
format: date-time
comment:
type: string
items:
type: array
items:
$ref: '#/components/schemas/ChangeRequestItemDto'
resubmittedAt:
type: string
format: date-time
ChangeRequestItemDto:
type: object
properties:
field:
type: string
documentType:
type: string
enum:
- gtc
- uca
- registration-extract
- power-of-attorney
- other
note:
type: string
Consent:
type: object
properties:
Expand Down Expand Up @@ -958,7 +988,7 @@ components:
- DRAFT
- SUBMITTED
- UNDER_REVIEW
- ON_HOLD
- CHANGES_REQUESTED
- APPROVAL_IN_PROGRESS
- APPROVAL_FAILED
- APPROVED
Expand Down Expand Up @@ -998,6 +1028,8 @@ components:
description: The request's `other` documents (also in `GET /{requestId}/documents`).
decision:
$ref: '#/components/schemas/Decision'
changeRequest:
$ref: '#/components/schemas/ChangeRequest'
description: An onboarding request with its status. `decision` is null until a decision is taken.
DataspaceOnboardingRequestWriteDto:
type: object
Expand Down
45 changes: 40 additions & 5 deletions dx-onboarding-api/onboarding.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,34 @@
While the request is a draft, you can still correct it (replace data/documents, remove documents, or delete the draft).

2. After submission, the request is read-only for you
Once a request is submitted, it belongs to the review. You can no longer change it; only the TSP operator can modify the request. If something needs correcting, only the operator can do it.
Once a request is submitted, it belongs to the review. You can no longer change it; only the TSP operator can modify the request. If something needs correcting, the operator either does it or asks you to (see 3).

You can still follow its progress with GET /{requestId} (Statuses: SUBMITTED, UNDER_REVIEW, ON_HOLD, APPROVAL_IN_PROGRESS, APPROVED, REJECTED) and download its documents. Nothing is provisioned until an operator approves the request.
You can still follow its progress with GET /{requestId} (Statuses: SUBMITTED, UNDER_REVIEW, CHANGES_REQUESTED, APPROVAL_IN_PROGRESS, APPROVED, REJECTED) and download its documents. Nothing is provisioned until an operator approves the request.

3. Things to know
3. When the review requests changes
The operator can hand the request back to you: its status becomes CHANGES_REQUESTED, and its changeRequest says what to change (a comment, and the fields and documents concerned in items).
- The request is yours again, like a draft: replace its data (PUT /{requestId}) and add, replace or remove documents. It cannot be deleted.
- Submit it again: POST /{requestId}/submit, with the current version. The TSP checks it as a whole, as on the first submit. The request goes back to UNDER_REVIEW, with the same reviewer, and changeRequest.resubmittedAt records when.

```mermaid
stateDiagram-v2
[*] --> DRAFT : POST /onboarding-requests

DRAFT --> DRAFT : PUT /{id} (replace request data) or POST/PUT/DELETE /{id}/documents (manage files)
DRAFT --> DELETED : DELETE (or auto-purge after 30 days)
DRAFT --> SUBMITTED : POST submit

SUBMITTED --> UNDER_REVIEW : assign reviewer

UNDER_REVIEW --> CHANGES_REQUESTED : request changes
UNDER_REVIEW --> APPROVED : approve
UNDER_REVIEW --> REJECTED : reject

CHANGES_REQUESTED --> CHANGES_REQUESTED : PUT /{id} (replace request data) or POST/PUT/DELETE /{id}/documents (manage files)
CHANGES_REQUESTED --> UNDER_REVIEW : POST submit (reviewer already assigned)
```

4. Things to know
- Versions: every response carries the request's version (in the body and ETag header). Every change must send it back in If-Match, otherwise the call is refused.
- Retries: Idempotency-Key is optional on create/submit and makes a retry safe.
- DID: the preferredDid must be new to the TSP.
Expand Down Expand Up @@ -64,12 +87,24 @@ sequenceDiagram
CSP-xTSP: PUT /{id} or any document change
TSP-->>CSP: 422 request already submitted
OP->>TSP: review, may edit the request and its documents
OP->>TSP: approve or reject
OP->>TSP: approve, reject or request changes
end

loop Follow-up
CSP->>GW: GET /{id}
GW->>TSP: relay
TSP-->>CSP: 200 { status: UNDER_REVIEW / ON_HOLD / APPROVED / REJECTED }
TSP-->>CSP: 200 { status: UNDER_REVIEW / CHANGES_REQUESTED / APPROVED / REJECTED }
end

rect rgba(150,110,220,0.12)
Note over CSP,OP: Only when CHANGES_REQUESTED: the request is the CSP's again
CSP->>GW: GET /{id}
GW->>TSP: relay
TSP-->>CSP: 200 { status: CHANGES_REQUESTED, changeRequest: { comment, items[] } }
CSP->>GW: PUT /{id} and document changes, as on a draft (no DELETE /{id})
GW->>TSP: relay
CSP->>GW: POST /{id}/submit (If-Match: version)
GW->>TSP: relay
TSP-->>CSP: 200 { status: UNDER_REVIEW, changeRequest.resubmittedAt }
end
```
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.fasterxml.jackson.annotation.JsonInclude;
import com.metaform.dxonboarding.domain.model.onboarding.Address;
import com.metaform.dxonboarding.domain.model.onboarding.ChangeRequest;
import com.metaform.dxonboarding.domain.model.onboarding.CompanyType;
import com.metaform.dxonboarding.domain.model.onboarding.Declarations;
import com.metaform.dxonboarding.domain.model.onboarding.DocumentType;
Expand All @@ -17,7 +18,8 @@

/**
* An onboarding request with its status ({@code DataspaceOnboardingRequestDto}). {@code submittedAt}
* is absent while it is a draft, {@code decision} until a decision is taken. The GTC and use case
* is absent while it is a draft, {@code decision} until a decision is taken, {@code changeRequest}
* unless the review asked for changes. The GTC and use case
* agreements carry no {@code versionId} nor {@code accepted}: the applicant names a version, and
* signs it with the document.
*/
Expand All @@ -39,7 +41,8 @@ public record OnboardingRequestView(
DocumentRefView registrationExtractDocument,
DocumentRefView powerOfAttorneyDocument,
List<DocumentView> otherDocuments,
ReviewDecision decision) {
ReviewDecision decision,
ChangeRequest changeRequest) {

public static OnboardingRequestView from(OnboardingRequest request) {
var data = request.data();
Expand Down Expand Up @@ -72,7 +75,8 @@ public static OnboardingRequestView from(OnboardingRequest request) {
request.document(DocumentType.REGISTRATION_EXTRACT, null).map(DocumentRefView::from).orElse(null),
request.document(DocumentType.POWER_OF_ATTORNEY, null).map(DocumentRefView::from).orElse(null),
request.documents(DocumentType.OTHER).stream().map(DocumentView::from).toList(),
request.decision());
request.decision(),
request.changeRequest());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ public ApprovalService(OnboardingRequestRepository repository, HolderRegistratio
}

/**
* Approves the request, if it still awaits review. Never throws: the outcome is the request's
* status.
* Approves the request, if it still awaits review — submitted, or under review. Never throws:
* the outcome is the request's status.
*/
public void approve(String requestId) {
var inProgress = assignDecadeXId(requestId);
Expand Down Expand Up @@ -92,8 +92,9 @@ public void approve(String requestId) {
*/
private synchronized Optional<OnboardingRequest> assignDecadeXId(String requestId) {
var request = repository.findById(requestId).orElse(null);
if (request == null || request.status() != OnboardingStatus.SUBMITTED) {
// nothing else decides a request today, but an approval must never overwrite a decision
if (request == null
|| request.status() != OnboardingStatus.SUBMITTED && request.status() != OnboardingStatus.UNDER_REVIEW) {
// an approval must never overwrite a decision, nor take a request its applicant is correcting
return Optional.empty();
}
var supplied = request.data().declaredLegalEntityId().orElse(null);
Expand Down
Loading
Loading