Retrieval and user permissions
Retrieval and user permissions for Q integrations.
Q1.3 binds retrieval and retained context to authenticated identity. This guide describes the implemented local boundary and the compatibility work needed before deployment. See ADR 0005 and the verification record.
Identity and delegation
Q derives tenant and principal from verified authentication. User credentials use their authenticated subject; API keys use a separate key:<api_key_id> principal. Service credentials without a subject/key retain the tenant service principal. Never use those service credentials as individual end-user identities.
A supplied user_id must match the authenticated user unless credentials explicitly include identity:delegate or *. Neither ai:* nor first-party status implies delegation. Issue delegation only to an integration trusted to authenticate its own users. User IDs are tenant-local: the same value in two tenants identifies separate owners.
The server-owned context is:
{
"tenant_id": "tenant-example",
"principal_id": "key:17",
"user_id": "user-example",
"space_id": "space-example"
}user_id and space_id may be null. Without a delegated user, ownership is the principal itself. Clients submit supported public fields, never _data_access or other reserved _data_ / _workspace_ fields. Those fields are rejected in request dictionaries. Nested tools, answer streams, MCP calls and public workers retain the server-issued restriction; a selected Space cannot become an unrestricted tenant search.
When the issuing key polls a delegated AI job or fetches a delegated artefact, Q reconstructs the saved user identity only if the current credentials still allow delegation. Another key cannot recover that authority from the saved context.
Local evidence labels
Document and chunk tenant must both match. Put restricted access in each chunk's metadata.access:
{
"access": {
"tenant_id": "tenant-example",
"visibility": "private",
"owner_id": "user-example",
"users": ["collaborator-example"]
},
"space_id": "space-example"
}- Supported access visibility is
privateortenant. A private chunk needs a matching owner or a matching entry inusers; tenant visibility permits the authenticated tenant.tenant_idis optional for local labels, but must match when present. - Owner/tenant identifiers must be strings and
usersan array of strings. Unknown visibility, null/invalid access objects and malformed Space labels are denied. - A Space label adds its current Space permission check. Space
tenant/publicvisibility remains confined to the authenticated tenant; private visibility permits its owner and viewer/editor collaborators. - Existing file IDs and
files[].metadata.document_idbindings are also checked. Conflicting or inaccessible Space membership cannot be ignored by querying withoutspace_id. - A selected Space is an additional restriction. A missing, foreign or unreadable Space is denied; an empty Space returns no evidence.
Legacy chunks without access or Space membership remain tenant-shared. Inventory and classify private legacy content before activation, including every live representation in the 768-, 1,536- and 3,072-dimension tables. This migration cannot infer ownership for unlabelled/orphaned documents. Chunk labels are not a new central document-ACL table; label every applicable chunk and index when changing document-wide permissions.
Vector and keyword queries apply the shared predicate before ordering and limiting results. Existing direct document text/embedding readers use the same predicate in their supported table. Space and memory permission reads use current PostgreSQL state. An unavailable permission store returns permission_state_unavailable (503); it does not reuse a cached Space grant.
New Space files receive a document identity scoped to tenant, Space and file, plus a stored Space label passed through ingestion. Removing a file or deleting its Space tombstones chunks referenced by legacy file bindings transactionally. ACL-only re-indexing changes are persisted without recomputing unchanged embeddings. Omitted ACL/Space fields preserve prior labels; newly partitioned chunks with conflicting inherited labels require explicit classification. Review in-flight ingestion/content-removal behaviour during rollout; this is not a distributed ingestion-cancellation protocol.
External retrieval and MCP
Q sends access_context with tenant retrieval/federated requests; context pulls include a JSON-encoded parameter and streaming requests carry the equivalent context. MCP data calls use _meta.access_context. The provider must authenticate Q's configured integration, interpret the tenant/user/Space context and filter before ranking or returning content.
Every external chunk or MCP data response must contain a top-level metadata.access envelope:
{
"metadata": {
"access": {
"tenant_id": "tenant-example",
"visibility": "private",
"users": ["user-example"]
},
"space_id": "space-example"
}
}External envelopes require a matching tenant. Private results require the effective owner in owner_id or users; tenant results permit that tenant. If Q selected a Space, metadata.space_id must match exactly. Missing, malformed or incompatible envelopes produce a safe integration error and no evidence from that result/source. Federated aggregation can still return other authorised sources. Unsupported legacy response shapes do not receive a permissive fallback.
These envelopes are assertions from the configured provider, not cryptographic proof against a malicious provider. Providers must implement current permissions and need their own conformance tests. Q's validation and Q1.2 outbound policy cannot inspect a provider's internal ranking or ACL database.
Memory, caches, artefacts and jobs
| Surface | Behaviour |
|---|---|
| User memory | Tenant and owner required on list/search/get/update/delete/touch; storage failure never returns cached private memory |
| Automatic memory extraction | Extracts from user input, excluding retrieved assistant output |
| Semantic completion cache | Global LiteLLM semantic answer reuse disabled, including when the old setting requests it |
| Tool and speculative prefetch results | Authenticated calls bypass unversioned result caches; protected prefetch does not populate the shared cache |
| Prompt-prefix optimisation | Exact provider prefix optimisation remains; it does not substitute a different user's semantic answer |
| Local spilled evidence | Exact originating context required; current document text, chunk ACL and Space access checked again |
| External spilled evidence | Retained reuse unavailable; fetch fresh evidence |
| Generated artefacts | Tenant/owner checks on get/list/download/refine; local source-document and Space permissions rechecked, including cache hits; protected provenance omitted from public metadata |
| AI jobs / unified gateway | Persist identity and source provenance; polling/streaming check owner, current delegation and source access |
| External-derived retained artefacts/jobs | Fetch returns external_evidence_requires_refetch (409); no provider revision/revalidation API exists yet |
| Historical answer/clarification replay and checkpoints | Authenticated reuse returns permission_aware_replay_required (409); send a fresh answer request |
The external-retention restriction also affects polling an asynchronous job whose answer used external data; use a fresh answer stream to receive currently authorised evidence. Artefact lists omit external-derived items that cannot be revalidated. Q5.3 must add provider revalidation/versioned provenance before restoring these retained-result flows. Legacy artefacts without server-owned provenance are hidden, and legacy public runs/jobs without a saved identity are rejected.
Local retained derivatives require current access to every live chunk of every recorded source document. This is conservative for documents with mixed chunk ACLs. Artefact versioning accumulates source restrictions. Downloads use private/no-store cache control. Bytes already delivered to a client cannot be revoked; checks occur at retrieval/consumption/read boundaries, not as a guarantee of instantaneous cancellation during every in-flight operation.
Rollout and rollback
- Apply migrations through
20261007_140000_retrieval_acl, which addsix_q_spaces_files_acl. Verify the earlier workspace/external-agent migrations and required restricted service-role permissions. All three chunk tables and Space storage must be available. - Inventory active indices, Space bindings, ownerless memories, old runs and artefacts. Classify private legacy chunks; explicitly migrate only ownership that can be established. Do not add a fallback that treats missing owner/grant metadata as permission.
- Issue narrowly scoped delegation where required. Update external retrievers and MCP adapters to accept the context and return permission envelopes. Test both authorised and denied users.
- Update clients to handle 403/404, unavailable permission storage (503), external-contract errors (502) and fresh-request conflicts (409). Migrate replay/checkpoint-dependent and external retained-result workflows before enabling them for customers.
- Run the required release checks, full hosted CI, generated SDK/frontend qualification and real staging integrations. Measure authenticated ACL queries and cache-disabled cost on a representative corpus before setting production performance targets.
The index downgrade is structurally reversible. Rolling application code back can restore the old permission gaps; it is not a safe security rollback. Preserve the boundary or disable the affected public surfaces. No production migration or deployment was performed in Q1.3.
A complete catalogue/control-plane ACL redesign, live provider conformance, credential revocation during in-flight workers, permission-versioned caching and production performance qualification remain separate work. Q1.1/Q1.2 deployment requirements and the 5 November 2026 release-debt expiry remain in force.