Skip to content

feat(calm-hub): add GLOBAL_ADMIN-gated delete endpoints for all resource types - #3020

Merged
rocketstack-matt merged 10 commits into
finos:mainfrom
willosborne:features/calmhub-admin-delete
Aug 26, 2026
Merged

feat(calm-hub): add GLOBAL_ADMIN-gated delete endpoints for all resource types#3020
rocketstack-matt merged 10 commits into
finos:mainfrom
willosborne:features/calmhub-admin-delete

Conversation

@willosborne

@willosborne willosborne commented Aug 25, 2026

Copy link
Copy Markdown
Member

Description

Adds a DELETE endpoint for every CALM Hub resource type that didn't already have one (Architecture, Pattern, Flow, Standard, Interface, Timeline, Adr, Decorator, Control Requirement, Control Configuration), gated behind GLOBAL_ADMIN.

Delete semantics: delete removes the whole resource — its header and every version — not just the latest version or the header alone. The shared MongoVersionDocumentStore/NitriteVersionDocumentStore helpers gain a new deleteResource(namespace, id) for this; it's distinct from the existing deleteHeader, which is internal-only compensation logic used when a first version write fails and deliberately never touches the version collection.

Control requirements caveat: a control requirement that still has configurations underneath it refuses to delete (409 Conflict) rather than cascading. Delete the configurations first, then the requirement. Configurations themselves delete normally.

Decorator isn't part of the versioned-document family (one doc per namespace, embedded array), so its delete is a bespoke pull/list-removal rather than a deleteResource call.

Audit logging picks up all the new endpoints automatically via the existing generic path-param resolution, except Control's two new methods, which needed one manual addition to AuditRequestFilter's per-method dispatch (Control is the one resource that doesn't use the generic resolution path).

Also updates PERMISSIONS.md (content-resource deletion is deliberately GLOBAL admin-only, distinct from namespace/domain admin) and two ADRs whose "nothing is ever deleted" premise this change reverses, and adds Mongo + Nitrite integration test coverage for Architecture (representative simple case) and Control (the cascade-refuse case).

Review notes

This PR is best reviewed commit by commit:

  1. Store interface method signatures
  2. Mongo implementations + tests
  3. Nitrite implementations + tests
  4. New ControlHasConfigurationsException + audit filter wiring + audit test
  5. REST resource endpoints + tests
  6. Docs (PERMISSIONS.md + ADRs 0001/0003)
  7. Integration tests (Architecture + Control, Mongo + Nitrite)

Type of Change

  • ✨ New feature (non-breaking change which adds functionality)

Affected Components

  • CALM Hub (calm-hub/)

Testing

  • I have tested my changes locally
  • I have added/updated unit tests
  • All existing tests pass

Full suite passes: ../mvnw clean verify -Ddependency-check.skip=true — 2835 tests, 0 failures, JaCoCo coverage gate satisfied. ../mvnw -P integration verify also passes in full (Docker/TestContainers). Also manually verified end-to-end against a running standalone instance: header+versions are genuinely gone from storage (not just the header), repeat delete returns 404, invalid namespace returns 400, audit log entries are recorded, and the control-requirement-with-configurations 409-refuse-then-succeed sequence behaves as intended.

Checklist

  • My commits follow the conventional commit format
  • I have updated documentation if necessary
  • I have added tests for my changes (if applicable)
  • My changes follow the project's coding standards

- PERMISSIONS.md: GLOBAL admin capability table now lists content-resource
  deletion; note that it's deliberately GLOBAL-only (namespace/domain admin
  does not grant it) and that a control requirement with configurations
  refuses to delete.
- ADR 0001: flag that the 'nothing is ever deleted' premise no longer holds
  now that deleteResource exists.
- ADR 0003: correct deleteHeader's rationale, which claimed no delete
  endpoint existed for any type.
Architecture (Mongo + Nitrite): delete removes the whole resource including
all versions, and repeat/missing-id delete returns 404.

Control (Mongo + Nitrite): a fresh requirement + configuration exercises the
full cascade-refuse-then-succeed sequence — delete refuses with 409 while a
configuration exists, deleting the configuration first then lets the
requirement delete succeed. Also covers 404 for missing control/domain/
configuration.

Fixes a pre-existing Control integration test that assumed GET on
.../configurations/{id} (a path with no GET handler) fell through to 404 —
the new DELETE endpoint now claims that exact path, so JAX-RS correctly
reports 405 (path matched, method didn't) instead.
Architecture, Flow, Interface, Pattern, and Standard delete their resource
but never cleaned up the matching resource_mappings entry, so recreating
under the same custom-id threw DuplicateMappingException and the custom-id
route kept resolving to a numeric ID that no longer existed.

Adds ResourceMappingStore.deleteMappingByNumericId (a silent no-op when
no mapping exists, since most resources are never given a custom ID) and
wires it into all five delete endpoints via a small shared MappingCleanup
helper: best-effort, logged rather than failing the response, since the
resource is already gone by the time it runs.

Found via PR review.
countHeaders then deleteResource isn't atomic, so a configuration created
in the gap survives under a deleted requirement. Not fixed, consistent with
the rest of the store layer using no transactions or locks anywhere: a
Nitrite-only lock wouldn't extend the guarantee to Mongo, and the failure
mode is a data-hygiene issue, not a correctness or security one.

Raised in PR review.
NamespaceNotFoundException was the only caught exception, so a driver/DB
failure from the mapping store (MongoException, a Nitrite lock/store error)
would surface as an unhandled 500 for a delete that had already succeeded —
contradicting the javadoc's own promise that cleanup failures are logged
and swallowed.

Found via PR review.

@rocketstack-matt rocketstack-matt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid, well-tested implementation of the delete pattern across every resource type. One real pre-merge defect: the five custom-id-mapped resource types (Architecture, Flow, Interface, Pattern, Standard) delete the resource but never clean up their ResourceMappingStore entry, so recreating under the same custom-id throws DuplicateMappingException. Also flagging a non-atomic check-then-act race in the control cascade-refuse logic for discussion, plus a non-blocking nit.

Comment thread calm-hub/src/main/java/org/finos/calm/resources/FlowResource.java

@rocketstack-matt rocketstack-matt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One edge-case finding below; everything else — the fixes for the earlier review round, the idiom audit, and a Copilot CLI pass — checks out.

Comment thread calm-hub/src/main/java/org/finos/calm/resources/MappingCleanup.java

@rocketstack-matt rocketstack-matt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All prior review feedback addressed and verified — the mapping-cleanup runtime-exception fix in adde216 is correct and tested. Nothing outstanding.

@rocketstack-matt
rocketstack-matt merged commit 565251e into finos:main Aug 26, 2026
15 checks passed
@willosborne
willosborne deleted the features/calmhub-admin-delete branch August 26, 2026 13:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

calm-hub Affects `calm-hub`

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants