Web Control Plane
The web control plane is the deployed, network-facing HTTP service for TRE Browser. It is distinct from the local daemon and from the separate TypeScript frontend that consumes its JSON contract. This Rust workspace owns the service, its protocol DTOs, and the versioned fixtures; it does not contain the frontend product.
Service Boundary
The service exposes a deliberately bounded browser surface:
- ORCID/OIDC login and a server-side browser session;
- datastore, study, resource, dictionary, and semantic/provenance discovery metadata, including bounded catalogue search across those discovery families;
- study access-request submission, requester status and withdrawal, and the custodian queue, rejection, and approval actions;
/health,/ready,/diagnostics,/version, and a limited/protocoldiagnostic surface.
The browser does not receive a daemon Session, raw database access, dataset
rows, datafile bytes, live runtime handles, or lake access. Discovery is
metadata-only. An approved request is a governed datastore workflow entry; it
is not asset-level access control and does not itself expose protected content.
The local daemon and CLI remain the interfaces for local live-session and automation workflow execution. The C ABI remains the lower-level interface for language bindings. All three faces use the same domain and application boundaries, but they have different callers, authority, and operational scope. Every substantive Web route crosses the authenticated stable-protocol boundary to the Trusted runtime; the Web process does not open PostgreSQL or Lake capabilities.
Identity And Authority
Five identity tiers must not be conflated:
| Identity or authority | Purpose | Does not imply |
|---|---|---|
| Web service identity | Dedicated mTLS identity used by Web to invoke its allowlisted stable-protocol operations. | Browser visitor identity, a Runtime login, arbitrary Session creation, or user/admin authority. |
| ORCID-authenticated browser visitor | Establishes a protected server-side browser session and identifies the requester. | Authenticated TRE user status, a Runtime client credential, or study access. |
| Runtime client credential | Server-held credential created by ordinary Runtime login when a visitor enters the custodian path. | The Web service identity, a live Datastore Session, or custodianship. |
Live Session | Datastore-scoped capability opened under the Runtime credential and retained in matching server-side Web session state. | Authority after closure or expiry, or authority for a different user or datastore. |
| Authenticated TRE user | Datastore identity established by the live Session, used for custodianship checks, governance, and audit attribution. | Browser metadata or custodial authority without the matching live Session. |
The Trusted runtime alone resolves and uses the read-only browser and narrow
access-request datastore authorities. The invoking Web service identity is
recorded separately from the represented visitor. Custodian approval runs the
governed study-access workflow through the matching live Session and remains
auditable under the Authenticated TRE user.
Browser Contract
The substantive routes are resource routes, not a browser wrapper around the
local daemon protocol. Their representative, synthetic responses are versioned
under docs/contracts/web-control-plane/v2.
The fixture manifest and stable IDs are the frontend handoff for the separate
web-application repository.
The companion web application specification and handoff maps supported browser journeys to these routes, specifies the same-origin session integration, and gives the receiving repository fixture and release expectations without prescribing its frontend stack.
Network responses provide X-Request-Id and X-Protocol-Version for support
and protocol correlation. POST /protocol is limited to daemon version and
doctor diagnostics; it is not a second grammar for discovery or access
requests. Public error responses use the documented stable error shape and do
not reveal authorization details.
The authenticated Domain collection route supports server-owned filtering with
optional text and text_mode parameters plus repeatable tag labels and
tag_id public references. Text matches the browser-safe name, description,
or URI using exact, prefix, or contains mode. Text and tags combine as text AND any tag; filtering occurs before deterministic ordering and paging. The
response returns normalized active filters, page count, has_more, and cursors
bound to the selected Datastore, Domain collection, filters, limit, and
protocol version. No client-side partial-page filtering is required.
Catalogue Search
Catalogue search is a Web v2 discovery capability. It lets an ORCID-authenticated browser visitor submit structured, protocol-owned search queries to one selected ready datastore; it is not a daemon search API, a federated/global catalogue, or an unbounded client-side filter over a downloaded catalogue.
| Search family | Route | Browser-safe result boundary |
|---|---|---|
| Studies | POST /browser/datastores/{datastore_id}/studies/search | Study Discovery metadata. |
| Datasets | POST /browser/datastores/{datastore_id}/datasets/search | Dataset Discovery metadata with Study context. |
| Datafiles | POST /browser/datastores/{datastore_id}/datafiles/search | Datafile Discovery metadata with Study context. |
| Dictionary variables | POST /browser/datastores/{datastore_id}/dictionary/variables/search | Dictionary Discovery metadata. |
| Model entities | POST /browser/datastores/{datastore_id}/model/entities/search | Semantic Discovery metadata. |
| Model relations | POST /browser/datastores/{datastore_id}/model/relations/search | Semantic Discovery metadata. |
Each route accepts the corresponding stable protocol search request as JSON.
Study search uses query.text and the nested facets.any_tags and
facets.any_domains groups; the other families preserve their noun-specific
predicates. This keeps text modes, selectors, validation, page bounds, and
deterministic ordering protocol-owned. Empty queries or predicate sets are rejected. The existing
unfiltered discovery routes remain the way to browse an entire available
collection. Where a reused request DTO has a daemon Session field, a
client-supplied value is rejected: the browser session and server-held
Discovery authority are the only Web identity and authority inputs.
Search is scoped to the path’s selected ready Datastore and uses server-held, read-only Discovery authority. It requires an ORCID-authenticated browser session, but its visibility is the broad TRE Browser Discovery view: Study access grants, TRE OAuth-group membership, custodianship, and access-request state neither broaden nor narrow results. A search result must be eligible for the equivalent browser Discovery projection, so search cannot bypass the ordinary route’s visibility or redaction decisions.
Results, Pagination, And Privacy
Every successful response identifies the selected datastore and search family,
returns the normalized active query or predicates, a deterministic ordering identifier and
description, the returned-page count, requested limit, has_more, and an
optional opaque next_cursor. It does not provide an exact total count. An
empty result is a successful 200 with an empty result array, zero returned
records, has_more: false, no next cursor, and a neutral server-provided empty
state.
Treat a returned cursor as opaque and use it only with the same search family,
datastore, normalized query or predicates, page limit, and protocol version. Do not
decode, manufacture, modify, silently substitute, or persist it beyond the
active pagination journey. A malformed or mismatched cursor is a 400
validation error; the caller must handle that explicitly before restarting
pagination.
Results remain metadata-only. Resource versions expose authoritative available or withdrawn lifecycle state. Datafile resource and search records may expose safe creation dates, stored size, format, and compression/encryption booleans. They exclude daemon session state, protected rows or bytes, previews and downloads, dataset row counts, datafile digests, Content-derived summaries, storage paths, credentials, raw SQL, diagnostics, membership lists, and custodian internals. Operational logging retains only correlation and bounded operational data—such as request ID, search family, datastore identity, status, duration, page count, and control outcome—not bodies, predicates, search text, cursors, result identifiers, cookies, ORCID identity, or authority details.
Failure And Resource Boundaries
Search requests are JSON bodies capped at 64 KiB. A request has at most 20 query terms or predicates and each text value is at most 256 characters; the family-specific page bounds remain protocol-owned. The default controls are 300 searches per minute per browser session with burst capacity 30, six concurrent searches, and a ten-second datastore timeout; an operator may tighten these controls without changing protocol limits.
| Public situation | Contract behaviour | Caller response |
|---|---|---|
| Malformed JSON, an invalid query or predicates, a client session, invalid page, or invalid cursor | 400 public validation error with safe field issues where useful | Correct the request; do not retry unchanged. |
| Request body exceeds 64 KiB | 413 / browser_search_body_too_large | Reduce or correct the request; do not retry unchanged. |
| No authenticated visitor state | 401 / browser_session_required | Re-establish ORCID login, then retry on user action. |
| No Discovery authority | 403 / browser_discovery_forbidden | Do not retry automatically or infer access or governance state. |
| Unknown or ineligible datastore | Neutral 404 / browser_datastore_not_found | Refresh datastore discovery without probing. |
| Datastore/discovery unavailability or a safe timeout | Retryable 503 search failure | Retry on user action or bounded backoff; never expose adapter detail. |
| Rate or concurrency limit | 429 with Retry-After | Honour the delay; do not fan out retries. |
| Unsupported protocol version | 400 / unsupported_protocol_version | Stop the affected workflow and resolve compatibility. |
Search requests may send the protocol version accepted from /version in
X-Protocol-Version; omission uses the current supported version. All
responses are Cache-Control: no-store and continue to provide
X-Request-Id and X-Protocol-Version for safe support correlation.
The Web control-plane v2 contract is the authoritative payload and fixture index. Its companion web-application handoff defines frontend consumption, fixture-driven tests, and retry presentation. The protocol 2.0.0 cutover replaces the prior reference encoding and fixture IDs. The companion must adopt web-control-plane.v2; a v1 image is incompatible. The existing HTTP route structure and authority classes are preserved.
Domain Browsing
Domain browsing is the datastore-scoped discovery journey for moving from a
Domain to its associated Studies, canonical Variables, and their Vocabulary
metadata. In the Web control-plane v2 contract, every reference
returned by a route is a stable, opaque public reference to use in the next
route. It is not a daemon Session API and it does not expose Dataset or
Datafile content.
All Domain-browser routes start with a selected ready datastore and require an ORCID-authenticated browser visitor. The Trusted runtime resolves that datastore through its Runtime-held discovery authority before reading metadata. An ORCID-authenticated browser visitor does not send a datastore credential, OIDC token, role, or other authority.
| Journey | Route | Navigation relationship |
|---|---|---|
| Find a Domain | GET /browser/datastores/{datastore_id}/domains | Each row supplies a Domain reference for its detail, Studies, and Variables routes. |
| Inspect a Domain | GET /browser/datastores/{datastore_id}/domains/{domain_id} | Returns the canonical Domain facts; it does not embed child collections. |
| Browse associated Studies | GET /browser/datastores/{datastore_id}/domains/{domain_id}/studies | Each row carries the same browser-safe Study reference used by the ordinary Study discovery journey. |
| Browse canonical Variables | GET /browser/datastores/{datastore_id}/domains/{domain_id}/variables | A row provides a Variable reference and, when present, a Vocabulary reference. |
| Inspect a Variable | GET /browser/datastores/{datastore_id}/domains/{domain_id}/variables/{variable_id} | Returns the canonical Variable facts, including its owning Domain and optional Vocabulary link. |
| Inspect a Vocabulary | GET /browser/datastores/{datastore_id}/domains/{domain_id}/vocabularies/{vocabulary_id} | Returns the Vocabulary’s owning Domain and descriptive facts; categories and mappings remain separate collections. |
| Browse Vocabulary categories | GET /browser/datastores/{datastore_id}/domains/{domain_id}/vocabularies/{vocabulary_id}/items | Returns the bounded category collection for that Vocabulary. |
| Browse Vocabulary mappings | GET /browser/datastores/{datastore_id}/domains/{domain_id}/vocabularies/{vocabulary_id}/mappings | Returns every mapping in which that Vocabulary is either endpoint. |
The public projection is deliberately narrow. Domain rows contain only their descriptive facts and tags. Domain Study rows contain shared browser-safe Study facts, not access-request actions or search-match evidence. Domain Variable rows are canonical definitions—not Dataset-version membership—and expose name, value type, description, and an optional Vocabulary link. Variable detail can add value format, tags, and ontology identifiers, but never Dataset membership, key role, note, or Dataset row role.
Collection Traversal
The Domain list, Domain Study list, Domain Variable list, Vocabulary item list,
and Vocabulary mapping list are independently paginated collections. Each
response has a returned count, requested limit, stable ordering identifier
and description, optional opaque previous_cursor and next_cursor, and an
optional neutral empty_state. The contract intentionally does not publish an
exact total count.
Use limit and a returned cursor only on the same collection route. The
default limit is 100 and the maximum is 500; zero, oversized, malformed, or
otherwise invalid page parameters produce a public validation error. A returned
cursor is bound to its datastore, route family, parent references, limit,
ordering, direction, boundary, and protocol version. It must not be edited,
decoded, or replayed for a different Domain, Vocabulary, collection direction,
or page size.
The first page has no Previous cursor; the terminal page has no Next cursor;
an empty collection has neither. An empty collection is a successful 200
response with an empty item array, returned count zero, and a stable neutral
empty-state code and message. A collection URL with its returned cursor and
unchanged limit is bookmarkable while the catalogue remains unchanged, but it
is not a permanent snapshot. If the catalogue changes, the service returns
409 with browser_domain_cursor_stale; restart only that collection from its
first page. Keep the Study and Variable cursors independent of each other and
of the Domain list, so moving between these tables never changes another
table’s traversal state.
Vocabulary Categories And Mappings
A Vocabulary category uses product-facing fields: integer code, string
label, and optional definition. The projection is explicit: stored integer
value becomes public code, stored string code becomes public label, and
optional stored description becomes public definition. The storage field
names are not alternative API fields. Categories are ordered by code then
stable item identity and contain no observed frequencies, counts, percentages,
distributions, or other Content-derived summaries.
Mappings retain their stored source and target orientation. A mapping row
includes its own stable reference and, at each endpoint, the Vocabulary
reference, item reference, integer code, and label. It does not duplicate item
definitions. The selected Vocabulary can therefore appear as the source or the
target, and the opposite Vocabulary can belong to a different Domain. This is
not a claim that the mapping is symmetric. Mapping ordering is stable across
source Vocabulary and item, target Vocabulary and item, then mapping identity.
Authority, Privacy, And Failure Boundaries
Domain browsing is broad Discovery metadata. It is independent of Study access grants, custodianship, TRE OAuth-group membership, and access-request state; those facts do not broaden or narrow a result. The service applies permission trimming before public projection and uses neutral not-found responses for unknown, cross-parent, unauthorized, or trimmed objects so the route cannot be used to probe hidden metadata.
Responses are metadata-only and must not expose protected rows or bytes,
previews, downloads, Dataset memberships, Content-derived summaries, storage or
Restricted local references, credentials, raw SQL, adapter detail, runtime handles, private
governance data, Study membership, custodianship, or access-request state.
They are Cache-Control: no-store. Operational logs retain only request ID,
route family, datastore identity, response status, duration, returned-page
count, requested limit, and limit outcome—not object references or names,
cursors, bodies, cookies, ORCID identity, tags, ontology values, or category or
mapping content.
| Public situation | Contract behaviour | Caller response |
|---|---|---|
| Invalid page or cursor | 400 public validation error | Correct the request; do not retry it unchanged. |
| Changed catalogue | 409 / browser_domain_cursor_stale | Restart the affected collection from its first page. |
| No authenticated visitor state | 401 / browser_session_required | Re-establish ORCID login, then retry on user action. |
| No discovery authority | 403 / browser_discovery_forbidden | Do not retry automatically or infer governance state. |
| Unknown or ineligible datastore, or hidden object | Neutral 404 | Refresh discovery or return to the parent without probing. |
| Discovery unavailable or a safe read timeout | Retryable 503 | Retry on user action or bounded backoff; never show adapter detail. |
| Per-session rate or concurrency limit | 429 with Retry-After | Honour the delay; do not fan out retries. |
| Incompatible protocol version | 400 / unsupported_protocol_version | Stop the affected workflow and resolve compatibility. |
Domain-browser callers may send the protocol version accepted from /version
in X-Protocol-Version; omission uses the current supported version. A
malformed or unsupported value fails safely. The operational defaults are 300
reads per minute per authenticated session with a burst capacity of 30, six
concurrent reads, and a ten-second datastore timeout; deployments may tighten
those values without changing protocol page maxima.
Separate Discovery Journeys
Domain Study browsing is not Study search. It has no search predicate, search-specific result evidence, aggregate search result, or access-request action; it simply follows the Domain–Study association and returns the shared safe Study facts. Its HTTP route has no runtime dependency on the Study search route.
Likewise, Domain Variables are canonical Domain-owned definitions, not the
Dataset-scoped dictionary. The dictionary journey is
GET /browser/datastores/{datastore_id}/studies/{study_id}/datasets/{dataset_version_id}/dictionary
and describes its owning Dataset version. Datafiles do not own dictionaries.
The legacy Datafile-shaped URL remains recognized as an explicit obsolete-route error and returns 410 / browser_datafile_dictionary_deprecated; new
navigation must not offer it. Neither journey calls or depends on the other’s
HTTP route. A Variable reused by Dataset versions or Studies is still
represented once through its owning Domain rather than by a reverse Dataset or
Study usage listing.
Deployment And Operations
Deploy the frontend and service at the same HTTPS origin. Browser session
cookies are server-side, HttpOnly, Secure, and SameSite=Lax or Strict.
The short-lived login-state cookie always uses SameSite=Lax so it can return
on the external provider’s top-level GET redirect. Both callback routes require
that cookie to match the pending transaction before consuming state or
exchanging a code, and clear it on success. With Strict session cookies, the
frontend landing page loads first and then fetches /session from the same
origin; the callback itself does not need a session cookie.
Pending logins are bounded by services.web.max_pending_logins (default 1,024).
Each new login reclaims expired records before admission; live transactions
remain usable. Capacity rejection returns 503 orcid_login_capacity_exceeded
with Retry-After: 60 and no cookie or redirect. Honour the delay before a fresh
attempt. The deployment security guide
details expiry, idle retention, and configuration limits.
Unsafe requests require a matching Origin; cross-origin credentialed CORS is
not enabled. The complete operator policy is in the
web control-plane deployment security guide.
The datastore deployment guide
describes the reverse-proxy and Runtime topology, identity and authority split,
configuration categories, verification, and current session-store constraint.
The standalone service starts only with explicit confidential-client OIDC
configuration: issuer, client ID, externally visible callback URI, scopes,
cookie policy, and a server-side client-secret file reference. It exchanges the
callback code and keeps token material in the service process. The callback is
the same-origin /auth/orcid/callback URL; production deployments require HTTPS
and secure cookies. The frontend never receives OIDC tokens or the secret.
The diagnostics operator guide describes
request correlation, the ten-second Runtime check, shared admission limits,
evidence bases and Local TRE log commands. /diagnostics retains HTTP 200 even
when a returned check fails; /ready returns 200 or 503 for its check result.
Admission can reject either route. Neither surface exposes owner-Session findings.
/health is a process-liveness check. /ready authenticates to the Trusted
runtime and verifies the required stable-protocol capabilities, returning
failure when that sole substantive path is unavailable or incomplete. It does
not probe PostgreSQL and has no direct fallback. /diagnostics exposes only
stable check names and status, redacting credentials, runtime references,
Restricted local references, Lake locations, and protected content.
Scope And Follow-up
This MVP is a browser discovery and access-request surface, not a general remote control plane or a content viewer. It intentionally excludes cross-origin browser deployment, frontend framework choices, Rust/Wasm, dataset/datafile content delivery, and asset-level grants. The frontend repository specification and release handoff are tracked in issue 20.