Skip to content

Commit de85d24

Browse files
authored
Merge pull request #192 from eScienceLab/122-documentation-website-mkdocs
122 documentation website mkdocs
2 parents 77644e6 + 6351b21 commit de85d24

8 files changed

Lines changed: 620 additions & 0 deletions

File tree

.github/workflows/deploy_docs.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: Deploy Documentation Site
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
paths:
7+
- "docs/**"
8+
- "mkdocs.yml"
9+
workflow_dispatch:
10+
11+
permissions:
12+
contents: read
13+
pages: write
14+
id-token: write
15+
16+
concurrency:
17+
group: "pages"
18+
cancel-in-progress: false
19+
20+
jobs:
21+
build:
22+
runs-on: ubuntu-latest
23+
steps:
24+
- name: Checkout code
25+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
26+
27+
- name: Set up Python
28+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
29+
with:
30+
python-version: '3.11'
31+
32+
- name: Install MkDocs
33+
run: |
34+
python -m pip install --upgrade pip
35+
pip install mkdocs mkdocs-mermaid2-plugin
36+
37+
- name: Build site
38+
run: mkdocs build --strict
39+
40+
- name: Upload Pages artifact
41+
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
42+
with:
43+
path: site
44+
45+
deploy:
46+
environment:
47+
name: github-pages
48+
url: ${{ steps.deployment.outputs.page_url }}
49+
runs-on: ubuntu-latest
50+
needs: build
51+
steps:
52+
- name: Deploy to GitHub Pages
53+
id: deployment
54+
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0

docs/api.md

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# API Reference
2+
3+
The examples below use the Compose stack's local address, `http://localhost:5001`. Note that the service serves its own OpenAPI specification at `/docs`.
4+
5+
!!! note
6+
`POST /v1/ro_crates/validate_metadata` is always available, but the **storage-backed endpoints** are only available when the service runs with `STORAGE_ENABLED=true` (see [Installation & Setup](installation.md#enabling-object-storage)); without this set, `POST /v1/ro_crates/{crate_id}/validation` or `GET /v1/ro_crates/{crate_id}/validation` requests will return a `404` result.
7+
8+
## Validate metadata
9+
10+
`POST /v1/ro_crates/validate_metadata`
11+
12+
This validates the contents of an `ro-crate-metadata.json` document and returns the result in the response.
13+
14+
| Field | Required | Description |
15+
|-------|----------|-------------|
16+
| `crate_json` | yes | The metadata document, as a JSON string |
17+
| `profile_name` | no | Profile to validate against, e.g. `ro-crate-1.2`. The validator will default to `ro-crate-1.1` when this is omitted |
18+
19+
!!! warning
20+
Currently, the validation profile is not detected from the RO-Crate. In other words, a `conformsTo` declaration in the metadata does not influence which validation profile is used by the validator, and the validation always runs against `profile_name`, or `ro-crate-1.1` when it is omitted.
21+
22+
To validate an ro-crate metadata file:
23+
24+
```bash
25+
jq -Rs '{crate_json: .}' ro-crate-metadata.json | curl -X POST http://localhost:5001/v1/ro_crates/validate_metadata -H 'Content-Type: application/json' -d @-
26+
```
27+
28+
To choose a profile, add it to the json object as a `profile_name` entry:
29+
30+
```bash
31+
jq -Rs '{crate_json: ., profile_name: "ro-crate-1.2"}' ro-crate-metadata.json | curl -X POST http://localhost:5001/v1/ro_crates/validate_metadata -H 'Content-Type: application/json' -d @-
32+
```
33+
34+
| Code | Meaning |
35+
|------|---------|
36+
| `200` | Validated; the result has a `status` of `valid` or `invalid` |
37+
| `422` | `crate_json` is either missing, empty or invalid, or the validation could not run (an `error` result) |
38+
39+
## Validate a stored RO-Crate
40+
41+
`POST /v1/ro_crates/{crate_id}/validation`
42+
43+
This queues validation of an RO-Crate held in the object store. The RO-Crate is resolved first, so a missing or ambiguous crate ID may be reported immediately; the validation process itself runs asynchronously on a worker.
44+
45+
!!! note
46+
See [Crate IDs](#crate-ids) for how `{crate_id}` maps to objects in the bucket.
47+
48+
| Field | Required | Description |
49+
|-------|----------|-------------|
50+
| `profile_name` | no | Profile to validate against; defaults to `ro-crate-1.1` when omitted |
51+
| `webhook_url` | no | URL that receives the result when validation finishes |
52+
53+
```bash
54+
curl -X POST http://localhost:5001/v1/ro_crates/my-dataset-2026/validation -H 'Content-Type: application/json' -d '{"profile_name": "ro-crate-1.2"}'
55+
```
56+
57+
| Code | Meaning |
58+
|------|---------|
59+
| `202` | Queued; the body is `{"message": "Validation in progress"}` |
60+
| `400` | Invalid Crate ID |
61+
| `404` | Either storage mode is not enabled, or there is no RO-Crate at the location defined by the given Crate ID |
62+
| `409` | Both a zip and a directory exist for this Crate ID |
63+
| `422` | Request body invalid |
64+
| `503` | Object store unreachable |
65+
66+
## Fetch a validation result
67+
68+
`GET /v1/ro_crates/{crate_id}/validation`
69+
70+
This returns the stored result for an RO-Crate.
71+
72+
```bash
73+
curl http://localhost:5001/v1/ro_crates/my-dataset-2026/validation
74+
```
75+
76+
| Code | Meaning |
77+
|------|---------|
78+
| `200` | The stored result, including persisted `error` results |
79+
| `400` | Invalid Crate ID |
80+
| `404` | No result stored for this Crate ID |
81+
82+
## Validation results
83+
84+
Every validation produces a result object:
85+
86+
```json
87+
{
88+
"status": "invalid",
89+
"profile": "ro-crate-1.2",
90+
"created_at": "2026-07-22T10:30:00+00:00",
91+
"detail": {}
92+
}
93+
```
94+
95+
An RO-Crate's `status` can be:
96+
97+
| `status` | Meaning |
98+
|----------|---------|
99+
| `valid` | The RO-Crate conforms to the profile |
100+
| `invalid` | The RO-Crate does not conform to the profile, issues listed in `detail` field |
101+
| `error` | The validation could not run; the reason is in an `error` field. No `detail` field is provided |
102+
103+
!!! note
104+
`detail` contains the complete validation report. `created_at` is the UTC time of a stored-crate validation, and `null` for metadata-only validation, which does not set it. `profile` is the requested profile name, or `null` when the default (`ro-crate-1.1`) was used.
105+
106+
For stored RO-Crates the same object is saved to `{S3_RESULTS_PREFIX}/<id>.json` and returned by the GET endpoint.
107+
108+
## Webhooks
109+
110+
If `webhook_url` was given, the worker POSTs the result object to it as JSON once validation finishes. The result is saved to the store first and the webhook sent after, so a notification is never sent for a result that was not stored.
111+
112+
Note that delivery is attempted three times, waiting `0.5s` then `1s` between attempts, with a `10s` timeout per attempt.
113+
114+
## Crate IDs
115+
116+
A Crate ID is the label in the URL path that identifies an RO-Crate in the object store: the service looks for `{S3_CRATE_PREFIX}/<id>.zip` (zip) or `{S3_CRATE_PREFIX}/<id>/` (directory). Crate IDs must match `^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$`: they start with a letter or digit, may contain letters, digits, `.`, `_` and `-`, and are at most 128 characters long. Anything else is rejected with `400`.
117+
118+
## Health
119+
120+
`GET /healthz` reports that the process is up, and always returns `200 {"status": "ok"}`. `GET /readyz` checks the object store and Celery broker, returning `200` when ready and `503` otherwise, with the individual checks in the body. When storage is off, both checks report `disabled`.

docs/contribution.md

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# Development and Contributions
2+
3+
The [RO-Crate Validation Service](https://github.com/eScienceLab/RO-Crate-Validation-Service) is an open source project and welcomes contributions of all kinds: bug reports, code or documentation changes, and reviews of proposed changes. The underlying [rocrate-validator tool](https://github.com/crs4/rocrate-validator) is also open source, and is a separate project maintained by CRS4.
4+
5+
This service is written with Python 3.11, built on Flask/APIFlask and Celery, and wraps the [`rocrate-validator`](https://rocrate-validator.readthedocs.io/) in a REST API. The RO-Crate Validation Service enables pipelines, other services, and Trusted Research Environments (TREs) to validate an RO-Crate over HTTP without running the validator themselves.
6+
7+
## Contributing
8+
9+
The easiest way to start contributing is to create an issue, either to let us know of a bug or error, or to propose a piece of work you want to do. For the RO-Crate Validation Service (the API service, Docker image, and Compose stack) use the [RO-Crate Validation Service issues](https://github.com/eScienceLab/RO-Crate-Validation-Service/issues) page. Issues with the validation checks themselves belong to the underlying tool rather than this service: report those on the [rocrate-validator issues](https://github.com/crs4/rocrate-validator/issues) page, and follow that project's own contribution guidance.
10+
11+
### Code contributions
12+
13+
If you want to contribute code changes via GitHub then you may want to read ['How to Contribute to an Open Source Project on GitHub'](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github). We use [GitHub flow](https://docs.github.com/en/get-started/using-github/github-flow) to manage changes:
14+
15+
1. Create a new branch in your local clone of this repository for each significant change.
16+
2. Commit the change in that branch.
17+
3. Push that branch to your fork of this repository on GitHub.
18+
4. Submit a pull request from that branch to the [upstream repository](https://github.com/eScienceLab/RO-Crate-Validation-Service).
19+
5. If you receive feedback, make the changes in your local clone and push to your branch on GitHub: the pull request will update automatically.
20+
21+
!!! warning
22+
Note that we use the `develop` branch for development work, and this is where your PR should be aimed. The `main` branch is used for releases, and only pull requests from the `develop` branch are accepted to this.
23+
24+
## Development stack
25+
26+
The development Compose file builds the image from the local `Dockerfile` and mounts the repository's test profiles into both the API and worker containers:
27+
28+
```bash
29+
docker compose -f docker-compose-develop.yml up --build
30+
```
31+
32+
Here `--build` matters: without it, Compose reuses the previously built image and local code changes are not picked up. Add `--profile objectstore` to start the bundled RustFS store for storage-backed work; configuration is the same as in [Installation & Setup](installation.md#configuration-reference).
33+
34+
## Tests
35+
36+
Install the development dependencies, then run the unit tests, which do not use Docker Engine:
37+
38+
```bash
39+
pip install -r requirements-dev.txt
40+
```
41+
42+
```bash
43+
pytest --ignore=tests/test_integration.py
44+
```
45+
46+
The integration tests bring up the full Compose stack (including the object store) and seed crates with `boto3`, for which they need Docker Engine to be running:
47+
48+
```bash
49+
pytest tests/test_integration.py
50+
```
51+
52+
`tests/` mirrors the layout of the `app/` package, so the tests for a module are in the matching directory.
53+
54+
## Linting
55+
56+
The project uses [Ruff](https://docs.astral.sh/ruff/) for linting and formatting, configured in `pyproject.toml`:
57+
58+
```bash
59+
ruff check . && ruff format --check .
60+
```
61+
62+
## Dependencies
63+
64+
Direct dependencies are declared in `pyproject.toml`; while the `requirements*.txt` files are locks generated using `pip-compile`:
65+
66+
```bash
67+
pip-compile pyproject.toml -o requirements.txt
68+
```
69+
70+
```bash
71+
pip-compile --extra dev pyproject.toml -o requirements-dev.txt
72+
```
73+
74+
## Continuous Integration
75+
76+
Pull requests to `develop` will trigger three workflows: unit tests, integration tests (which start the Compose stack), and lint (`ruff check` and `ruff format --check`).
77+
78+
## How the API works
79+
80+
The API server handles HTTP and runs metadata-only validation inline. Object storage-backed validation is queued through Redis to a Celery worker, which reads the crate from the S3-compatible store, validates it, and writes the result back:
81+
82+
```mermaid
83+
flowchart LR
84+
Client([Client])
85+
API["Flask API"]
86+
Broker[("Redis")]
87+
Worker["Celery worker"]
88+
Validator["rocrate-validator"]
89+
Store[("S3-compatible store")]
90+
91+
Client --> API
92+
API -->|metadata-only: inline| Validator
93+
API --> Broker --> Worker --> Validator
94+
Worker <--> Store
95+
```
96+
97+
The worker runs its stages strictly in order: fetch, validate, persist, webhook; so a storage write failure can never be followed by a success notification, and every outcome (including `error` outcomes) is persisted so a later `GET` reflects what happened.
98+
99+
## Project structure
100+
101+
```
102+
app/
103+
├── __init__.py # app factory: config, blueprints, error handlers, request IDs
104+
├── health.py # /healthz and /readyz
105+
├── storage/ # object-storage abstraction
106+
│ ├── base.py # StorageBackend protocol
107+
│ ├── s3.py # boto3 implementation (any S3-compatible store)
108+
│ ├── memory.py # in-memory backend (tests / local)
109+
│ └── errors.py # StorageError, ObjectNotFound
110+
├── crates/ # crate identity, layout, resolution
111+
│ ├── ids.py # Crate ID validation
112+
│ ├── layout.py # object keys
113+
│ └── resolver.py # deterministic zip/directory resolution
114+
├── validation/ # validation boundary
115+
│ ├── results.py # ValidationOutcome (valid/invalid/error)
116+
│ └── runner.py # wraps rocrate-validator
117+
├── ro_crates/routes/ # HTTP endpoints (metadata + ID-based)
118+
├── services/ # request handling and logging
119+
├── tasks/validation_tasks.py # Celery task: fetch, validate, persist, webhook
120+
└── utils/ # validated settings, webhook delivery
121+
```

docs/five-safes.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Five Safes RO-Crate validation
2+
3+
The [Five Safes RO-Crate 0.4 profile](https://trefx.uk/5s-crate/) describes an RO-Crate used to request and record workflow runs on sensitive data in Trusted Research Environments (TREs), supporting the Five Safes framework. The RO-Crate Validation Service validates against this profile when `profile_name` is set to `five-safes-crate`.
4+
5+
!!! warning
6+
Note that the Five Safes RO-Crate 0.4 profile is not bundled with the base validator, so the service needs the profile to be made available. There are two ways to do this, described below.
7+
8+
## Getting a service with the profile
9+
10+
The prebuilt `ghcr.io/esciencelab/ro-crate-validation-service-fivesafes-profile` image packages the `five-safes-crate` profile with the standard RO-Crate Validation Service. The image also carries a pre-warmed validation cache, so it supports offline validation (`VALIDATION_OFFLINE=true`) inside restricted networks. The profile version is fixed when the image is built, and is recorded in the image label `org.ro-crate-validation-service.five-safes-profile-version`.
11+
12+
Alternatively, you may run the standard service image with the profile directory mounted and `EXTRA_PROFILES_PATH` set, [as described in custom profiles](installation.md#custom-profiles).
13+
14+
The `five-safes-crate` profile itself is defined in the [eScienceLab rocrate-validator fork](https://github.com/eScienceLab/rocrate-validator).
15+
16+
## Validating a Five Safes RO-Crate
17+
18+
A [complete example crate](https://github.com/eScienceLab/rocrate-validator/blob/five-safes-0.7.4-beta/tests/data/crates/valid/five-safes-crate-result/ro-crate-metadata.json) is available in the fork's test data.
19+
20+
!!! note
21+
The current `-fivesafes-profile` image pairs the profile with a base profile for RO-Crate 1.1, whilst the profile itself expects RO-Crate 1.2. The walkthrough below mounts the matched profile set instead.
22+
23+
For this walkthrough, run the service from a checkout of this repository, with the repository's profile set mounted in place of the bundled profiles:
24+
25+
```bash
26+
docker run --rm -p 5001:5000 \
27+
-e FLASK_APP=wsgi.py \
28+
-e PROFILES_PATH=/app/profiles \
29+
-v "$PWD/tests/data/rocrate_validator_profiles:/app/profiles:ro" \
30+
ghcr.io/esciencelab/ro-crate-validation-service-fivesafes-profile:latest
31+
```
32+
33+
Download the example `ro-crate-metadata.json`:
34+
35+
```bash
36+
curl -sO https://raw.githubusercontent.com/eScienceLab/rocrate-validator/five-safes-0.7.4-beta/tests/data/crates/valid/five-safes-crate-result/ro-crate-metadata.json
37+
```
38+
39+
and validate it with `profile_name` set to `five-safes-crate`:
40+
41+
```bash
42+
jq -Rs '{crate_json: ., profile_name: "five-safes-crate"}' ro-crate-metadata.json | curl -X POST http://localhost:5001/v1/ro_crates/validate_metadata -H 'Content-Type: application/json' -d @-
43+
```
44+
45+
The crate conforms, so the response (abridged) is:
46+
47+
```json
48+
{
49+
"status": "valid",
50+
"profile": "five-safes-crate",
51+
"created_at": null,
52+
"detail": {
53+
"issues": [],
54+
"passed": true
55+
}
56+
}
57+
```
58+
59+
To see how conformance issues are reported, remove something the profile requires, such as the `CreateAction` recording the requested workflow run. Validate again:
60+
61+
```bash
62+
jq '."@graph" |= map(select(."@type" != "CreateAction"))' ro-crate-metadata.json > broken.json
63+
```
64+
65+
```bash
66+
jq -Rs '{crate_json: ., profile_name: "five-safes-crate"}' broken.json | curl -X POST http://localhost:5001/v1/ro_crates/validate_metadata -H 'Content-Type: application/json' -d @-
67+
```
68+
69+
The result will now show `invalid`, and each entry in `detail.issues` identifies the failed check, its severity, and the entity at fault (abridged):
70+
71+
```json
72+
{
73+
"status": "invalid",
74+
"profile": "five-safes-crate",
75+
"detail": {
76+
"issues": [
77+
{
78+
"check": {
79+
"identifier": "five-safes-crate-0.4_25.1",
80+
"name": "mentions"
81+
},
82+
"severity": "REQUIRED",
83+
"message": "`RootDataEntity` MUST reference at least one `CreateAction` through `mentions`",
84+
"violatingEntity": "./"
85+
}
86+
],
87+
"passed": false
88+
}
89+
}
90+
```
91+
92+
Complete RO-Crates work the same way through the storage-backed endpoints: upload the crate as `crates/<id>.zip` (or a directory under `crates/<id>/`), then queue validation with the profile:
93+
94+
```bash
95+
curl -X POST http://localhost:5001/v1/ro_crates/my-5s-crate/validation -H 'Content-Type: application/json' -d '{"profile_name": "five-safes-crate"}'
96+
```
97+
98+
The [API reference](api.md) covers the endpoints, results and webhooks in full.

0 commit comments

Comments
 (0)