A separate own-user service#

Portal's optional preferences service stores installation-wide language and theme choices, plus application/workspace-scoped private filter favorites. PostgreSQL is authoritative. The service is independent of the application directory: users do not need a Projects grant to read or update their own enrolled preferences.

Consumers opt in through matching Preferences packages and a captured-user credential provider. CRUD is unnecessary for language and theme. Filter favorites require an explicitly registered supported workspace and the relevant CRUD integration. Portal's configuration keys are its own adapter settings, not automatic bindings that every consumer inherits.

Enrollment and credentials#

The installation owner supplies the explicit HTTPS preferences endpoint, dedicated audience, stable installation namespace, trusted tenant policy and authenticated requester-to-application mapping. Do not derive the endpoint from Portal's public Web origin. It can be private and reachable only by enrolled backends.

The consumer backend obtains a separate short-lived delegated token for the current human. The service checks issuer, signature, lifetime, human subject/session, audience, scopes, requester enrollment and tenant. Writes also require read scope because they return the owned representation. A client-credentials token with a supplied user ID is not an own-user credential.

Ownership derives from validated identity and trusted installation values. Verify subject consistency across clients; pairwise subjects require an approved mapping. Email and browser-supplied ownership are unsuitable keys. Tokens remain in backend memory and must not enter URLs, browser storage or logs. Clear scoped credentials and cancel in-flight work when the principal/session changes or signs out.

Global choices and concurrency#

The service prefix is /api/preferences/v1. GET /me/global returns the current representation and a quoted strong ETag. A global replacement has this shape:

json
{"schemaVersion":1,"language":"el-GR","theme":"system"}

PUT /me/global replaces the complete document and requires If-Match, including the first write. An unset document uses ETag "0". Preserve the other field when changing language or theme. A null value selects the applicable default; system records an intent to follow the device's theme.

Response Client behavior
412 stale revision Reload for review; do not overwrite silently
428 missing precondition Supply the current ETag
401 or 403 Reassess authentication, scopes and enrollment
503 unavailable storage Report the failed save; preserve existing persisted state

The documented shared controls persist each selection conditionally. Saved choices restore at application startup/full reload. Existing independent tabs receive no live broadcast, and page activation does not automatically reread the document. A failed save must not become a queued replay for a later user or session.

Private filter favorites#

A workspace registration identifies the application, workspace, definition, schema version and authorized fingerprint. Permitted values are bounded typed parameters or approved rules, never executable query text. Changing that definition requires deliberate compatibility review.

Relative route Purpose
/me/apps/{application}/workspaces/{workspace}/definition Read the authorized definition and fingerprint
/me/apps/{application}/workspaces/{workspace}/favorites List or create private favorites
The collection plus /{favoriteId} Read, update or delete an owned favorite
/me/apps/{application}/workspaces/{workspace}/default-favorite Read, set or clear the default

Selecting a favorite fills a draft. Explicit Apply still invokes ordinary application validation and authorization. The service stores filters; it never executes them. Mutations use ETags, and creation uses an idempotency key. The documented create-recovery window is bounded to 24 hours; it does not resurrect a deleted favorite. Verify quotas and payload limits against the selected service version.

Storage and upgrade boundary#

The application owns immutable schema migrations. First installation uses a separate initialization job and bounded setup/migration identities; the serving API receives only runtime data access, and Web receives no database credential. Reviewed existing allocations can use the documented AlreadyInitialized publication mode when schema and provenance checks permit it.

Back up and restore the dedicated database independently. Restore into a separate allocation with matching migrations and verify content and authenticated reads before switching connections. Disabling exposure is an application rollback; deleting preference data or rewriting applied migrations is not.