Skip to content

[BE] Build Update / Delete (archive) endpoint for a Competency Rule Profile (scoped to user's permissions) #632

Description

@jesperhodge

Blocked by: #613/#641 (data model), and #631's shared scaffolding (RuleProfileSerializer, is_rule_profile_in_use, the rest_api/v1/ package) — whichever of #631/#632/#633 lands first creates it. Also the one-time openedx-platform wiring from #664.

Repo: openedx-core.

Use Case

As a Platform Administrator, I want to update a Competency Rule Profile's rule (and archive a taxonomy-scoped one I no longer want), so that I can adjust the default mastery threshold — while being warned before a change affects learners who already have recorded statuses, and without ever hard-deleting a profile that learner history depends on.

Description

Current state

No CompetencyRuleProfile API exists. Per the merged ADR: a profile's scope fields are immutable after creation — only rule_type/rule_payload may be edited. Retirement is archive-only, never a hard delete, even pre-use (a carve-out from the general delete-protection rule) via the archived column; archived profiles remain queryable so existing criteria and learner history stay resolvable. The system-default profile is not edited through this API at all (6.7, Django admin only) — this ticket covers taxonomy-scoped profiles exclusively.

Requested change

  • Update PATCH /cbe/rest_api/v1/rule-profiles/<pk>/ — edits rule_type/rule_payload only, for a taxonomy-scoped profile (permission: can_change_taxonomy on its taxonomy; the system default is 6.7/Django-admin only, not this endpoint) and to the system-default profile (permission: is_taxonomy_admin/platform-admin only. Attempting to change any scope field returns 400 (never silently ignored). No backend in-use gate (2026-07-20): ADR 0003 Decision 4's warning/confirmation requirement is a Studio-level guardrail (0003-competency-criteria-versioning.rst:40-43), not an API requirement — this endpoint applies the update unconditionally; the warning dialog lives in Studio (6.5).
  • Delete = archive POST /cbe/rest_api/v1/rule-profiles/<pk>/archive/ — sets archived = true, idempotent. Taxonomy-scoped only; the system-default cannot be archived/deleted. No in-use gate on archive — archiving doesn't retroactively change what already-resolved criteria point to (ADR 0003 Decision 4 lists edit / reassignment / override-switching as the warning triggers; archiving is not among them). Never a hard delete; no DELETE verb.

Explicitly out of scope

Acceptance Criteria

Verifiable via Postman.

Scenario: Update a taxonomy-scoped profile's rule when not in use
  Given a taxonomy-scoped profile exists and is not in use, requester has can_change_taxonomy
  When a PATCH updates "rule_type"/"rule_payload"
  Then the response is 200 and the change is applied

Scenario: In-use edit requires confirmation
  Given a taxonomy-scoped (or system-default) profile is in use
  When a PATCH changes a rule field  Then the response is 200 and the change is applied

Scenario: Reject a scope-field change
  Given any profile exists
  When a PATCH includes "competency_taxonomy_id" (or organization_id/course_id)
  Then the response is 400 identifying the scope field as immutable


Scenario: Archive a taxonomy-scoped profile (no in-use gate)
  Given a taxonomy-scoped profile, in use or not
  When an archive request is sent (no confirm flag)
  Then the response is 200 and the profile is archived
  And a subsequent read shows "archived": true, and the row + referencing criteria remain resolvable

Scenario: System-default cannot be archived
  When an archive request targets the system-default profile
  Then the response is 400/403 (archiving the seeded default is not allowed)

Scenario: No hard delete
  When a DELETE verb is sent to the profile endpoint
  Then the response is 405

Scenario: Reject update/archive without permission
  Given the requester lacks the applicable permission
  Then update and archive return 403

Context

Technical Notes

Files to Modify

File Nature
src/openedx_learning/applets/cbe/api.py add update_rule_profile(profile_id, *, rule_type=None, rule_payload=None) and archive_rule_profile(profile_id) (idempotent)
src/openedx_learning/applets/cbe/rest_api/v1/views.py add RuleProfileDetailView(generics.RetrieveUpdateAPIView) (patch only for writes here; get is #633) and RuleProfileArchiveView(generics.GenericAPIView)
src/openedx_learning/applets/cbe/rest_api/v1/permissions.py can_change_taxonomy for taxonomy-scoped
src/openedx_learning/applets/cbe/rest_api/v1/urls.py register rule-profiles/<int:pk>/ (patch) and rule-profiles/<int:pk>/archive/ (post)
.../rest_api/v1/tests/test_views.py update / immutable-scope / archive / no-hard-delete / permission tests

Implementation Notes

Archive is a dedicated POST .../archive/ action, not PATCH {"archived": true} — keeps rule-editing and lifecycle retirement on separate code paths, legible from the URL (neither is backend in-use-gated, only frontend in-use-gated). perform_update: reject any scope-field key (400); else call update_rule_profile(...) unconditionally (never serializer.save() directly; no in-use confirmation gate). Resolve the applicable permission via can_change_taxonomy on the target row's taxonomy (system-default is out of scope for this endpoint so no platform-admin branch is needed here). archive_rule_profile refuses the system-default. http_method_names excludes delete on the detail view (→ 405).

Example Resolution Prompt

In openedx-core, add Update and Archive for CompetencyRuleProfile (reusing #631's RuleProfileSerializer, is_rule_profile_in_use). Add update_rule_profile(profile_id, *, rule_type=None, rule_payload=None) and archive_rule_profile(profile_id) (idempotent; refuses the system-default) to applets/cbe/api.py. Add RuleProfileDetailView(generics.RetrieveUpdateAPIView) at rule-profiles/<int:pk>/ (http_method_names excludes delete) whose perform_update rejects scope-field changes (400) and calls update_rule_profile unconditionally (no in-use confirmation gate). Add RuleProfileArchiveView at rule-profiles/<int:pk>/archive/ (POST, no in-use gate). Gate taxonomy-scoped edits to can_change_taxonomy; system-default is out of scope for this endpoint (6.7, Django admin) and its archive is disallowed. Return 200/400/403/405 per the AC. Never implement a hard delete.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Final Axim Review

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions