Released docs. You are viewing the documentation published with v0.34.0. Development docs are available at Latest.
An author has handed you a reviewed Registry Casework policy package and you want it serving. At
the end of this page a casework process serves that package against PostgreSQL behind your own
TLS proxy, answers GET /ready, and has a directory whose teams serve every queue the package
declares.
Authoring, packaging, and the policy inside the package stay with the author; authoring a Casework policy covers that side. This page starts from the package directory and ends at a serving deployment.
What you provide
Section titled “What you provide”- One PostgreSQL database and two login credentials. The migration credential owns the Casework schema and applies migrations. The runtime credential serves requests and the background workers. Casework creates no extension and no role, and its migrations carry no version-gated SQL; the product’s own database suites run against PostgreSQL 17. The runtime requires TLS on both connections and refuses a plaintext one.
- One OpenID Connect issuer. Casework verifies bearer access tokens and issues none. Configure
the issuer to add the exact human-identity assertion,
registry_actor_kind: humanby default, only to interactive human sessions, and to issue the Requester scope to the services that call on a person’s behalf. A Staff, Supervisor, or Administrator token without that assertion is refused even when it carries valid scopes and names a directory member. - A TLS proxy or ingress in front of the listener. The process serves plain HTTP on a private or container-private address and reads no forwarded header, so TLS termination, HSTS, and client addresses stay with your proxy.
- Secrets the process can read. Every credential in the runtime file is a reference:
secret:file/<name>names an owner-only file under one file provider root, andsecret:env/<NAME>names an environment variable. - Durable storage for the audit file. The runtime appends a keyed hash chain to one JSONL file,
seals it under an eight-digit numeric suffix once it reaches 10 MiB, and deletes no sealed file.
It holds a process-lifetime lock on a sentinel file beside it, so exactly one process writes one
chain. The directory must belong to the runtime user with mode
0700. - A host for one process, or a place to run the container image.
What it does not need
Section titled “What it does not need”A submitted-context review deployment that declares no source needs no Base Registry Engine (BReg): the review requests its callers create live in Casework’s own database, and the source bindings in the runtime file exist only for the sources a package declares. Work, claims, drafts, attempts, accountability records, and the idempotency ledger all live in PostgreSQL, so no message broker and no cache take part in a request. Browser traffic reaches your own application host, which calls Casework server to server, so the API enables no CORS.
Install the runtime
Section titled “Install the runtime”Install the binaries on the host that will serve, and on the operator host that holds the migration credential:
curl -fsSL https://github.com/registrystack/registry-stack/releases/latest/download/casework-install.sh | bashcasework --versioncaseworkctl --versionThe installer verifies every downloaded binary against the release SHA256SUMS before anything
reaches the install directory, and installs casework and caseworkctl together or not at all.
The installer does
not verify release authenticity. Replace | bash with | less to read it before running it on a
host you operate. CASEWORK_INSTALL_DIR selects the install directory, and the default is
~/.local/bin; CASEWORK_VERSION pins one release; CASEWORK_ASSET_DIR installs from a directory
you verified yourself. Binaries are published for linux-amd64, linux-arm64, and macos-arm64,
and the release also publishes casework-install.sh as a movable alias of the pinned installer.
The container image is ghcr.io/registrystack/casework:v0.30.0, built on a distroless nonroot base
for linux/amd64. Its entrypoint is /usr/local/bin/casework, its default arguments are
--runtime-config /etc/registry-casework/runtime.yaml serve, and it exposes port 8100. It carries the
runtime binary and its license and nothing else: no shell, no healthcheck command, and no
caseworkctl. migrate is a casework subcommand, so the same image runs it, given the
migration credential instead of the runtime one; doctor belongs to caseworkctl, so it still
runs from a separate operator host that has that binary installed. One directory is
writable, /var/lib/registry-casework/audit, owned by UID and GID 65532 with mode 0700, for the
audit chain; mount the runtime file, the policy package, and the secret root read-only. The
release manifest published beside the binaries records the promoted digest of the image; pin that
digest rather than the movable tag.
Provision PostgreSQL
Section titled “Provision PostgreSQL”Create two login roles: a migration role that owns the schema and applies migrations, and a runtime role the serving process and background workers use. Keep the migration credential off the serving host when no migration is in progress.
CREATE ROLE casework_migration LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS PASSWORD '<migration-password>';CREATE ROLE casework_runtime LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS PASSWORD '<runtime-password>';CREATE DATABASE casework;Casework’s migrations create ordinary tables in the database’s default public schema and declare
no extension and no other schema, so the migration role owns that one schema and the runtime role
only reads and writes through it. As an administrator, in the casework database:
REVOKE ALL ON DATABASE casework FROM PUBLIC;GRANT CONNECT ON DATABASE casework TO casework_migration, casework_runtime;ALTER SCHEMA public OWNER TO casework_migration;REVOKE ALL ON SCHEMA public FROM PUBLIC;GRANT USAGE ON SCHEMA public TO casework_runtime;ALTER DEFAULT PRIVILEGES FOR ROLE casework_migration IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO casework_runtime;ALTER DEFAULT PRIVILEGES FOR ROLE casework_migration IN SCHEMA public GRANT USAGE, SELECT ON SEQUENCES TO casework_runtime;Those default privileges cover every table and sequence migrate creates
afterwards, because it always runs as the migration role. After the first migrate, or when
reusing a database an earlier session already migrated, grant the runtime role the same privileges
on the objects that already exist:
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO casework_runtime;GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO casework_runtime;Casework creates no extension, no schema, and no role of its own, so a missing grant here surfaces
as a permission-denied database error at serve rather than at migrate.
Write the runtime file
Section titled “Write the runtime file”The runtime file binds one policy package to one database, one issuer, one listener, and one binding per source the package declares. It is a deployment artifact: keep it outside the authoring project, and put no credential in it.
apiVersion: registry.registrystack.org/casework-runtime/v1alpha1kind: CaseworkRuntimeConfigpackage: # The selected package always contains the policy at casework.yaml. root: /etc/registry-casework/package # Optional: the policyDigest of the package you reviewed; any other package is refused. expectedPolicyDigest: sha256:0000000000000000000000000000000000000000000000000000000000000000listener: # The proxy in front of this private listener terminates TLS. bind: 10.42.0.7:8100 # operator-controlled-upstream for production; development-loopback only for loopback. tlsTermination: operator-controlled-upstream # container-private also accepts a wildcard bind on a private container network. networkExposure: private-addresssecretProviders: file: # Owner-only files, one per reference. root: /etc/registry-casework/secretsdatabase: runtimeUrlRef: secret:file/casework-runtime-database-url migrationUrlRef: secret:file/casework-migration-database-url # Optional: a private certificate authority for the PostgreSQL connection. trustedRootCertificateRef: secret:file/casework-database-root.pemauthentication: oidc: issuer: https://identity.example.org/realms/registry audience: urn:example:casework scopeClaim: scope humanIdentity: claim: registry_actor_kind value: human jwksSource: kind: discoveryaudit: path: /var/lib/registry-casework/audit/casework.ndjson hashKeyRef: secret:file/casework-audit-keysources: # One entry per source the package declares, keyed by its exact source id. professional-licences: baseUrl: https://registry.example.org readerProfile: casework-reader tokenEndpoint: https://identity.example.org/realms/registry/token clientIdRef: secret:file/breg-reader-client-id clientAssertionKeyRef: secret:file/breg-reader-key webhookSecretRef: secret:file/breg-casework-webhook eventSource: urn:registrystack:registry:professional-licences:instance:professional-licences-1| Section | What it binds |
|---|---|
package | The absolute root of the package containing casework.yaml, its manifest, and exact source descriptions, and optionally the one policy digest the runtime may load from it. |
listener | The listener address and the transport boundary the deployment declares for it. |
secretProviders | The explicitly enabled file and environment secret providers. |
database | Secret references for the runtime and migration connection URLs, and an optional trusted root certificate for the PostgreSQL connection. |
authentication | The OpenID Connect issuer, audience, claim names, human-identity assertion, and the source of the issuer’s keys. |
audit | The path of the external JSONL chain and the reference to the key that chains it. |
sources | One BReg binding per source the package declares: base URL, reader profile, token endpoint, and references to the client identifier, client assertion key, and webhook secret. The set of keys must equal the set of declared source ids exactly, and a package with no source needs no block. |
The package root, file secret root, and audit path must be absolute. An environment reference is
valid only when secretProviders.environment: {} explicitly enables that provider.
The listener boundary is checked, not advisory. With operator-controlled-upstream and
private-address, the address must be loopback or private (IPv4 private range or IPv6 unique
local); container-private also accepts a wildcard bind for a container on a private network.
development-loopback accepts only a loopback address with private-address, and it is the one
mode that serves an authored project with no manifest beside it. Apply HSTS on the proxy’s TLS
responses; the runtime adds its remaining security headers and Cache-Control: no-store to every
response it returns.
scopeClaim defaults to registry_scopes for compatibility with earlier deployments. Stock
ThunderID emits the standard OAuth scope claim, so set scopeClaim: scope explicitly when using
that issuer. humanIdentity defaults to the claim
registry_actor_kind with the value human, must
differ from scopeClaim, and no access profile in the package may use it as its principal claim.
Each access profile names its own principalClaim; there is no runtime-wide principal claim.
The runtime accepts RS256 and ES256 signatures and admits one access-token type, the RFC 9068
media type spelled at+jwt or application/at+jwt. A token carrying any other typ, a plain
JWT included, is refused, so an issuer that mints ordinary JWTs for this audience must be
configured to mint the access-token type.
With the discovery source, the runtime reads the issuer’s discovery document at startup and
fetches its keys from the jwks_uri that document names; an optional jwksUri in this file
overrides that one value and leaves the rest of discovery in place. Prefer the static alternative
when the Casework host cannot reach discovery, or when you pin the issuer’s keys deliberately:
jwksSource: kind: static documentRef: secret:file/casework-issuer-jwksThe document must be a JWKS with at least one RSA or elliptic-curve key, each carrying a distinct
non-empty kid. It is read once, at startup, so a key rotation at the issuer is a replacement of
that secret and a restart of every serving process.
Each source binding also accepts trustedRootCertificatesRef for a private certificate authority
in front of the source, requestTimeoutMilliseconds and connectTimeoutMilliseconds, which
default to 30000 and 10000, accept at most 300000, and are refused when the connect timeout is the
larger of the two, and reconciliationIntervalMilliseconds, how often the runtime re-reads the
source for work the source’s own notifications did not deliver, which defaults to 60000 and accepts
1000 to 3600000. If one pass outlasts the interval, Casework skips missed ticks instead of
replaying them back-to-back against the source.
A binding has no event type to set. Each lifecycle event carries, as its ce-type, the id of the
hook caseworkctl source add wrote on the request entity, casework-lifecycle-v1-<entity>, and
Casework refuses an event whose type is not the one derived from the request entity its body
names. A binding that still carries eventType is refused at startup.
Every source-backed work item, task grant, and saved attempt carries the source’s binding
generation, computed from the source id, the binding’s eventSource, and the digest of the
imported source description. Changing one of those three supersedes the source’s open work items
and opens fresh ones on the next observation, because the source now means something else. Every
other binding field is operational: rotating a client key, moving tokenEndpoint or baseUrl,
changing resource, scopes, readerProfile, the trusted root, a timeout, the
interval, displayReference, or contextProjection keeps the generation, and with it every
in-flight work item, claim, and durable attempt.
A pinned document keeps accepting the keys it holds and refusing every other one, so an issuer rotation that nobody mirrors into the secret refuses fresh logins. Replace the secret and restart the process as part of the rotation, and confirm the replacement keys with the issuer’s operator before they become the anchor for verification.
Create the secret files
Section titled “Create the secret files”Create each file as the user that runs casework, because the resolver refuses a file owned by
anyone else:
install -d -m 0700 /etc/registry-casework/secrets(umask 077; openssl rand -hex 32 | tr -d '\n' \ > /etc/registry-casework/secrets/casework-audit-key)(umask 077; printf '%s' 'postgresql://casework_runtime:<runtime-password>@db.example.org:5432/casework' \ > /etc/registry-casework/secrets/casework-runtime-database-url)(umask 077; printf '%s' 'postgresql://casework_migration:<migration-password>@db.example.org:5432/casework' \ > /etc/registry-casework/secrets/casework-migration-database-url)Each file must be a regular file owned by the effective user, with mode 0400 or 0600, exactly
one link and no symbolic link to it, at most 64 KiB, non-empty, and free of NUL bytes, so generate
key material as text rather than raw bytes. The name starts with a lowercase letter and carries
lowercase letters, digits, ., _, and -. The bytes are used exactly as written, neither
trimmed nor decoded, so write them without a trailing newline. The audit key needs at least 32
bytes, which 64 hexadecimal characters satisfy. Percent-encode a password that carries reserved
characters, and give the database user in each URL the role that owns the matching credential. A
reference that cannot resolve is refused by name, together with the rule it broke, and the
resolved bytes never appear in a message or a log.
The external audit file is a keyed hash chain, and the runtime authenticates the retained chain before it appends to it. A lost or replaced key leaves every retained record unverifiable and stops the process from continuing that file, and no command reconstructs it. Back the key up separately from the audit storage, and keep both for as long as the records must stay provable.
Verify the package before it opens the listener
Section titled “Verify the package before it opens the listener”The runtime verifies the package every time it loads the runtime file, before it opens the
database, contacts the issuer, or binds the listener. It reads casework.package.json beside
casework.yaml, recomputes the policy digest over the sorted file list, checks every file’s
SHA-256 digest and byte count, and refuses a package directory holding any file the manifest does
not declare or any symbolic link. A start that verifies the package records the policy digest in
the runtime log before anything else happens.
With tlsTermination: operator-controlled-upstream, an absent manifest is a refusal rather than a
fallback to the authored project.
A verified package proves its files match its own manifest, not that it is the package you
reviewed. Set package.expectedPolicyDigest to the policyDigest that caseworkctl package
reported for the reviewed package, and the runtime starts only on that package: a package naming
another digest, or a directory with no manifest, is refused with both digests named, so a
replaced package directory cannot change the policy a restart loads.
Each of these refusals is one run’s entire output on standard error, written before the listener binds, and each exits non-zero:
casework: operator-controlled production requires a verified Casework policy packagecasework: the Casework policy package is invalidcasework: package.expectedPolicyDigest is sha256:4f0c…, but package.root holds the package with policy digest sha256:9b2e…casework: the Casework runtime configuration is not valid YAMLcasework: the Casework runtime configuration is invalidcasework: the Casework secret-provider configuration is invalid; secretProviders.file.root must be an absolute pathThe first line reports a production configuration with no manifest. The second reports a manifest
whose digests, byte counts, or file set do not match the directory. The third reports a verified
package other than the one package.expectedPolicyDigest names, shown here with both digests
shortened. The fourth and fifth report the runtime file itself: unparseable YAML, and a
configuration the checks refuse, which covers an invalid listener boundary, an empty issuer or
claim name, a human-identity claim equal to the scope claim, an access profile using the
human-identity claim as its principal claim, and a sources map that does not match the declared
source ids exactly. The sixth reports a secret provider root the resolver cannot use.
Migrate and serve
Section titled “Migrate and serve”Apply the migrations with the migration credential, from the operator host:
casework --runtime-config /etc/registry-casework/runtime.yaml migratemigrate loads the same runtime file, resolves migrationUrlRef, takes a PostgreSQL advisory
lock, and applies every unapplied migration in order under its ledger, so a second migrate on the
same database waits rather than racing it. Run it before the first serve and before rolling out a
release that adds migrations.
The unified review schema arrives in a single migration that replaces the experimental hosted-item
tables of Casework 0.32.0 and earlier. No data is carried over, and this release has
no in-place legacy conversion command. When those tables are empty, migrate replaces them. When
any of them still holds a row, such as an in-flight hosted item or a retained accountability
record, migrate refuses before applying anything and writes nothing. The refusal begins the Casework database holds hosted work that schema migration 15 would drop, then names each table that holds rows with
its row count, such as casework_hosted_items (1 row).
Keep that database with the release that wrote it long enough to export the work it holds, take
the normal backup, and provision a fresh Casework database for the unified review package. Do not
delete rows, edit the migration ledger, or run migration SQL by hand to get past the refusal. For a
disposable local session, use caseworkctl dev stop PROJECT --remove and start it again.
Upgrading from Casework 0.33.0 or earlier supersedes the open work items of BReg sources once. That release also computed the source binding generation from the credential, transport, and presentation fields, so the generation stored with each work item differs from the one this release computes, and each source’s next observation opens fresh work items beside the superseded ones. Claims, drafts, and pending attempts on the superseded items do not carry over. Finish or settle source-backed work before upgrading.
Upgrade from a release that rotated the audit file
Section titled “Upgrade from a release that rotated the audit file”Casework 0.33.0 and earlier rotated the audit file: a full file became casework.ndjson.1, older
files moved up to casework.ndjson.49, and the oldest was deleted. This release seals a full file
as casework.ndjson.00000001 and keeps every sealed file, and it continues only a chain whose
first retained record is the chain’s first. Before the first serve of this release:
-
Stop every earlier
caseworkprocess. While one still holds the lock beside the audit file, the new process refuses to start withanother process holds the single-writer lock. -
List the audit directory. If no numbered file such as
casework.ndjson.1sits beside the audit file, the file never reached 10 MiB, and the new release continues its chain as it is. If one does, move the audit file and every numbered file beside it into an archive directory outside the audit directory:Terminal window install -d -m 0700 /var/lib/registry-casework/audit-archivecd /var/lib/registry-casework/auditmv casework.ndjson casework.ndjson.[0-9]* /var/lib/registry-casework/audit-archive/The new process then starts a fresh chain at the configured path. The archive stays keyed with the same audit key, so keep both for as long as your retention policy requires. An event the earlier process appended but never marked published appears again at the start of the fresh chain. Without the move, the new process refuses to start and names the numbered file it found.
-
Make the audit directory mode
0700, and the audit file and its.lockfile mode0600, all owned by the runtime user. The earlier release accepted a group-readable directory; this one refuses it withaudit directory must be owner-only.
The migration credential owns the schema, and the serving process never needs it. Supply it only for the duration of a migration, and keep the runtime credential as the one the service manager holds.
Then run the service:
casework --runtime-config /etc/registry-casework/runtime.yaml serveSet CASEWORK_LOG to choose the operational log level: error, warn, or info, which is the
default when the variable is unset. Any other value, including a tracing filter directive that
would enable a dependency’s own debug or trace logging, refuses to start. serve writes structured
JSON to standard output at every level, so redirecting it to a file never carries colour codes, and
the level applies to Casework’s own logging only; the ambient RUST_LOG variable has no effect on
this process.
A restart of this same version against the same package, runtime file, and database is the normal
recovery path after process or host interruption. It reloads the verified package and resumes the
durable review, clock, result-delivery, and reconciliation state. Run migrate again only when the
release procedure calls for it; never use a restart as a legacy-state conversion.
Run serve under your service manager and let it restart the process. serve refuses to start on
an unverified production package, a listener the declared boundary does not allow, source bindings
that do not match the declared sources, a database reference or connection it cannot use, an OIDC
issuer it cannot initialize, and an audit secret or audit file it cannot open. Once it is serving,
the listener stops when any supervised background worker stops, and the process exits reporting the
stopped worker, because a deployment whose maintenance, reconciliation, or audit publication loop
is gone keeps neither its deadlines nor its accountability records.
Probe GET /health for liveness: it answers 200 as long as the process runs. Probe GET /ready
before routing traffic: it answers 503 while audit publication is unhealthy or PostgreSQL is
unreachable, and 200 otherwise. Readiness checks the audit publisher first, then the store.
The audit publication worker runs once a second: it reads pending records, appends them to the
keyed chain, and marks them published. A failed pass makes readiness fail, logs only the stage that
failed (pending-read, record-identity, sink-append, or published-mark) rather than the
record, and retries; a passing run restores readiness. A maintenance pass runs every two seconds
and a source reconciliation pass per bound source at that binding’s
reconciliationIntervalMilliseconds, every 60 seconds unless set. A source outage stays visible
on the affected work-item operations rather than turning an inbox into an apparently complete empty
page. When the source reader fails, the runtime logs Casework source reader request to BReg failed
with the cause, such as the refusal status and problem code or the token request failure, once when
the cause appears or changes. A missing record is not a reader failure and is not logged; a 404
from the registry contract, readiness, or a list is. The next successful reader request logs
Casework source reader requests to BReg succeed again. That entry means BReg answers the reader
again, not that reconciliation has caught up: a pass that still cannot apply the source logs
Casework reconciliation pass did not complete.
Between a source change and the reconciliation that applies it, an item whose binding moved within
the same source generation stays in the inbox, the next-item result, and holdings, and its view,
history, and clocks stay readable with the binding Casework last applied. It offers no actions.
Operations that act on the item or its tasks are refused with work-item.proposal-changed: claim,
release, drafts, decisions, attempt recovery, assignment, delegation, task preview, listing, and
approval, and caseload move apply. A caseload move preview is refused as a whole while such an item
is among the items it reads.
Establish directory authority
Section titled “Establish directory authority”A serving process has no directory until an Administrator creates one, and a queue with no serving
team holds work nobody can reach. Bootstrap the first team with an Administrator access token that
carries the human-identity assertion. If-Match carries the directory revision the caller expects,
which is "0" for the first call, and Idempotency-Key binds the request to one exact mutation:
POST /v1/directory/bootstrap HTTP/1.1Authorization: Bearer <administrator-access-token>Registry-Casework-Profile: administratorIf-Match: "0"Idempotency-Key: <caller-selected-key>Content-Type: application/json
{ "teamId": "licence-review", "staff": [ { "issuer": "https://identity.example.org/realms/registry", "subject": "<staff-subject>" } ], "supervisors": [ { "issuer": "https://identity.example.org/realms/registry", "subject": "<supervisor-subject>" } ], "queueId": "corrections"}Afterwards, replace one team at a time with PUT /v1/directory/teams/{team_id}, carrying the same
three headers with If-Match set to the revision the last directory response reported, and a body
of staff, supervisors, and servedQueues. The replacement is whole: the members and queues it
names become the team. A served queue belongs to one team, so a queue another team already serves
is refused with precondition.failed; remove it from that team first, then assign it. A team holds
at most 100 staff, 100 supervisors, and 100 served queues, and the whole directory document fits
2 MiB. Authority changes take effect immediately, newly ineligible held items are released through
maintenance, and items with an unresolved source attempt stay held for a later retry. Administrator
authority covers the directory and holiday maintenance only; it grants no item payload access and
no source review or application authority.
Check readiness with doctor
Section titled “Check readiness with doctor”caseworkctl doctor opens every live dependency the runtime opens, without binding a listener:
caseworkctl doctor --runtime-config /etc/registry-casework/runtime.yamlIt checks the configuration, the exact imported source descriptions, each source connection and its reader grants, the database, the OIDC issuer, and directory readiness, and it stops at the first one that refuses and names it. Its secret preflight resolves the audit reference before it opens anything, and a reference that cannot resolve is reported by name with the rule it broke. Directory readiness requires a team serving every queue the package declares, and a gap is reported as an instruction to complete the queue assignments as an Administrator. A run that reaches the end prints a report naming each check and each source it contacted.
doctor answers whether the deployment is ready to do work. It does not summarize an individual
review. Inspect the Casework request, task, context, result, and result-feed resources for review
progress, and the BReg request’s data.request.review projection for submission, delivery,
application, and recovery state. A durable submission reports its fixed recoveryDeadline.
Automatic application jobs report bounded attempts and expose nextAttemptAt only while queued
or applying. Use application.state with recovery.code for recovery decisions. Do not query
either product’s database directly.
Change the active package
Section titled “Change the active package”The runtime verifies and loads the package once, at startup. Install the reviewed successor into a
new directory beside the current one, point package.root at the new directory, and
restart or roll out the process. Keep the previous package directory until the new one serves, so a
rollback is a configuration change rather than a rebuild. A package or source binding change that
alters the source binding generation supersedes the source’s open work items. Rolling that change
back does not reopen the work items it superseded: they stay superseded, and the source’s next
observation opens fresh work items beside them. A change to operational binding fields alone keeps
the generation and the work items.
A running process keeps the policy it verified at startup, so files replaced under it leave the deployment serving a package that no longer matches its own manifest, and the next restart refuses the directory. Install the successor beside the current package and restart.
Activating a package does not rewrite running clock occurrences: each keeps the clock policy and
calculation pinned when it started. Holiday changes are a separate, deliberate operation. An
Administrator publishes an immutable holiday-set revision, then previews and applies the change in
batches of at most 100 active occurrences, repeating both until every occurrence is pinned to the
selected revision. A preview records the expected calculation generation for each occurrence and is
bound to the actor and selected profile for 15 minutes; an expired preview returns
clock.recompute-preview-expired, and an already-applied preview or a changed generation returns
precondition.failed. Create and review a new preview after either response.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Next move |
|---|---|
operator-controlled production requires a verified Casework policy package, or the Casework policy package is invalid | The first names a production configuration with no manifest, the second a package directory whose files disagree with the manifest. Point package.root at the installed package, and reinstall it from the reviewed artifact rather than editing it. |
the Casework runtime configuration is invalid | Check the listener against tlsTermination and networkExposure, the non-empty issuer and claim names, the human-identity claim against scopeClaim and the package’s profiles, and sources against the declared source ids. |
the Casework secret-provider configuration is invalid | The file provider root is relative or unusable. Write an absolute root. |
| A secret reference is refused | The named file must be a regular file owned by the running user, mode 0400 or 0600, one link, no symbolic link, non-empty, and free of NUL bytes. |
Startup refuses the database or doctor stops at it | Check the resolved URL’s user and database name, TLS on the server, and the trusted root reference when the server uses a private authority. |
| Startup refuses the OIDC issuer | Check discovery reachability from the Casework host, or the pinned JWKS document’s keys and their distinct kid values. |
the Casework database schema version N is newer than this binary supports (M) | A newer release already migrated this database. Run that release or a later one; Casework does not migrate a schema down. |
the Casework database holds hosted work that schema migration 15 would drop | The database still holds hosted work from Casework 0.32.0 or earlier. Keep it with that release until its work is exported, then migrate a fresh database as Migrate and serve describes. |
the Casework audit journal could not be initialized naming a numbered file rotated by an earlier Casework release, or saying the file does not begin at the first record of its chain | The audit directory still holds the layout of Casework 0.33.0 or earlier. Archive it as the upgrade steps describe. |
the Casework audit journal could not be initialized: audit directory must be owner-only | Set the audit directory to mode 0700, owned by the runtime user. |
GET /ready returns 503 while GET /health returns 200 | Audit publication or PostgreSQL is failing. Read the runtime log for the failed audit stage, and check the audit file, its lock, and the database. |
profile.not-human on a token you believe is human | The issuer did not add the configured human-identity claim to that session. Check the claim name and value against the issuer’s mapping for interactive sessions. |
| Work items report a source outage | The bound source is unreachable or refusing the reader. Run caseworkctl doctor, which names the failing source, read the runtime log’s Casework source reader request to BReg failed entry for the cause, then check that binding’s base URL, token endpoint, and reader grants. |
- Author a Casework policy for the package this deployment serves, and the checks that produce it.
- Erase and settle retained source work for the operator commands that erase source-backed payload copies and settle an uncertain source attempt.
- How Casework works for the model behind queues, claims, attempts, and the directory.
- Read the Casework API for the exact headers, request schemas, and recovery problems every operation returns.