← Guide

Reviewing & approving permits

The two approval gates — coordinator endorsement and HSE approval — plus rejection, close-out, extensions and the permit board.

Try it: the Hot works permit awaiting endorsementTry it ↗ scenario seeds a fresh Demo World with a Hot Works permit sitting at the stage-1 endorsement gate, so you can walk the endorse decision below against real data.

For Coordinator / stage-1 reviewer

Your role

You are the coordination check — the first of the two approval gates a permit must clear before it can go live. When a contractor submits a permit, it lands in your court as a requested permit awaiting stage-1 Endorsement. Your job is to verify the work is coordinated: that it doesn't collide with other live or pending work, that the location and timing make sense, and that the right controls are in place — then endorse it up to the H&S desk (stage 2), or bounce it back with corrections, or reject it. You see every permit on the site across all contractors (you are in the "review band"), you can close out completed or expired permits, and you run the legacy XLSX permit import. You do not make the final go-live decision — that is the H&S/admin desk (stage 2) — and you do not configure the permit programme.

What you can and can't do

You can
  • Endorse a requested permit (requestedendorsed) — the stage-1 coordination check. (permit_approve_stage1)
  • Reject or request corrections at the stage-1 gate (a note/reason is required).
  • See all permits on the site — cross-contractor, including the in-review middle — except other users' private drafts.
  • Close out an issued or expired permit (→ closed) via the coordinator-band path.
  • Raise permits yourself (you also hold permit_request).
  • Run the XLSX permit import: upload spreadsheets, list/delete permit sets, re-resolve locations, reconcile counts. (contractor_manage)
  • Add free-text controls (stage1_controls) and a note at endorsement.
  • Edit/author the permit-conflict clash rules like any other clash rule.
You can't
  • Approve a permit — stage-2 is HSE/admin only (permit_approve_stage2). This is the separation-of-duties boundary: you do the coordination check, they do the H&S desk review.
  • Decide extension requests (approve/reject) — stage-2 band only.
  • Manage permit types, authorised-issuer appointments, or SitePermitConfig — those need permit_settings_manage (HSE/admin).
  • Issue at the walk-down — issuing requires an active PermitAppointment, which is a separate appointment, not your coordinator role. (You can be appointed on top of your role; the appointment, not the role, grants issuing.)
  • Act at the stage-1 gate on a single_stage permit type — those skip endorsement entirely, so there is no stage-1 action for you to take.
  • Edit, submit, or withdraw another user's draft — requestor-ownership still applies.

Note: The HTTP route for endorse/reject/request-corrections only declares the coarse permit_view gate at the boundary; the real coordinator-band check runs inside PermitWorkflowService. So if you try to endorse without the band you get a 403 ("Stage-1 endorsement requires the coordinator band"), not a route-level rejection. The button simply won't be offered to you unless can_endorse is true.


Your tasks

Task 1 — Endorse, reject, or request corrections at stage 1

This is your core job. Two ways in: a Task from My Work, or directly from the Permits board.

Via My Work (the usual route):

  1. When a contractor submits a non-single_stage permit, you receive an endorse_permit Task titled around "Permit {number} awaiting endorsement", and a matching notification. Open the My Work dock panel (the Tasks tab).
  2. Find the card labelled "Review & endorse". It carries a ● new badge until acknowledged and a due/overdue badge. Click it.
  3. The system opens the Permits dock panel and drops you into the review workbench (openReview) for that permit.

Via the board directly:

  1. Open the Permits dock panel — dock key P (tooltip "Open permits (P)"). (Inside Add mode, A then P places a PoI instead — a separate two-key path.)
  2. In the "In review" section of the board, click the permit row (type-code chip, number, contractor, headcount glyph, and a severity-tinted clash badge).
  3. Open it; for a permit at your gate the detail footer's action routes you into the review workbench.

In the review workbench (three-pane):

  1. Pane 1 — the application: a read-only render of the permit (header, controls, pre-checks, RAMS, linked entities, document + signature chain so far).
  2. The map (pane 2): opening review snaps the scrubber to the permit's start_at so the linked entities are visible, and casts the map lens — linked entities glow, everything else dims to ~18% opacity. (The prior scrubber position is captured and restored when you close review.)
  3. Pane 3 — Conflicts: the permit's clashes, ranked red-first, each a RED/AMBER badge + the other party's label + reason + evidence (distance). The framing line tells you what's demanded, e.g. "2 red — acknowledgement required / 1 amber — advisory", or "No conflicts on the permit's linked tokens at its window."
  4. Optionally type added controls into the "Added controls" box (these become stage1_controls, stamped "Added at endorsement") and/or a note.
  5. If there are RED conflicts and you are the acting gate: tick "Approve despite N red conflict(s)" and enter a reason — the reason is folded into the approval note. Amber conflicts only inform; they never gate.
  6. Choose your decision in the decision bar:
    • EndorsePOST .../endorse. Permit moves requestedendorsed; it now goes to the HSE/admin desk for stage-2 approval. Your endorse_permit Task auto-resolves.
    • Request correctionsPOST .../request-corrections (reason required). Permit moves requestedcorrections_requested, bounces back to the requestor as editable; on their resubmit the chain restarts at requested. All open gate Tasks on the permit close.
    • RejectPOST .../reject (reason required). Permit moves requestedrejected (terminal). Requestor + receiver are notified with your reason.

Note: On a single_stage permit type you have no stage-1 action — endorsement is skipped and the permit goes straight from requested to the stage-2 (HSE) approve gate. If you try to endorse one, you get "not awaiting stage-1 endorsement." The "Endorse" button won't be offered.

Note: "Endorse" is not "Approve". You cannot push a permit to approved — that's the HSE/admin desk. Your endorsement is the coordination sign-off that unlocks the H&S review.

Task 2 — Close out a completed or expired permit

You can close out an issued or expired permit when no appointed issuer is driving it.

  1. Open the Permits panel (P). Live permits sit in "Live"; overdue ones sit in the loud red "Expired — unclosed" section.
  2. Open the permit and trigger close-out (the detail footer's close-out action, available when can_close_out is true, opens the close-out surface).
  3. Work 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)"
  4. Optionally add a close-out note. "Sign & close out" stays disabled until all three are ticked.
  5. Sign → POST .../close-out. Permit moves issued/expiredclosed; the PDF is re-snapshotted.

Note: Close-out is blocked while the permit has open child permits (children not yet closed/rejected/withdrawn). The system surfaces the blocking child permit numbers inline and keeps the close-out gate open. Close those children first.

Note: expired is not closed. The scheduler only flips an overdue issued permit to expired (an alarm state) — it never closes it. A human close-out sign-off is still required to reach closed, and if the close-out gate policy is on, the contractor's expired-unclosed permits will block their next permit being issued until you close these out.

Task 3 — Run the XLSX permit import (Work Permits)

You are one of the only roles (coordinator+, via contractor_manage) that can import customer permit spreadsheets.

  1. Open the Imports panel (key I) → expand the "Work Permits" accordion. (This hosts the permits-modal upload UI.)
  2. Click Upload and pick the .xlsx file. If more than one format is configured a format <select> appears; otherwise the single shipped format (AFRY PTW Export, afry_ptw_xlsx) is used. → POST /api/permits/upload?site_id=… (multipart field file).
  3. The system parses the sheet with the config-driven parser, interpreting wall-clock times in the site's timezone, resolves each row's contractor (matched by name; auto-created if unknown) and location/zone (matched to buildings/zones), then upserts permits keyed by (site_id, permit_number) under a new PermitSet.
  4. Read the result banner: N new / M updated, contractors created, and unresolved locations. Imported permits land directly at status issued, source imported — they skip the whole approval chain.
  5. To manage sets: the uploaded permit-sets list shows each set as "filename (N permits)" with a delete control → DELETE /api/permits/sets/{id} (bulk-deletes the set and its permits).
  6. After you add buildings/zones to the site, click Re-resolvePOST /api/permits/re-resolve to re-match locations for already-uploaded permits without re-uploading. The banner shows newly_resolved / still_unresolved.

Note: The upsert key is (site_id, permit_number)re-uploading the same file updates existing permits in place (and re-points them to the newest set) rather than duplicating. Rows with blank permit numbers or unparseable dates are silently skipped (bad dates are listed in skipped_invalid_dates; blank numbers aren't reported), so "N new + M updated" can be fewer than the row count.

Note: Location strings like "site wide", "N/A", "TBC", "various" are intentionally treated as non-locations and are not counted as unresolved. A floor level the matched building doesn't actually have resolves to null — the requestor will have to pick it manually.

Task 4 — Site-wide coordination awareness (board + ribbon + lens)

  1. Open the Permits panel (P). You see all permits on the site (review band), grouped into Live / Approved — tomorrow / In review / Expired — unclosed / My drafts & corrections.
  2. Use the board toolbar Type / Contractor / Day filter dropdowns to narrow the view (these narrow both the stack and the ribbon).
  3. Toggle Stack ⇄ Timeline (the view-columns / calendar-days toggle). Timeline shows scheduled permits as bars across today + tomorrow (06:00–22:00), per-contractor rows, a NOW line, and clash glyphs.
  4. Click any permit row (stack) or bar (ribbon) to cast the lens — its linked entities glow on the live map, the rest dims, bounds fit. This is how you read a permit's footprint and conflicts in map context (a permit has no pin of its own).

Note: Selecting a permit casts the lens but is non-destructive in time — a plain board select does not move the scrubber. If the scrubber is parked outside the permit's window, the lens can be "lit empty." Use the "N tokens outside this time — jump to window" pill to jump the scrubber to the permit's start. (Only the review workbench moves the scrubber, and it restores your prior position on close.)

Note: The board's clash badges are fetched lazily by a separate call (GET /api/permits/applications/clash-badges) after the rows render — the clash compute runs off the event loop so it can't freeze first-load. A row's badge may appear a beat after the row, and on a cold open of a mature site it can lag the live clashboard. The badge tints by severity (red vs amber), never by permit type.

What you'll be notified about

Trigger You get Where it lands
A non-single_stage permit is submitted (permit_requested) "Permit {number} awaiting endorsement" notification + an endorse_permit Task My Work card "Review & endorse"
(As review band) general in-review activity In-app notifications Notification bell

You receive the stage-1 endorse Task on every two-stage permit submission. You do not receive the stage-2 approve_permit Task (that's HSE/admin), the issue_permit walk-down Task (appointed issuers + receiver), or the close_out_permit Task on expiry (requestor + receiver) — though you can still close those out from the board. One caveat: clicking a permit notification (as opposed to a Task) currently does not open the Permits panel — the in-app notification click-router has no permit case yet, so it logs an "Unhandled" warning and no-ops on navigation. Permit Tasks deep-link correctly; permit notifications are informational only for now, so reach the permit from My Work or the board.

Gotchas

  • Endorse ≠ Approve. You hold permit_approve_stage1 (the coordination check) but not permit_approve_stage2 (the H&S desk). You can never push a permit to approved — only HSE/admin can. This is a deliberate separation-of-duties boundary.
  • Reject and request-corrections are keyed to the current gate. You can reject/request-corrections a permit sitting at the stage-1 (requested) gate. Once it's endorsed (at the HSE desk) or approved (at the walk-down), the acting band is no longer you — it's HSE/admin or the appointed issuer. Both reject and request-corrections require a note/reason, or the call returns 422.
  • Issuing is appointment-gated, not role-gated. Being a coordinator does not let you sign the walk-down. If you need to issue, an HSE/admin must create a PermitAppointment for you (optionally scoped to one permit type). The appointment layers on top of your role.
  • You can't configure the programme. Permit types, authorised-issuer appointments, and SitePermitConfig (coverage policy, close-out gate, nominee/deputy, expiring-warning lead) are all HSE/admin only (permit_settings_manage). The settings gear in the Permits panel header won't show for you.
  • Drafts you can't see don't 404 by accident — that's deliberate. You see every permit in the in-review middle and every site-public (issued/expired/closed) permit, but other users' private drafts are hidden (a not-visible permit returns 404, so existence never leaks). If a contractor says "I raised a permit" and you can't find it, it's almost certainly still a draft they haven't submitted.
  • Imported permits enter at issued, not the chain. They never pass through your endorsement gate — they're inserted live with no requestor and skip stage-1/stage-2 entirely. Don't expect to endorse an imported permit; only in-app applications walk the chain from draft.

For HSE / stage-2 reviewer & permit-programme owner

Your role

You are the H&S desk review and the owner of the permit programme on this site. Two jobs sit on your shoulders. First, you are the stage-2 "Approval" gate — the final desk decision (usually the evening before work goes live) that turns an endorsed permit into an approved one, or for single-stage types takes a requested permit straight to approved. Second, you own the whole permit subsystem's configuration: the permit-type catalogue (and the codes like HW/CSE/EXC that the clash rules match on), the authorised-issuer appointments, and the two site policies (coverage badge + close-out gate). You also decide extension requests. You sit in both review bands, so you have full stage-1 cover as well — you can endorse a permit if the coordinator is unavailable. What you deliberately don't hold is contractor_manage, so the legacy XLSX permit import is not yours, and issuing a permit at the workface is gated on an appointment, not on your role.

What you can and can't do

You can
  • Approve (endorsed → approved, or requested → approved for single-stage types) — your stage-2 desk review (permit_approve_stage2).
  • Endorse at stage 1 as well — you also hold permit_approve_stage1, so you have full stage-1 cover.
  • Reject or request corrections at the stage-2 gate (a note/reason is required).
  • Decide extension requests — approve (moves end_at in place, records an extend_approve signature, re-arms the expiry warning) or reject (a reason is required); permit_approve_stage2.
  • Own the PermitType catalogue — create, edit, and retire (active=false) permit types, including their codes (permit_settings_manage).
  • Appoint / revoke authorised issuersPermitAppointment, optionally scoped to one type (permit_settings_manage).
  • Edit SitePermitConfig — coverage policy, close-out gate, nominee/deputy, expiring-warning lead hours (permit_settings_manage).
  • See every permit on the site (review band, cross-contractor) and the board clash badges.
  • Raise permits yourself (you also hold permit_request) and close out issued/expired permits (you hold permit_approve_stage1, the coordinator-band close-out path).
You can't
  • Issue at the walk-down unless you have been separately appointed. Issuing is gated on a PermitAppointment, never on your role — even though you are the one who creates appointments. Appointing yourself is a deliberate, explicit step.
  • Run the legacy XLSX permit import (upload / re-resolve / reconcile-counts). That needs contractor_manage, which is a coordinator+ permission you don't hold.
  • Endorse a single-stage permit at stage 1 — single-stage types skip endorsement entirely; there is no stage-1 action on them. You approve them directly from requested.
  • Edit or withdraw another user's draft — the requestor-ownership gate (_assert_owner) still applies to you for someone else's draft.
  • Countersign unless you happen to be the named receiver — countersign is identity-gated to permit.receiver_id.

Note: Stage-2 is the separation-of-duties boundary. Coordinators run stage-1 (the coordination check) but cannot approve — only you (HSE) and admin hold permit_approve_stage2. This is by design: the coordination check and the H&S desk decision are deliberately different hands.

Your tasks

1. Approve (or reject / request corrections) a permit at stage 2

This is your core gate. You normally arrive at it from My Work.

  1. Open the dashboard. A permit awaiting your approval shows up in the My Work dock panel (the Tasks tab) as an intent-named card labelled "Review & approve" (the underlying Task action_kind is approve_permit). Click it.
  2. The system opens the Permits dock panel (keyboard shortcut P) and the review workbench for that permit (openReview). The review workbench is a three-pane layout: a read-only application pane on the left, the live map behind the panel as the middle "pane" (the permit has no pin — its linked workers/plant/areas glow via the map lens while everything else dims), and a Conflicts list on the right ranked red-first.
  3. The system snaps the timeline scrubber to the permit's start_at so the permit's linked tokens are actually visible, and fetches the clash superset for the permit's window. (It restores your prior scrubber position when you close the workbench.)
  4. Read the application pane (type, contractor, window, pre-checks, RAMS reference, controls) and the Conflicts list. Add any conditions in the "Added controls" textarea — these become stage2_controls on the conditions of issue.
  5. In the decision bar, choose one:
    • ApprovePOST .../approve. The permit moves to approved ("approved for tomorrow, not yet live"). The approve_permit Task auto-resolves. A permit_approved notification + email goes to the appointed issuers + receiver, and an issue_permit walk-down Task is raised for them.
    • RejectPOST .../reject. A reason is required (the button stays disabled until you type one). The permit goes to rejected (terminal); requestor + receiver are notified with your reason; all open gate Tasks on the permit close.
    • Request correctionsPOST .../request-corrections. A note is required. The permit bounces to corrections_requested (editable by the requestor like a draft); on their resubmit the chain restarts at requested.

Note: For single-stage permit types the chain skips endorsement — you receive an approve_permit Task directly on submission (requested), and your approve takes it straight requested → approved. A coordinator has no gate action on a single-stage permit.

Note: Approval is not go-live. After you approve, the permit spends the night in approved. It only becomes issued (live) after the appointed issuer signs the walk-down and the named receiver countersigns. Don't expect work to be authorised just because you approved it.

2. Acknowledge a red clash before approving

Clashes never hard-block a permit decision — they are advisory + acknowledge-red.

  1. In the review workbench's Conflicts list, each conflict shows a RED or AMBER badge, the other party's label, the reason, and the rule evidence (e.g. distance). They are ranked red-first.
  2. The decision bar's framing line tells you what's demanded, e.g. "2 red — acknowledgement required / 1 amber — advisory". Amber merely informs. A red conflict gates Approve.
  3. When there is ≥1 red conflict, a bordered acknowledgement box appears: a checkbox "Approve despite N red conflict(s)" plus a reason input, with the note "The reason below is captured into the approval note."
  4. Tick the checkbox and type a reason. Only then does Approve enable (canApprove = red count is 0, OR red acknowledged AND reason non-empty). Your reason is folded into the permanent approval note as "Approved despite N red conflicts: {reason}".

Note: Permit-conflict clash rules (e.g. Hot Works near Confined Space Entry) deliberately ignore the same-contractor exemption — they fire as RED even when one contractor owns both sides, because an incompatible-permit hazard is physical and a single contractor can't self-coordinate it away. So a red you must acknowledge can come from one subbie's own two crews.

3. Decide an extension request

An extension stretches a live permit's end_at in place — no re-issue.

  1. Extension requests are not Tasks (unlike the gates). You learn of one via a permit_extension_requested notification + email (stage-2 band audience). Open the permit's detail view in the Permits panel (board → click the row).
  2. The pending extension shows in the Extensions section projected into the detail body, with a pending status pill, the requested new end, and the requestor's reason.
  3. Choose:
    • ApprovePOST /api/permits/extensions/{request_id}/approve. The system moves permit.end_at to the requested time in place (status stays issued), records an extend_approve signature, and re-arms expiring_warned_at so the "expiring soon" warning fires again against the new end. The requestor + receiver are notified.
    • RejectPOST .../reject. A note/reason is required. The window is left untouched.

Note: Only issued permits whose type is extendable can be extended (UK default: only General). High-risk types (HW/CSE/WAH/EXC/ELE) are not extendable — a spent or expired one must be reissued (a fresh draft clone walking the whole chain, including a fresh walk-down), not extended. There is no extension affordance on those.

Note: A child permit can never outlive its parent. At extension-approval time the system re-checks that the child's new window still sits inside the parent's; an approval that would breach that is refused.

4. Configure the permit programme (Types / Authorised issuers / Policy)

You own all three settings tabs. They are HSE/admin only.

  1. In the Permits dock panel header, click the gear (cog icon, title "Permit settings", data-test-id permit-settings-gear). It is only visible to HSE/admin. This opens the "Permit settings" modal, which renders at the dashboard root (not inside the panel) and has three tabs: Types, Authorised issuers, Policy. It always opens on Types.

Types tab — the permitted-activity catalogue: 2. Each type has a name, a code (max 16 chars, unique per site — e.g. HW, CSE, EXC), pre-checks, expected_attachments (soft prompts only), and capability flags: requires_parent, is_extendable, single_stage. 3. Create (POST /permits/types) or edit (PATCH /permits/types/{id}) as needed. Retire a type by setting active=false — there is no hard delete.

Note: The type code is exactly what the clash engine matches on (permit_type_in_a: [HW] etc.). If you add a new permit type that should clash with others, you must also author a matching clash rule — the type alone does nothing in the clash picture. Codes are case-sensitive: a rule keyed on HW won't match a type coded hw.

Note: requires_powra exists on the type but is a reserved V2 hook — unenforced in v1. Setting it does nothing yet. expected_attachments are soft UI prompts; nothing enforces an actual upload.

Authorised issuers tab — who may sign the walk-down: 4. Add an appointment (POST /permits/appointments) naming a user and optionally a permit type (leave the type blank = appointed for all types). Revoke with DELETE /permits/appointments/{id} — revoked rows are kept for audit.

Note: Issuing keys on the appointment, not on role. A coordinator, HSE, admin — or even yourself — cannot issue at the walk-down without an active appointment. If you need to issue, appoint yourself here explicitly.

Policy tab — the two site policies: 5. The Policy tab (permit-settings-policy-tab) edits SitePermitConfig as a full-state PUT. Set:

  • Coverage policy (permit-policy-coverage) — when on, surfaces a "No permit" advisory badge on active entities with no covering permit. Advisory only; never blocks placement.
  • Close-out gate (permit-policy-close-out-gate) — when on, a contractor with expired-unclosed permits cannot have new permits issued until those are closed out. This is the one hard stop in the whole subsystem.
  • Nominee / Deputy nominee (permit-policy-nominee / -deputy) — added to the permit_endorsed notification audience.
  • Expiring-warning lead (permit-policy-lead-hours) — hours (1–720, default 24) before a live permit's end that the "expiring soon" warning fires.
  1. Click "Save policy" (permit-policy-save). On success the persisted config replaces local state; a 403/4xx surfaces in a red error banner (permit-policy-error).

Note: PUT is full-state replace, not partial — the UI always loads-then-submits the whole object. If you script this against the API directly you must send all five fields, or omitted ones reset to their schema default (e.g. coverage_policy_enabled back to false).

Note: A brand-new site has no SitePermitConfig row — both policies behave as off until you save the Policy tab once. Don't go looking for the row; it materialises on first save.

Note: The close-out gate fires only at issue time (go-live), and only for expired unclosed permits belonging to the same contractor. Approvals still proceed. Coordinator-raised permits with no contractor are never gated.

5. Endorse at stage 1 (cover for the coordinator)

Because you also hold permit_approve_stage1, you can run the stage-1 gate.

  1. A requested (non-single-stage) permit awaiting endorsement appears either as an endorse_permit Task ("Review & endorse") if you are in the stage-1 audience, or you can open it directly from the board.
  2. In the review workbench, add any stage1_controls + note and choose Endorse (POST .../endorse, requested → endorsed), Reject, or Request corrections.

Note: There is no stage-1 action on a single-stage type — those skip endorsement, so an attempt to endorse one is rejected ("not awaiting stage-1 endorsement"). For those you approve directly.

6. Close out a completed or expired permit

You can close out via the coordinator-band path you inherit.

  1. From the permit's detail footer (or the board), trigger close-out — POST /api/permits/applications/{id}/close-out. The system shows the fixed 3-item safety checklist (work complete & plant removed / area made safe & checked / fire watch held). Tick all three to enable "Sign & close out".
  2. The permit moves issued/expiredclosed and the PDF is re-snapshotted.

Note: Close-out is blocked while the permit has open child permits (children not in {closed, rejected, withdrawn}). The error names the blocking child numbers — close those first.

Note: expiredclosed. The scheduler only flips an overdue issued permit to expired (an alarm); it never closes a permit itself. An expired permit still needs your (or an issuer's) close-out sign-off, and — if the close-out gate is on — it blocks the contractor's next issue until you do.

What you'll be notified about

Event You get Why
Permit submitted (requested, non-single-stage) endorse_permit Task ("Review & endorse") You're in the stage-1 band too
Permit submitted (single-stage) approve_permit Task ("Review & approve") Single-stage skips endorsement
Permit endorsed (permit_endorsed) In-app notification + email + approve_permit Task Your stage-2 desk review is due
Extension requested (permit_extension_requested) In-app notification + email You decide extensions (not a Task)
Permit expired unclosed (permit_expired) In-app notification + email (role hse audience) A live permit overran without close-out

The board also shows an advisory amber "Expiring soon" badge on any issued permit within the configured lead window — the visual mirror of the same warning sweep.

Note: Clicking a permit notification does not (yet) open the Permits panel — the notification click-router has no permit case, so it logs an "Unhandled" warning and no-ops on navigation. Permit Tasks in My Work do deep-link correctly. So treat permit notifications as informational and navigate to the permit via the Permits panel (P) or the My Work card.

Note: The expiry warning fires exactly once per permit (an expiring_warned_at stamp), even though the lead-window condition stays true on every scheduler tick until expiry. Approving an extension re-arms that stamp so you'll be warned again against the new end.

Gotchas

Note: You can do almost everything in the lifecycle — but issuing is the one act your role alone can't reach. It requires a PermitAppointment, with no role shortcut and no superadmin shortcut. If you'll be signing walk-downs, appoint yourself on the Authorised issuers tab.

Note: Permit visibility is workflow-scoped, but as a review-band member you see all permits on the site (every contractor, including the in-review middle) — except other users' private drafts. A permit you genuinely can't see returns 404, not 403, so existence never leaks. Don't assume a "missing" permit is a bug; it may simply be someone's unsubmitted draft.

Note: requested is the post-submit state — there is no state called "submitted", "pending", "active", or "under review". The live/active state is issued. Use the real names: draft → requested → endorsed → approved → issued → closed, with expired as an alarm and rejected/corrections_requested/withdrawn as off-ramps.

Note: Reject and request-corrections are keyed to the current gate's band, not a fixed role. At endorsed (or requested for single-stage) that's you (stage-2); at requested for a two-stage type it's the coordinator; at the approved walk-down it's the appointed issuer. So whether you can refuse a given permit depends on which gate it's sitting at.

Note: The board's clash badges are fetched lazily by a separate endpoint (GET /api/permits/applications/clash-badges) after the list renders, computed off the event loop. A badge may appear a beat after its row, and on a deadline overrun it can be briefly absent while the headcount stays. Don't read "no badge yet" as "no clashes" — open the review workbench for the authoritative, live conflict list.


Permits guide: Overview · Applying · Reviewing & approving · On-site signing · Configuring · Reference

Generated from source · SimOops living system reference