Consolidated state machines for every entity in oxFlow that has one. Pulls together what’s distributed across the entity specs into a single reference, resolves the provisional state list in foundation/concept-map.md §7, and pins down transitions, triggers, and cascade rules.
Related: foundation/concept-map.md · foundation/roles-permissions.md
1. Entities with state
| Entity | States | Trigger model |
|---|---|---|
| Tender | Active · Submitted · Won · Lost · Archived | Mixed (auto on publish, manual for terminal states) |
| Estimate | In Progress · Reviewed · Submitted · Archived | Mixed (derived from Items for Reviewed, auto on publish, cascade from Tender) |
| Item | Unpriced · Plugged · Priced · Reviewed · Locked | Mostly derived (from Worksheet + plug_rate); Reviewed is manual; Locked cascades from Estimate |
| Price Book | Active · Archived | Auto (scope_end_date expiry) or manual |
| Price Book Adjudication | Draft · Adjudicated | Manual (Transfer step); auto-freeze from Estimate cascade |
| Subcontract Package | (none — derived from latest Adjudication) | Structural |
| Subcontract Package Adjudication | Draft · Adjudicated | Manual (Transfer step); auto-freeze from Estimate cascade |
| Tender Program | pending · success · failed | Auto (parse outcome) |
| Publisher Output | Draft · Published | Manual (Publish action) |
Entities without a state machine: Heading · Worksheet · Resource · Recipe · Variable · Calculation Block · Content Block instance · Worksheet Resource · Worksheet Recipe · Company · User · Unit · Categorization · Code · Reference Rate · Program Task · Rule · Submission Value.
2. Tender
Outermost container. Five states covering the lifecycle from opportunity through outcome.
| State | Meaning | Trigger | Who |
|---|---|---|---|
| Active | Default on creation. Estimates being built | Tender created | Auto |
| Submitted | Estimate has been published to the client; awaiting decision | Publisher Output transitions to Published on a child Estimate | Auto |
| Won | Client accepted. Submitted Estimate is kept; all others archived | Manual transition | Admin + Lead Estimator |
| Lost | Client rejected. All Estimates archived | Manual transition | Admin + Lead Estimator |
| Archived | Withdrawn or not pursued. All Estimates archived | Manual transition | Admin + Lead Estimator |
Transitions:
┌───────────────┐
│ Active │
└──┬─────────┬──┘
│ │
(publish) (withdraw)
│ │
▼ ▼
┌───────────────┐
│ Submitted │
└─┬────┬────┬───┘
│ │ │
(won)(lost)(withdraw)
│ │ │
▼ ▼ ▼
┌─────┐ ┌─────┐ ┌────────┐
│ Won │ │Lost │ │Archived│
└─────┘ └─────┘ └────────┘
(terminal — no further transitions)
Cascade to Estimates:
| Tender → | Effect on child Estimates |
|---|---|
| Won | The one submitted Estimate is retained (stays Submitted); all other Estimates → Archived |
| Lost | All Estimates → Archived |
| Archived | All Estimates → Archived |
Constraints:
- Terminal states (Won / Lost / Archived) are non-reversible via the state machine. Admin recovery only.
- Won requires exactly one Submitted Estimate to exist; otherwise the transition is rejected.
3. Estimate
A priced build-up inside a Tender. Four states.
| State | Meaning | Trigger | Who |
|---|---|---|---|
| In Progress | Default. Editing allowed; Items being built | Estimate created | Auto |
| Reviewed | All Items = Reviewed; ready for submission | Automatic when every Item reaches Reviewed status | Auto (derived) |
| Submitted | Publisher Output has been published; Estimate fully locked | Publisher publishes | Auto (on publish) |
| Archived | No longer active; read-only, recoverable | Tender cascade or manual | Auto / Manual |
Transitions:
┌──────────────┐
│ In Progress │
└──┬────────┬──┘
│ │
(all Items Reviewed) (archive)
│ │
▼ │
┌──────────┐│
│ Reviewed ││
└──┬───────┘│
│ │
(publish) │
│ │
▼ ▼
┌──────────────────┐
│ Submitted │
└────────┬─────────┘
│
(Tender cascade)
│
▼
┌──────────┐
│ Archived │
└──────────┘
Reviewed → In Progress can happen two ways:
- Manual — Lead Estimator reopens for edits
- Automatic — rate-change cascade drops any Item below Reviewed, which un-satisfies the “all Items = Reviewed” condition and reverts the Estimate to In Progress
Submit guard: the Submit action is blocked if any Item has status Unpriced or Plugged. All Items must be Priced, Reviewed, or Locked. Publisher Output preview is always accessible — only the final Submit action runs the gate.
Locked behaviour:
- Submitted → read-only across all children (Items, Worksheets, Commercials, Adjudications)
- Tender cascade moves Estimate → Archived (also read-only)
- Only Admin can unlock (recovery scenario)
4. Item
The priceable unit. Five states; four of them derived, one manual, one cascaded.
| State | Meaning | Trigger |
|---|---|---|
| Unpriced | Empty Worksheet AND no plug_rate set | Derived |
| Plugged | plug_rate set, Worksheet has no cost-contributing children | Derived |
| Priced | Worksheet has cost-contributing Resources, Recipes, or sub-Items | Derived |
| Reviewed | Priced + senior review complete, no blocking anomalies | Manual (Lead Estimator / Admin) |
| Locked | Parent Estimate is Submitted; no edits allowed | Cascade from Estimate |
Transitions:
┌───────────┐
│ Unpriced │
└─────┬─────┘
│ (set plug_rate)
▼
┌───────────┐
│ Plugged │
└─────┬─────┘
│ (add Worksheet Resource / Recipe — clears plug_rate)
▼
┌───────────┐
│ Priced │◄──────────┐
└─────┬─────┘ │
│ │ (rate change cascade
│ (mark reviewed) │ drops back to Priced)
▼ │
┌───────────┐ │
│ Reviewed │───────────┘
└─────┬─────┘
│ (Estimate → Submitted)
▼
┌───────────┐
│ Locked │
└───────────┘
(Admin unlock only can reverse)
Note: Unpriced → Priced is also reachable directly (add Worksheet Resource / Recipe to an empty Worksheet with no plug_rate set).
Derivation rules:
plug_rateset ∧ Worksheet empty → Pluggedplug_ratenull ∧ Worksheet empty → Unpriced- Worksheet has cost-contributing children → Priced (and
plug_rateis auto-cleared — mutual exclusion)
Rate-change cascade: any rate edit on a Worksheet Resource (per-instance / apply-to-Estimate / fork — see rate-edit-mechanics.md) drops affected Items from Reviewed back to Priced. Senior review must be re-done.
Locked cascade:
- Estimate → Submitted drives all Items → Locked
- Estimate → Archived leaves Item state untouched structurally but the Items are read-only via Estimate’s read-only behaviour
5. Price Book
Two states. Auto-archives by date; manual archive also supported.
| State | Meaning | Trigger | Who |
|---|---|---|---|
| Active | Current, in-use rates; available for Worksheet Resource drag-in | Default on creation | — |
| Archived | Superseded or out-of-window; read-only; queryable by Anomaly Review | Auto when scope_end_date < today, or manual | Auto / Lead Estimator / Admin |
Transitions:
Active ──(scope_end_date < today, auto)──▶ Archived
Active ──(manual archive)────────────────▶ Archived
Archived ──(unarchive)───────────────────▶ Active
Auto-archive: evaluated at query time. Archived Price Books don’t appear in the default drag-in picker. Existing Worksheet Resources that reference them continue to hold their snapshots.
Unarchive is infrequent — used when historical rates need to be reactivated for reference.
6. Price Book Adjudication
Two states. Follows the six-step workflow; see price-book-adjudication.md.
| State | Meaning | Trigger | Who |
|---|---|---|---|
| Draft | Assembling scope, importing returns, comparing, normalising | Adjudication created | Estimator · Lead Estimator · Admin |
| Adjudicated | Award applied — winning rates replaced on scoped Resources | Transfer step completed | Same |
Transitions:
Draft ──(Transfer / Award)──▶ Adjudicated
Adjudicated ──(Re-open)─────▶ Draft (new round)
Round semantics: each re-open increments round_number and produces a new round. The scoped Resource set is updated again on award.
Re-open gate: re-open is blocked once the parent Estimate transitions to Submitted (or Won / Lost / Archived). Adjudications are frozen alongside the Estimate.
7. Subcontract Package Adjudication
Same state machine as Price Book Adjudication. See subcontract-package-adjudication.md.
| State | Meaning | Trigger | Who |
|---|---|---|---|
| Draft | Scope set (Package + competing Subcontractors), returns imported | Adjudication created | Estimator · Lead Estimator · Admin |
| Adjudicated | Award applied — system-generated Price Book created/updated; Subcontract Resources written; Worksheet Resources linked | Transfer step | Same |
Transitions:
Draft ──(Transfer / Award)──▶ Adjudicated
Adjudicated ──(Re-open)─────▶ Draft (new round)
Round semantics: each round adjudicates the Package’s current Item membership. The system-generated Price Book persists across rounds (same ID; contents updated in place).
Item-membership gate: Items can be added to or removed from the parent Package only while the current Adjudication round is in Draft. Once Adjudicated, the Item set is frozen until re-opened.
Re-open gate: same as Price Book Adjudication — blocked once the parent Estimate is Submitted.
8. Subcontract Package — derived state
A Subcontract Package has no state attribute of its own. Its effective state is derived from its latest Adjudication:
| Derived state | Condition |
|---|---|
| Draft (package) | Latest Adjudication is Draft |
| Adjudicated (package) | Latest Adjudication is Adjudicated |
Item-membership editability follows directly: editable when Draft, frozen when Adjudicated.
9. Tender Program (parse lifecycle)
Three states reflecting the parse outcome of the uploaded MS Project file. Not a workflow state — a processing state.
| State | Meaning |
|---|---|
| pending | File uploaded; parse not yet complete |
| success | Tasks extracted; Program Tasks available for linking to Items |
| failed | Parse error (corrupt file, unsupported format); flagged in Anomaly Review with error details |
Transitions:
pending ──(parse OK)──▶ success
pending ──(parse err)─▶ failed
failed ──(retry / re-upload — new record)
Re-uploading the .mpp creates a new Tender Program record (immutable history); Program Tasks are reconciled by ms_project_id. Only the latest successful Tender Program is active for linking.
10. Publisher Output
Two states. Only the latest Publisher Output is retained per Estimate.
| State | Meaning | Trigger | Who |
|---|---|---|---|
| Draft | Cover letter, conditions, branding being edited; preview accessible | Publisher Output opened for editing | Admin · Lead Estimator |
| Published | Final artifact generated; parent Estimate transitions to Submitted | Publish action (gated by Submit check) | Admin · Lead Estimator |
Transitions:
Draft ──(Publish, Submit gate)──▶ Published
Published is terminal for that Publisher Output record — republishing replaces it with a new draft that, on Publish, supersedes the prior Output. No version history is retained.
Submit gate: Publish is blocked if any Item in the Estimate is Unpriced or Plugged. Preview is always accessible.
11. Cross-entity cascade summary
Single table showing how state changes ripple across the model.
| Trigger | Cascade |
|---|---|
| Estimate → Reviewed | None (derived state only; no writes elsewhere) |
| Estimate → Submitted | All child Items → Locked; Commercials Rules, Adjudications, Publisher Output frozen |
| Tender → Won | One submitted Estimate kept as Submitted; other Estimates → Archived; kept Estimate promoted to project (outside oxFlow) |
| Tender → Lost | All Estimates → Archived |
| Tender → Archived | All Estimates → Archived |
| Worksheet Resource rate change (per-instance / apply-to-Estimate / fork) | Affected Items: Reviewed → Priced. Estimate may revert Reviewed → In Progress if it was Reviewed |
| Adjudication → Adjudicated (PBA) | Scoped Resource rates replaced in place; Worksheet Resources still carry old snapshots → Anomaly Review flags divergence |
| Adjudication → Adjudicated (SPA) | System-generated Price Book created/updated; Items in Package get new Worksheet Resources; status cascade on Items (Unpriced/Plugged → Priced) |
| Price Book scope_end_date < today | Price Book → Archived; excluded from default drag-in picker; existing Worksheet Resources unaffected (snapshots hold) |
| Worksheet content added to empty Worksheet | Parent Item: Unpriced/Plugged → Priced; plug_rate auto-cleared |
plug_rate set on empty Worksheet | Parent Item: Unpriced → Plugged |