PMS Complete Workflow Map
Architecture & Relationship Report
Generated: {{ $generatedAt }}
Source: codebase audit (facts from source only)
Project: {{ $projectRoot }}
Executive summary
PMS does not have one workflow engine. Multiple parallel mechanisms touch the same pc_forms row:
Draft, Forward, Legacy counter, Meeting (MWS), MoM co-signatory, Approval surface, Revision, Recovery, and PCFMS Import.
Runtime authority is determined by PcForm::usesForwardWorkflow() plus meeting-link state — not by a single pipeline.
Table of contents
- Master relationship diagram
- Engine selection (runtime router)
- Per-workflow map (0–9)
- Coexistence on one row
- Conflict & bypass patterns
- Determination answers
- Merge recommendations
- Interaction matrix
1. Master relationship diagram
Conceptual stack (workflows are parallel engines, not a strict pipeline):
PC (entity: pc_forms / PcForm)
|
+--------------------+--------------------+
| | |
DRAFT IMPORT (PCFMS) REVISION flags
| | |
v v |
submit PC-I/II lands row begin workspace
| (incomplete) |
v | v
FORWARD WORKFLOW <---------+---------------- submit
(current_actor_id, |
workflow_stage, |
pc_form_forwards) |
| |
| markMeetingLinked |
v |
MEETING WORKFLOW (MWS) |
| |
v |
CO-SIGNATORY (MoM chain) |
| MoM complete |
v |
APPROVAL SURFACE ----------+---- LEGACY COUNTER (state + counts + pc_form_reviews)
(approve/reject) syncLegacyState mirrors state
|
v
APPROVED -> REVISION WORKFLOW (new row / chain)
|
RECOVERY (admin side-channel patches columns anytime)
2. Engine selection (authoritative runtime router)
PcForm loaded
|
+-- PC-I / PC-II? --NO--> uses_forward_workflow column? --YES--> Forward engine
| |
| NO--> Legacy engine
|
+-- YES: status=true AND state NOT IN (draft, rejected, approved)?
|
YES --> Forward engine (PcFormWorkflowService)
|
NO --> Legacy engine (PcFormApprovalCountService)
Forward engine + hasLinkedMeeting() OR stage=meeting_linked
--> forward/decline BLOCKED
--> approve() allowed WITH meeting context (mws_meeting_id)
Forward engine + approve() WITHOUT meeting
--> Exception: "Use Forward" (PcFormService:11693-11697)
Proven selector: PcForm::usesForwardWorkflow() — app/Models/PC/PcForm.php:496-508
3. Per-workflow map
0. PC (entity — not an engine)
| Entry | POST pc-forms → PcFormsController::store() → PcFormService::create() |
| Exit | Terminal state=approved/rejected; inactive status=0 |
| Services | PcFormService |
| Tables | pc_forms + child section tables |
| Controllers | PcFormsController |
| Models | PcForm |
| Ownership | created_by; forward adds current_actor_id; legacy uses permission pools |
| Permissions | pc-forms → form key "pc 1" (config/permission.php) |
1. Draft Workflow
| Entry | GET pc-forms/create; POST draft-autosave; EAED DraftController API |
| Exit | Submit (save_as_draft=false) → PcForm state=requested or forward init |
| Services | PcFormService, EaedDraftService |
| Tables | pc_forms_drafts, pc_form_draft_*, pc_forms (state=draft) |
| Controllers | PcFormsController, DraftController |
| Models | PcFormDraft, PcForm |
| Ownership | created_by; DataVisibilityHelper for delete |
| Permissions | pc-forms add/edit on autosave routes |
| Interactions | PC-I/II submit → finalizePcFormForwardSubmit(); revision workspace uses post_approval_revision_workspace |
2. Forward Workflow
| Entry | initializeOnSubmit(); POST forward/decline; GET pc-forms/{id}/show (lazy reconcile); CLI pc-forms:reconcile-actors |
| Exit | Terminal workflow_stage=approved/declined; markMeetingLinked() → meeting engine |
| Services | PcFormWorkflowService, PcFormForwardActorResolver, PcFormWorkflowRecipientResolver, PcFormWorkflowStageResolver, PcFormActorReconciliationService |
| Tables | pc_forms (workflow_stage, current_actor_id, uses_forward_workflow); pc_form_forwards |
| Controllers | PcFormsController: forward, decline, forwardRecipients, workflowActions |
| Models | PcForm, PcFormForward |
| Ownership | current_actor_id; currentUserMayAct() requires actor match (PcFormWorkflowService:394-421) |
| Permissions | EnsurePermissionForRoute actor check + can(pc-forms,view); PcFormWorkflowAction buckets |
| Interactions | syncLegacyState() mirrors state; blocks approve() without meeting; post-meeting uses legacy approve |
3. Legacy Counter Workflow
| Entry | Submit sets counters; POST approve/reject; PcFormService::show() auto-advance; CLI pc:sync-workflow-counts |
| Exit | state=approved when approve_count >= required_approvals; state=rejected on reject |
| Services | PcFormApprovalCountService, PcFormService::approve/reject |
| Tables | pc_forms (counts, state); pc_form_reviews; pc_form_department_approvals |
| Controllers | PcFormsController; MeetingWorkspaceMeetingController::approveLinkedPcForm |
| Models | PcForm, PcFormReview |
| Ownership | Permission pool by stage (checker → dept → approver); no current_actor_id |
| Permissions | can_department_approve, can_approve (service-layer; routes not in permission map) |
| Interactions | Skipped when usesForwardWorkflow(); forward reject → decline() |
4. Meeting Workflow (MWS)
| Entry | meeting-workspace/meetings/* → MeetingWorkspaceMeetingController; markMeetingLinked() |
| Exit | Meeting Completed/Cancelled; PC decision via approve/defer |
| Services | MeetingWorkspaceMeetingService, MwsPcFormMeetingDecisionService, MwsAdviseLetterService |
| Tables | mws_meetings, mws_meeting_subject_links, mws_participants, pc_form_reviews (mws_meeting_id) |
| Controllers | MeetingWorkspaceMeetingController, MeetingWorkspacePlannerController |
| Models | MwsMeeting, MwsMeetingSubjectLink, MwsMeetingSchemeMom |
| Ownership | MwsMeeting::userMayAccessMeeting(); decision gates in MwsPcFormMeetingDecisionService |
| Permissions | meeting_workspace list/view/add/edit; MoM flags |
| Interactions | Locks pre-meeting forward; approve in meeting calls PcFormService::approve() with meeting flags |
5. Co-signatory Workflow (MoM chain)
| Entry | POST .../mom (storeMom); POST .../mom/co-signer-sign |
| Exit | mom_co_signatory_status=complete or returned |
| Services | MwsSchemeMomCoSignatoryWorkflowService, MwsSchemeMomService |
| Tables | mws_meeting_scheme_moms, mws_meeting_scheme_mom_actions |
| Controllers | MeetingWorkspaceMeetingController::storeMom, signMomCoSignatory |
| Models | MwsMeetingSchemeMom |
| Ownership | pending_user_id in JSON; momUserIsActiveCoSignerTurn() |
| Permissions | meeting_workspace edit/mom flags; co-sign route uses runtime turn checks |
| Interactions | Blocks PC meeting approve until MoM signatures complete; separate from pc_form_forwards |
6. Approval Workflow (action surface, not separate engine)
| Entry | POST pc-forms/approve/reject; meeting linked-pc-form/approve; WorkflowBypassService; PcFormAaAuthLetterService |
| Exit | state=approved + BpmsWebhookService::notifyAfterPcApproval() |
| Services | PcFormService::approve/reject, MwsPcFormMeetingDecisionService, WorkflowBypassService |
| Tables | pc_forms, pc_form_reviews |
| Controllers | PcFormsController, MeetingWorkspaceMeetingController |
| Models | PcForm, PcFormReview |
| Ownership | Legacy pools; forward actor until meeting; bypass sets terminal directly |
| Permissions | can_department_approve, can_approve; meeting_workspace view + service gates |
| Interactions | Forward: approve throws without meeting; forward reject → decline() |
7. Revision Workflow
| Entry | GET begin-approved-revision; shouldCreateRevisionOnSubmit() on resubmit |
| Exit | Submit creates new active row; workspace shell purged |
| Services | PcFormService::spawnPostApprovalRevisionWorkspace, createPcFormRevisionFromSubmit |
| Tables | pc_forms: is_revised, parent_id, post_approval_revision_workspace, status |
| Controllers | PcFormsController::beginApprovedRevision |
| Models | PcForm |
| Ownership | Workspace: created_by + can(edit); one active row per chain (status=1) |
| Permissions | can(add) to begin; can(edit) to submit workspace |
| Interactions | Inactive revisions excluded from forward; import sets is_revised/parent_id |
8. Recovery Workflow (two admin modules)
| Entry | admin-controls/workflow-recovery/* (Recovery Center); admin-controls/workflow-rescue/* (Rescue Center) |
| Exit | workflow_recovery_logs / workflow_rescue_logs; rollback restores snapshot fields |
| Services | WorkflowRecoveryApplyService (write, NO syncLegacyState); WorkflowRecoveryService (Rescue, some actions sync) |
| Tables | workflow_recovery_logs, workflow_rescue_logs, pc_forms, mws_meeting_subject_links |
| Controllers | WorkflowRecoveryController, WorkflowRescueController |
| Models | WorkflowRecoveryLog, WorkflowRescueLog |
| Ownership | Patches workflow_stage, current_actor_id, current_allowed_actions, uses_forward_workflow |
| Permissions | legacy_pc_recovery / workflow_rescue + Super Admin |
| Interactions | Recovery Center skips syncLegacyState (proven drift); Rescue safety gate blocks overwrite with forward history |
9. Import Workflow (PCFMS)
| Entry | CLI pc:migrate-pcfms → PcfmsPcMigrationService::migrate() |
| Exit | pcfms_import_maps row; stats returned |
| Services | PcfmsPcMigrationService, PcfmsSchemeResolver |
| Tables | pc_forms, pcfms_import_maps |
| Controllers | None (CLI only) |
| Models | PcForm, PcfmsImportMap |
| Ownership | Does NOT set current_actor_id, created_by, workflow_stage, uses_forward_workflow |
| Permissions | None (CLI) |
| Interactions | Sets state, status, counters=1, is_revised, parent_id; no pc_form_reviews or pc_form_forwards |
4. Coexistence on one pc_forms row
Single pc_forms row simultaneously holds:
state ...................... Legacy engine (PcFormApprovalCountService)
check_count / approve_count .. Legacy progress
workflow_stage ............... Forward engine (PcFormWorkflowService)
current_actor_id ............. Forward owner
status / is_revised / parent_id ... Revision chain
post_approval_revision_workspace .. Revision prep shell
meeting_linked_at ............ Meeting lock
Forward + Legacy BOTH active for PC-I/II in forward mode:
Forward owns actor actions; syncLegacyState() mirrors state on each forward step.
MoM co-sign owns mws_meeting_scheme_moms (separate table) but gates PC meeting approve.
5. syncLegacyState mapping (proven)
| workflow_stage | Synced state |
| approved | approved |
| declined | rejected |
| ad_review, ad_approve | requested |
| pnd_*, meeting_linked | under approval |
| ddwp_department_handoff | department_approval_pending |
Source: PcFormWorkflowStageResolver::syncLegacyState() — app/Services/PC/PcForm/PcFormWorkflowStageResolver.php:67-82
6. Proven conflicts
| Conflict | Evidence |
| state vs workflow_stage drift | WorkflowRecoveryApplyService does not call syncLegacyState (comment line 23) |
| Import gap | state=requested but no current_actor_id / no pc_form_forwards |
| Inactive revisions | status=0 still may show state=requested in DB; usesForwardWorkflow() returns false |
| Lazy reconcile path gap | ensureForwardActorAssigned only on GET pc-forms/show; not on forward/edit |
| Approve vs forward | approve() throws on forward forms without meeting (PcFormService:11693-11697) |
7. Proven bypasses
- WorkflowBypassService — admin path to terminal approved
- Workflow Rescue recover actions — assign actor, close_as_approved, mark_active_revised
- ensureForwardActorAssigned — mutates DB on index details modal view
- CLI pc-forms:reconcile-actors — bulk actor assignment
- canBypassPermissions() — Super Admin forward/act without being current_actor_id
- Forward reject on forward form → decline() instead of state=rejected
8. Determination answers
Can more than one workflow own a PC simultaneously?
YES — proven.
One row carries legacy state+counters, forward stage+actor, revision flags, and meeting lock concurrently. Forward mirrors legacy state via syncLegacyState().
Can workflows conflict?
YES — proven.
Recovery Center skips syncLegacyState; import leaves incomplete forward fields; reconcile runs on only one endpoint.
Can one workflow bypass another?
YES — proven.
Admin Recovery/Rescue/Bypass, lazy reconcile, meeting handoff, and canBypassPermissions() all bypass normal engine paths.
Which workflow is authoritative?
| Condition | Authoritative engine |
| usesForwardWorkflow() = false | Legacy counter (state + pc_form_reviews) |
| usesForwardWorkflow() = true, no meeting link | Forward (current_actor_id + pc_form_forwards) |
| Meeting linked | Meeting + legacy approve (PcFormService::approve with mws context) |
| Admin Recovery/Rescue applied | Admin write wins on patched columns (may drift from both engines) |
Which workflows should be merged? (code-evidence only)
| Candidate | Reason |
| Forward + Legacy | Already partially merged via syncLegacyState; dual progression models remain |
| Recovery Center + Rescue Center | Both write same columns; Recovery skips syncLegacyState, Rescue does not |
| Import + Actor reconcile | Import never sets forward fields; three separate fix paths exist |
| Lazy reconcile + CLI reconcile | Same logic, different triggers (modal view vs artisan command) |
| Do NOT merge | MoM co-sign (separate table); Draft (pc_forms_drafts); Revision chain semantics |
9. Interaction matrix
| From → To | Trigger | Service |
| Draft → Forward | PC-I/II submit | finalizePcFormForwardSubmit() |
| Draft → Legacy | PC-III+ submit | PcFormService::create counter defaults |
| Forward → Legacy state | every forward/decline | syncLegacyState() |
| Forward → Meeting | meeting linked | markMeetingLinked() |
| Meeting → Approval | linked PC approve | PcFormService::approve() |
| Co-sign → Approval | MoM incomplete | blocks approveLinkedPcForm |
| Forward → Approval | direct approve | throws unless meeting context |
| Legacy reject → Forward | forward form reject | PcFormWorkflowService::decline() |
| Revision → Forward/Legacy | workspace submit | new row normal submit |
| Import → gap | no actor/stage | requires reconcile or Rescue |
| Recovery → Forward fields | admin save | WorkflowRecoveryApplyService::apply() |
10. Key file index
| Workflow | Primary services |
| PC entity | PcFormService |
| Draft | PcFormService, EaedDraftService |
| Forward | PcFormWorkflowService, PcFormActorReconciliationService |
| Legacy | PcFormApprovalCountService, PcFormService |
| Meeting | MeetingWorkspaceMeetingService, MwsPcFormMeetingDecisionService |
| Co-signatory | MwsSchemeMomCoSignatoryWorkflowService |
| Approval surface | PcFormService::approve/reject |
| Revision | PcFormService revision methods |
| Recovery | WorkflowRecoveryApplyService, WorkflowRecoveryService |
| Import | PcfmsPcMigrationService |
| Admin bypass | WorkflowBypassService |
Parallel stack: PCN forward workflow on project_concept_notes (PcnWorkflowService, pcn_forwards) — separate entity, same pattern.