This is an automated email from the ASF dual-hosted git repository.

DImuthuUpe pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-custos.git

commit f392978aeb75f4ffcf328f23f003b89c710194ad
Author: DImuthuUpe <[email protected]>
AuthorDate: Sat May 16 21:21:59 2026 -0400

    Implemented the service layer for Allocation Change Requests and related 
events
---
 docs/API-Docs.md                                   | 186 +++++++++++++++++
 ...007_compute_allocation_change_requests.down.sql |  19 ++
 ...00007_compute_allocation_change_requests.up.sql |  49 +++++
 internal/server/server.go                          | 126 ++++++++++++
 ...ompute_allocation_change_request_event_store.go |  97 +++++++++
 .../compute_allocation_change_request_store.go     | 109 ++++++++++
 internal/store/store.go                            |  38 ++++
 pkg/models/allocation.go                           |  28 +--
 pkg/service/compute_allocation_change_request.go   | 219 +++++++++++++++++++++
 .../compute_allocation_change_request_event.go     | 119 +++++++++++
 pkg/service/service.go                             |   8 +
 11 files changed, 984 insertions(+), 14 deletions(-)

diff --git a/docs/API-Docs.md b/docs/API-Docs.md
index 69674e582..7854758d0 100644
--- a/docs/API-Docs.md
+++ b/docs/API-Docs.md
@@ -767,6 +767,192 @@ Return the most recent diff (highest `timestamp`) for the 
given allocation.
 
 ---
 
+## Compute Allocation Change Requests
+
+A change request represents a user- or admin-initiated proposal to mutate a
+compute allocation — e.g. asking for additional Service Units or to change
+its status. Each request carries a lifecycle (`change_status`: `PENDING`,
+`APPROVED`, `REJECTED`, etc.). Change requests are cascade-deleted when their
+parent allocation is deleted. Every create, update, and delete of a change
+request transactionally appends an entry to its event log (see below); the
+event log is intentionally **not** cascade-deleted so the audit trail
+survives the deletion of the parent change request.
+
+### `POST /compute-allocation-change-requests`
+
+Submit a new change request.
+
+**Required fields:** `compute_allocation_id`, `requester_id`
+**Optional fields:** `id`, `requested_su_amount`, `requested_status`, 
`reason`, `change_status` (defaults to `PENDING`), `approver_id`, `timestamp` 
(defaults to the server's current UTC time)
+
+**Request**
+
+```json
+{
+  "compute_allocation_id": "2f6a8c1d-3e4b-4a7d-8c91-aa12bb34cc56",
+  "requested_su_amount": 120000,
+  "requested_status": "ACTIVE",
+  "reason": "Need more SUs for upcoming HPC runs",
+  "requester_id": "11112222-3333-4444-5555-666677778888"
+}
+```
+
+**Response 201**
+
+```json
+{
+  "id": "9988aabb-ccdd-eeff-0011-223344556677",
+  "compute_allocation_id": "2f6a8c1d-3e4b-4a7d-8c91-aa12bb34cc56",
+  "requested_su_amount": 120000,
+  "requested_status": "ACTIVE",
+  "reason": "Need more SUs for upcoming HPC runs",
+  "change_status": "PENDING",
+  "requester_id": "11112222-3333-4444-5555-666677778888",
+  "timestamp": "2026-05-16T17:42:11.918Z"
+}
+```
+
+**Errors**
+
+- `400` — required field missing, or `compute_allocation_id` does not exist.
+
+---
+
+### `GET /compute-allocation-change-requests/{id}`
+
+Retrieve a single change request by its ID.
+
+**Errors**
+
+- `404` — no change request matches the supplied ID.
+
+---
+
+### `PUT /compute-allocation-change-requests/{id}`
+
+Replace mutable fields of a change request. Typically used by an approver to
+transition `change_status` (e.g. to `APPROVED` or `REJECTED`) and stamp
+`approver_id`. Omitted fields are preserved from the existing record.
+
+**Request**
+
+```json
+{
+  "change_status": "APPROVED",
+  "approver_id": "aaaa-bbbb-cccc-dddd-eeee"
+}
+```
+
+**Errors**
+
+- `400` — request id missing.
+- `404` — no change request matches the supplied ID.
+
+---
+
+### `DELETE /compute-allocation-change-requests/{id}`
+
+Remove a change request and (cascading) its event log.
+
+**Response 204** — empty body on success.
+
+---
+
+### `GET /compute-allocations/{id}/change-requests`
+
+List every change request ever submitted against the given allocation,
+ordered by `timestamp` ascending.
+
+---
+
+### `GET /users/{id}/change-requests`
+
+List every change request submitted by the given user, ordered by
+`timestamp` ascending.
+
+---
+
+## Compute Allocation Change Request Events
+
+Events are an append-only audit trail of state transitions applied to a
+change request — typically `CREATED`, `APPROVED`, `REJECTED`, `UPDATED`,
+`DELETED`, or arbitrary workflow markers. Create / update / delete of a
+change request each emit an event automatically; clients may also append
+custom events via the endpoint below. Events are **not** cascade-deleted
+when their parent change request is removed, so the audit trail is
+preserved indefinitely.
+
+### `POST /compute-allocation-change-request-events`
+
+Append a new event to a change request.
+
+**Required fields:** `compute_allocation_change_request_id`, `event_type`
+**Optional fields:** `id`, `description`, `timestamp` (defaults to the 
server's current UTC time)
+
+**Request**
+
+```json
+{
+  "compute_allocation_change_request_id": 
"9988aabb-ccdd-eeff-0011-223344556677",
+  "event_type": "APPROVED",
+  "description": "Change request approved by admin"
+}
+```
+
+**Response 201**
+
+```json
+{
+  "id": "ee11ff22-3344-5566-7788-99aabbccddee",
+  "compute_allocation_change_request_id": 
"9988aabb-ccdd-eeff-0011-223344556677",
+  "event_type": "APPROVED",
+  "description": "Change request approved by admin",
+  "timestamp": "2026-05-16T18:00:00.000Z"
+}
+```
+
+**Errors**
+
+- `400` — required field missing, or `compute_allocation_change_request_id` 
does not exist.
+
+---
+
+### `GET /compute-allocation-change-request-events/{id}`
+
+Retrieve a single event by its ID.
+
+**Errors**
+
+- `404` — no event matches the supplied ID.
+
+---
+
+### `DELETE /compute-allocation-change-request-events/{id}`
+
+Remove an event record. Intended for administrative cleanup; events are
+otherwise append-only.
+
+**Response 204** — empty body on success.
+
+---
+
+### `GET /compute-allocation-change-requests/{id}/events`
+
+List every event recorded against the given change request, ordered by
+`timestamp` ascending.
+
+---
+
+### `GET /compute-allocation-change-requests/{id}/events/latest`
+
+Return the most recent event for the given change request.
+
+**Errors**
+
+- `404` — the change request has no events recorded.
+
+---
+
 ## End-to-end example
 
 ```bash
diff --git 
a/internal/db/migrations/000007_compute_allocation_change_requests.down.sql 
b/internal/db/migrations/000007_compute_allocation_change_requests.down.sql
new file mode 100644
index 000000000..8140a01cb
--- /dev/null
+++ b/internal/db/migrations/000007_compute_allocation_change_requests.down.sql
@@ -0,0 +1,19 @@
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements.  See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership.  The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance
+-- with the License.  You may obtain a copy of the License at
+--
+--   http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing,
+-- software distributed under the License is distributed on an
+-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+-- KIND, either express or implied.  See the License for the
+-- specific language governing permissions and limitations
+-- under the License.
+
+DROP TABLE IF EXISTS compute_allocation_change_request_events;
+DROP TABLE IF EXISTS compute_allocation_change_requests;
diff --git 
a/internal/db/migrations/000007_compute_allocation_change_requests.up.sql 
b/internal/db/migrations/000007_compute_allocation_change_requests.up.sql
new file mode 100644
index 000000000..aa04a8571
--- /dev/null
+++ b/internal/db/migrations/000007_compute_allocation_change_requests.up.sql
@@ -0,0 +1,49 @@
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements.  See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership.  The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance
+-- with the License.  You may obtain a copy of the License at
+--
+--   http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing,
+-- software distributed under the License is distributed on an
+-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+-- KIND, either express or implied.  See the License for the
+-- specific language governing permissions and limitations
+-- under the License.
+
+CREATE TABLE IF NOT EXISTS compute_allocation_change_requests
+(
+    id                    VARCHAR(255) NOT NULL,
+    compute_allocation_id VARCHAR(255) NOT NULL,
+    requested_su_amount   BIGINT       NOT NULL DEFAULT 0,
+    requested_status      VARCHAR(64)  NOT NULL,
+    reason                TEXT         NOT NULL,
+    change_status         VARCHAR(64)  NOT NULL,
+    requester_id          VARCHAR(255) NOT NULL,
+    approver_id           VARCHAR(255) NOT NULL DEFAULT '',
+    timestamp             TIMESTAMP(6) NOT NULL,
+    created_at            TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+    PRIMARY KEY (id),
+    KEY idx_compute_allocation_change_requests_allocation 
(compute_allocation_id, timestamp),
+    KEY idx_compute_allocation_change_requests_status (change_status),
+    KEY idx_compute_allocation_change_requests_requester (requester_id),
+    CONSTRAINT fk_compute_allocation_change_requests_allocation FOREIGN KEY 
(compute_allocation_id)
+        REFERENCES compute_allocations (id) ON DELETE CASCADE
+) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+
+CREATE TABLE IF NOT EXISTS compute_allocation_change_request_events
+(
+    id                                  VARCHAR(255) NOT NULL,
+    compute_allocation_change_request_id VARCHAR(255) NOT NULL,
+    event_type                          VARCHAR(64)  NOT NULL,
+    description                         TEXT         NOT NULL,
+    timestamp                           TIMESTAMP(6) NOT NULL,
+    created_at                          TIMESTAMP(6) NOT NULL DEFAULT 
CURRENT_TIMESTAMP(6),
+    PRIMARY KEY (id),
+    KEY idx_compute_allocation_change_request_events_request 
(compute_allocation_change_request_id, timestamp),
+    KEY idx_compute_allocation_change_request_events_type (event_type)
+) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
diff --git a/internal/server/server.go b/internal/server/server.go
index 3638c0751..70f1e5948 100644
--- a/internal/server/server.go
+++ b/internal/server/server.go
@@ -86,6 +86,19 @@ func (s *Server) routes() {
        s.mux.HandleFunc("DELETE /compute-allocation-diffs/{id}", 
s.deleteComputeAllocationDiff)
        s.mux.HandleFunc("GET /compute-allocations/{id}/diffs", 
s.listDiffsForAllocation)
        s.mux.HandleFunc("GET /compute-allocations/{id}/diffs/latest", 
s.getLatestDiffForAllocation)
+
+       s.mux.HandleFunc("POST /compute-allocation-change-requests", 
s.createComputeAllocationChangeRequest)
+       s.mux.HandleFunc("GET /compute-allocation-change-requests/{id}", 
s.getComputeAllocationChangeRequest)
+       s.mux.HandleFunc("PUT /compute-allocation-change-requests/{id}", 
s.updateComputeAllocationChangeRequest)
+       s.mux.HandleFunc("DELETE /compute-allocation-change-requests/{id}", 
s.deleteComputeAllocationChangeRequest)
+       s.mux.HandleFunc("GET /compute-allocations/{id}/change-requests", 
s.listChangeRequestsForAllocation)
+       s.mux.HandleFunc("GET /users/{id}/change-requests", 
s.listChangeRequestsByRequester)
+
+       s.mux.HandleFunc("POST /compute-allocation-change-request-events", 
s.createComputeAllocationChangeRequestEvent)
+       s.mux.HandleFunc("GET /compute-allocation-change-request-events/{id}", 
s.getComputeAllocationChangeRequestEvent)
+       s.mux.HandleFunc("DELETE 
/compute-allocation-change-request-events/{id}", 
s.deleteComputeAllocationChangeRequestEvent)
+       s.mux.HandleFunc("GET /compute-allocation-change-requests/{id}/events", 
s.listEventsForChangeRequest)
+       s.mux.HandleFunc("GET 
/compute-allocation-change-requests/{id}/events/latest", 
s.getLatestEventForChangeRequest)
 }
 
 func (s *Server) healthz(w http.ResponseWriter, _ *http.Request) {
@@ -391,6 +404,119 @@ func (s *Server) getLatestDiffForAllocation(w 
http.ResponseWriter, r *http.Reque
        writeJSON(w, http.StatusOK, diff)
 }
 
+func (s *Server) createComputeAllocationChangeRequest(w http.ResponseWriter, r 
*http.Request) {
+       var req models.ComputeAllocationChangeRequest
+       if err := decodeJSON(r, &req); err != nil {
+               writeError(w, http.StatusBadRequest, err)
+               return
+       }
+       created, err := s.svc.CreateComputeAllocationChangeRequest(r.Context(), 
&req)
+       if err != nil {
+               writeServiceError(w, err)
+               return
+       }
+       writeJSON(w, http.StatusCreated, created)
+}
+
+func (s *Server) getComputeAllocationChangeRequest(w http.ResponseWriter, r 
*http.Request) {
+       req, err := s.svc.GetComputeAllocationChangeRequest(r.Context(), 
r.PathValue("id"))
+       if err != nil {
+               writeServiceError(w, err)
+               return
+       }
+       writeJSON(w, http.StatusOK, req)
+}
+
+func (s *Server) updateComputeAllocationChangeRequest(w http.ResponseWriter, r 
*http.Request) {
+       var req models.ComputeAllocationChangeRequest
+       if err := decodeJSON(r, &req); err != nil {
+               writeError(w, http.StatusBadRequest, err)
+               return
+       }
+       req.ID = r.PathValue("id")
+       updated, err := s.svc.UpdateComputeAllocationChangeRequest(r.Context(), 
&req)
+       if err != nil {
+               writeServiceError(w, err)
+               return
+       }
+       writeJSON(w, http.StatusOK, updated)
+}
+
+func (s *Server) deleteComputeAllocationChangeRequest(w http.ResponseWriter, r 
*http.Request) {
+       if err := s.svc.DeleteComputeAllocationChangeRequest(r.Context(), 
r.PathValue("id")); err != nil {
+               writeServiceError(w, err)
+               return
+       }
+       w.WriteHeader(http.StatusNoContent)
+}
+
+func (s *Server) listChangeRequestsForAllocation(w http.ResponseWriter, r 
*http.Request) {
+       rows, err := s.svc.ListChangeRequestsForAllocation(r.Context(), 
r.PathValue("id"))
+       if err != nil {
+               writeServiceError(w, err)
+               return
+       }
+       writeJSON(w, http.StatusOK, rows)
+}
+
+func (s *Server) listChangeRequestsByRequester(w http.ResponseWriter, r 
*http.Request) {
+       rows, err := s.svc.ListChangeRequestsByRequester(r.Context(), 
r.PathValue("id"))
+       if err != nil {
+               writeServiceError(w, err)
+               return
+       }
+       writeJSON(w, http.StatusOK, rows)
+}
+
+func (s *Server) createComputeAllocationChangeRequestEvent(w 
http.ResponseWriter, r *http.Request) {
+       var evt models.ComputeAllocationChangeRequestEvent
+       if err := decodeJSON(r, &evt); err != nil {
+               writeError(w, http.StatusBadRequest, err)
+               return
+       }
+       created, err := 
s.svc.CreateComputeAllocationChangeRequestEvent(r.Context(), &evt)
+       if err != nil {
+               writeServiceError(w, err)
+               return
+       }
+       writeJSON(w, http.StatusCreated, created)
+}
+
+func (s *Server) getComputeAllocationChangeRequestEvent(w http.ResponseWriter, 
r *http.Request) {
+       evt, err := s.svc.GetComputeAllocationChangeRequestEvent(r.Context(), 
r.PathValue("id"))
+       if err != nil {
+               writeServiceError(w, err)
+               return
+       }
+       writeJSON(w, http.StatusOK, evt)
+}
+
+func (s *Server) deleteComputeAllocationChangeRequestEvent(w 
http.ResponseWriter, r *http.Request) {
+       if err := s.svc.DeleteComputeAllocationChangeRequestEvent(r.Context(), 
r.PathValue("id")); err != nil {
+               writeServiceError(w, err)
+               return
+       }
+       w.WriteHeader(http.StatusNoContent)
+}
+
+func (s *Server) listEventsForChangeRequest(w http.ResponseWriter, r 
*http.Request) {
+       rows, err := s.svc.ListEventsForChangeRequest(r.Context(), 
r.PathValue("id"))
+       if err != nil {
+               writeServiceError(w, err)
+               return
+       }
+       writeJSON(w, http.StatusOK, rows)
+}
+
+func (s *Server) getLatestEventForChangeRequest(w http.ResponseWriter, r 
*http.Request) {
+       evt, err := s.svc.GetLatestEventForChangeRequest(r.Context(), 
r.PathValue("id"))
+       if err != nil {
+               writeServiceError(w, err)
+               return
+       }
+       writeJSON(w, http.StatusOK, evt)
+}
+
 // LoggingMiddleware logs every request once it completes.
 func LoggingMiddleware(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
diff --git a/internal/store/compute_allocation_change_request_event_store.go 
b/internal/store/compute_allocation_change_request_event_store.go
new file mode 100644
index 000000000..087a696d5
--- /dev/null
+++ b/internal/store/compute_allocation_change_request_event_store.go
@@ -0,0 +1,97 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package store
+
+import (
+       "context"
+       "database/sql"
+       "errors"
+
+       "github.com/jmoiron/sqlx"
+
+       "github.com/apache/airavata-custos/pkg/models"
+)
+
+const computeAllocationChangeRequestEventColumns = "id, 
compute_allocation_change_request_id, event_type, description, timestamp"
+
+type mysqlComputeAllocationChangeRequestEventStore struct {
+       db *sqlx.DB
+}
+
+// NewComputeAllocationChangeRequestEventStore returns a MySQL-backed
+// ComputeAllocationChangeRequestEventStore.
+func NewComputeAllocationChangeRequestEventStore(db *sqlx.DB) 
ComputeAllocationChangeRequestEventStore {
+       return &mysqlComputeAllocationChangeRequestEventStore{db: db}
+}
+
+func (s *mysqlComputeAllocationChangeRequestEventStore) FindByID(ctx 
context.Context, id string) (*models.ComputeAllocationChangeRequestEvent, 
error) {
+       var e models.ComputeAllocationChangeRequestEvent
+       err := s.db.GetContext(ctx, &e,
+               `SELECT `+computeAllocationChangeRequestEventColumns+` FROM 
compute_allocation_change_request_events WHERE id = ?`, id)
+       if err != nil {
+               if errors.Is(err, sql.ErrNoRows) {
+                       return nil, nil
+               }
+               return nil, err
+       }
+       return &e, nil
+}
+
+func (s *mysqlComputeAllocationChangeRequestEventStore) 
FindByChangeRequest(ctx context.Context, changeRequestID string) 
([]models.ComputeAllocationChangeRequestEvent, error) {
+       var rows []models.ComputeAllocationChangeRequestEvent
+       err := s.db.SelectContext(ctx, &rows,
+               `SELECT `+computeAllocationChangeRequestEventColumns+`
+                FROM compute_allocation_change_request_events
+                WHERE compute_allocation_change_request_id = ?
+                ORDER BY timestamp`, changeRequestID)
+       if err != nil {
+               return nil, err
+       }
+       return rows, nil
+}
+
+func (s *mysqlComputeAllocationChangeRequestEventStore) 
FindLatestByChangeRequest(ctx context.Context, changeRequestID string) 
(*models.ComputeAllocationChangeRequestEvent, error) {
+       var e models.ComputeAllocationChangeRequestEvent
+       err := s.db.GetContext(ctx, &e,
+               `SELECT `+computeAllocationChangeRequestEventColumns+`
+                FROM compute_allocation_change_request_events
+                WHERE compute_allocation_change_request_id = ?
+                ORDER BY timestamp DESC
+                LIMIT 1`, changeRequestID)
+       if err != nil {
+               if errors.Is(err, sql.ErrNoRows) {
+                       return nil, nil
+               }
+               return nil, err
+       }
+       return &e, nil
+}
+
+func (s *mysqlComputeAllocationChangeRequestEventStore) Create(ctx 
context.Context, tx *sql.Tx, e *models.ComputeAllocationChangeRequestEvent) 
error {
+       _, err := tx.ExecContext(ctx,
+               `INSERT INTO compute_allocation_change_request_events
+                    (id, compute_allocation_change_request_id, event_type, 
description, timestamp)
+                VALUES (?, ?, ?, ?, ?)`,
+               e.ID, e.ComputeAllocationChangeRequestID, e.EventType, 
e.Description, e.Timestamp)
+       return err
+}
+
+func (s *mysqlComputeAllocationChangeRequestEventStore) Delete(ctx 
context.Context, tx *sql.Tx, id string) error {
+       _, err := tx.ExecContext(ctx, `DELETE FROM 
compute_allocation_change_request_events WHERE id = ?`, id)
+       return err
+}
diff --git a/internal/store/compute_allocation_change_request_store.go 
b/internal/store/compute_allocation_change_request_store.go
new file mode 100644
index 000000000..b0b87fe8f
--- /dev/null
+++ b/internal/store/compute_allocation_change_request_store.go
@@ -0,0 +1,109 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package store
+
+import (
+       "context"
+       "database/sql"
+       "errors"
+
+       "github.com/jmoiron/sqlx"
+
+       "github.com/apache/airavata-custos/pkg/models"
+)
+
+const computeAllocationChangeRequestColumns = "id, compute_allocation_id, 
requested_su_amount, requested_status, reason, change_status, requester_id, 
approver_id, timestamp"
+
+type mysqlComputeAllocationChangeRequestStore struct {
+       db *sqlx.DB
+}
+
+// NewComputeAllocationChangeRequestStore returns a MySQL-backed
+// ComputeAllocationChangeRequestStore.
+func NewComputeAllocationChangeRequestStore(db *sqlx.DB) 
ComputeAllocationChangeRequestStore {
+       return &mysqlComputeAllocationChangeRequestStore{db: db}
+}
+
+func (s *mysqlComputeAllocationChangeRequestStore) FindByID(ctx 
context.Context, id string) (*models.ComputeAllocationChangeRequest, error) {
+       var c models.ComputeAllocationChangeRequest
+       err := s.db.GetContext(ctx, &c,
+               `SELECT `+computeAllocationChangeRequestColumns+` FROM 
compute_allocation_change_requests WHERE id = ?`, id)
+       if err != nil {
+               if errors.Is(err, sql.ErrNoRows) {
+                       return nil, nil
+               }
+               return nil, err
+       }
+       return &c, nil
+}
+
+func (s *mysqlComputeAllocationChangeRequestStore) FindByAllocation(ctx 
context.Context, allocationID string) ([]models.ComputeAllocationChangeRequest, 
error) {
+       var rows []models.ComputeAllocationChangeRequest
+       err := s.db.SelectContext(ctx, &rows,
+               `SELECT `+computeAllocationChangeRequestColumns+`
+                FROM compute_allocation_change_requests
+                WHERE compute_allocation_id = ?
+                ORDER BY timestamp`, allocationID)
+       if err != nil {
+               return nil, err
+       }
+       return rows, nil
+}
+
+func (s *mysqlComputeAllocationChangeRequestStore) FindByRequester(ctx 
context.Context, requesterID string) ([]models.ComputeAllocationChangeRequest, 
error) {
+       var rows []models.ComputeAllocationChangeRequest
+       err := s.db.SelectContext(ctx, &rows,
+               `SELECT `+computeAllocationChangeRequestColumns+`
+                FROM compute_allocation_change_requests
+                WHERE requester_id = ?
+                ORDER BY timestamp`, requesterID)
+       if err != nil {
+               return nil, err
+       }
+       return rows, nil
+}
+
+func (s *mysqlComputeAllocationChangeRequestStore) Create(ctx context.Context, 
tx *sql.Tx, c *models.ComputeAllocationChangeRequest) error {
+       _, err := tx.ExecContext(ctx,
+               `INSERT INTO compute_allocation_change_requests
+                    (id, compute_allocation_id, requested_su_amount, 
requested_status, reason, change_status, requester_id, approver_id, timestamp)
+                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
+               c.ID, c.ComputeAllocationID, c.RequestedSUAmount, 
string(c.RequestedStatus), c.Reason, c.ChangeStatus, c.RequesterID, 
c.ApproverID, c.Timestamp)
+       return err
+}
+
+func (s *mysqlComputeAllocationChangeRequestStore) Update(ctx context.Context, 
tx *sql.Tx, c *models.ComputeAllocationChangeRequest) error {
+       _, err := tx.ExecContext(ctx,
+               `UPDATE compute_allocation_change_requests
+                   SET compute_allocation_id = ?,
+                       requested_su_amount   = ?,
+                       requested_status      = ?,
+                       reason                = ?,
+                       change_status         = ?,
+                       requester_id          = ?,
+                       approver_id           = ?,
+                       timestamp             = ?
+                 WHERE id = ?`,
+               c.ComputeAllocationID, c.RequestedSUAmount, 
string(c.RequestedStatus), c.Reason, c.ChangeStatus, c.RequesterID, 
c.ApproverID, c.Timestamp, c.ID)
+       return err
+}
+
+func (s *mysqlComputeAllocationChangeRequestStore) Delete(ctx context.Context, 
tx *sql.Tx, id string) error {
+       _, err := tx.ExecContext(ctx, `DELETE FROM 
compute_allocation_change_requests WHERE id = ?`, id)
+       return err
+}
diff --git a/internal/store/store.go b/internal/store/store.go
index 6624d8f5a..54a367c49 100644
--- a/internal/store/store.go
+++ b/internal/store/store.go
@@ -170,3 +170,41 @@ type ComputeAllocationDiffStore interface {
        // Delete removes a diff by ID within the provided transaction.
        Delete(ctx context.Context, tx *sql.Tx, id string) error
 }
+
+// ComputeAllocationChangeRequestStore defines persistence operations for
+// user/admin requests to change a compute allocation (e.g. asking for more
+// SUs or to change its status).
+type ComputeAllocationChangeRequestStore interface {
+       // FindByID returns the change request with the given ID, or nil if it 
does not exist.
+       FindByID(ctx context.Context, id string) 
(*models.ComputeAllocationChangeRequest, error)
+       // FindByAllocation returns every change request ever recorded against 
the
+       // given allocation, ordered by timestamp ascending.
+       FindByAllocation(ctx context.Context, allocationID string) 
([]models.ComputeAllocationChangeRequest, error)
+       // FindByRequester returns every change request made by the given user,
+       // ordered by timestamp ascending.
+       FindByRequester(ctx context.Context, requesterID string) 
([]models.ComputeAllocationChangeRequest, error)
+       // Create inserts a new change request within the provided transaction.
+       Create(ctx context.Context, tx *sql.Tx, c 
*models.ComputeAllocationChangeRequest) error
+       // Update replaces mutable fields of an existing change request within 
the provided transaction.
+       Update(ctx context.Context, tx *sql.Tx, c 
*models.ComputeAllocationChangeRequest) error
+       // Delete removes a change request by ID within the provided 
transaction.
+       Delete(ctx context.Context, tx *sql.Tx, id string) error
+}
+
+// ComputeAllocationChangeRequestEventStore defines persistence operations
+// for the append-only audit trail of state transitions applied to a
+// ComputeAllocationChangeRequest.
+type ComputeAllocationChangeRequestEventStore interface {
+       // FindByID returns the event with the given ID, or nil if it does not 
exist.
+       FindByID(ctx context.Context, id string) 
(*models.ComputeAllocationChangeRequestEvent, error)
+       // FindByChangeRequest returns every event recorded against the given
+       // change request, ordered by timestamp ascending.
+       FindByChangeRequest(ctx context.Context, changeRequestID string) 
([]models.ComputeAllocationChangeRequestEvent, error)
+       // FindLatestByChangeRequest returns the most recent event for the given
+       // change request, or nil if none exist.
+       FindLatestByChangeRequest(ctx context.Context, changeRequestID string) 
(*models.ComputeAllocationChangeRequestEvent, error)
+       // Create inserts a new event within the provided transaction.
+       Create(ctx context.Context, tx *sql.Tx, e 
*models.ComputeAllocationChangeRequestEvent) error
+       // Delete removes an event by ID within the provided transaction.
+       Delete(ctx context.Context, tx *sql.Tx, id string) error
+}
diff --git a/pkg/models/allocation.go b/pkg/models/allocation.go
index b3e284927..a6003f987 100644
--- a/pkg/models/allocation.go
+++ b/pkg/models/allocation.go
@@ -58,23 +58,23 @@ type ComputeAllocationDiff struct { // Diff will occur 
either through a change r
 }
 
 type ComputeAllocationChangeRequest struct { // Represents a request to change 
the allocation, e.g., requesting more SUs, requesting a reduction in SUs, etc 
from users or admins.
-       ID                  string           `json:"id"`
-       ComputeAllocationID string           `json:"compute_allocation_id"`
-       RequestedSUAmount   int64            `json:"requested_su_amount"`   // 
The requested allocation amount in SUs, e.g., 1200 SUs, etc.
-       RequestedStatus     AllocationStatus `json:"requested_status"`      // 
ACTIVE, INACTIVE, DELETED, etc.
-       Reason              string           `json:"reason"`                // 
The reason for the change request, e.g., "Need more SUs for upcoming jobs", 
"Requesting reduction in SUs due to project completion", etc.
-       ChangeStatus        string           `json:"change_status"`         // 
"PENDING", "APPROVED", "REJECTED", etc.
-       RequesterID         string           `json:"requester_id"`          // 
The ID of the user who made the change request.
-       ApproverID          string           `json:"approver_id,omitempty"` // 
The ID of the user who approved/rejected the change request, if applicable.
-       Timestamp           time.Time        `json:"timestamp"`             // 
The time when the change request was made.
+       ID                  string           `json:"id"                    
db:"id"`
+       ComputeAllocationID string           `json:"compute_allocation_id" 
db:"compute_allocation_id"`
+       RequestedSUAmount   int64            `json:"requested_su_amount"   
db:"requested_su_amount"` // The requested allocation amount in SUs, e.g., 1200 
SUs, etc.
+       RequestedStatus     AllocationStatus `json:"requested_status"      
db:"requested_status"`    // ACTIVE, INACTIVE, DELETED, etc.
+       Reason              string           `json:"reason"                
db:"reason"`              // The reason for the change request, e.g., "Need 
more SUs for upcoming jobs", "Requesting reduction in SUs due to project 
completion", etc.
+       ChangeStatus        string           `json:"change_status"         
db:"change_status"`       // "PENDING", "APPROVED", "REJECTED", etc.
+       RequesterID         string           `json:"requester_id"          
db:"requester_id"`        // The ID of the user who made the change request.
+       ApproverID          string           `json:"approver_id,omitempty" 
db:"approver_id"`         // The ID of the user who approved/rejected the 
change request, if applicable.
+       Timestamp           time.Time        `json:"timestamp"             
db:"timestamp"`           // The time when the change request was made.
 }
 
 type ComputeAllocationChangeRequestEvent struct {
-       ID                               string    `json:"id"`
-       ComputeAllocationChangeRequestID string    
`json:"compute_allocation_change_request_id"`
-       EventType                        string    `json:"event_type"`          
  // "CREATED", "APPROVED", "REJECTED", etc.
-       Description                      string    
`json:"description,omitempty"` // Optional description of the event, e.g., 
"Change request created by user", "Change request approved by admin", etc.
-       Timestamp                        time.Time `json:"timestamp"`           
  // The time when the event occurred.
+       ID                               string    `json:"id"                   
                db:"id"`
+       ComputeAllocationChangeRequestID string    
`json:"compute_allocation_change_request_id" 
db:"compute_allocation_change_request_id"`
+       EventType                        string    `json:"event_type"           
                db:"event_type"`  // "CREATED", "APPROVED", "REJECTED", etc.
+       Description                      string    
`json:"description,omitempty"                db:"description"` // Optional 
description of the event, e.g., "Change request created by user", "Change 
request approved by admin", etc.
+       Timestamp                        time.Time `json:"timestamp"            
                db:"timestamp"`   // The time when the event occurred.
 }
 
 type ComputeAllocationUsage struct { // Represents the usage of a compute 
allocation, e.g., when a job consumes some of the allocated SUs, etc.
diff --git a/pkg/service/compute_allocation_change_request.go 
b/pkg/service/compute_allocation_change_request.go
new file mode 100644
index 000000000..06bda3024
--- /dev/null
+++ b/pkg/service/compute_allocation_change_request.go
@@ -0,0 +1,219 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package service
+
+import (
+       "context"
+       "database/sql"
+       "fmt"
+
+       "github.com/apache/airavata-custos/pkg/models"
+)
+
+// CreateComputeAllocationChangeRequest records a new change request against a
+// compute allocation. The referenced allocation must exist and a requester
+// must be supplied. ChangeStatus defaults to "PENDING" and Timestamp to the
+// server's current UTC time. A "CREATED" event is appended to the change
+// request's audit log in the same transaction.
+func (s *Service) CreateComputeAllocationChangeRequest(ctx context.Context, 
req *models.ComputeAllocationChangeRequest) 
(*models.ComputeAllocationChangeRequest, error) {
+       if req == nil {
+               return nil, fmt.Errorf("%w: compute allocation change request 
is nil", ErrInvalidInput)
+       }
+       if req.ComputeAllocationID == "" {
+               return nil, fmt.Errorf("%w: compute_allocation_id is required", 
ErrInvalidInput)
+       }
+       if req.RequesterID == "" {
+               return nil, fmt.Errorf("%w: requester_id is required", 
ErrInvalidInput)
+       }
+
+       if alloc, err := s.allocs.FindByID(ctx, req.ComputeAllocationID); err 
!= nil {
+               return nil, fmt.Errorf("lookup compute allocation: %w", err)
+       } else if alloc == nil {
+               return nil, fmt.Errorf("%w: compute allocation %q not found", 
ErrInvalidInput, req.ComputeAllocationID)
+       }
+
+       if req.ID == "" {
+               req.ID = newID()
+       }
+       if req.ChangeStatus == "" {
+               req.ChangeStatus = "PENDING"
+       }
+       if req.Timestamp.IsZero() {
+               req.Timestamp = nowUTC()
+       }
+
+       if err := s.inTx(ctx, func(tx *sql.Tx) error {
+               if err := s.changeRequests.Create(ctx, tx, req); err != nil {
+                       return err
+               }
+               return s.changeEvents.Create(ctx, tx, 
&models.ComputeAllocationChangeRequestEvent{
+                       ID:                               newID(),
+                       ComputeAllocationChangeRequestID: req.ID,
+                       EventType:                        "CREATED",
+                       Description:                      "Change request 
created",
+                       Timestamp:                        req.Timestamp,
+               })
+       }); err != nil {
+               return nil, fmt.Errorf("create compute allocation change 
request: %w", err)
+       }
+       return req, nil
+}
+
+// GetComputeAllocationChangeRequest retrieves a change request by its ID.
+// Returns ErrNotFound when no request matches.
+func (s *Service) GetComputeAllocationChangeRequest(ctx context.Context, id 
string) (*models.ComputeAllocationChangeRequest, error) {
+       c, err := s.changeRequests.FindByID(ctx, id)
+       if err != nil {
+               return nil, fmt.Errorf("get compute allocation change request: 
%w", err)
+       }
+       if c == nil {
+               return nil, ErrNotFound
+       }
+       return c, nil
+}
+
+// ListChangeRequestsForAllocation returns every change request ever recorded
+// against the given allocation, ordered chronologically by timestamp.
+func (s *Service) ListChangeRequestsForAllocation(ctx context.Context, 
allocationID string) ([]models.ComputeAllocationChangeRequest, error) {
+       if allocationID == "" {
+               return nil, fmt.Errorf("%w: compute_allocation_id is required", 
ErrInvalidInput)
+       }
+       rows, err := s.changeRequests.FindByAllocation(ctx, allocationID)
+       if err != nil {
+               return nil, fmt.Errorf("list change requests for allocation: 
%w", err)
+       }
+       return rows, nil
+}
+
+// ListChangeRequestsByRequester returns every change request submitted by the
+// given user, ordered chronologically by timestamp.
+func (s *Service) ListChangeRequestsByRequester(ctx context.Context, 
requesterID string) ([]models.ComputeAllocationChangeRequest, error) {
+       if requesterID == "" {
+               return nil, fmt.Errorf("%w: requester_id is required", 
ErrInvalidInput)
+       }
+       rows, err := s.changeRequests.FindByRequester(ctx, requesterID)
+       if err != nil {
+               return nil, fmt.Errorf("list change requests by requester: %w", 
err)
+       }
+       return rows, nil
+}
+
+// UpdateComputeAllocationChangeRequest replaces mutable fields of an existing
+// change request — typically used to transition change_status and record the
+// approver. An audit event is appended in the same transaction: when
+// change_status transitions to a new value the event_type is the new status
+// (e.g. "APPROVED"), otherwise "UPDATED".
+func (s *Service) UpdateComputeAllocationChangeRequest(ctx context.Context, 
req *models.ComputeAllocationChangeRequest) 
(*models.ComputeAllocationChangeRequest, error) {
+       if req == nil || req.ID == "" {
+               return nil, fmt.Errorf("%w: compute allocation change request 
id is required", ErrInvalidInput)
+       }
+       existing, err := s.changeRequests.FindByID(ctx, req.ID)
+       if err != nil {
+               return nil, fmt.Errorf("lookup compute allocation change 
request: %w", err)
+       }
+       if existing == nil {
+               return nil, ErrNotFound
+       }
+       if req.ComputeAllocationID == "" {
+               req.ComputeAllocationID = existing.ComputeAllocationID
+       }
+       if req.RequesterID == "" {
+               req.RequesterID = existing.RequesterID
+       }
+       if req.ChangeStatus == "" {
+               req.ChangeStatus = existing.ChangeStatus
+       }
+       if req.Timestamp.IsZero() {
+               req.Timestamp = existing.Timestamp
+       }
+
+       eventType := "UPDATED"
+       eventDescription := "Change request updated"
+       if req.ChangeStatus != existing.ChangeStatus {
+               eventType = req.ChangeStatus
+               eventDescription = fmt.Sprintf("Change request transitioned 
from %s to %s", existing.ChangeStatus, req.ChangeStatus)
+       }
+
+       // When a change request transitions into APPROVED, materialise the
+       // approved change as a ComputeAllocationDiff so the allocation's audit
+       // log reflects the agreed-upon new SU amount and status.
+       approved := req.ChangeStatus == "APPROVED" && existing.ChangeStatus != 
"APPROVED"
+
+       if err := s.inTx(ctx, func(tx *sql.Tx) error {
+               if err := s.changeRequests.Update(ctx, tx, req); err != nil {
+                       return err
+               }
+               return s.changeEvents.Create(ctx, tx, 
&models.ComputeAllocationChangeRequestEvent{
+                       ID:                               newID(),
+                       ComputeAllocationChangeRequestID: req.ID,
+                       EventType:                        eventType,
+                       Description:                      eventDescription,
+                       Timestamp:                        nowUTC(),
+               })
+       }); err != nil {
+               return nil, fmt.Errorf("update compute allocation change 
request: %w", err)
+       }
+
+       if approved {
+               diffStatus := req.RequestedStatus
+               if diffStatus == "" {
+                       diffStatus = models.ACTIVE
+               }
+               if _, err := s.CreateComputeAllocationDiff(ctx, 
&models.ComputeAllocationDiff{
+                       ComputeAllocationID: req.ComputeAllocationID,
+                       DiffType:            "CHANGE_REQUEST_APPROVED",
+                       NewSUAmount:         req.RequestedSUAmount,
+                       Status:              diffStatus,
+                       Description:         fmt.Sprintf("Applied approved 
change request %s", req.ID),
+               }); err != nil {
+                       return nil, fmt.Errorf("record approved change request 
diff: %w", err)
+               }
+       }
+       return req, nil
+}
+
+// DeleteComputeAllocationChangeRequest removes a change request by ID. A
+// "DELETED" event is appended to the audit log first so the trail survives
+// the parent record's removal.
+func (s *Service) DeleteComputeAllocationChangeRequest(ctx context.Context, id 
string) error {
+       if id == "" {
+               return fmt.Errorf("%w: compute allocation change request id is 
required", ErrInvalidInput)
+       }
+       existing, err := s.changeRequests.FindByID(ctx, id)
+       if err != nil {
+               return fmt.Errorf("lookup compute allocation change request: 
%w", err)
+       }
+       if existing == nil {
+               return ErrNotFound
+       }
+       if err := s.inTx(ctx, func(tx *sql.Tx) error {
+               if err := s.changeEvents.Create(ctx, tx, 
&models.ComputeAllocationChangeRequestEvent{
+                       ID:                               newID(),
+                       ComputeAllocationChangeRequestID: id,
+                       EventType:                        "DELETED",
+                       Description:                      "Change request 
deleted",
+                       Timestamp:                        nowUTC(),
+               }); err != nil {
+                       return err
+               }
+               return s.changeRequests.Delete(ctx, tx, id)
+       }); err != nil {
+               return fmt.Errorf("delete compute allocation change request: 
%w", err)
+       }
+       return nil
+}
diff --git a/pkg/service/compute_allocation_change_request_event.go 
b/pkg/service/compute_allocation_change_request_event.go
new file mode 100644
index 000000000..7dd277796
--- /dev/null
+++ b/pkg/service/compute_allocation_change_request_event.go
@@ -0,0 +1,119 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package service
+
+import (
+       "context"
+       "database/sql"
+       "fmt"
+
+       "github.com/apache/airavata-custos/pkg/models"
+)
+
+// CreateComputeAllocationChangeRequestEvent records a new event against a
+// change request. The referenced change request must exist. If Timestamp is
+// zero the server's current UTC time is used. Event records are append-only
+// and have no update operation.
+func (s *Service) CreateComputeAllocationChangeRequestEvent(ctx 
context.Context, evt *models.ComputeAllocationChangeRequestEvent) 
(*models.ComputeAllocationChangeRequestEvent, error) {
+       if evt == nil {
+               return nil, fmt.Errorf("%w: compute allocation change request 
event is nil", ErrInvalidInput)
+       }
+       if evt.ComputeAllocationChangeRequestID == "" {
+               return nil, fmt.Errorf("%w: 
compute_allocation_change_request_id is required", ErrInvalidInput)
+       }
+       if evt.EventType == "" {
+               return nil, fmt.Errorf("%w: event_type is required", 
ErrInvalidInput)
+       }
+
+       if cr, err := s.changeRequests.FindByID(ctx, 
evt.ComputeAllocationChangeRequestID); err != nil {
+               return nil, fmt.Errorf("lookup compute allocation change 
request: %w", err)
+       } else if cr == nil {
+               return nil, fmt.Errorf("%w: compute allocation change request 
%q not found", ErrInvalidInput, evt.ComputeAllocationChangeRequestID)
+       }
+
+       if evt.ID == "" {
+               evt.ID = newID()
+       }
+       if evt.Timestamp.IsZero() {
+               evt.Timestamp = nowUTC()
+       }
+
+       if err := s.inTx(ctx, func(tx *sql.Tx) error {
+               return s.changeEvents.Create(ctx, tx, evt)
+       }); err != nil {
+               return nil, fmt.Errorf("create compute allocation change 
request event: %w", err)
+       }
+       return evt, nil
+}
+
+// GetComputeAllocationChangeRequestEvent retrieves an event by its ID.
+// Returns ErrNotFound when no event matches.
+func (s *Service) GetComputeAllocationChangeRequestEvent(ctx context.Context, 
id string) (*models.ComputeAllocationChangeRequestEvent, error) {
+       e, err := s.changeEvents.FindByID(ctx, id)
+       if err != nil {
+               return nil, fmt.Errorf("get compute allocation change request 
event: %w", err)
+       }
+       if e == nil {
+               return nil, ErrNotFound
+       }
+       return e, nil
+}
+
+// ListEventsForChangeRequest returns every event recorded against the given
+// change request, ordered chronologically by timestamp.
+func (s *Service) ListEventsForChangeRequest(ctx context.Context, 
changeRequestID string) ([]models.ComputeAllocationChangeRequestEvent, error) {
+       if changeRequestID == "" {
+               return nil, fmt.Errorf("%w: 
compute_allocation_change_request_id is required", ErrInvalidInput)
+       }
+       rows, err := s.changeEvents.FindByChangeRequest(ctx, changeRequestID)
+       if err != nil {
+               return nil, fmt.Errorf("list events for change request: %w", 
err)
+       }
+       return rows, nil
+}
+
+// GetLatestEventForChangeRequest returns the most recent event for the given
+// change request. Returns ErrNotFound when the change request has no events.
+func (s *Service) GetLatestEventForChangeRequest(ctx context.Context, 
changeRequestID string) (*models.ComputeAllocationChangeRequestEvent, error) {
+       if changeRequestID == "" {
+               return nil, fmt.Errorf("%w: 
compute_allocation_change_request_id is required", ErrInvalidInput)
+       }
+       e, err := s.changeEvents.FindLatestByChangeRequest(ctx, changeRequestID)
+       if err != nil {
+               return nil, fmt.Errorf("get latest event for change request: 
%w", err)
+       }
+       if e == nil {
+               return nil, ErrNotFound
+       }
+       return e, nil
+}
+
+// DeleteComputeAllocationChangeRequestEvent removes an event record by ID.
+// This is intended for administrative cleanup of erroneous entries; the
+// event log is otherwise append-only.
+func (s *Service) DeleteComputeAllocationChangeRequestEvent(ctx 
context.Context, id string) error {
+       if id == "" {
+               return fmt.Errorf("%w: compute allocation change request event 
id is required", ErrInvalidInput)
+       }
+       if err := s.inTx(ctx, func(tx *sql.Tx) error {
+               return s.changeEvents.Delete(ctx, tx, id)
+       }); err != nil {
+               return fmt.Errorf("delete compute allocation change request 
event: %w", err)
+       }
+       return nil
+}
diff --git a/pkg/service/service.go b/pkg/service/service.go
index 90de9e238..1942451be 100644
--- a/pkg/service/service.go
+++ b/pkg/service/service.go
@@ -43,6 +43,8 @@ type Service struct {
        resourceMappings store.ComputeAllocationResourceMappingStore
        resourceRates    store.ComputeAllocationResourceRateStore
        allocDiffs       store.ComputeAllocationDiffStore
+       changeRequests   store.ComputeAllocationChangeRequestStore
+       changeEvents     store.ComputeAllocationChangeRequestEventStore
 }
 
 // New constructs a Service backed by the supplied database handle.
@@ -59,6 +61,8 @@ func New(database *sqlx.DB) *Service {
                resourceMappings: 
store.NewComputeAllocationResourceMappingStore(database),
                resourceRates:    
store.NewComputeAllocationResourceRateStore(database),
                allocDiffs:       store.NewComputeAllocationDiffStore(database),
+               changeRequests:   
store.NewComputeAllocationChangeRequestStore(database),
+               changeEvents:     
store.NewComputeAllocationChangeRequestEventStore(database),
        }
 }
 
@@ -76,6 +80,8 @@ func NewWithStores(
        resourceMappings store.ComputeAllocationResourceMappingStore,
        resourceRates store.ComputeAllocationResourceRateStore,
        allocDiffs store.ComputeAllocationDiffStore,
+       changeRequests store.ComputeAllocationChangeRequestStore,
+       changeEvents store.ComputeAllocationChangeRequestEventStore,
 ) *Service {
        return &Service{
                db:               database,
@@ -88,6 +94,8 @@ func NewWithStores(
                resourceMappings: resourceMappings,
                resourceRates:    resourceRates,
                allocDiffs:       allocDiffs,
+               changeRequests:   changeRequests,
+               changeEvents:     changeEvents,
        }
 }
 


Reply via email to