> For the complete documentation index, see [llms.txt](https://docs.opendatadiscovery.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.opendatadiscovery.org/developer-guides/architecture-decision-log/adr-0003-read-collaborative-authorization.md).

# ADR-0003: The catalog is read-collaborative — only mutations are permission-gated

## Status

**Accepted.** Reconstructed from the codebase on 2026-05-31; the decision is live in the source today.

## Context

A data catalog earns its value from **discovery** — people finding datasets, tracing lineage, and reusing what others have built across an organisation. That goal pulls against fine-grained read access control: if every entity carried a per-owner or per-role read ACL, users could not discover what they cannot see, and the catalog would fragment into silos that defeat its purpose. The platform had to choose where, on the read side, to sit on that spectrum.

ADR-0002 established *how* authorization is wired — one central `SECURITY_RULES` table and a catch-all that closes the surface. ADR-0002 deliberately left *what that means for reads* to this record.

## Decision

**The platform is read-collaborative: every gated rule in `SECURITY_RULES` guards a mutation, and reads fall through to authenticated-only.** The rule table is made up almost entirely of `POST`/`PUT`/`DELETE`/`PATCH` rows — create, update, delete, regenerate — each requiring a specific permission. `GET` endpoints are, with one deliberate exception, **not** in the table, so they hit the `pathMatchers("/**").authenticated()` catch-all (ADR-0002): **any authenticated user can read any entity, its lineage, the catalog search, and the activity feed**, with no role, owner, or per-resource check.

The one gated read is the owner-association management queue (`GET /api/owner_association_request` → `OWNER_ASSOCIATION_MANAGE`) — an administrative surface, not catalog content.

Owner-scoping is available but **opt-in**: a user can narrow to their own entities through the dedicated `/api/dataentities/my`, `/my/upstream`, `/my/downstream` reads (and the `my_objects` search flag), but the default read is the whole catalog. Scoping is a convenience the caller asks for, not a boundary the platform imposes.

## Consequences

* **Discovery works without read modelling.** Any authenticated user can search, browse, and trace the entire catalog; there is no per-entity read ACL to author or maintain, and nothing is hidden from a logged-in user because it belongs to someone else.
* **There is no read-level isolation between users.** Read-collaborative means exactly that: catalog metadata — names, descriptions, schemas, lineage, ownership, activity — is visible to every authenticated principal. An operator who must restrict *who can see which* metadata cannot do it with in-app permissions; that boundary has to be drawn outside the application (separate deployments, network segmentation). This is the deliberate trade-off of a collaboration-first catalog, and operators should size their deployment boundary accordingly.
* **Write and read are asymmetric by design.** Editing is permission-gated per `SECURITY_RULES`; reading is not. A contributor adding a *mutating* endpoint must add its rule (ADR-0002); a new *read* endpoint inherits the authenticated-only floor automatically — which is the intended default, not an oversight.
* This posture assumes an authentication mode is enabled; whether authentication is required at all is a separate decision (the platform's auth modes), covered by the security-configuration docs.

## Evidence

* `odd-platform-api/.../auth/util/SecurityConstants.java:98-355` — `SECURITY_RULES`: every rule guards a mutating method (`POST`/`PUT`/`DELETE`/`PATCH`), with a single `GET` exception at `:148-150` (`/api/owner_association_request` → `OWNER_ASSOCIATION_MANAGE`, the owner-association admin queue). No catalog read (entity, lineage, search, activity) appears in the table.
* `odd-platform-api/.../auth/authorization/AuthorizationCustomizer.java:20-31` — permits the whitelist, registers each `SECURITY_RULES` row, then closes with `.pathMatchers("/**").authenticated()` (`:29-30`) — the catch-all that makes every un-listed read available to any authenticated user.
* `odd-platform-api/.../controller/DataEntityController.java:284-300` — `getMyObjects` / `getMyObjectsWithDownstream` / `getMyObjectsWithUpstream`, the opt-in owner-scoped reads (`/api/dataentities/my[/upstream|/downstream]`); the default catalog read carries no owner predicate.

## See also

* [ADR-0002 — Centralised path-matcher authorization](/developer-guides/architecture-decision-log/adr-0002-centralised-path-matcher-authorization.md) — the mechanism behind this posture: the one rule table and the `authenticated()` catch-all that reads fall through to. ADR-0002 names this record as where the read posture is decided.
* [Enable security](/configuration-and-deployment/enable-security.md) — how operators turn on authentication and authorization.
* [Permissions](/configuration-and-deployment/enable-security/authorization/permissions.md) — the permission model the mutation rules check (and which reads do not).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.opendatadiscovery.org/developer-guides/architecture-decision-log/adr-0003-read-collaborative-authorization.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
