Management
The operator-facing surface for everything configured inside the platform UI — nine tabs covering data sources, integrations, collectors, ownership, RBAC, taxonomy, and namespacing.
The Management section is the operator-facing surface inside the platform UI: every tab here lets an operator add, edit, or remove a piece of catalog configuration. Where the Catalog and the Directory are read-oriented (a user finds an existing entity), Management is mutating (an operator changes how the catalog is wired). Most operator workflows after the platform is running — registering a data source, issuing a collector token, granting roles, managing owners, curating tags — start here.
Open it from the top-level navigation Management. The page renders a vertical tab sidebar on the left (one entry per Management area) and a content pane on the right; clicking a sidebar entry navigates to /management/{tab} and mounts the corresponding sub-page.
What is in Management
The nine Management tabs (in their UI order, from ManagementTabs.tsx):
Namespaces
/management/namespaces
Logical groupings used to scope tags, terms, and other taxonomy concepts. Acts as a label dimension applied across the catalog. See Namespaces for the full lifecycle and the four-sister-service auto-create caveat.
Create a namespace before authoring tags or terms that should be scoped to a particular team or domain.
Datasources
/management/datasources
Registered data sources — the platform's view of every system a Collector or Push-client is reporting from. View existing sources, audit ingestion timestamps, edit metadata, link to a Collector + Namespace.
Inspect what was registered after a Collector first reported; add a description / tag a source; remove a source no longer ingested.
Integrations
/management/integrations
The Integration Wizard surface — pick an integration template, fill in its parameters, and copy the generated collector_config.yaml snippet.
Generate a working snippet for a new pull adapter; wired-up template registry is documented at the Integration Wizard page.
Collectors
/management/collectors
Registered Collectors and their tokens. Issue new tokens, view existing token IDs, regenerate or revoke.
Issue a token before deploying a Collector; rotate a leaked token; remove a Collector that's been retired.
Owners
/management/owners
Owner entities — the catalog-side identity that gets associated with users and attached to data entities for stewardship.
Create owners that map to teams or individuals; attach them to data entities (the attach surface is on each entity's page). See Owners.
Tags
/management/tags
The catalog's tag taxonomy. Tags can be marked important (rendered visually distinct) and namespace-scoped.
Curate a stable tag vocabulary so search facets and filters stay coherent.
Associations
/management/associations
Three sub-tabs: New (incoming pending requests — Accept / Reject), Active (existing user-owner bindings — Remove), History (resolved requests audit). The header carries Create association, the admin direct-bind affordance. The Associations tab is hidden unless the signed-in user has the OWNER_ASSOCIATION_MANAGE permission.
Process incoming association requests, audit who-approved-what, and create or remove bindings directly. The full workflow — three write-paths, sub-tabs, and operator caveats — is on User-owner association.
Roles
/management/roles
Named RBAC roles — bundles of policies applied to users (or owners) to grant catalog-side permissions.
Create roles (Editor, Reader, Owner-of-Domain-X) and attach Policies to them. See Roles.
Policies
/management/policies
Permission rules — the building blocks of RBAC. Each Policy defines who-can-do-what on resources matched by ODDRN patterns (or per-feature surfaces like Lookup Tables, Query Examples, Owner Association).
Define fine-grained access (e.g. "data engineers can edit transformer entities under prefix airflow://..."). See Policies and Permissions for the available permission keys.
The whole section is gated behind the platform's authentication. Tab visibility is permission-aware (Associations is the explicit case today; other tabs are visible to any signed-in operator and the per-tab actions enforce permission checks). See Tab visibility versus mutate gates below for the per-tab breakdown.

Other Management surfaces
Beyond the in-app Management tabs, several operator-facing controls live on the platform's authentication, configuration, and collector-secrets surfaces. They are part of how an operator manages the system, even though they don't render as a Management UI tab today:
Machine-to-Machine (M2M) tokens — Server-to-server (S2S) API key authentication for non-UI programmatic callers (CI/CD jobs, ingestion pipelines, automation scripts). Disabled by default; configured on the platform's auth surface — see Server-to-server (S2S) authentication.
Custom navigation links — the App Info menu items (top-right toolbar) that surface to every signed-in user. Configured via
odd.links[]on the platform side — see Configure ODD Platform → Additional navigation links.Alternative Secrets Backend — store collector configuration (Platform tokens, plugin credentials) in AWS SSM Parameter Store instead of plaintext in
collector_config.yaml. Configured on the collector deployment — see Collector secrets backend.Integration Wizard — the in-app generator for
collector_config.yamlsnippets that backs the Integrations tab above; handy when bootstrapping a new pull adapter without hand-authoring the YAML.
Tab visibility versus mutate gates
The platform's authorization model on the Management section is read-collaborative by default — only the Associations sub-route enforces a route-level permission gate; the other eight Management areas render their list views and detail pages for any signed-in operator. Under auth.type=DISABLED (see DISABLED authentication) the same read surface is reachable by every anonymous network caller. The per-tab mutate actions (create, edit, delete, regenerate-token) enforce permissions at the controller; the read surface (lists, detail pages, Policy JSON, Role bindings, partially-redacted tokens) does not.
Namespaces
Any authenticated user (anonymous under DISABLED)
NAMESPACE_CREATE / _UPDATE / _DELETE
None at route or controller
Datasources
Any authenticated user
DATA_SOURCE_CREATE / _UPDATE / _DELETE / _TOKEN_REGENERATE
None at route or controller
Integrations
Any authenticated user
No permission gate today — see the bare-route caveat below
None at route or controller
Collectors
Any authenticated user (anonymous under DISABLED — see the Collectors caveats below)
COLLECTOR_CREATE / _UPDATE / _DELETE / _TOKEN_REGENERATE
None at route or controller
Owners
Any authenticated user
OWNER_CREATE / _UPDATE / _DELETE
None at route or controller
Tags
Any authenticated user
TAG_CREATE / _UPDATE / _DELETE
None at route or controller
Associations
Only users holding OWNER_ASSOCIATION_MANAGE
OWNER_ASSOCIATION_MANAGE (composite — see User-owner association)
Route-level — RestrictedRoute wrapper
Roles
Any authenticated user — full Role JSON including bound permissions
ROLE_CREATE / _UPDATE / _DELETE
None at route or controller
Policies
Any authenticated user — full Policy JSON including grants, ODDRN selectors, and role bindings
POLICY_CREATE / _UPDATE / _DELETE
None at route or controller
The "Read-side enforcement" column distinguishes between the UI's permission-aware affordance hiding (the Add / Edit / Delete buttons disappear for users without the mutate permission — this is a cosmetic UX gate, not an authorization gate) and the route / controller / network reachability of the read endpoint itself (the underlying GET reachability). The Associations tab is the only Management area whose route is gated against the read endpoint as well as the write endpoint.
Read access on Management exposes information beyond the cosmetic list view. An operator deep-linking to /management/policies/{id} reads the full Policy JSON — every permission grant, every ODDRN selector, every role binding. The same applies to /management/roles/{id} (full Role composition) and the Datasources / Collectors list views (partially-redacted tokens — the last six characters of the token leak through the list response). Operators expecting per-team read isolation on Management (a posture some compliance regimes assume by default) need a network-perimeter mitigation — a reverse-proxy authz filter on /api/policies/**, /api/roles/**, /api/datasources/**, /api/collectors/**, /api/owners/**, /api/namespaces/**, /api/tags/**, and /api/integrations*. The platform's authorization layer does not gate these reads today.
The /management/integrations route has no route-level permission wrapper — any integration-specific permission check resolves via the wrong context. The Integrations route is the only Management area whose route is not wrapped in a WithPermissionsProvider or RestrictedRoute; the inner Integrations component renders directly. The outer Management page seeds only OWNER_ASSOCIATION_MANAGE into the permissions context, so any usePermissions().hasAccessTo(...) call inside the Integrations subtree that names an integration-specific permission resolves to false by default. If a future integration ships a feature gated by a different permission, operators will see deny-by-default until the route's wrapper is fixed. The upstream fix is to add a WithPermissionsProvider wrapping the route with the integration-relevant permission list (or to make the deny-by-default deliberate via a no-op wrapper). The doc-side caveat is in place today; the bare-route reads continue to work, but write-actions gated on integration permissions silently deny.
Cascade-block on Owner / Namespace / Datasource deletion
Three Management tabs (Owners, Namespaces, Datasources) share a uniform delete contract: the platform rejects the delete with CascadeDeleteException (HTTP 400, error code USR004) when dependent rows reference the entity. The UI's ConfirmationDialog says only "Are you sure? X will be deleted permanently." — it does not name the conditional outcome. The Roles and Policies tabs already document the equivalent block; the rule applies to the Owner / Namespace / Datasource trio just as deterministically.
Owner delete is blocked when any of three referent tables still link to the owner. OwnerServiceImpl.delete zips three existence-predicates — Term-ownership rows, Data-entity-ownership rows, and User-Owner association rows — and rejects the operation if any of the three returns true. The operator must clear the relevant relations first: re-assign or remove term-ownership rows on the Business Glossary; re-assign or remove data-entity ownership rows on each entity's Owners panel; remove user-owner associations on the Associations tab. After all three are clear, the delete succeeds.
A known related gap: the owner_association_request table is not part of the cascade-block guard today — a rejected or stale association-request row can survive owner deletion and surface in the Associations History tab as "rejected by NULL owner." Operators auditing the association-request history of a deleted owner see the legacy rows with a placeholder identifier; a future platform-side fix would add owner_association_request to the cascade-guard's predicate set.
Namespace delete is blocked when any of four referent tables still link to the namespace. NamespaceServiceImpl.delete zips four existence-predicates — Data-sources, Collectors, Terms, and non-deleted Data-entities — and rejects the operation if any returns true. Clearing namespace-linked data sources (re-assign the source to a different namespace, or remove the source entirely from the Datasources tab); clearing namespace-linked collectors (re-assign or remove via the Collectors tab); clearing namespace-linked terms (re-assign via the Business Glossary); and clearing or re-assigning live data entities (which arrive via ingestion — typically requires reconfiguring the collector's namespace_name and re-ingesting) frees the namespace for deletion.
A Namespace can also be soft-deleted and re-created with the same name later — the namespace table carries a partial-unique index that frees the name slot on soft-delete, so an operator who wants to "reset" a namespace by deleting and re-creating with the same name can do so once the cascade-block is clear.
Datasource delete is blocked when any live (non-deleted) data entity still references the source. DataSourceServiceImpl.delete checks one existence-predicate — non-deleted data entities under the source — and rejects the operation if any exist. The operator must either re-ingest the source's entities to a different source (rarely practical) or soft-delete every entity under the source first. Once the source has no live children, the delete succeeds — but the operation has two additional side-effects worth knowing about; see Data Sources known caveats below.
Both errors render through the same ControllerAdvice chain as a toast on the UI; the operator sees a brief "cannot be deleted" message and the dialog stays open with the cancel option highlighted. There is no per-row breakdown of what's blocking the delete — the operator has to enumerate the referents themselves.
Collectors known caveats
Under auth.type=DISABLED, every Collector CRUD endpoint is anonymously reachable — the UI's permission-aware affordance hiding is a cosmetic gate, not an authorization gate. When the platform runs under DISABLED authentication, the platform's security configuration bypasses every SecurityRule entry and accepts every request unauthenticated. The Management UI's WithPermissionsProvider HOC hides the Create / Edit / Delete / Regenerate-Token buttons because /api/identity returns an empty permission set under DISABLED — an operator inspecting the UI concludes "locked down." The backend disagrees: an attacker with network reach to the platform mints new Collectors, copies the last six characters of every existing Collector token through the list response, and regenerates tokens forcing in-flight Collectors to 401 on their next ingestion call. The same pattern applies to every Management mutate endpoint under DISABLED, not just Collectors — Collectors is the most operator-visible example because the token is operationally load-bearing.
Mitigation today. Do not run auth.type=DISABLED in production. If a non-production deployment must run DISABLED (for example a local Docker stack used in an evaluation), place a network-perimeter authz proxy in front of the platform — or accept that every Management surface is anonymously reachable.
Collector tokens are stored verbatim plaintext at rest; the comparison path uses non-constant-time equality; and the token itself is generated from a non-cryptographic RNG. The TOKEN table column is declared as value VARCHAR(40) NOT NULL with no hashing column and no UNIQUE constraint; the token generator writes a 40-character alphanumeric string verbatim into the column on mint. That string is produced by RandomStringUtils.randomAlphanumeric(40) (backed by ThreadLocalRandom), not a cryptographically-secure source — so the 40-character token does not carry CSPRNG-grade entropy and should not be treated as one. Every DB-side reader recovers every live credential — pg_dump outputs, streaming replica reads, scheduled backups, jOOQ statement logs (if enabled), and any DB observability pipeline that captures full SQL all expose the plaintext token. Soft-deleted Collectors leave their TOKEN row orphaned — the TOKEN table has no deleted_at column today, so retired Collector tokens persist in the table indefinitely. Two TOKEN rows may share the same value (no UNIQUE on VALUE), though the random-generation entropy makes a natural collision vanishingly unlikely.
Separately, the ingestion-filter's application-layer comparison (tokenPojo.getValue().equals(token)) is non-constant-time — a careful attacker with sub-millisecond timing measurement of the platform's /ingestion/entities endpoint can in principle build a timing oracle against the token. Combined with the plaintext storage, the operator-visible posture is: treat any DB backup, any log pipeline that captures SQL, and any monitoring stack that captures HTTP latencies as credential material.
Mitigation today. Restrict DB read access to the TOKEN table; audit pg_stat_statements for SELECT * FROM TOKEN patterns; treat application-server logs as credential material if jOOQ statement logging is on; treat WAL archives + backups as credential material. The upstream platform-side fix is a hash-at-rest migration (add a value_hash column, write only the hash on mint and rotate, compare via constant-time HMAC); the one-shot reveal at mint becomes the operator's only chance to copy the plaintext token.
Token rotation has no grace period — and on default deployments rotation has no security effect on the bulk-ingestion path. CollectorServiceImpl.regenerateToken issues a single in-place UPDATE TOKEN SET value = ?, updated_at = ? — the prior token is overwritten in one statement. In-flight Collectors using the prior token start returning 401 on their next ingestion call immediately. There is no grace window during which both tokens are honoured.
Separately, the default application.yml carries auth.ingestion.filter.enabled: false — the IngestionDataEntitiesFilter is inactive on the bulk-ingestion path (POST /ingestion/entities) by default. Rotating a leaked token under default config has no security effect on the bulk-ingestion path; it only affects the always-on data-source filter on POST /ingestion/datasources. Operators rotating a leaked token under a default deployment fix half the attack surface.
Mitigation today. Set auth.ingestion.filter.enabled: true if token rotation is part of the deployment's security posture (see Configure ODD Platform for the configuration reference). Schedule rotations during quiet ingestion windows. Treat rotation as a destructive operation against in-flight collectors and coordinate with the team running the Collectors before rotating.
Data Sources known caveats
Datasource create and update silently mint Namespace rows through the namespace_name form field — bypassing NAMESPACE_CREATE. Both POST /api/datasources and PUT /api/datasources/{id} call NamespaceServiceImpl.getOrCreate(name) when the form's namespace_name field is non-empty — the service does getByName(name).switchIfEmpty(createByName(name)), minting a new Namespace row if the name does not already exist. A caller holding DATA_SOURCE_CREATE (or _UPDATE) but not NAMESPACE_CREATE proliferates namespaces through this side-door. The Permissions page describes DATA_SOURCE_CREATE and NAMESPACE_CREATE as independent gates; the side-door collapses them. The same auto-create pattern exists on three other resource-creation endpoints (Term, Collector, Data-entity-group) — see Namespaces → Auto-create side-door for the four-vertex cluster.
Mitigation today. Treat DATA_SOURCE_CREATE and DATA_SOURCE_UPDATE as implicitly granting namespace-creation rights when the namespace_name form field is supplied. Monitor the Namespaces list for unexpected growth (typo-introduced duplicates are the most common operator-visible effect). If strict separation between Datasource creation and Namespace creation is a deployment requirement, treat the namespace_name form field as a closed allowlist at the network-perimeter layer.
Datasource delete orphans the Collector token row and leaves the full-text search index uncleared. When a Datasource passes the cascade-block check and the soft-delete fires, two side-effects do not happen:
The Datasource's
TOKENrow is not deleted or marked. TheTOKENtable has nodeleted_atcolumn, so the row persists indefinitely after the parent Datasource is soft-deleted. Operators auditing live tokens viaSELECT * FROM TOKENsee retired tokens alongside live ones with no obvious distinguishing field.The full-text-search index entry for the Datasource is not cleared. The update path calls
updateSearchVectors; the delete path does not. Catalog search continues to surface the soft-deleted Datasource's name in faceted results until some other event re-runs the index on the affected entries.
Mitigation today. Audit TOKEN rows for orphans on a schedule (any row whose data_source_id references a soft-deleted source); force a re-index after Datasource deletion if catalog search hygiene matters. The upstream platform-side fix is a one-commit symmetry fix on the delete path (clear the token row, clear the FTS vector).
regenerateDataSourceToken returns the plaintext token in the response body without a Cache-Control: no-store header — and is the only non-@ReactiveTransactional mutating method on the Datasource service. The mint, update, and delete paths all carry @ReactiveTransactional; the regenerate path does not. If the tokenRepository.updateToken step fails after the initial dataSourceRepository.getDto read, the partial state is undefined — the new token may or may not have been written depending on the exact failure mode.
Separately, the response body carries the new 40-character plaintext token; the controller does not set Cache-Control: no-store on the response. Intermediate proxies, browser-history archives, browser DevTools network logs, and any corporate inspection appliance in the response path may cache the response and the token along with it. No Activity Feed event is emitted on token regeneration — the rotation is invisible to the platform's audit trail (see Activity Feed → Scope).
Mitigation today. Rotate Datasource tokens during low-traffic windows; ensure TLS terminates at the operator's controlled boundary (no caching proxies in the response path); clear the browser DevTools network log immediately after a regenerate operation. Treat the regenerate operation as a non-atomic write and audit success via a follow-up GET /api/datasources/{id} rather than relying on the regenerate response.
Audience and typical sequencing
The audience is operators — people who run an ODD deployment for their team. A typical first-deployment sequence touches several Management tabs in order:
Namespaces — create the logical groups (per team, per domain) you'll scope tagging and term curation to.
Datasources — verify that the first Collector has registered its sources after the initial ingest.
Collectors — issue a token and configure additional Collectors as more sources come online.
Owners — create the catalog-side owner entities for teams and individuals.
Roles + Policies — author the RBAC bundles that fit the operator's organisation, then attach roles to owners.
Tags — start curating the tag vocabulary as ingest expands.
Integrations (as needed) — generate per-integration snippets via the wizard when adding new pull adapters.
Associations — process incoming requests as users self-associate to owners.
Daily-operations workflows tend to live on tabs 2 (Datasources audit), 4 (Owners maintenance), 5 (RBAC tweaks), and 8 (Associations queue).
Where to next
For per-tab security model — RBAC building blocks, the permission keys, and the request flow for owner association — see the Authorization section.
For the user-facing Catalog counterpart — search, the Directory, the Catalog Overview page — see the Data Discovery pillar.
For the operator's first-run path (deploy the platform, deploy a Collector, register a source) — see Try locally and the Configure ODD Platform reference.
For the integration template registry that the Integrations tab consumes — see the Integration Wizard page.
For the API surface behind the Integrations tab — see API Reference → Integrations.
Last updated