Skip to content
Registry StackDocsDevelopment (unreleased)

Decide your first ReviewRequest

For the operator

View as Markdown

You will submit one ReviewRequest as a producer, decide it as staff, and read the answer as the producer. The request carries its own snapshot, so no source registry is needed.

Outcome
One ReviewRequest decided by staff and read by its producer, who is refused the task.
Time
About 20 minutes, plus image downloads
Level
Local evaluation only
Prerequisites
Running DockerjqFree loopback ports 8092, 8093 and 15433The tools required by the release verification procedure

Download one exact rstack-vX.Y.Z-install.sh and the verification material from the same Registry Stack release. Follow the release verification procedure before running the installer. From the verified release directory, install the casework component:

Terminal window
tag="vX.Y.Z"
bash "rstack-${tag}-install.sh" casework
rstack casework --version

The managed installation exposes rstack on your PATH. The casework component supplies the Casework runtime and its command-line tools from the same release.

Create a project from the standalone template and work inside it:

Terminal window
mkdir -p tutorial-work
rstack casework init tutorial-work/casework --template standalone-decision
cd tutorial-work/casework
{
"command": "init",
"status": "complete",
"created": "tutorial-work/casework",
"next": "Run caseworkctl check, test and types, then caseworkctl dev start.",
"template": "standalone-decision"
}

The project declares one ReviewPolicy, decision: who may ask, which team answers, and the outcomes allowed. Check it offline and run its fixture:

Terminal window
rstack casework check .
rstack casework test .
{
"policyBindings": [
{"digest": "sha256:<digest>", "id": "decision", "version": "1"}
]
}

The digest pins the policy’s behavior. To read what that behavior is, explain it:

Terminal window
rstack casework explain .
{
"contextCustody": "admitted bounded snapshot",
"refusals": [
"untrusted producer",
"policy binding mismatch",
"concealed source",
"changed subject or contract",
"stale task revision",
"ineligible reviewer"
]
}

Casework keeps the snapshot the producer sends and refuses each listed case.

Terminal window
rstack casework dev start
{
"status": "complete",
"outcome": "ready",
"caseworkUrl": "http://127.0.0.1:8092",
"directory": {"revision": 1, "teams": 1}
}

PostgreSQL and a token issuer run in Docker, with one staff team seeded. .casework/dev/ holds private credentials; keep it out of version control. The API commands get short-lived tokens for the registered local clients in memory.

Save the request. It names the policy, the subject under review, and the snapshot a reviewer will read:

request.json
{
"policy": {"id": "decision"},
"subject": {
"source": "standalone",
"type": "batch",
"id": "LIC-2026-0041",
"version": "1",
"digest": "sha256:6a3167d79ae3308448b24f2c9c0b9ac1cf116a7ad8be0c167c7e8660e43662bb"
},
"requesterReference": "LIC-2026-0041",
"context": {
"mode": "submitted",
"snapshot": {"reference": "LIC-2026-0041", "summary": "Review licence batch LIC-2026-0041"}
}
}

The subject digest is the producer’s own fingerprint of the version it asks about, here the SHA-256 of the snapshot’s compact JSON; Casework binds the request and its result to it. Send it:

Terminal window
rstack casework api /v1/review-requests \
--client requester \
--idempotency-key tutorial-review-1 \
--input request.json \
--output accepted.json
jq . accepted.json
request_id="$(jq -r .requestId accepted.json)"
{
"requestId": "<id>",
"policy": {"id": "decision", "version": "1", "digest": "sha256:<digest>"},
"submissionDigest": "<digest>"
}

The request named only the policy ID, so Casework bound it to the active version and the digest check printed. The same body under the same key returns this request, not a second one.

The request opened a ReviewTask. List it as staff:

Terminal window
rstack casework api '/v1/review-tasks?limit=25' \
--client staff \
--output tasks.json
task_id="$(jq -r '.items[0].taskId' tasks.json)"
task_revision="$(jq -r '.items[0].revision' tasks.json)"

Try to read it as the producer:

Terminal window
rstack casework api "/v1/review-tasks/$task_id" \
--client requester \
--output refused.json || test "$?" -eq 1
jq . refused.json
{"code": "operation.not-authorized", "status": 403, "title": "Operation not authorized"}

A producer may read its result, never the human task.

Claim the task at the revision you read, so no one else decides it while you work:

Terminal window
rstack casework api "/v1/review-tasks/$task_id/claim" \
--client staff \
--method POST \
--if-match "\"$task_revision\"" \
--idempotency-key tutorial-claim-1 \
--output claimed.json
jq . claimed.json
claimed_revision="$(jq -r .revision claimed.json)"
{
"revision": 2,
"state": {"held": {"holder": {"issuer": "http://127.0.0.1:8093", "subject": "<subject>"}}}
}

You hold the task at revision 2. Save one of the policy’s outcomes as decision.json:

decision.json
{"decision":{"type":"answer","outcome":"confirmed"}}

Send the decision:

Terminal window
rstack casework api "/v1/review-tasks/$task_id/decisions" \
--client staff \
--if-match "\"$claimed_revision\"" \
--idempotency-key tutorial-answer-1 \
--input decision.json \
--output /dev/null

The command exits successfully when Casework records the decision and answers 204.

Back as the producer, fetch the answer:

Terminal window
rstack casework api "/v1/review-requests/$request_id/result" \
--client requester \
--output result.json
jq . result.json
{"status": "answered", "outcome": "confirmed", "availableUntil": "<instant>"}

The ReviewResult repeats the request’s policy, subject and submission digest. It names no reviewer and is kept until availableUntil.

The next page continues with this session and its policy, so leave it running. When you are finished with Casework, run rstack casework dev stop --remove in tutorial-work/casework to delete the session’s containers, database and credentials.

How Casework works