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."

  1. A configured change approval should be enforceable. Today an analyst can set a change straight to Approved without the approval having happened.
  2. 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.
  3. 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.

  1. ✅ Kept as one issue. All three parts ship together in one release, so #180 stays as the single issue.
  2. F6 now? Security fixes, so under the release rule they are a same-day patch, separate from all of this.
  3. ✅ "Own change" - requester, assigned-to and created-by, all ticked by default; a tick list, so a team can change it.
  4. ✅ Administrator override - off by default; a setting, for the two-person team where the administrator may be the only independent person.
  5. ✅ 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.
  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.
  7. ✅ Emergency changes - may reach Done while the retrospective approval is still open, and are flagged on Watchtower until it is decided.
  8. ✅ 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.
  9. ✅ Read only - Changes and Forms only in this release, because they are what #180 evaluated. Other modules later, as people ask.
  10. ✅ 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_templates itself is the draft the Templates tab edits; Submit freezes it as a version in change_template_versions (state draft until approved). Who may use a template is the table change_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-templates was not built. The change editor's picker reads a UI endpoint; POST /changes accepts template_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 manual round 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.php enforces 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