Configuring permits
Configuring the permit programme — permit types, authorised-issuer appointments, site policy (coverage and close-out gate), and the XLSX import.
For Site admin / configurator
Your role
You are the full-privilege site administrator (SiteMembership role admin, rank 3). You inherit both review bands plus all configuration rights — permit_request, permit_approve_stage1, permit_approve_stage2, and permit_settings_manage — so you can drive a permit through every desk gate (endorse, then approve), reject or request corrections at any gate, close out live/expired permits, run the legacy XLSX import (you hold contractor_manage), and own the entire permit programme: the permit-type catalogue, authorised-issuer appointments, and the two site policies. The two things you can't do by virtue of your role alone are the two field signatures: issuing a permit at the walk-down needs an active PermitAppointment (issuing keys on the appointment, never on your role — even admin), and countersigning needs you to actually be the permit's named receiver. Everything else on the site is yours.
What you can and can't do
You can
- Raise permits yourself (
permit_request) — create, edit, submit, withdraw your own drafts. - Endorse at stage 1 (permit_approve_stage1) — the coordination check, requested → endorsed.
- Approve at stage 2 (permit_approve_stage2) — the H&S desk review, endorsed → approved (or
requested → approveddirectly forsingle_stagetypes). - Reject or request corrections at whichever gate is currently acting.
- Close out issued/expired permits (
→ closed) via the inherited coordinator-band path. - Decide extension requests (approve/reject), moving a live permit's
end_atin place. - Own all configuration: permit types, authorised-issuer appointments, and
SitePermitConfig(coverage policy, close-out gate, nominee/deputy, expiring-warning lead). - Run the legacy XLSX permit import — upload, delete sets, re-resolve, reconcile (contractor_manage).
- See every permit on the site (you are in the review band — cross-contractor, including the in-review middle), except other users' private drafts.
You can't
- Issue at the walk-down without an active
PermitAppointment— issuing is appointment-gated, not role-gated. Being admin (or even superadmin) is no shortcut. You appoint issuers; you are only an issuer if someone appointed you. - Countersign unless you are the permit's named receiver (
permit.receiver_id) — countersign is identity-gated. - Edit/submit/withdraw another user's draft — requestor-ownership (
_assert_owner) still applies; only a superadmin short-circuits it. You can see in-review permits across contractors, but you can't author someone else's draft.
Your tasks
Task 1 — Act at the endorsement (stage-1) gate
- A submitted permit raises an endorse_permit Task to the stage-1 band. Open My Work’s Tasks tab and look for the "Review & endorse" card (it deep-links into the review surface), or open the Permits panel directly (dock key P) and pick a permit sitting in the "In review" section.
- The review workbench opens: a read-only application pane, the live map lens behind the panel (linked entities glow, everything else dims), and a ranked Conflicts list (red-first).
- Optionally type free-text controls into "Added controls" (these become
stage1_controls, stamped "Added at endorsement") and a note. - Press Endorse. The system calls
POST /api/permits/applications/{id}/endorse, records the action, and moves the permitrequested → endorsed. Theendorse_permitTask auto-resolves and the permit now awaits stage-2 approval. - To bounce instead: Request corrections (
→ corrections_requested, note required, returns it to the requestor editable) or Reject (→ rejected, terminal, reason required).
Note:
single_stagepermit types skip endorsement entirely — there is no stage-1 action on them. They go straight fromrequestedto the stage-2 approve gate, so you'll only ever see an approve_permit Task for them, never an endorse one.
Task 2 — Act at the approval (stage-2) gate
- Open the "Review & approve" card in My Work, or open the review workbench on an
endorsedpermit (or arequestedsingle_stagepermit). - Review the application, the map lens, and the Conflicts list. Optionally add
stage2_controls(stamped "Added at approval") and a note. - If there are red conflicts: the decision bar shows a bordered acknowledgement box — tick "Approve despite N red conflict(s)" and type a reason. Approve stays disabled until the box is ticked and the reason is non-empty. The reason is folded permanently into the approval note ("Approved despite N red conflicts: …"). Amber conflicts only inform — they never gate.
- Press Approve. The system calls
POST /api/permits/applications/{id}/approveand moves the permit toapproved("approved for tomorrow, not yet live"). Theapprove_permitTask auto-resolves. - The permit now sits in
approved, awaiting the dual-signature walk-down (issue + countersign) — which is a field act, not yours unless you are appointed/the receiver.
Note:
approvedis the state most permits spend the night in. The issuer's/issuesignature does not make it live — it leaves status atapprovedand sets a derived "awaiting countersign" flag. Only the named receiver's countersign flips it toissued. Don't expect Approve to put work on the ground.
Task 3 — Close out a live or expired permit
- In the Permits panel (P), open a permit in the "Live" section (
issued) or "Expired — unclosed" (expired). You can drive close-out via the coordinator-band path you inherit — no appointment needed. - Trigger close-out (the "Close out" Task card in My Work routes here too). The close-out surface shows a fixed 3-item safety checklist: "Work complete and all tools / plant removed", "Work area made safe, clean and checked", "Fire watch held to completion (where required)".
- Tick all three (the "Sign & close out" button stays disabled until all are confirmed), add an optional note, and press Sign & close out. The system calls
POST /api/permits/applications/{id}/close-out, moves the permitissued/expired → closed, and re-snapshots the PDF.
Note: Close-out is blocked while the permit has open child permits (any child not in
closed/rejected/withdrawn). The rejection surfaces inline and names the blocking child permit numbers — close the children first.
Note:
expired ≠ closed. The scheduler only flips an overdueissuedpermit toexpired(an alarm); it never closes it. An expired permit still needs your close-out sign-off, and — if the close-out gate is on — it actively blocks that contractor's next permit from being issued until you close it.
Task 4 — Decide an extension request
- Extensions live in the detail body, not in Tasks. Open the permit (it must be
issuedand an extendable type — UK default: only General) and find the pending extension request row. - Review the requested new end time. Press Approve (
POST /api/permits/extensions/{request_id}/approve) — this movespermit.end_atin place (no status change, the permit staysissued), records anextend_approvesignature, and re-arms the expiring-soon warning. Or press Reject (reason required) to leave the window untouched.
Note: Extension is stretch-in-place, not reissue. High-risk types (HW/CSE/WAH/EXC/ELE) are not extendable — a request can only exist on a live General permit. A spent/expired permit cannot be extended; it must be reissued (a fresh draft) instead.
Task 5 — Configure the permit programme (types, appointments, policy)
- In the Permits panel header, click the gear (data-test-id
permit-settings-gear, tooltip "Permit settings"). It only renders for HSE/admin (gated on permit_settings_manage) — which you are. The "Permit settings" modal opens with three tabs: Types, Authorised issuers, Policy. - Types tab — create/edit/retire
PermitTypes. Thecode(e.g.HW,CSE,EXC) is what the permit-conflict clash rules match on, so adding a new permitted activity usually means also authoring a matching clash rule. Set capability flags:requires_parent,is_extendable,single_stage. Retire a type by setting it inactive (active=false) — there's no hard delete. (requires_powraexists as a flag but is a reserved V2 hook — it enforces nothing in v1.) - Authorised issuers tab — appoint a user as an issuer via POST /api/permits/appointments. An appointment is
(user_id, optional permit_type_id): leave the type null to appoint them for all types, or scope them to one. Revoke via delete (revoked rows are kept for audit). - Policy tab — edit
SitePermitConfigas a full-state save:- Coverage policy (
permit-policy-coverage) — surface-only. When on, active entities with no linked permit get a "No permit" advisory badge. It never blocks placement. - Close-out gate (
permit-policy-close-out-gate) — the one hard stop. When on, a contractor with expired-unclosed permits cannot have a new permit issued until those are closed out. Approvals still proceed; the block fires only at issue time. - Nominee / Deputy nominee selects and Expiring-warning lead (hours, 1–720) input (default 24) — these feed the notification audience and the expiring-soon timing, not the two headline behaviours.
- Press "Save policy". The system does a full PUT /api/permits/config. On failure (e.g. a mid-session role downgrade 403s the write) the red error banner surfaces it.
- Coverage policy (
Note: The Policy save is full-state replace, not partial. The UI loads the whole object then submits it back, so don't worry in the app — but if you script the API, you must send all five fields or omitted ones reset to their schema default (e.g.
coverage_policy_enabled→ false).
Note: There is no seeded config row. A brand-new site behaves as if both policies are off until someone saves the Policy tab once — the row materialises on first save. Don't go hunting for it before then.
Task 6 — Run an XLSX permit import
- Open the Imports panel (key I) → "Work Permits" accordion. The Upload button, format selector, and permit-sets list are visible to you because you hold
contractor_manage. - Click Upload, pick a customer permit spreadsheet (
.xlsx/.xls). If more than one format is configured, choose it from the format dropdown; otherwise it auto-detects by extension. Today only the AFRY PTW Export format ships. - The backend (
POST /api/permits/upload) parses the sheet, auto-creates unknown contractors by name, resolves locations against site buildings/zones, and upserts permits by(site_id, permit_number). Imported permits land directly at statusissued, sourceimported— they skip the whole approval chain. - Read the result banner: new / updated counts, contractors created, unresolved locations. If you uploaded the batch before the buildings existed, click Re-resolve later to re-match locations without re-uploading.
Note: Wall-clock times in the spreadsheet are read in the site's timezone, not your browser's or UTC. Re-uploading the same file updates matching permit numbers in place (and re-points them to the newest set) rather than duplicating — the banner shows "N new, M updated".
What you'll be notified about
| Trigger | You get |
|---|---|
| Permit submitted (two-stage type) | "Permit {number} awaiting endorsement" notification + an endorse_permit Task ("Review & endorse") |
Permit submitted (single_stage type) |
An approve_permit Task ("Review & approve") directly — no endorse |
| Permit endorsed | "Permit {number} awaiting approval" notification + an approve_permit Task ("Review & approve") |
| Extension requested | Notification + email (you're in the stage-2 band that decides extensions) |
| Permit expired unclosed | Notification + email (role hse/admin audience) |
Because you sit in both review bands you receive both gate Tasks and notifications, and permit gate Tasks deep-link correctly from My Work into the right surface. One known gap: clicking a permit notification (the bell, not a Task) does not currently open the Permits panel — the in-app notification click-router has no permit case, so it logs an "Unhandled" warning and no-ops on navigation. Use the My Work panel (which does deep-link) to act on permits; treat the bell notifications as informational only.
Gotchas
- You can't issue or countersign by being admin. Issuing requires an active
PermitAppointmentfor the permit's type (or an all-types appointment) — appoint yourself on the Authorised issuers tab if you genuinely need to sign walk-downs. Countersigning requires you to be the named receiver (permit.receiver_id). These two are deliberate separation-of-duties gates that role alone never satisfies. - Stage 2 is the separation-of-duties line you straddle. A coordinator can endorse but cannot approve; you hold both bands, so be deliberate about not self-approving your own coordination check unless your site's process allows it. Stage-1 = coordination check, stage-2 = H&S desk review — they exist to be two sets of eyes.
- You can see in-review permits across all contractors, but not other users' private drafts. A not-visible permit returns 404 (not 403), so existence is hidden. You can act on the whole in-review middle and everything site-public (
issued/expired/closed), just not author someone else's WIP. - Adding a permit type ≠ adding a clash rule. The clash engine matches on
PermitType.code(e.g.HW,CSE) via thepermit_type_in_a/permit_type_in_bpredicates, and codes are case-sensitive. If you create a new high-risk type and want it to conflict with others in proximity, you must author the matching clash rule yourself — creating the type does not auto-generate one. - The close-out gate blocks at issue, not at approval. If a contractor has expired-unclosed permits and the gate is on, you can still endorse and approve their next permit — it only fails when the appointed issuer tries to make it live. The error names the specific blocking expired permit numbers. Coordinator-raised permits with no contractor are never gated.
For Superadmin (platform operator)
Your role
You are the platform operator — a user with is_superadmin = True. You sit outside the per-site role system entirely: your is_superadmin flag short-circuits both _has_perm (so every permit permission check returns true) and _assert_owner (so the requestor-ownership gate never stops you). You can see every permit on every site — including other users' private drafts — and you can drive every desk gate in the lifecycle: endorse, approve, reject, request corrections, close out, reissue, and decide extensions. You can also configure the whole subsystem (permit types, authorised-issuer appointments, and the per-site SitePermitConfig policy). There are exactly two things your god-mode does not unlock, because they are deliberately gated on identity and physical appointment rather than permission: you cannot issue a permit at the walk-down unless you hold a PermitAppointment, and you cannot countersign unless you are the permit's named receiver. Everywhere else, treat yourself as able to act — your job is operating and administering the platform, not normally sitting in the day-to-day permit chain.
What you can and can't do
You can
- See every permit on any site, in any state, including other users' private
draftpermits (you read via the review-band visibility path, so nothing 404s for you). - Drive every desk gate regardless of role band: endorse (stage-1), approve (stage-2), reject, request corrections, close out, reissue, and approve/reject extensions.
- Edit, submit, withdraw, or delete any user's draft —
_assert_ownershort-circuits for you (noPermitOwnershipError). - Configure the subsystem on any site: the PermitType catalogue, authorised-issuer appointments, and
SitePermitConfig(coverage policy, close-out gate, nominee/deputy, expiring-warning lead).
You can't
- Issue at the walk-down without a
PermitAppointment.is_appointed_issuer()has no superadmin shortcut — it matches only a non-revoked appointment (type-scoped or all-types). Being superadmin does not place you at the workface. - Countersign unless you are the permit's
receiver_id. The countersign identity gate has no superadmin bypass — even you must be the named receiver to flip a permit live.
Note: These two exceptions are not bugs to route around. They encode real-world accountability — the walk-down signature attests physical verification by an appointed person, and the countersign attests the receiver accepted the conditions. If you genuinely need to issue, appoint yourself first (you hold
permit_settings_manage); if you need to countersign, you must be set as the receiver on that permit.
Your tasks
Task 1 — Find and inspect any permit on any site
- Select the site whose permits you want from the left site browser (key
B), then open the Permits dock panel with keyP(tooltip "Open permits (P)"). The panel hosts the state-grouped board. - The board groups permits into Live, Approved — tomorrow, In review, Expired — unclosed, and My drafts & corrections. As superadmin you see every permit in these sections — including drafts authored by other users, which a normal member would never see.
- Click any row to open the read-only detail workbench (
permit-detail). This callsGET /api/permits/applications/{id}(which never 404s for you) and renders the header/meta, controls, pre-checks, the embedded document + signature chain, and any extensions. - Selecting a permit casts the map lens (
MapPermitLens) — linked entities glow, everything else dims to 0.18 opacity, no pin is dropped. If the lens lights up empty, the scrubber is parked outside the permit's window; click the "N tokens outside this time — jump to window" pill to snap the scrubber tostart_at.
Note: Board selection is non-destructive in time — it does not move the scrubber. Only the review workbench snaps the scrubber to the permit window (and restores it on close).
Task 2 — Drive a desk gate (endorse / approve / reject / request corrections)
- Open the permit's detail footer or the review workbench (board row → the gate actions route into
permit-review). Button visibility is driven by the permit'sPermitPermissionsflags, all of which compute true for you. - To endorse a
requestedpermit (stage-1): press Endorse. The system records astage1_approvesignature and moves the permitrequested → endorsed. (Skipped forsingle_stagetypes.) API:POST /api/permits/applications/{id}/endorse. - To approve (stage-2): from
endorsed(or directly fromrequestedfor single-stage types), in the review decision bar acknowledge any red conflict — tick "Approve despite N red conflicts" and type a reason — then press Approve. Approve stays disabled until the red conflicts are acknowledged. The reason is folded into the permanent approval note (stage2_approvesignature). State:→ approved. API:POST .../approve. - To reject (terminal) or request corrections (bounce back): both require a note/reason in the decision bar. Reject →
rejected(POST .../reject); request corrections →corrections_requested, editable by the requestor, chain restarts on resubmit (POST .../request-corrections).
Note: Amber conflicts only inform — they never gate Approve. Only red conflicts demand the acknowledgement + reason. Clashes are always advisory; they never hard-block a decision.
Task 3 — Close out a live or expired permit
- From the permit's detail footer (or its My Work card), choose Close out to open the
permit-close-outceremony. - Tick the fixed 3-item safety checklist — Work complete and all tools / plant removed, Work area made safe, clean and checked, Fire watch held to completion (where required). The Sign & close out button stays disabled until all three are confirmed.
- Press Sign & close out. The permit moves
issued|expired → closed, the PDF is re-snapshotted, and the board re-lists. API:POST .../close-out.
Note: Close-out is blocked while the permit has open child permits (children not in
{closed, rejected, withdrawn}). The rejection surfaces inline and names the blocking child numbers — close the children first.
Task 4 — Decide an extension request
- Open the permit detail; the extension request lives in the projected Extensions section (not a Task — extensions are not deep-linked from My Work).
- On the pending row, press Approve (or Reject with a required note). Approve moves
permit.end_atin place — the status staysissued, this is not a status transition — records anextend_approvesignature, and re-armsexpiring_warned_at. API:POST /api/permits/extensions/{request_id}/approve(or.../reject).
Note: Extensions only apply to
issuedpermits whose type isis_extendable(UK default: only General). A child's extended window must still sit inside its parent's. A spent/expired permit cannot be extended — it must be reissued.
Task 5 — Reissue a spent permit
- Open the detail of an
issued,expired, orclosedpermit and press Reissue. - The system clones it into a fresh draft (
source = requested,reissued_from_idset, copying type/parent/contractor/description/controls/window/pre-checks) and opens the apply workbench. The original permit is unchanged. A new number is minted on the clone's own submit. API:POST .../reissue(returns 201).
Task 6 — Configure the subsystem (types, appointments, policy)
- In the Permits panel header click the gear (title "Permit settings",
data-test-id permit-settings-gear) to open the 3-tab Permit settings modal: Types, Authorised issuers, Policy. - Types tab — create/edit/retire
PermitTypes. Thecode(e.g.HW,CSE,EXC) is what the clash engine matches viapermit_type_in_a/_b; adding a new type means adding a matching clash rule if you want it to conflict. Retire withactive = false(no hard delete). API:POST/PATCH /api/permits/types. - Authorised issuers tab — appoint or revoke
PermitAppointments(user_id, optional permit_type_id); a null type = appointed for all types. This is also how you appoint yourself to gain issuing rights (see the gotcha below). API:POST/DELETE /api/permits/appointments. - Policy tab — edit
SitePermitConfigwith a full-state PUT: the Coverage policy switch (surface-only badge, never blocks), the Close-out gate switch (the one hard stop — blocks new issues for a contractor with expired-unclosed permits), Nominee / Deputy nominee selects, and Expiring-warning lead (hours, 1–720). Press Save policy. API:PUT /api/permits/config?site_id=....
Note: The Policy tab is a full-state replace — the UI loads the current config then submits the whole object. If you script
PUT /permits/configdirectly, send all five fields or you'll silently reset the omitted ones to schema defaults (e.g.coverage_policy_enabled → false).
Note: There is no seeded
SitePermitConfigrow — an absent row means both policies off. The row materialises on the first Save policy. Don't go hunting for a row that doesn't exist yet on a fresh site.
What you'll be notified about
The permit notification rules engine is keyed off role bands and named identities (stage-1/stage-2 bands, requestor, receiver, appointed issuers, nominee/deputy) — not off the superadmin flag. So in normal operation you will not receive permit gate Tasks or lifecycle notifications simply for being superadmin. You only get permit notifications/Tasks when you are also in the relevant audience for a specific permit — for example, if you are the named receiver on a permit you'll get the permit_awaiting_countersign notification, or if you raised a permit yourself you'll get its outcome pings. The four gate Tasks (endorse / approve / issue / close-out) land on the bands, so to see them in My Work you'd need the corresponding membership role on that site, not just superadmin.
Note: Clicking a permit notification does not currently open the Permits panel — the notification click-router has no permit case, so a permit notification logs an "Unhandled" warning and does not navigate. Permit Tasks (in My Work) deep-link correctly; permit notifications are informational only for now.
Gotchas
- Issuing is the hard wall in your god-mode.
POST .../issuechecksis_appointed_issuer(you, site, type)with no superadmin shortcut — if you hold noPermitAppointmentfor that type (or an all-types one), you getPermitGateError "Issuing requires an authorised-issuer appointment". To issue, go to Permit settings → Authorised issuers and appoint yourself first (you havepermit_settings_manage). - Countersign is identity-locked. Only
permit.receiver_idmay countersign — there is no superadmin bypass. You cannot rubber-stamp a permit live on someone's behalf; the permit only goesissuedwhen both an appointed issuer signs (/issue) and the named receiver countersigns (/countersign). /issuedoes not make a permit live. It records the issuer signature and leaves the status at approved (the board/detail shows a derived "awaiting countersign" flag — there is no separate enum state). Only the receiver's countersign flips it toissued. Don't expect a status change after a walk-down signature alone.expired≠closed. The scheduler sweep flips overdueissued → expiredautomatically (an alarm state) but never closes a permit — a human close-out sign-off is still required to reachclosed. Expired permits are still reissuable, and (if the close-out gate is on) they block the contractor's next issue.- Even with full visibility, remember the two intake paths share one
Permitentity. Imported (XLSX) permits are inserted directly atissuedwithsource = importedand norequestor_id— they never had a draft/approval chain, so their detail won't show one. Don't read a missing approval chain as a data fault on an imported permit. - Your edits to another user's draft work (ownership is bypassed), but mind the blast radius — you are editing someone else's in-flight application. Prefer request corrections (bounces it back to the author to fix) over silently editing their draft, unless you're deliberately operating on their behalf.
Site policy — coverage & close-out gate
SitePermitConfig — at most one row per site. An absent row means both policies OFF; the row is created lazily on the first save (PUT /permits/config is a full-state replace — omitting a field resets it to default). In the UI it lives behind the Permits panel header gear ("Permit settings", permit-settings-gear), visible to hse/admin only, which opens a 3-tab modal (Types / Authorised issuers / Policy) rendered at the dashboard root (the panel's backdrop-filter would clip a fixed dialog).
| Setting | Type | Default | Effect |
|---|---|---|---|
coverage_policy_enabled |
bool | false |
Surface-only. Badges active in-window entities (workers/plant/deliveries) with no linked permit — noticeboard panel, map decoration, "No permit" properties chip. Never blocks placement or editing. |
close_out_gate_enabled |
bool | false |
The one hard stop. When on, a contractor with expired-but-unclosed permits cannot have a new permit issued until those are closed out. Enforced in issue() only — approvals still proceed. Coordinator-raised permits with no contractor are never gated. |
nominee_user_id |
UUID | null | null |
Added to the permit_endorsed notification audience (#302). Does not change the two headline behaviours. |
deputy_nominee_user_id |
UUID | null | null |
As above — deputy notification audience. |
expiring_warning_lead_hours |
int (1–720) | 24 |
How far ahead the "expiring soon" warning fires and the board's "Soon" badge appears. Validated both sides. |
Importing permits from a spreadsheet (XLSX)
A coordinator uploads a customer spreadsheet via Imports panel → Work Permits.
Config-driven parsing:
POST /permits/uploadselects a parser byformat_id, or auto-detects by.xlsxextension when omitted.- A per-customer YAML config (
backend/app/permit_format_configs/*.yaml) maps spreadsheet headers → permit fields (case-insensitive, whitespace-stripped match) and applies named transforms:parse_datetime,split_values,strip,to_string,regex_extract. - Wall-clock cells are interpreted in the SITE's timezone — the YAML's
timezonearg is always overridden by the site tz. - Each row is resolved: contractor matched by name (auto-created with a deterministic colour if unknown); location/zone strings matched to buildings/zones with level parsing validated against the building's actual
levels. - Permits are upserted by
(site_id, permit_number)into a newPermitSet, landing at statusissued, sourceimported, with no requestor. Re-uploading the same file updates in place (banner shows "N new, M updated"), never duplicates.
Note: Only one customer config ships (
afry_ptw.yaml,format_idafry_ptw_xlsx). There is no UI to add configs — they are YAML files. Present the AFRY layout as "the" format, not a self-service feature. Rows with blank permit numbers are silently skipped; rows with unparseable dates are skipped and reported inskipped_invalid_dates. Use Re-resolve to match permits uploaded before their buildings existed.
Recurring-occurrence generation (buildPermitScheduleOccurrences, frontend, when an issued permit seeds entity creation):
isMultiDayPermitis duration-based (>24h), not calendar-based — a single overnight shift (16:00→00:00) is one occurrence.- A multi-day permit emits one occurrence per calendar date using the permit's own HH:MM (
shiftIndex: -1, custom time). - Overnight trim: if
end_time < start_time, the trailing calendar date is dropped (Mon 22:00 → Sat 06:00 = 5 occurrences, Mon–Fri). - Weekend awareness: if either endpoint falls on a Sat/Sun, the day filter expands to include weekends; otherwise weekdays only.
Permits guide: Overview · Applying · Reviewing & approving · On-site signing · Configuring · Reference