# OGSG Asset Management System — Production Readiness Audit

**Audit date:** 20 June 2026  
**Scope:** Current repository at commit `6587a10`, including Laravel application code, Blade UI, routes, models, migrations, policies, services, imports/exports, automated tests, environment template, and GitHub Actions workflow. Requirements context was also taken from the **OGSG Asset Management System Project Plan** dated 23 January 2026 and the **Proposal for the Ogun State ICT Asset Management and Request Management System (IAMS)** dated 18 September 2024.  
**Assessment posture:** State-wide government system handling public assets and public funds. Controls are assessed for enforceability and evidence, not merely for presence in the UI.  
**Code changes:** None. This document is the only artifact added by the audit.

## Executive Summary

The application is **not ready for production deployment** as the Ogun State Government's authoritative asset register.

It is a functional departmental prototype with a coherent Laravel structure, role-routed screens, ministry-scoped read paths, procurement/request happy paths, UUID route binding, login throttling, soft deletes, basic activity records, and a green automated baseline. Those are useful foundations. They do not provide the controls required for government custody, procurement traceability, financial integrity, or defensible audit evidence.

The most serious defect is an approval bypass: the second or final assigned approver can post directly to the approval endpoint before earlier levels approve. The POST action does not repeat the sequence check used by the GET screen, and the service marks the request approved when the acting level equals the maximum level without proving that all earlier decisions are approved (`ApprovalController::approve`, `AssetRequestService::approve`). A user does not need to defeat authentication; an authenticated final approver can bypass the intended chain.

Procurement has equally serious segregation-of-duties and integrity failures. The supplier that submits the quote is permitted to mark its own order delivered, and that action immediately creates active government assets. There is no goods-received officer, inspection/acceptance evidence, delivery note, invoice match, quantity variance, or independent receiving approval. Concurrent delivery requests are not locked or idempotent and can create duplicate assets. An administrator can then cancel a delivered order while the generated assets remain active.

The product is not yet a lifecycle asset-management system. It is mainly an asset register plus request/procurement workflow. Transfer, custody, physical location history, maintenance work orders, repair history, verification campaigns, reconciliation, loss/theft investigation, insurance, disposal approvals/evidence, consumable inventory, funding/project codes, purchase-order linkage on assets, and immutable lifecycle history are absent. Several features claimed by the README, including cross-ministry transfers, are not implemented.

The audit trail is insufficient for public accountability. Logs are ordinary mutable database rows, are deleted indirectly with subject data in some relationships, cover only selected events, and do not preserve old/new field values, authorization context, reason, evidence hash, or tamper-evident sequencing. Direct asset update, status change, delete, user/role change, report export, ministry change, inventory fulfilment, attachment access, and many procurement actions lack complete auditable records.

Automated checks are green but materially incomplete:

- `php artisan test`: **70 passed, 156 assertions**.
- `vendor/bin/pint --test`: **172 files passed**.
- `npm.cmd run build`: **passed**.
- `npm.cmd audit --omit=dev --json`: **0 known production dependency advisories**.
- `composer audit --locked --no-interaction`: **0 known advisories or abandoned packages**.
- Route inventory: **97 registered application routes; no versioned API**.

These checks establish that tested happy paths currently execute. They do not establish production safety, concurrency correctness, authorization completeness, disaster recoverability, accessibility, or performance at state scale.

## Requirements Baseline and Traceability

The two supplied planning documents describe overlapping but different scopes:

- The **2026 Project Plan** is an implementation plan for centralized inventory, straight-line depreciation, ministry requests, sequential approvals, supplier pricing, procurement PDFs, inventory fulfilment, notifications, testing, and shared-hosting deployment.
- The **2024 IAMS Proposal** is the broader product commitment. It adds real-time ICT asset tracking, unique QR codes and bulk label printing, location/department/user assignment, condition and usage history, scheduled maintenance and repair history, service-request assignment, utilization and lifecycle analytics, redistribution/disposal insight, spending analysis, encryption, privacy compliance, detailed user-action logging, statewide data migration, training, and post-launch support.

The current repository substantially reflects the skeleton of the 2026 phase plan, but it does not complete that plan safely and does not deliver much of the broader 2024 proposal.

| Source requirement | Source | Implementation result | Audit conclusion |
|---|---|---|---|
| Six roles: super admin, admin, ministry admin, approver 1, approver 2, supplier | 2026 Plan p.2 | Partial | Five role names exist; approver levels are dynamic rows under one `approver` role rather than separately governed mandates. No conflict/delegation model exists. |
| Supplier login distinct from staff login | 2026 Plan p.2 | Fail | All users authenticate through the same `/login` route and guard. Role-based post-login routing is not a distinct supplier authentication surface or security policy. |
| Assets strictly tied to ministries | 2026 Plan p.2 | Partial/inconsistent | Normal records have an MDA, but the inventory-fulfilment design searches for null-MDA assets while the schema makes `ministry_id` non-null. No department, custodian, or location exists. |
| Straight-line depreciation | 2026 Plan pp.2–4 | Partial | Fields, calculation, queue job, and monthly schedule exist; accounting controls, period approval, robust idempotency, and authoritative reconciliation do not. |
| CSV/Excel initial inventory migration | 2026 Plan p.2; 2024 Proposal p.4 | Fail in current implementation | Import UI exists, but it writes mismatched/non-fillable fields and permits null MDA against a non-null schema. It lacks staging, preview, reconciliation, approval, rollback report, and migration sign-off. |
| Admin checks stock: fulfil or forward | 2026 Plan p.2 | Partial/broken | Routes exist, but fulfilment depends on an impossible null-MDA inventory model and does not preserve a transfer/custody ledger. |
| Sequential Approver 1 then Approver 2 | 2026 Plan p.2 | Critical fail | The final approver can directly POST approval before earlier levels; the mutation does not enforce sequence. |
| Rejection returns to Admin, then Admin rejects to Ministry with comments | 2026 Plan p.2 | Fail | An approver directly changes the request to `rejected`; there is no returned-to-admin adjudication state or admin rejection action. The rejection listener is empty. |
| Email alert whenever action is required | 2026 Plan p.2 | Fail/partial | New-request mail to `admin` exists. Approval and rejection listeners are empty; no evidence shows actionable emails to approvers or ministries, delivery status, escalation, or retry monitoring. Super admins are not included in the new-request recipient query unless also assigned `admin`. |
| Approved-order PDF | 2026 Plan p.3 | Implemented basic | Procurement PDF download exists, but it is not a digitally signed/immutable approved order and has no document version or evidence hash. |
| Supplier enters unit price and total cost | 2026 Plan p.3 | Implemented basic | Assigned supplier can submit line-item quotes; no bid comparison, tax, validity period, supporting quote, or quote revision history exists. |
| **Admin marks order delivered** | **2026 Plan p.3** | **Critical contract breach** | The application also lets the supplier mark its own confirmed order delivered, immediately creating active assets. This contradicts the explicit agreed workflow and breaks segregation of duties. |
| End-to-end request → approval → supplier → delivery test | 2026 Plan p.3 | Partial | Happy-path tests pass, but bypass, replay, concurrency, partial failure, and adversarial tests are absent. |
| SMTP, scheduler, and queue workers on hosting | 2026 Plan pp.3–4 | Not production-proven | Defaults still log mail; deploy only restarts workers and does not provision/supervise them; no live scheduler/SMTP evidence exists. |
| Unique QR per asset and bulk QR printing | 2024 Proposal pp.3–5 | Fail | No QR generation, label layout, bulk print, scan lookup, replacement, or tag-history module exists. |
| Location, assigned department/user, condition, usage history | 2024 Proposal p.3 | Fail | These core tracking fields and history entities are absent. A location column appears only in the import template and is not persisted. |
| Scheduled maintenance, alerts, repair history | 2024 Proposal p.4 | Fail | No maintenance domain exists beyond an asset status/dashboard count. |
| Maintenance and service request management with task assignment | 2024 Proposal p.4 | Fail | Current requests are procurement/asset requests, not maintenance/service tickets; no assignee, SLA, task, work order, or cost-performance workflow exists. |
| Utilization, lifecycle, underutilization, redistribution/disposal, and spending analytics | 2024 Proposal p.4 | Fail/partial | Basic counts and values exist. The source data and workflows needed for the promised analytics are absent. |
| Encryption at rest and in transit | 2024 Proposal p.4 | Not demonstrated | TLS and storage/database encryption are not established by repository configuration; request evidence is deliberately placed on a public disk and session encryption defaults false. |
| Detailed logging of user actions | 2024 Proposal p.4 | Fail | Selected activities are recorded, but coverage is incomplete and records are mutable rather than tamper-evident. |
| Statewide asset assessment, data collection, and migration | 2024 Proposal pp.4–5 | Not evidenced | No data dictionary, cleansing/mapping rules, duplicate strategy, reconciliation controls, migration rehearsal, acceptance metrics, or signed inventory baseline is present. |
| Training, manuals, and post-launch support | 2024 Proposal p.5 | Not evidenced | README and UAT notes are developer artifacts, not role-specific training, operating procedures, support SLAs, or handover materials. |

### Requirements-level conclusion

Even if the system were assessed only against the narrower 2026 plan—not the broader enterprise checklist—the launch verdict would remain **Not Ready**. Sequential approval, rejection routing, inventory fulfilment, notifications, supplier/staff separation, import integrity, and the explicit admin-only delivery step are incomplete or violated. Against the 2024 proposal, the gaps are larger: QR, real asset tracking, maintenance, utilization, security/privacy evidence, migration governance, and training/support deliverables remain outstanding.

The documents also expose an infrastructure mismatch. “Shared Hosting (cPanel)” may support a demonstration, but it is not an acceptable architecture by assertion for a statewide system. Before selection, OGSG needs measured capacity, tenant isolation, queue supervision, scheduler reliability, private document storage, encryption/key management, centralized logs, monitoring, backup/PITR, disaster recovery, patching responsibility, privileged-access controls, penetration testing, and contractual availability/support evidence. If the shared platform cannot provide those controls, the hosting requirement must change.

## Critical Issues

### C-01 — Approval sequence can be bypassed

**Severity:** Critical  
**Evidence:** `app/Http/Controllers/Approval/ApprovalController.php:98-114`; `app/Services/AssetRequestService.php:94-119`  
**Exploit scenario:** An assigned final approver sends `POST /approvals/{uuid}/approve` directly while level 1 is pending. The controller only verifies that the caller has a pending approval. The prior-level check exists in `show()` but not in `approve()`. The service then approves the caller's level and marks the entire request approved because the level is the maximum. Procurement can proceed without the required first approval.  
**Required fix:** Enforce the state machine in one transactional domain service. Lock the request and all approval rows with `SELECT ... FOR UPDATE`; require `awaiting_approval`; require the caller to be the assigned active approver; require every lower level approved; require the current decision pending; and mark the request approved only when no pending/rejected rows remain. Add direct-POST, replay, stale-page, and concurrent-request tests.

### C-02 — Supplier self-certifies delivery and creates active public assets

**Severity:** Critical  
**Evidence:** `app/Policies/ProcurementOrderPolicy.php:44-57`; `app/Http/Controllers/Supplier/OrderController.php:60-68`; `app/Services/ProcurementService.php:99-157`  
**Exploit scenario:** A supplier submits its own quote, waits for confirmation, then marks the order delivered. The system immediately creates active assets at the quoted values and quantities. No government receiving officer confirms physical receipt or condition. A dishonest or compromised supplier account can create fictitious assets and apparent expenditure evidence. This also directly contradicts page 3 of the 2026 Project Plan, which assigns the delivered action to the Admin.  
**Required fix:** Separate supplier dispatch from government receipt. Introduce dispatch, receipt, inspection, acceptance/rejection, and capitalization states. Only an authorized receiving/asset officer, distinct from requester, approver, procurement officer, and supplier, may accept goods. Require delivery note, invoice, PO, inspection evidence, accepted quantity, serial numbers, and exception handling before capitalization.

### C-03 — Delivery is not concurrency-safe or idempotent

**Severity:** Critical  
**Evidence:** `app/Services/ProcurementService.php:99-119` and `:130-157`  
**Exploit scenario:** Two requests read a confirmed order before either commits. Both change it to delivered and both create one asset per quoted unit. The order has one delivered state but inventory and value are doubled. Retries after ambiguous network failure produce the same risk.  
**Required fix:** Lock the order and line items in a transaction, use an explicit goods-receipt record with a unique idempotency key, add a unique capitalization link from generated asset to receipt line/unit, and reject repeated transitions at database level. Test parallel requests against the production database engine.

### C-04 — The deployed application can have no compiled frontend assets

**Severity:** Critical operational blocker  
**Evidence:** `.gitignore:16` excludes `/public/build`; deploy steps only pull code, install Composer dependencies, migrate, cache, and restart queues. The CI job builds frontend assets, but the deploy job does not download or transfer that artifact.  
**Failure scenario:** The ignored Vite build is absent on a fresh server, so the release can render without CSS/JavaScript. A green build in the separate CI job does not place its output on the target host.  
**Required fix:** Build once in CI; test and sign the release artifact; deploy atomically; transfer the compiled Vite artifact; run health/smoke checks; and support automatic rollback. The current working-tree change aligning CI to PHP 8.4 should be retained and verified after commit.

### C-05 — The system lacks the lifecycle controls needed to be the government system of record

**Severity:** Critical product/governance blocker  
**Evidence:** No transfer, custody, location-history, maintenance, repair, verification, variance, loss/theft, insurance, disposal-case, or consumable-stock models/routes/migrations exist in the 97-route inventory.  
**Failure scenario:** Staff must conduct core asset operations outside the system. The database ceases to be authoritative, reconciliation depends on spreadsheets and email, and no defensible chain of custody exists for public assets.  
**Required fix:** Do not launch as the authoritative state register until the lifecycle modules and control owners described under Product Gaps are implemented and accepted through UAT.

### C-06 — Audit evidence is mutable and incomplete

**Severity:** Critical for accountability  
**Evidence:** `ActivityLogService` inserts ordinary `activity_logs`; the table has no immutability controls, sequence, hash, signature, retention class, or protected archival sink. Direct asset updates/deletes and many privileged actions do not call the service.  
**Exploit scenario:** A database administrator or application defect can alter/delete evidence without detection. A privileged user can change asset value, MDA, status, or documents with no before/after record. An investigation cannot prove who changed what, under which approval, or whether the record was later altered.  
**Required fix:** Make business history append-only; record actor, impersonation/delegation, role, MDA, request/correlation ID, old/new values, reason, approval/evidence links, source IP/device, and timestamp. Stream signed or hash-chained events to a separately controlled WORM/central audit store with retention, alerting, and restricted access. Database administrators must not be able to silently erase the only copy.

## Security Findings

| ID | Vulnerability | Severity | Exploit / impact | Required remediation |
|---|---|---:|---|---|
| S-01 | Broken approval-level authorization | Critical | Final approver directly posts approval before prior levels; request becomes approved. | Implement locked server-side workflow invariants as in C-01. Never rely on page visibility for authorization. |
| S-02 | Supplier has incompatible quote and receipt powers | Critical | Supplier self-attests delivery and creates active government inventory. | Separate dispatch, receiving, inspection, acceptance, and capitalization permissions. |
| S-03 | Unvalidated asset media upload | High | `StoreAssetRequest` has no rule for `files`, yet `AssetController` sends each upload to Media Library (`AssetController.php:111-114`, `:179-182`). File type and size are not enforced by this request. On a public disk, dangerous content may be stored and served. | Validate array count, size, MIME from content, allowed image/PDF types, dimensions, and extension; randomize names; scan malware; store privately; serve through authorized download responses; disable script execution. |
| S-04 | Request attachments are publicly addressable | High | Procurement-supporting documents are stored on the `public` disk and linked as `/storage/...` (`AssetRequestController.php:41`; `ministry/requests/show.blade.php:66`). Anyone possessing/leaking the URL bypasses authorization. | Store evidence on a private encrypted disk/object store; use short-lived signed downloads after policy checks; audit every access; classify and retain documents. |
| S-05 | Email verification middleware is ineffective | High | `User` does not implement `MustVerifyEmail`, while protected routes use `verified`. Laravel's verification middleware does not enforce verification for users that do not implement the contract. | Implement `MustVerifyEmail`, test unverified access to every protected group, and decide which government identities require authoritative directory verification. |
| S-06 | Disabled users retain existing sessions | High | `is_active` is checked only during login (`LoginRequest.php:45`). No per-request active-user middleware or session revocation exists. Disabling an account does not immediately terminate current browser sessions. | Add per-request active-account enforcement; revoke all sessions/tokens on disable, password reset, role/MDA change, and security events; expose session inventory and remote logout. |
| S-07 | No MFA or phishing-resistant authentication | High | A stolen password gives full role authority, including approvals and central administration. | Require MFA for all staff and phishing-resistant WebAuthn/passkeys or hardware-backed MFA for privileged/approver roles; support recovery controls and step-up authentication. |
| S-08 | Password-reset endpoint supports account enumeration and lacks route-level IP throttling | Medium | The controller returns different broker outcomes for known vs unknown users; forgot-password routes have no throttle middleware. This enables account discovery and distributed email abuse. | Return a uniform response, add IP and identity throttles, monitoring, CAPTCHA/risk controls after abuse, and security notifications. |
| S-09 | Session hardening is not production-defined | High | `.env.example` leaves secure-cookie configuration absent and sets `SESSION_ENCRYPT=false`; there is no documented idle/absolute privileged-session policy. A bad deployment can issue cookies without `Secure`. | Production template must require HTTPS, `SESSION_SECURE_COOKIE=true`, HttpOnly, appropriate SameSite, encrypted session payloads, idle and absolute expiry, key rotation, HSTS, and proxy trust validation. Fail deployment on insecure values. |
| S-10 | No application security-header policy | Medium | No CSP, HSTS, frame-ancestors, Referrer-Policy, Permissions-Policy, or explicit MIME-sniffing middleware/configuration was found. | Add centrally tested headers at reverse proxy and application layers. Start CSP in report-only mode, then enforce with nonces/hashes. |
| S-11 | Procurement order duplication is database-possible | High | `procurement_orders.asset_request_id` is not unique. Two administrators/races can create multiple orders for one approved request despite the UI's `doesntHave` filter. | Add a unique active-order invariant or versioned procurement-case model; lock the request during creation; test concurrency and cancellation/reprocurement rules. |
| S-12 | Arbitrary users can be assigned as approvers | High | Forward validation checks only `exists:users,id|distinct`; it does not require active approver role, authority level, delegation validity, MDA/jurisdiction, or conflict-of-interest controls. | Resolve approvers from configured approval policy, not arbitrary form IDs; enforce active role, mandate, value threshold, jurisdiction, delegation window, and separation of duties. |
| S-13 | User and MDA deletion can threaten evidence | High | Core foreign keys use cascades, while audit/history is not independently retained. A future force-delete or MDA deletion can erase related operational records. | Replace destructive cascades on records of public accountability with restrict/archive semantics and immutable historical identifiers. |
| S-14 | UUIDs reduce guessing but do not replace object authorization | Informational control note | UUID route keys are present on major models and ministry asset/request reads are scoped. This is positive, but IDs still require policy checks on every route and download. | Keep UUIDs; add policy tests for all CRUD/download/export routes and deny by default. |
| S-15 | No known dependency advisories in the current lockfiles | Pass at audit date | Composer and production npm advisory checks returned zero. This is a point-in-time result, not a guarantee. | Run Composer/npm audits and SBOM/license scanning on every build; pin and sign artifacts; enable automated updates with review. |

### OWASP coverage conclusion

- **Broken access control:** Failed due to approval bypass, supplier delivery authority, public evidence URLs, and incomplete action-level controls.
- **Authentication failures:** Partially controlled by password hashing, login throttling, session regeneration, inactive-login check, and secure logout; failed for MFA, current-session revocation, effective email verification, and reset enumeration.
- **Injection:** No direct SQL injection was identified in reviewed query paths; Eloquent bindings are generally used. Raw date expressions are static. Continue SAST and production-engine tests.
- **XSS:** Blade escaping is generally used. No confirmed stored XSS was found. CSP is still absent and uploaded active content remains a concern.
- **CSRF:** Web POST/PATCH/DELETE routes run through Laravel's web middleware and forms include CSRF tokens. No confirmed CSRF bypass was found.
- **SSRF:** No server-side URL fetch feature was found.
- **Security misconfiguration:** Failed due to weak environment template, absent security-header policy, and incomplete deployment artifact handling.
- **Sensitive data exposure:** Failed because request and asset media are exposed via public storage patterns.

## Product Gaps

### Domain capability assessment

| Capability | Result | Evidence-based assessment |
|---|---|---|
| Asset registration | Partial | Admin can create assets, but records can become active immediately with no draft/review/capitalization approval. |
| Categorization | Partial | Hierarchical categories exist. No controlled asset-class accounting policy, category code, capitalization threshold, fixed/consumable distinction, or category-specific required fields. |
| Fixed assets vs consumables | Fail | No stock ledger, units of measure, batches, issue/return, reorder, expiry, or consumption controls. Procurement creates an `Asset` per quantity. |
| Unique identifiers | Partial | UUID and unique asset tag exist. Count-based `OGSG-00001` generation is race-prone; delivery uses random `AST-*`; imports use another path. There is no single governed numbering scheme. |
| Barcode/QR | Fail | No tag rendering, label format, printing, scan lookup, scan verification, replacement, or tag history. |
| Procurement source/vendor/PO linkage | Partial | Request, supplier, quote items, and procurement order exist. Assets do not retain a foreign key to PO, receipt, supplier, invoice, funding source, or procurement method; supplier is copied as a mutable text attribute in manual creation. |
| Cost/value | Partial | Purchase, current, salvage values exist. No currency field, tax, landed cost, impairment, revaluation, valuation basis, component accounting, or posting period lock. Value can be edited directly without approval/history. |
| Funding source/budget/project | Fail | No budget line, appropriation, project code, vote, fund, grant, donor, or utilization control. |
| Ownership/custodian/department | Fail | Only one MDA ownership field exists. No legal owner vs controlling MDA, department/unit, named custodian, custody acceptance, issue/return, or history. |
| Physical location | Fail | Import template advertises location, but schema/model do not persist it. No facility/building/room hierarchy, geolocation, movement history, or location custodian. |
| Lifecycle state machine | Fail | A status enum exists but lifecycle cases and transitions are inconsistent between PHP and database. Users can directly set active/maintenance/disposed without cases, evidence, or approvals. |
| Depreciation/valuation | Partial/unsafe | Straight-line fields and monthly job exist. No accounting period, method policy, pro-rating, revaluation/impairment, posting approval, ledger reconciliation, or robust idempotency. The job compares an enum-cast status to a string (`CalculateAssetDepreciationJob.php:25`). |
| Insurance | Fail | No policy, insurer, insured value, premium, expiry, claim, or evidence model. |
| Warranty | Partial | Warranty is stored as free-form EAV data. No supplier/manufacturer terms, claim/workflow, alert owner, or immutable source document. |
| Maintenance/repair | Fail | A status and dashboard count are not a maintenance system. No request, approval, work order, vendor, SLA, schedule, parts, cost, downtime, completion evidence, or history. |
| Transfer between MDAs | Fail | No transfer entity or route. Inventory fulfilment directly overwrites `ministry_id`; the README's transfer claim is unsupported. |
| Retirement/disposal | Fail | Admin can set `disposed` directly. No board/committee approval, valuation, disposal method, bidder/buyer, proceeds, certificate, sanitization, evidence, or irreversible archive rule. |
| Lost/stolen/damaged | Fail | PHP enum includes `lost`, database enum does not; there is no incident, police report, investigation, liability, recovery, insurance claim, or write-off chain. Damaged is absent. |
| Periodic verification | Fail | No campaign, assignment, scan, observed condition/location/custodian, exception, reconciliation, sign-off, or closure. |
| Audit trail/history | Fail | Selected event logging exists, but it is neither complete nor tamper-evident and is not a lifecycle history. |

### Alignment with ISO 55000 and public-sector accountability

The application does not currently demonstrate an ISO 55000-aligned asset-management system. It lacks policy/objective linkage, lifecycle decision criteria, risk/criticality, asset-management plans, performance evaluation, assurance, continual improvement, controlled information quality, and governance over value/risk/cost trade-offs. Software alone cannot certify ISO alignment, but the current product does not supply the records needed to operate or evidence those principles.

Public-sector procurement traceability is also incomplete. A defensible chain should connect appropriation/project → requisition → approvals → procurement method/tender → bids/evaluation → contract/PO → supplier → dispatch → goods receipt → inspection/acceptance → invoice/three-way match → payment reference → capitalization → custody/location → verification → disposal/proceeds. The current chain covers only a subset from request through quote/confirmation and then jumps directly to active assets.

## Role and Permission Audit

### Implemented roles

The seed defines `super-admin`, `admin`, `ministry-admin`, `approver`, and `supplier`. The requested government operating model additionally needs at least department heads, asset officers, procurement officers, auditors, finance officers, maintenance officers, receiving/inspection officers, disposal committee roles, and read-only viewers. Mapping all central functions to `admin` creates excessive privilege concentration.

### Findings

1. **No formal role hierarchy or incompatibility matrix.** There is no enforcement preventing a person from holding conflicting roles, aside from current UI conventions.
2. **No delegation model.** Approval delegation has no start/end date, delegator, reason, jurisdiction, maximum value, acceptance, revocation, or audit evidence.
3. **No transaction/value-based approval matrix.** Any selected approver is treated as a numbered level regardless of mandate or amount.
4. **Approver authorization is UI-dependent and bypassable.** See C-01.
5. **Central admin is over-privileged.** The same role can register/edit/delete assets, select approvers, create/confirm/cancel procurement, fulfil inventory, manage suppliers/MDAs, and export reports.
6. **Supplier conflicts with receiving.** See C-02.
7. **Ministry read scoping is implemented on main ministry asset/request paths**, and tests verify cross-ministry denial. However, no reusable mandatory tenant/global scope protects every future query; scoping is manually repeated.
8. **Super admin bypasses every Gate ability through `Gate::before`.** This may be operationally necessary, but government-grade use requires step-up MFA, break-glass reason, time-limited elevation, alerting, and independent review.
9. **No auditor/read-only role.** Auditors cannot independently access complete immutable evidence without receiving broader operational powers.
10. **Role/MDA changes do not revoke sessions or create a complete before/after audit event.** Privilege changes can remain active in existing sessions.

## Engineering Review

### Strengths

- Laravel conventions, Form Requests, enums, policies, services, DTOs, repositories, events/listeners, queues, and tests provide a maintainable starting structure.
- Primary domain models use UUID route keys, reducing predictable public identifiers.
- Database transactions exist around several multi-write operations.
- Passwords use Laravel's hashed cast/Bcrypt configuration; login regenerates sessions and logout invalidates them.
- Query pagination exists on main list pages.
- The lockfiles had no known advisories at audit time, code formatting passed, and the current test/build suite is green.

### Material engineering defects

1. **Business invariants are split between screens/controllers/services.** The sequence check is in approval `show()` but not the mutation service. This directly caused C-01.
2. **Transactions lack row locks and idempotency.** Approval, PO creation, delivery, inventory fulfilment, and tag generation are race-prone.
3. **State machines are informal.** Methods update statuses without centralized allowed-transition rules. `cancelOrder()` can cancel any status, including delivered; `sendToApprovers()` can reset prior decisions; direct asset updates can dispose without a disposal case.
4. **Schema/model drift is already present.** `AssetStatus::Lost` is absent from the database enum; the ministry filter queries nonexistent `status_id` before `status` (`Ministry/AssetController.php:38`); imports write `category_id`, `description`, `location`, and `serial_number` even though `Asset` persists `asset_category_id` and does not fill the others.
5. **The inventory fulfilment design contradicts the schema.** Code searches for assets where `ministry_id IS NULL` (`RequestReviewController.php:79`) while the original assets migration makes `ministry_id` non-null. The import can also return a null MDA, which cannot persist under that schema.
6. **Asset tag generation is race-prone.** `Asset::count() + 1` can produce the same tag in concurrent requests. The unique constraint turns this into user-visible failure rather than safe sequencing.
7. **Manual asset update is incomplete.** It updates purchase cost but not current value, useful life, or salvage value; it replaces all EAV attributes; it does not log before/after changes; and it lacks optimistic concurrency/version checks.
8. **Audit writes are coupled to request context.** `request()` and `Auth` are assumed in a service also usable by jobs/listeners. There is no outbox pattern ensuring durable event publication across database and external audit sink.
9. **Repositories add uneven abstraction.** Some asset/request queries use repositories while controllers and dashboards query Eloquent directly. The layer does not establish a consistent security or transaction boundary.
10. **Exceptions are not rendered into safe domain responses.** `bootstrap/app.php` has no custom exception mapping. Invalid transitions can become generic 500 pages rather than conflict/validation responses with correlation IDs.
11. **README and runtime drift.** README says Laravel 12/PHP 8.3 and claims cross-ministry transfers; Composer declares Laravel 13/PHP 8.4 and no transfer workflow exists.
12. **Character encoding corruption is visible** in README/workflow and an activity-log title (`â‚¦`, broken emoji/drawing characters). Government-facing artifacts must use controlled UTF-8 and rendering tests.
13. **Notification workflow is largely stubbed.** `NotifyMinistryOfRejection` and `ProcessApprovedRequest` have empty `handle()` methods. The available approved/rejected mail classes are not invoked by those listeners, so the phase-plan notification commitment is not complete.

### API review

There is no separate API layer or `routes/api.php`; the app exposes 97 session-authenticated web routes. Therefore REST versioning, API tokens, JSON error contracts, API pagination envelopes, idempotency headers, machine-client scopes, and API rate limits are **not implemented**, not “passed.”

For current web endpoints:

- Request validation is present on common forms but inconsistent and sometimes inline.
- Status codes for domain conflicts are not explicit; exceptions generally surface as server errors.
- No optimistic concurrency (`version`, ETag/If-Match) exists.
- The configured `forms` and `api` rate limiters are not attached to route groups.
- Mutation idempotency is absent.
- Search fields use bound Eloquent queries; no confirmed SQL injection was found.

If external MDA integrations, mobile scanning, or central finance integration are planned, design a versioned API with OAuth/OIDC service identities, least-privilege scopes, signed webhooks, idempotency, consistent problem responses, and complete audit correlation. Do not expose controllers ad hoc.

### Database review

**Positive:** Foreign keys, UUID unique columns, asset-tag uniqueness, request-code uniqueness, approval uniqueness per request/level, depreciation uniqueness per asset/date, common indexes, decimals for money, and soft deletes are present.

**Failures and scale risks:**

- No unique constraint prevents multiple procurement orders per request.
- No immutable history tables exist for asset ownership, custody, location, value, condition, status, or field revisions.
- MDA deletion cascades asset/request deletion, which is unsafe for public records.
- EAV attributes weaken type validation, indexing, reporting, and referential integrity for supplier/warranty/serial data.
- Money has no currency, exchange rate, tax, or accounting-period context.
- Enum schema changes are brittle and already out of sync with PHP.
- There is no data classification, retention, legal hold, archival, partitioning, or purge policy.
- Indexes do not yet cover likely state-scale queries such as `(ministry_id,status,asset_category_id,purchase_date)`, normalized serial number, location, custodian, verification campaign, and reporting periods because those domains are missing.
- No production query-plan or million-row benchmark evidence exists.

## Workflow Validation

| Workflow | Verdict | Reason |
|---|---|---|
| Asset creation | **Fail** | Validates basic financial/category/MDA fields but can create active assets directly; no draft/approval/capitalization, procurement evidence, duplicate serial detection, governed tag issue, custodian/location, or complete upload validation. |
| Asset update | **Fail** | Admin can change MDA, value, status, and descriptive data without approval, reason, immutable revision, before/after log, field restrictions, or concurrency token. |
| Asset transfer | **Fail / absent** | No request, dual-MDA acceptance, custodian/location handover, in-transit state, evidence, or transfer ledger. Direct MDA overwrite is not a transfer workflow. |
| Maintenance | **Fail / absent** | No maintenance request/work order/approval/vendor/cost/repair/downtime/history. |
| Disposal | **Fail / absent** | Direct status update only; no approval board, evidence, financial treatment, proceeds, or immutable terminal state. A disposed asset can be edited back to active. |
| Lost/stolen/damaged | **Fail / absent** | No incident/investigation/reconciliation workflow; enum/schema mismatch for lost. |
| Physical audit/verification | **Fail / absent** | No campaign, scan, observation, variance, reconciliation, sign-off, or closure. |
| Asset request | **Partial** | Creation is MDA-bound server-side and tested. No budget/funding check, cancellation lifecycle, duplicate demand detection, complete evidence controls, or implemented rejection-return-to-admin flow. |
| Approval | **Fail — critical** | Direct POST bypasses sequence; approver selection is weak; no locking, delegation, threshold matrix, conflict controls, or immutable decision evidence. |
| Procurement quote | **Partial** | Assigned supplier scoping and quote validation exist. No tender/bid comparison, procurement method, contract, taxes, validity, attachments, or resubmission/version workflow. Supplier delivery authority contradicts the project plan. |
| Goods receipt/capitalization | **Fail — critical** | Supplier self-receives; no inspection/acceptance or three-way match; concurrency can duplicate assets. |
| Inventory fulfilment | **Fail** | Depends on null-MDA inventory contradicted by schema; does not enforce quantity/category under lock or preserve transfer history. |
| Depreciation | **Partial/unsafe** | Scheduled queued calculation exists, but accounting policy, period locking, job-state refresh, transactional idempotency, and financial review are inadequate. |
| Reporting | **Partial** | Basic asset/request/procurement views and exports exist. Required condition, maintenance, depreciation, missing, disposal, transfer, verification variance, funding, and budget reports cannot exist because source domains are absent. |

## Frontend and UX Review

The Blade UI has responsive cards/tables, pagination on operational lists, search/filter controls on several pages, empty-state components, CSRF-backed forms, flash messages, and clear role-specific navigation. Recent auth layouts account for mobile/tablet widths, and the production build succeeds.

Production gaps remain:

- Destructive and state-changing actions often use native `confirm()` or no confirmation, with no structured reason/evidence capture.
- Workflow screens expose actions by current status but cannot compensate for missing backend invariants.
- Accessibility has not been demonstrated through WCAG 2.2 AA automated/manual testing, keyboard-only operation, screen-reader review, focus management, contrast testing, error association, and zoom/reflow testing.
- Several mobile layouts replace tables with cards, but the full workflow needs device testing on low-end Android phones and unreliable Nigerian mobile networks.
- No offline/queued physical-verification scanning experience exists.
- Error recovery for expired sessions, failed uploads, duplicate submissions, and network loss is not intentionally designed.
- Public-storage evidence links open new tabs without a controlled authorized download flow.
- Encoding corruption reduces professionalism and can corrupt generated records.

## Reporting and Analytics

Current reports provide basic asset, request, and procurement lists, plus asset PDF/Excel exports and dashboard totals. They are insufficient for public-sector oversight.

Missing or unreliable outputs include net book value by period, depreciation register, additions/disposals reconciliation, asset roll-forward, condition/criticality, custodian/location register, overdue verification, verification variances, missing/lost/stolen/damaged assets, maintenance due/cost/downtime, warranty/insurance expiry, transfer chain, disposal proceeds, budget/project/funding utilization, procurement lead time, supplier performance, exception reports, and user/privilege/audit reports.

Export security is role-based only. There is no export reason, row-level watermark, classification label, download audit, asynchronous generation, expiry, encryption, or data-loss control. PDF/list methods call `get()` and load the full result set (`ReportController.php:22-27`, `:40-43`, `:55-59`, `:69-73`), which is not viable for millions of records.

## Failure Handling Review

| Failure condition | Verdict | Observed behavior/risk |
|---|---|---|
| Bad input | Partial | Form validation covers many fields, but cross-field rules are missing (future purchase date, salvage <= cost, supplier role, category hierarchy, requested vs quoted quantity), and some files are unvalidated. |
| Unauthorized action | Partial/Fail | Role middleware and policies deny many paths, but approval and receiving business authorization fail. |
| Session expiry | Partial | Laravel redirects web users to login; no preservation/recovery design for long forms or uploads was found. |
| Duplicate submission | Fail | No idempotency token for approval, PO creation, delivery, import, or fulfilment. |
| Concurrent updates | Fail | No row locking or optimistic versioning on critical workflows. |
| File upload failure | Partial | Standard validation exists for request attachments/imports, but no transactional cleanup, retry, quarantine, or user recovery contract. Asset media is uploaded after asset commit, producing partially created records if media storage fails. |
| Database failure | Fail operationally | Transactions help some writes, but no user-safe exception mapping, correlation ID, retry policy, failover, or tested recovery evidence exists. |
| Queue failure | Fail operationally | Failed-job table exists and jobs retry, but no monitoring/alerting/dead-letter runbook or guaranteed worker supervision is defined. `queue:restart` does not start a worker. |
| Cache/Redis failure | Not demonstrated | Defaults use database cache; no degraded-mode or outage test evidence exists. |
| External service/mail failure | Not demonstrated | Mail defaults to log. No production provider, retry/outbox, alert, or notification delivery status is established. |
| Partial workflow failure | Fail | Asset record can commit before media upload; status transitions and audit writes are inconsistently atomic; external audit preservation is absent. |

The application does not yet fail safely and predictably under the failure modes most dangerous to financial and custody integrity.

## Performance Assessment

**Current evidence:** The production frontend build and 70-test suite pass. There is no load, soak, stress, concurrency, large-import, large-export, failover, or production-database benchmark.

**Bottlenecks:**

1. Report controllers load entire datasets into PHP memory and DomPDF; state-scale exports will exhaust memory/time out.
2. Dashboards execute many aggregate queries on each request and cache service use is limited/inconsistent.
3. EAV serial/supplier/warranty searches require joins and text scans and will degrade sharply.
4. Bulk import is synchronous, creates categories during import, and lacks a staging/validation/approval pipeline.
5. Asset creation from a large delivery performs nested per-unit inserts and tag generation in the web request.
6. No search index or normalized searchable fields exist for tag/serial/location/custodian at millions-row scale.
7. PDF generation is synchronous and unbounded.
8. No capacity model exists for MDA count, assets, concurrent users, scans per verification campaign, document volume, or retention duration.

**Required performance architecture:** Queue bulk imports/exports/PDFs with progress and expiring authorized downloads; stage and validate imports before commit; batch inserts; pre-aggregate reporting; add production-engine indexes from measured plans; use cache with explicit invalidation; introduce read replicas only after query and consistency analysis; partition/archive high-volume audit/history data; and run representative MySQL/PostgreSQL tests at 1M+ assets with defined SLOs. Financial/custody reads must not silently use stale replicas where read-after-write correctness matters.

## Production Operations and Resilience

### Failed or unproven controls

- No production-safe environment template or automated configuration validation.
- CI is aligned to PHP 8.4 in the current uncommitted workflow, but that change still requires a real CI run and controlled release.
- Deployment is mutable `git pull` in place, not an atomic versioned release.
- Compiled frontend is neither tracked nor transferred during deploy.
- No database backup policy, encrypted off-site backup, point-in-time recovery, or restore-test evidence.
- No documented RPO/RTO, disaster recovery architecture, secondary site/region, or annual exercise.
- No migration rollback/forward-fix plan or pre-deploy backup gate.
- No observability stack, security alerting, audit-event alerting, SLOs, tracing, or on-call runbook.
- No worker process manager configuration; restarting absent workers has no effect.
- No staging/UAT environment definition with anonymized representative data.
- No DAST/SAST/secret scanning/SBOM/container or host hardening evidence in CI.
- No load testing, accessibility gate, browser/device matrix, or penetration test.
- No data retention, legal hold, privacy impact assessment, records classification, or incident response plan.
- `/up` is a shallow framework health endpoint; no readiness check proves database, queue, storage, or critical configuration health.

### Minimum release gates

1. Close C-01 through C-06 and all High security findings with regression and adversarial tests.
2. Implement the minimum authoritative lifecycle: governed registration/capitalization, custody/location, transfer, maintenance history, incident/loss, physical verification, and disposal.
3. Establish an independently protected audit evidence pipeline and retention policy.
4. Complete role design, segregation-of-duties matrix, delegation, value thresholds, and privileged-access management.
5. Implement private evidence storage, malware scanning, authorized downloads, and evidence retention.
6. Align and harden CI/CD; deploy immutable tested artifacts with rollback and smoke tests.
7. Demonstrate backup restore, queue/storage/database failure recovery, and agreed RPO/RTO.
8. Pass production-engine concurrency tests, million-record performance tests, WCAG 2.2 AA assessment, threat model, independent penetration test, and government business UAT.
9. Reconcile README, operating procedures, data dictionary, API/interface specifications, support runbooks, and user training with actual behavior.
10. Obtain formal sign-off from Asset Management, Finance/Accounts, Procurement, Internal Audit, ICT/Security, Legal/Records, and representative MDAs.

## Production Readiness Score

### **3.0 / 10**

| Dimension | Score | Reason |
|---|---:|---|
| Product/domain completeness | 2/10 | Register and request/procurement subset exists; most lifecycle and public-accountability capabilities are absent. |
| Authorization/segregation | 2/10 | Basic role and MDA scoping exists, but approval bypass and supplier self-receipt are release blockers. |
| Authentication/session security | 5/10 | Strong password defaults, hashing, throttling, regeneration, and logout; no MFA, effective verification, session revocation, or hardened production policy. |
| Data integrity/auditability | 2/10 | Some transactions, constraints, UUIDs, and activity rows; critical workflows are race-prone and evidence is mutable/incomplete. |
| Engineering maintainability | 6/10 | Clear Laravel structure and green formatting/tests, offset by state-machine, schema drift, and inconsistent abstraction defects. |
| UX/accessibility | 5/10 | Usable responsive foundation; lifecycle UX and formal accessibility/offline validation absent. |
| Performance/scalability | 3/10 | Pagination on lists, but reports/imports/delivery are synchronous/unbounded and no scale evidence exists. |
| Operations/resilience | 2/10 | Pipeline exists in name, but artifact omission, no DR evidence, and weak observability make release unsafe. |

The score is not raised by the green test suite because those tests do not cover the controls whose failure would misstate public assets or bypass government approvals.

## Final Verdict

# **Not Ready**

This application must not be deployed as the state-wide authoritative OGSG asset-management platform in its current form. It can continue as a controlled development/UAT prototype using synthetic or non-sensitive data. It should not be used to authorize expenditure, certify receipt, establish official custody, calculate authoritative financial values, or serve as the sole audit record.

The immediate engineering priority is not cosmetic expansion. It is to establish enforceable state machines, segregation of duties, concurrency/idempotency controls, private evidence handling, and tamper-evident audit history. After those foundations are in place, the missing lifecycle modules can be built without reproducing the current integrity defects across a larger surface.

## Verification Record and Limitations

### Performed

- Reviewed routes, authentication, policies, controllers, Form Requests, services, repositories, models, enums, migrations, imports/exports, jobs/schedule, activity logging, Blade views, environment template, README, and GitHub Actions.
- Enumerated 97 non-vendor routes.
- Ran the full PHP test suite: 70 passed, 156 assertions.
- Ran Pint formatting check: 172 files passed.
- Built Vite production assets successfully.
- Ran current Composer locked-package advisory check: no advisories/abandoned packages.
- Ran current npm production dependency advisory check: no advisories.

### Not performed / cannot be inferred

- No deployed production/staging environment, reverse proxy, TLS, server, database topology, object storage, mail provider, monitoring, or secret manager was supplied.
- No live penetration test, DAST, malware-upload test, browser automation, screen-reader audit, production-database race test, or infrastructure scan was performed.
- No representative million-record dataset, load targets, RPO/RTO, procurement rules, financial policy, retention schedule, or signed business requirements were supplied.
- ISO 55001 certification cannot be established by source review; this report only assesses whether the product supports relevant ISO 55000-style operating evidence.

These limitations do not soften the verdict. The confirmed code-level blockers are sufficient on their own to reject production launch.
