← Guide

Blockers & delays

What a blocker is (and what it isn't), the suggest→confirm triage model, who can do what, the intakes that raise one, how lost time is counted, and where blockers surface.

A Blocker is SimOps' record that a cause stopped or impaired specific work over an actual window of time — the access road the council closed, the scaffold that wasn't struck, the delivery that never came. It is the site's single source of truth for Lost time, so it sits on the commercial axis: the QS and coordination team own it, and it is never created automatically. This page is the orientation for coordinators and QS users who confirm, reject, resolve and read blockers. The how-to pages carry the step-by-step: Reviewing & triaging, Raising a blocker and Snags & work close-out.

In this guide

The family reads as a curriculum — concepts first, then the doing, then the scenes, then the reference.

Concepts

  • This page — blocker vs snag vs delay, the suggest→confirm model, who can do what, and where blockers surface.
  • How lost time is counted — the counting rules in plain words: the two questions, the window, what a count rests on, chains, and the two tiers that never mix.

Doing

Scenes

Reference

  • Blockers reference — the API surface, state table, lost-time derivation and a source map.

Blocker vs snag vs defect vs delay — the words matter

Four nearby concepts are deliberately kept apart, because conflating them is how sites lose track of who owes whom time.

Term What it is Who raises it Feeds lost time?
Blocker A timed work stoppage or impairment — the sole canonical record of "work couldn't proceed, here's the window", however it arose. Anyone on site suggests; the commercial band declares / confirms. Yes — blockers are the only thing Lost time counts.
Snag An own-work caveat — work your own team built that isn't to spec, flagged while recording completion. A snag can never be raised against another trade. The team that built the work, on its completion record (there is no standalone raise button). No — snag clocks are responsiveness metrics only, zero crew-hours.
Defect A contractual quality event — work not in accordance with the Scope, notified between the two contracting parties under NEC4. It lives in the register the contract names (Sypro, on South Clyde) and reaches SimOps as an imported register item. Nobody, in SimOps: it is raised and accepted in the named system, then arrives on the next per-contractor upload (the coordinator band uploads it). No — the register is a quality/contractual record; only a blocker declared alongside it carries hours.
delay The planning concept: a declared work-line dependency that has been contradicted (internally a Dependency violation). A delay is a cause; it doesn't hold a stoppage window itself. Surfaces automatically from the dependency graph; a human then declares the blocker. Only once it creates a blocker.

The rule of thumb: a cross-trade grievance is a blocker, not a snag. A snag is you flagging your own not-quite-finished work; the moment one trade's problem stops another trade, that's a blocker. And a delay creates a blocker through a one-click handoff — the blocker stays the one record that carries the actual stopped-work window, so any surface named "Delays" is really pointing you at blockers.

Defect and snag are disjoint nouns with no promotion path in either direction. A snag is not a small defect and a defect is not a formal snag: they carry different parties, different registers and different legal weight. A snag that turns out to be contractually serious becomes a defect by being raised in the named system — and then arrives here on the next import; SimOps never converts one record into the other, in either direction. If you are hunting the contractual register rather than the wee box, that is Defects — the contractual register. See defect-domain-object.

Note: "Delay" is user-facing copy for a contradicted dependency; the schema and events still say violation. You'll see "delays" on the noticeboard banner and "blocker" everywhere a stoppage is recorded — two ends of the same story, not two competing records. See work-dependencies-and-violation-surfacing.

A raise is a suggestion, never an active blocker

SimOps does not mint an active blocker automatically. Every raise from the field enters as a suggestion and waits for a coordinator to look at it — the same shape as the safety-Observation and incident reviews. Triage is where a coordinator either agrees ("yes, that stopped work — confirm it") or waves it away ("I can clear that in five seconds — reject it").

A blocker runs on two independent axes, which is worth holding in your head:

  • a lifecycleactiveresolvedarchived (shared with every WorkGroup);
  • a triage statesuggested · confirmed · rejected.
   suggest                              confirm
  ────────▶  suggested  ─────────────────────────▶  confirmed  ──▶ resolved ──▶ archived
                  │                               (a live, counted blocker)
                  │  reject
                  ▼
              rejected  (terminal — stays readable, but never counted)

A member's raise is a suggestion. A direct declaration skips the queue and is confirmed at birth only when three things hold together: the declarer holds blocker_triage (so the coordinator band, not the QS); the record is not about another firm's work; and the door actually asked and got an answer to "who is responsible?". A QS's declare, any cross-firm declare and every door with no responsible select — the phone, the re-raise, the delay handoff, the injury feeder — enter suggested like any other raise. A confirmed blocker is live, counts toward lost time, and appears site-wide; a rejected suggestion drops out of every active surface but stays on the record as an audit trail.

Note: There is no "reopen" edge. If a rejected — or a resolved — situation recurs, it's a fresh suggestion with a fresh clock, the same discipline snags and permits use. declared_at and resolved_at are set once and never move.

Who does what — the blocker_* band

Blockers are the commercial axis, so authority tracks it. The backend is always the source of truth; the UI simply hides controls you can't use.

Role View Suggest Declare / edit / set-affected / snag-link Confirm / reject / set-responsible Resolve / close Acknowledge / archive / soft-delete Read lost time
viewer / client
contractor hse ✅⁵
hse **✅**¹ own²
member —³ own²
commercial (QS) **—**⁴
coordinator / site manager / admin

¹ HSE can raise a blocker as a suggestion (blocker_suggest, #1561). Blockers are commercial, so HSE holds none of the commercial write verbs — it cannot declare, confirm, reject, acknowledge or archive, and it cannot read lost time — but it is not a read-only bystander: an HSE user who trips over a stoppage suggests it into the triage queue like any member, and may edit or attach evidence to their own still-suggested raise.

² "Own" resolve = your firm's record, not your own raise. The reach is the firm, not the person: any member of the record's own firm may close it, whoever raised it — the restart is a firm fact, not a personal one, so a colleague's raise and a coordinator's direct declaration are both closable by the firm that lost the time. The declarer-only rule survives in exactly one place: a firm-less record (no losing firm named), where there is no own firm to reach for. Triage state is not a precondition either — the close admits a still-suggested record just as it admits a confirmed one, and refuses only rejected (blocker_not_resolvable); a crew should not wait on a coordinator to record that it is back at work. Closing launders nothing: a closed-but-suggested record's figures stay in the awaiting tier and it remains rejectable.

³ A member's raise enters as a suggestion; a member may still edit their own still-suggested record and attach evidence to it, but declaring, linking a snag cause, and every triage correction happen in the coordinator's hands.

The QS declares, edits, links, resolves, acknowledges and archives — but does not adjudicate triage. Confirm, reject and set-responsible are the coordinator band's (blocker_triage, held by coordinator / site manager / admin, not QS). A suggestion "goes to the coordinator"; the QS is a first-class blocker author with lost_time_view, not a triage gatekeeper. Because birth-confirm rides the triage band, a QS's own declare also enters suggested and waits for the same queue.

contractor_hse — the per-employer H&S representative — is the viewer band plus the incapacitation register's own read (hse_register_view, row-scoped server-side to their own firm's people). On the blocker axis they read and nothing more: no suggest, no declare, no triage, no lost time. They are in the site-wide "blocker declared" audience like every role.

The contested counter-signal is held by nobody in this table. A plain member of the firm named responsible may contest a confirmed record's attribution — a person holding no blocker_edit, no blocker_manage and no blocker_triage at all. It is row-scoped on responsible_contractor_id, not banded, which is exactly why a band gate would keep out the one reader the rule exists for. It is the symmetric partner of the coordinator's acknowledge / dispute, which contests the figure; both are arithmetically inert and neither moves a status. See the reference.

The intakes — how a blocker gets raised

Everything funnels into the same pre-filled confirm modal — the intake only decides whether the record enters suggested or confirmed. Raising a blocker walks each one step by step.

  • Mobile "Raise a blocker" — the phone-first stoppage surface, and a two-tap raise. From the site-home launcher, Raise a blocker asks five things and nothing more: the extent ("Did work stop, or just slow down?"), whose work stopped (only when you are raising for another firm), when it stopped, an optional photo, and the note — "What's in the way?" — which is owed on every raise. Everything a clerk might have to hand — which of your own work today is stopped, the gang size and how many were sent home, the cause, and a "Whose stuff?" tag — sits behind one fold, Add details (optional), shut by default. A raise that never opens the fold is complete and correct: it carries no work refs, no count and a cause of cause not given. It always enters suggested — the phone presents no responsible select, so no band riding it can self-confirm. Coordinator vocabulary (blame, snag links) is deliberately kept off the phone.
  • The same raise with no signal — the phone tries to send live, and only a dead wire saves the whole raise on the device (answers and photos together) and sends it by itself when you are next online. Nothing is retyped or edited in between; a refusal from the server is never queued, and the record is not on anyone's table until it lands.
  • Delay handoff (dependency violation) — when the dependency graph is contradicted, the noticeboard raises an uncollapsible "Delays" banner carrying a one-click, pre-filled declare-blocker handoff. It presents no responsible select either, so it always enters suggested for triage, whoever clicks it. Never an auto-declare — a human always raises the blocker.
  • Desktop raise — the Add menuA then B, the Blocker tile of the Add menu's Report group (Alert · Incident · Observation · Blocker), drawn for every holder of blocker_suggest: a member and a QS reach it, not only the coordination band. B arms the map like its Report-group neighbours — the next click anchors the record and opens the modal there, Escape cancels the arm — so a pinless desk raise is click, then clear, never place-from-nothing. The modal is the phone's cut on a desk: the same five required questions (plus Resumed (blank = still stopped) and the pin line), the same single Add details (optional) fold, no Title field (the title is the description's first line) and a last Reported to us by… fold for a report somebody messaged in. What the submit says is a post-time fork, not a second gate: Raise the blocker for a suggester, Declare blocker for a blocker_edit holder. The Noticeboard's Blockers section carries no launcher of its own — when it is empty it points you at A, then B.
  • Desktop declare from a record — a Declare blocker button on an incident / observation / alert / delivery pin, or turning a red clash into a blocker with "mark clash as blocker". These, and the desk modal's declare arm, are the doors that do ask who is responsible, so they self-confirm — but only for a declarer who holds blocker_triage and only on their own firm's work.
  • Permit-wait doorway — a waiting permit card in the mobile wallet offers "Work held up by this permit? Declare blocker", pre-filling the permit as the cause (#1585). "Waiting on a permit" is not mobile-only: since #1912 every door offers the one shared cause vocabulary, the desk declare included, so a permit-caused blocker can be authored from either.

Note: A blocker carries the same fields whichever way it's raised — a single cause, an actuals window, the gang-and-send-home count pair, a note, an optional photo, and the affected work, which rides the create itself. On the two forms (the phone sheet and the desk modal) the extent is the first thing asked and the note is owed on every raise — those two, and nothing else, hold the submit; the cause and the counts never do. The doors that mint a record with no form (the delay handoff, the injury feeder) leave extent "not declared" rather than making a machine guess between stoppage and disruption. At confirm, every field is coordinator-correctable: the confirm modal is a pre-filled declare form, not a bare "approve" button.

Nothing is routed to a person. A blocker names the firm whose site team reported it — there is no person assignee, no ad hoc task at a named user and no spawned action. What closes the "assignment" is the coordination meeting: the row is on the table and that firm's supervisor is in the room.

How lost time is counted

Note: this section is the one-paragraph version. The full counting rules — the send-home test, what a count rests on, multi-day chains, and the two tiers — live on How lost time is counted, and the everyday cases are walked through on Lost time, scene by scene.

Lost time counts blockers, and only blockers. It is a read-only rollup, derived on demand and never stored, that a coordinator or QS reads on the Commercial surface (I → Lost time), gated on lost_time_view.

The arithmetic is deliberately simple: crew-hours = the blocker's actuals window × the stood-down headcount. The window is work_stopped_at → work_resumed_at; while the blocker is still open, an unresumed window reads as still stopped and its age keeps running against now. An age is not a loss — a stoppage that has not finished counts towards no total, because a total that grew while nobody administered the site and shrank when a record was closed would be its own wrong number. Close the blocker without ever saying when work restarted and it reads as no restart recorded instead: again no window to measure, so no hours. The panel says how many are in each state rather than quietly showing a smaller figure. Two numbers a QS relies on:

  • who lost the time (the loser) — the blocker's own contractor;
  • who's to blame (the responsible party) — the triage-accepted attribution, set at confirm (see below), never provisionally.

A blocker with no stopped time recorded contributes nothing. A suggested blocker does appear — silence is not a decision, so an untriaged record's figures sit in their own awaiting-triage tier beside the confirmed one, rather than vanishing until a coordinator gets round to them. The two tiers are shown side by side and are never fused: the awaiting tier carries the loser axis only (nobody has been named responsible yet), no cause or place breakdown and no money. Only rejected rows leave the rollup — somebody has spoken, and the recourse is a fresh raise, not a hidden row. Snags show up in the same ledger as clocks-only rows: their raised→closed responsiveness time, with zero crew-hours, because a snag that actually stops work does so through a blocker, and the cost shows there.

Blame is recorded at confirm, not at raise

Who's responsible for a stoppage is a commercial claim, so SimOps records it only when the commercial band takes ownership — at a coordinator's confirm, or at a self-confirmed direct declaration — never provisionally off a field suggestion. At confirm, the modal pre-derives a likely responsible party only when the cause is a Dependency violation (it points at the predecessor/upstream trade); every other cause starts blank, and the coordinator accepts, overrides, or clears it. That accepted value — and only that — is what the lost-time rollup attributes by.

A suggested blocker emits no attribution at all — its rows read pending on the responsible axis and sit in the awaiting-triage tier, which has no by-responsible axis to render, so the blame numbers a QS reads never flicker on an unvetted grievance. The "Whose stuff?" tag a field user adds on the phone is exactly that — a hint that feeds nothing automatically; it is never read by the rollup and lands as blame only if a coordinator signs off on it, at which point a responsible_provenance stamp records how the value arrived (typed, the raiser's tag accepted, the machine suggestion accepted, a later correction, or the first-class answer "Nobody — no firm at fault").

Rejecting a suggestion

Reject is a first-class outcome, not a failure. In the confirm modal, Reject… opens a reason box ("Why is this not a blocker?"), and Reject suggestion files it. Rejection:

  • is terminal — there's no path back except a fresh raise, which names what it re-raises through re_raise_of — and also flips the record's lifecycle to resolved so it leaves every active surface;
  • takes a required reason. A reason-less reject is refused (422 blocker_reject_reason_required): the reason rides the rejection notification and the raise this again affordance beside it, and it is what structurally precludes bulk rejection;
  • admits a confirmed record too. Rejecting something already counted is a licensed correction — the shared predicate withdraws its figures at read while the responsible key, its provenance and any contested mark stay in place as history;
  • stays readable to the suggester — a rejected record is audit trail, and "I can clear that in five seconds — close it" is itself a legitimate reject;
  • is withheld from the site at large. An unvetted, possibly cross-trade grievance is not site-wide: a suggested or rejected blocker is read by its raiser, by the commercial band (blocker_manage / blocker_edit), by HSE through hse_register_manage, and by any member of the firm whose work stopped — a verb-less read that runs at every triage status, rejected included. Crucially it is not read by the firm it accuses: the accused-firm arm starts at confirmed, so the subject of an unvetted accusation is never told. Anyone outside those arms gets a plain 404 "not found", never a hint it existed.

Note: The rejection notification carries your reason — the raiser's in-app card reads "Not accepted — ⟨your reason⟩". Write it as the message it is, not just as audit trail. Rejecting a confirmed record additionally tells the firm it had named responsible that the attribution has been withdrawn. A genuinely junk suggestion can instead be soft-deleted (reversibly) by the commercial band — the reject keeps the audit trail; the delete is the mistake path.

Who gets notified

Notifications ride the domain-event pipeline (in-app, and email where SMTP is configured — in production email degrades to in-app). The precise per-event audience is on the reference page; the shape to remember:

  • A new suggestion pings the triage band only (coordinator / site manager / admin) in-app and by email, and mints a confirm_blocker task ("Review suggestion") in their My Tasks.
  • A raise naming another firm's work tells that firm, in-app and by email — a reciprocity duty, not an FYI: only the named firm can say how many of its crew stopped.
  • A confirmed blocker announces itself site-wide in-app, emails the involved firms, and tells the firm it names responsible that it has been named.
  • A rejected record tells its raiser, carrying the reason; where the record was confirmed, it also tells the firm it had blamed that the attribution is withdrawn.
  • A resolved blocker notifies the declarer and involved firms (in-app only), and — where it closed with an unlogged hazard cause — offers the raiser a log the hazard as an observation task.
  • A correction to a confirmed record — window, counts, basis, evidence, extent, cause or the blame key — tells every written row's confirmer, the record's own firm, any firm named responsible, and (on a re-decision) the firm named before.
  • An acknowledged / disputed blocker notifies the declarer.

Note: The confirm_blocker task does not auto-close on confirm, reject or resolve. Closing a record is not adjudication — a closed-but-suggested record still sits in the awaiting-triage tier and stays rejectable — and the queue item is declaration-grain while triage decides per segment, so the task is disposed in-service only when nothing on that declaration is left undecided.

The QS is not pinged on a suggestion or its confirm/reject — triage traffic flows to the coordinator band — but the QS does receive the site-wide "blocker declared" notice on every confirmed blocker, like every role (as does the contractor H&S rep). A retroactive edit is no longer silent: correcting a confirmed blocker's window, counts or blame now pings the people carrying that figure. The genuinely quiet acts are contest, chase and the cause link — a disclosure or a link is not an accusation.

Where blockers surface

Surface What it shows
My Tasks (T) → confirm_blocker task The push channel. A new suggestion mints a Review suggestion task beside your incident approvals. The task is a deep link into the confirm modal; it clears when nothing on that whole declaration is left undecided — not on any one confirm, reject or close.
Commercial (I) → Completions → Blockers tab The ledger home, and the coordination meeting's own table. Ordinarily: a Suggested inbox (each row a pending triage badge + a Review… button that opens the shared confirm modal) above the confirmed Ledger — still-stopped rows floated to the top, knock-on blockers threaded beneath the cause they stem from. In meeting mode: two bands (every open raise however old, then the report day's decisions), grouped by firm, each row expanding in place into the same triage form, with the discussion thread beneath it as the rebuttal. There is no meeting surface and no "meeting held" stamp.
Noticeboard (N) → Blockers section Confirmed records only (active + resolved). The noticeboard is not a suggestion home — it carries no launcher and lists no suggestion; its "N awaiting triage" line is a count and a door into the meeting table. The loud "Delays" banner is a separate item kind above red, and its handoff opens the very same confirm modal.
Phone launcher → Still stopped The close home on a phone, in the My work band beneath Your firm's lost time. The still-stopped cards your membership may close, badged with their count; the card's body holds the knock-on door, the corrections and the evidence. Empty it reads "Nothing of yours is stopped". Gated on blocker_suggest — the band every legal closer holds.
Blocker properties (aux-pin panel) Where the lost-time substance is captured and edited: the actuals window and the stood-down headcount, plus the responsible-party correction and the slim inline triage verbs.
Commercial (I) → Lost time The read-only crew-hours rollup the commercial side reads: two tiers stacked (confirmed, then awaiting triage), the confirmed tier's four axes — by loser, by responsible, by cause, by place — the Still running chase section spanning both, and the two disclosure counts beside the totals. Gated on lost_time_view.

The suggestion inbox and the ledger both read the same live store, so a confirm, reject, or resolve anywhere updates every surface at once.

A blocker can name a Snag as its cause — not-to-spec work one trade left behind that is now stopping another. Crucially, that link is authored from the blocker side, by the commercial band (coordinator + QS), never by the person who logged the snag. A snag never raises a blocker by itself — "if it does, it's a coincidence". The band is the safeguard: linking is a claims instrument, so it lives with the people who own the commercial record, and a site-wide blocker only ever exposes the label of the snag it cites, never a snag body the viewer couldn't already read. The mechanics are on Snags & work close-out.


See also: How activity reaches people · For coordinators. The authoritative terms live in the glossaryBlocker, Snag, Dependency violation and Lost time.

Blockers & lost time guide: Overview · Reviewing & triaging · Raising · How lost time is counted · The ledger · Your firm's panel · Slip & nil returns · Standing cost · The register · Scene by scene · Snags & close-out · Reference

Was this page useful? Report a problem

No cookies and no third-party analytics. A verdict records this page's path — and a search that finds nothing records the words you typed — in this site's own server log.

Generated from source · SimOops living system reference