MOWD Phase 1 — Master UI Data Contract (Branch D)
Branch D — Master UI Data Contract (read models only; NO implementation)
Four surfaces. Backend-enforced visibility (Directus permissions + view-level filtering); the UI never sees rows/actions it lacks rights to. No Nuxt code here (Điều 28 — contract only). Must scale to thousands of workflows / 10k+ tasks → all lists are server-paginated, server-sorted, server-filtered; no client-side full scans; traffic-lights computed in SQL/DOT, not the browser.
A. View definitions backing the contract
v_mow_design_workflow and v_mow_design_step (doc 02 §3). Scale note: before exposing to UI at thousands of rows, replace the two correlated subqueries in v_mow_design_workflow with a pre-aggregated join:
LEFT JOIN (SELECT workflow_id, count(*) step_count,
count(*) FILTER (WHERE step_iu_ref IS NOT NULL) bound_step_count
FROM workflow_steps GROUP BY workflow_id) sc ON sc.workflow_id=w.id
A third view v_mow_governance_cockpit (doc 07) backs surface 4.
Surface 1 — MOW Tree / List
- Source:
v_mow_design_workflow(+parent_workflow_id,level,category_idfrom base for tree). - Fields: id, process_code, title, status, owner_gov_code+owner_name, active_design_version, freeze_active, step_count, bound_step_count, design_health(rollup), level, parent.
- Filters: status (active/draft/retired), owner_gov_code, frozen (y/n), category_id, has-unbound-steps (bound<step_count), free-text on title/process_code.
- Sorting: process_code, title, status, bound_step_count/step_count ratio, date_updated. Default: status then process_code.
- Traffic-light: 🟢 owner set + version set + 100% steps bound + not frozen-for-error · 🟡 partial binding or no active_design_version · 🔴 no owner_gov_code OR validation failures OR dangling refs.
- Drill-down: row → Surface 2 (detail) for that workflow_id; tree expand via parent_workflow_id.
- Actions → DOT: Open=
dot_mow_design_get· Validate=dot_mow_design_validate· Freeze=dot_mow_design_freeze· Unfreeze=dot_mow_design_unfreeze. - Permission: read = any MOW-scoped role; Freeze/Unfreeze = MOW owner role; mutating actions hidden for read-only users.
- States: empty="No workflows match"; loading=skeleton rows; error=banner + retry, never partial silent list.
Surface 2 — MOW Design Registry List / Detail
- Source: header
v_mow_design_workflow; stepsv_mow_design_step; relations fromworkflow_step_relations(incl.condition_iu_ref); change history viadot_mow_design_audit. - Fields (detail): header (as Surface 1) + per-step: step_key, step_type, actor_type, title, step_iu_ref, guide_iu_ref, dot_ref, output_table_ref, event_domain_ref/event_type_ref, step_version, is_iu_bound; relation list (from→to, relation_type, condition_expression, condition_iu_ref, label).
- Filters (steps): step_type, actor_type, is_iu_bound, has-dot-ref, has-event-ref.
- Sorting: sort_order (DAG order), step_type.
- Traffic-light (per step): 🟢 bound (step_iu_ref set) + dot_ref resolves + (event refs resolve or N/A) · 🟡 inline-only / not yet bound · 🔴 dangling ref (dot_ref/iu_ref/event not found).
- Drill-down: step_iu_ref → IU viewer; dot_ref → DOT catalog entry; output_table_ref → table_registry; render DAG via
dot_mow_design_render_tree. - Actions → DOT: Bind IU=
dot_mow_design_bind_step_iu· Bind DOT=dot_mow_design_bind_dot· Bind Event=dot_mow_design_bind_event· Bind design IU (header)=dot_mow_design_bind_iu· Propose change=dot_mow_design_propose_change· Activate=dot_mow_design_activate(council) · Rollback=dot_mow_design_rollback(council). - Permission: bind = MOW owner; activate/rollback = council only (button disabled+explained for non-council); proposing allowed for any author incl. agent (queued, never auto-applied).
- States: empty steps="Design not yet decomposed"; activate disabled until validation 🟢 + approval present; error per-action toast with the failing check.
Surface 3 — MOT / JFT Task surface
- Source:
tasks(10 rows live) joined to owning workflow viaworkflow_steps.task_id/workflows.task_id; read-only in Phase 1 (MOT runtime is out of scope). Designed for 10k+ tasks → server pagination + indexed filters mandatory. - Fields: task id, title/content summary, linked workflow (process_code/title), linked step (step_key), status, actor_type, date_updated.
- Filters: workflow, status, actor_type, assignee (when assignee_policy lands Phase 2), date range.
- Sorting: date_updated desc, status. Server-side keyset pagination (id/date) — never OFFSET at 10k+.
- Traffic-light: 🟢 task linked to a bound step · 🟡 linked to inline step · 🔴 orphan task (no workflow/step link).
- Drill-down: task → its workflow (Surface 2) → step.
- Actions → DOT: Phase 1 = read/inspect only; "Open design" → Surface 2. (No task runtime DOT — doc 08.)
- Permission: read = task-scoped role; no mutate buttons in Phase 1.
- States: empty="No tasks"; loading=paged skeleton; error=retry; large-result guard="showing first N, refine filters".
Surface 4 — Governance Cockpit
- Source:
v_mow_governance_cockpit(doc 07) overapproval_requests(211),apr_approvals(42),workflow_change_requests(3),governance_registry(9). - Fields: pending proposals (entity, request_type, author, age), approvals needed (cross-sign count vs ≥2), recent activations/rollbacks, frozen workflows, validation-failing workflows, gate status (
fn_iu_gate_verify_closed). - Filters: request_type, status (draft/approved/applied/rejected/expired), entity_type, owner_gov_code, age bucket.
- Sorting: age desc, priority.
- Traffic-light: 🟢 no pending > SLA, gates all_safe · 🟡 proposals awaiting review · 🔴 stale proposal past SLA OR a gate not safe OR an activation attempted without ≥2 signatures.
- Drill-down: proposal → target workflow (Surface 2) + its
dsl_diff; approval → signers. - Actions → DOT: Approve/Reject = human/council only, writes
apr_approvals(NOT a DOT the agent can call); Withdraw proposal=dot_mow_design_propose_change(status=withdrawn); View gate=dot_iu_gate_verify_closed. - Permission: approve/reject visible only to council; agent sees read-only; backend rejects any agent decision row (Điều 32 + automated-agent CHECK from prior G3 work).
- States: empty="No pending governance items"; error=banner; never hide a 🔴 item.
B. Cross-cutting contract rules
- Backend-only visibility enforcement: every list is a DB view + Directus permission policy; the API filters by role before serialization. The UI requests a page; it cannot enumerate beyond its policy. Action buttons are rendered from a server-provided capability list per row, not hardcoded.
- Stale-data control: every payload carries
as_of(query time) + per-rowdate_updated; cockpit shows gatechecked_at. UI must re-fetch on action completion (optimistic updates forbidden for governance). - Scale: keyset pagination, server sort/filter, pre-aggregated counts, indexed filter columns (
workflows.status,workflow_steps.workflow_id,taskslink cols,approval_requests.status). Hard page cap (e.g. 100) with "refine filters" guard. - No write path outside DOTs: the UI never issues raw SQL/Directus item writes to design columns; every mutation goes through a named DOT (doc 03), which enforces gate/owner/approval.
C. UI data contract verdict
Concrete. 4 surfaces fully specified with source view/function, fields, filters, sort, traffic-light rule, drill-down, action→DOT mapping, permission, backend enforcement, and empty/error/loading states; scale strategy defined for thousands of workflows / 10k+ tasks. No implementation performed.