Security & Architecture
Straight answers to the questions a municipal IT evaluator should ask before trusting a platform with department data: how data is kept isolated, how changes are tracked, how access is controlled, how records are retained, and what this is actually built on.
Data Isolation Between Departments
Every department-scoped record carries a department reference, and isolation is enforced with EF Core Global Query Filters defined once, centrally, in the application's database context, not scattered across individual screens as a convention someone has to remember to apply. Every query against department-scoped data is automatically rewritten to include the correct access check before it ever reaches the database, so a missing filter in one controller action cannot leak data across departments; the filter isn't in the controller, it's in the query itself.
Three access paths exist, and only three: an Administrator with cross-department access sees every department's tickets; a Field Worker or a department-scoped Administrator sees only their own department's tickets; a Citizen sees only the tickets they personally submitted, regardless of department.
Audit Trail
Every change to a tracked field is recorded as its own row, which field changed, the old value, the new value, who made the change, and when, not just a single "last modified" timestamp that overwrites the previous one. This history is generated automatically inside the database save path itself, so it cannot be skipped or forgotten by a specific screen.
The application never updates or deletes an audit entry once it's written, every action that changes a record adds new history rows, it never edits the past. That's an application-level guarantee built into how every save happens, not a database-level lock; there's no separate database trigger or permission restriction enforcing it independently of the application code.
Role-Based Access Control
Three roles: Citizen (submits and tracks their own service requests), Field Worker (updates the status of tickets assigned to them), and Administrator (manages departments, staff accounts, and ticket assignment). Access is enforced at two independent layers: a route-level check on every sensitive action that requires a specific role, and the same department/ownership Global Query Filters described above at the data layer. Those two layers are deliberately redundant, if a role check on one screen were ever misconfigured, the underlying data query still can't return records outside what that user is allowed to see.
Data Retention
Records are never permanently removed from the database. The only delete action in the application flips a soft-delete flag rather than deleting the row; that same flag is honored by the Global Query Filter, so a "removed" ticket disappears from every normal screen without the underlying record, or its audit history, ever being destroyed. This is consistent with typical public-records retention practices for government data.
Technology Stack
Built on .NET 10 and ASP.NET Core MVC, with server-rendered Razor views rather than a separate single-page-app front end, a deliberate choice for security, simplicity, and compatibility with older hardware sometimes found in government environments. Data access goes through Entity Framework Core with the SQL Server provider, and authentication is handled by ASP.NET Core Identity.