Approvals and Evidence Design - edmozley/freeitsm GitHub Wiki
Approvals and evidence - design (#180)
Status: built in 3.8.0. This page and its three parts set out what was changed, why, and how - written before the code so the logic could be checked first, and kept as the record. Where the build differs, the list below says so. Anything marked today describes 3.7.0. User guide: Change Management. Comments are welcome on issue #180.
Updated 16 Oct 2026 after the requester's follow-up: votes are tied to the version on screen, a submitted change is cancelled rather than deleted, and standard changes come from approved templates (decisions 6, 8 and 10 in §5; three new settings in §2; slice G in §4).
The request, from issue #180 (raised by Dr4inder after evaluating 3.7.0 for a small IT team):
"Three functional gaps currently prevent us from using FreeITSM as the sole record of these decisions and their supporting evidence."
- A configured change approval should be enforceable. Today an analyst can set a change straight to Approved without the approval having happened.
- The long text on a change should keep its history. Today a test plan or rollback plan can be overwritten, and the previous text is gone.
- Approving, reviewing and configuring should be separate permissions. Today a Forms approver can also redesign the form, and the only way to let somebody read change records is to let them edit them too. The issue puts this in terms of roles: it was not possible to configure "an approval-only role" or "a useful read-only reviewer role".
The three parts:
| Part | Page | In one line |
|---|---|---|
| 1 | Enforceable change approvals | Approval becomes a recorded event tied to the exact content that was reviewed, and, when switched on, a rule the status cannot get past |
| 2 | Change text history | Every saved version of the eight long-text fields is kept in full, with who and when, in the same transaction as the save |
| 3 | Approver and reviewer access | Roles gain everyday-work permissions (design forms, choose approvers), module access gains a Read only level, and an approver can approve without being given the module |
Related pages: Change Management · REST API: Changes · Catalogue Request Approvals · Roles & Permissions · Module Access Control · Service Layer
1. The principle: nothing changes until you choose it
FreeITSM has real installs, and plenty of teams use Change Management happily as a lightweight record. Some of what #180 asks for would be disruptive if it were simply switched on: a team of two cannot have "nobody may approve their own change", and a team that edits the test plan after approval would suddenly find its changes sent back for re-approval.
So every rule that takes something away from somebody is a setting, and the default is today's behaviour. Upgrading changes nothing anybody can see, apart from the bug fixes in §3 and some new history that starts being kept.
What does happen on upgrade, quietly: records start being kept. Approval rounds and text revisions are recorded from the first day, whichever settings are chosen. A team that switches enforcement on six months later then has six months of evidence already, rather than none.
2. Every new setting, in one table
| Where | Setting | Choices | Default (= today) | Part |
|---|---|---|---|---|
| Change Management → Settings → Approvals (new tab) | Approval rules | Record approvals / Enforce approvals | Record | 1 |
| ↳ same tab | Approving your own change | Not allowed / Allowed | Not allowed - in Record mode this only flags the approval as not independent; it refuses nothing | 1 |
| ↳ same tab | Who counts as the change's own people | ticks: Requester, Assigned to, Created by | all three ticked | 1 |
| ↳ same tab | Administrator override | Off / On, with a written reason | Off | 1 |
| ↳ same tab | Edits that need a fresh approval | ticks, one per field | description, reason, risk evaluation, risk likelihood, risk impact, test plan, rollback plan, type, impact | 1 |
| ↳ same tab | When a required CAB member rejects | a status (Draft-stage or Stopped-stage only) | Draft | 1 |
| ↳ same tab | Who can choose a change's approvers | Anyone with Change Management / Only people with "Choose approvers" | Anyone | 1 |
| ↳ same tab | When a change is edited after approval | Keep the approval and mark it / Withdraw the approval (Record mode only - Enforce always withdraws) | Keep the approval and mark it | 1 |
| ↳ same tab | A vote from an out-of-date page | Record it and mark it / Refuse it (Record mode only - Enforce always refuses) | Record it and mark it | 1 |
| ↳ same tab | Deleting a change that has been submitted | Allowed / Not allowed - cancel it instead (Record mode only - Enforce never allows it) | Allowed | 1 |
| ↳ same tab | Standard changes from approved templates | Off / On | Off | 1 |
| Change Management → Settings → Templates (new tab) | per template: Completed per change, Who may use it | ticks per field; Everyone / Only these teams | description only; Everyone | 1 |
| Change Management → Settings → Statuses | Stage of each status | Draft · Awaiting approval · Approved · Implementing · Done · Stopped | filled in from today's names on upgrade | 1 |
| Change Management → Settings → Types | How it is approved | Needs review / Emergency - approve afterwards (pre-authorisation comes from templates, not types) | Needs review, for every existing type | 1 |
| Forms → Settings → Access (new tab) | Who can design forms | Everyone with Forms / Only people with "Design forms" | Everyone | 3 |
| System → Modules | Access level of each module grant | Full / Read only (only on modules that support it - Changes and Forms first) | Full | 3 |
All of them are dropdowns and tick boxes. None of them is typed.
The new tab is guarded by a new capability, changes.approvals, marked sensitive, because it governs the audit trail; the Templates tab by changes.templates. Three new everyday-work permissions appear in System → Roles: Choose approvers and Approve standard change templates (Change Management), and Design forms (Forms). How Roles learns to hold permissions that are not settings tabs is in Part 3 §2.
3. What I found while mapping it
Reading the code for this turned up faults that are worth fixing whatever is decided about the settings. Some are bugs that would bite anybody, today.
| # | Found | Effect today | Fixed by |
|---|---|---|---|
| F1 | A manual move to Approved does not stamp changes.approval_datetime; only a CAB vote does |
Watchtower counts a hand-approved change as awaiting approval for ever, and a Projects gate item "Change approved" never ticks for it | Part 1 - every approval stamps it |
| F2 | CAB votes are kept on the member row across resubmissions | After a required member rejects and the change is resubmitted, that old Reject is still counted, so the next vote by anybody sends it straight back to Draft. The only way out is to remove and re-add the member | Part 1 - votes belong to a round; a new round starts clean |
| F3 | The code finds Approved, Pending Approval and Draft by name (ChangesService, api/change-management/get_approvals.php, assets/js/change-management.js), but names are editable in Settings |
Rename Approved to Authorised and the CAB's deciding vote sets the change's status to nothing (status_id = NULL - the name lookup finds no row); rename Pending Approval and votes never transition at all, and the Approvals page goes empty |
Part 1 - a stage column |
| F4 | Anybody with Change Management can edit a CAB roster at any time, including after votes, including on their own change | Somebody can add themselves as the only required member and approve their own change by vote, without touching the status | Part 1 - roster locked outside Draft; independence |
| F5 | No beginTransaction anywhere in includes/services/changes.php |
A vote, its audit row and the resulting status change are three separate writes; two last votes landing together can both tip the threshold and fire change.approved twice |
Part 1 / 2 - one transaction, row lock |
| F6 | Two security issues in code this work touches | Details withheld until they are fixed | Separately, as a patch - see §5 |
| F7 | api/v1 never consults the acting analyst's module access |
A key keeps working for a module its analyst has since lost. Only administrators create keys, so this is a decision rather than a hole - but it matters for read-only access | Part 3 - service-level backstop |
| F8 | Nothing is dispatched when a change goes to Pending Approval | The help page says "reviewers are notified to vote"; nothing notifies them, and no workflow could, because there is no event to hang it on | Part 1 - new events |
| F9 | Attachment upload/delete and comment delete write nothing to change_audit |
The help page tells people to attach "approval emails" as evidence; deleting one leaves no trace | Part 2 |
Documentation that is wrong today
| Where | Says | Reality |
|---|---|---|
| In-app help, Quick tips | "Every field change is automatically tracked" | The eight long-text fields are deliberately not audited (ChangesService::updateChange, the comment "Longtext bodies - updated but NOT audited") |
| In-app help, Lifecycle | "If CAB is enabled, reviewers are notified to vote" | No notification exists (F8) |
| In-app help, Lifecycle | "Approver or CAB signs off" | The named Approver has no action; only the CAB can approve |
| In-app help, Change types | Standard = "Pre-approved… No CAB review needed"; Emergency = "approved after the fact" | A type has no behaviour at all; nothing in the code branches on it |
| Change Management wiki, Post-Implementation Review | "These are mandatory before a change can move to a fully-closed state" | Nothing enforces it |
| Module Access Control, Enforcement | "read-only endpoints are not yet individually gated" | Out of date since 2.10.2, when reads were gated (Module-Access-Reads-Were-Open) |
Each of these gets corrected in the same commit as the code that makes it true, or now if nothing is going to change.
4. The order to build it in
Each slice ships on its own and leaves the product working.
| Slice | What | Setting needed? | Release |
|---|---|---|---|
| 0 | F6 (the two security fixes); F1 (stamp approval on a manual move); F2 (clear votes when a change is resubmitted); the documentation fixes in §3 | No - bug fixes | 3.7.1 patch, first (F6 is security) |
| A | Status stages - replaces every name literal (F3) | No - filled in on upgrade | next feature release |
| B | Part 2: text revisions, transactions (F5), attachment and comment audit (F9) | No - records only | next feature release |
| C | Part 1 in Record mode: approval rounds, snapshots, votes tied to the version on screen, the approver's own Approve/Reject, new events (F8), the Approvals tab, Cancel and the deletion setting | No - Record is the default | next feature release |
| D | Part 1 Enforce mode, independence, the emergency route, override | Yes - off by default | next feature release |
| E | Part 3: Roles everyday-work permissions, Design forms, the approver's own way into Forms | Yes - off by default | next feature release |
| F | Part 3: Read only module level for Changes and Forms | Yes - nobody has it until given it | next feature release |
| G | Part 1: approved standard-change templates - versions, template approval, who may use it, the pre_authorised route |
Yes - off by default | next feature release |
All seven feature slices ship together in one feature release, as promised on #180. Each still lands as its own commit, leaving the product working.
Slice B comes before C on purpose: an approval has to be able to point at the exact revision it approved, so the revisions have to exist first. Slice G comes after E, because approving a template needs the everyday-work permissions.
5. Open decisions, before anything is built
All decided on 16 Oct 2026, and built in 3.8.0.
- ✅ Kept as one issue. All three parts ship together in one release, so #180 stays as the single issue.
- F6 now? Security fixes, so under the release rule they are a same-day patch, separate from all of this.
- ✅ "Own change" - requester, assigned-to and created-by, all ticked by default; a tick list, so a team can change it.
- ✅ Administrator override - off by default; a setting, for the two-person team where the administrator may be the only independent person.
- ✅ Record mode and edits after approval - a setting, When a change is edited after approval: Keep the approval and mark it (default, so Record mode changes nothing) or Withdraw the approval. See Part 1 §6.
- ✅ Standard changes - decided 16 Oct. Pre-authorised by approved template, in this release. The by type route is dropped, after the requester pointed out that a type alone does not show a change is inside an approved scope. See Part 1 §8.
- ✅ Emergency changes - may reach Done while the retrospective approval is still open, and are flagged on Watchtower until it is decided.
- ✅ Deleting a change - decided 16 Oct. A submitted change can never be deleted in Enforce mode, only cancelled. In Record mode it is a setting, defaulting to today's behaviour. A never-submitted draft can always be deleted. The requester supported this on #180. See Part 1 §4.
- ✅ Read only - Changes and Forms only in this release, because they are what #180 evaluated. Other modules later, as people ask.
- ✅ Votes from an out-of-date page - decided 16 Oct (raised by the requester). Refused in Enforce mode; a setting in Record mode, defaulting to record it and mark it. See Part 1 §5.
Where the build differs
Built in 3.8.0 in seven slices, as §4 planned, plus the evidence export. Where the code differs from the design pages:
- Templates keep a working draft.
change_templatesitself is the draft the Templates tab edits; Submit freezes it as a version inchange_template_versions(statedraftuntil approved). Who may use a template is the tablechange_template_teams(no rows = everyone). - A template's fixed fields are compared by their words, not their markup. The change editor rewrites HTML (plain text comes back wrapped in
<p>), so a markup comparison would have taken every untouched recipe out of pre-authorisation. A template field left blank is not part of the recipe and is not compared. - Template approvers see the Templates tab without holding its capability, through a new manifest key,
also_visible_to. GET /change-templateswas not built. The change editor's picker reads a UI endpoint;POST /changesacceptstemplate_version_id.- Cancel goes to a configurable Stopped status, by default the last one (Cancelled), not the first: the stage cannot tell Rejected from Cancelled, and Rejected comes first on a seeded install.
- The Enforce guard runs after the save, inside the same transaction, so a standard change is judged on its fields as they will stand. A refusal still rolls the save back.
- A submission with neither a CAB nor an approver opens a
manualround in Record mode (Enforce refuses it). - The editor filters its status list; the right-click menu does not. The server refuses a move from either, with the reason.
- Not built in 3.8.0: the Approvals page's Awaiting my decision and Emergency - decide afterwards filters (its existing filters are unchanged), and a D005 check for the write guard -
tests/module-access-coverage.phpenforces it instead. - Forms Read only: the designer and the fill page send a read-only analyst back to the list, and clicking a form opens its submissions.
- Deleted comments are revisions with
source = 'deleted'; the evidence export says images inside the text are not reproduced.
6. What is deliberately out of scope
- Report Pack blocks for changes or forms. The issue asks that a reviewer can read "corresponding reports/exports". Report Packs have no Change or Forms blocks today, so the reviewer's reports are the Changes table view with its CSV export, the new evidence export (Part 2 §6) and the Forms submissions export. New blocks are a separate piece of work.
- Reconstructing history that was never recorded. Text edited before the upgrade cannot be recovered. The first revision of each field is marked as "as found when history began", and is never presented as the original (Part 2 §4).
- Multi-stage approval chains (manager, then CAB, then change manager). One round, decided by a CAB or by one named approver, is what exists today and what #180 asks to enforce.
- Approval by people who are not analysts. Approvers are analysts, as today.
Related pages
- Part 1 - Enforceable change approvals
- Part 2 - Change text history
- Part 3 - Approver and reviewer access
- Change Management · REST API: Changes · Roles & Permissions · Roles - Developer Guide · Module Access Control · Catalogue Request Approvals - Developer Guide · Service Layer · Projects: governance