Branch C — Node / Card Data Contract
Branch C — Node / Card Data Contract
The canonical shape of a single canvas node-card, every field mapped to a backend view/function. Reconciles the v1 CanvasNode object with the MOWD Phase 1 read views (v_mow_design_workflow, v_mow_design_step) and the additive EXTEND columns. Contract only — no implementation.
1. CanvasNode (one card)
{
"node_id": "uuid|int", // identity of the node at its tier
"tier": "T1|T2|T3|T4|T5|T6",
"code": "string", // "WF-001", "NV01", "T03", "PB002"
"title": "string",
"subtitle": "string|null", // owner name / category / specialty
"parent_id": "uuid|int|null",
"order_index": "integer",
"child_count": "integer",
"child_preview": [ {"code","title","status","traffic_light"} ], // max 3
"status": "draft|active|deprecated|retired",
"traffic_light": "green|yellow|red|gray", // rolled up (4-state for nav)
"automation_level": "full_auto|agent_in_loop|human_in_loop|manual|null",
"owner_gov_code": "string|null", // GOV-MOW etc.
"active_version": "integer|null", // active_design_version
"freeze_status": { "active": "bool", "reason": "string|null" },
"health_summary": { /* design_health jsonb: owner_set, version_set, bound_pct, dangling[] */ },
"iu_ref": "uuid|null", // design_iu_ref (wf) or step_iu_ref (step)
"workflow_ref": "uuid|int|null",
"task_ref": "uuid|int|null",
"dot_refs": [ "command_name" ],
"event_refs": [ {"domain","type"} ],
"proposal_count": "integer", // open workflow_change_requests for this node
"permission_actions":[ "view","propose","admin" ],
"last_updated_at": "timestamptz",
"instance_data": null // populated only in runtime/instance mode (Phase 2)
}
2. CanvasContext (the frame state, not a card)
{
"active_tier": "T3",
"breadcrumb": [ {"tier","id","code","title"} ],
"current_node_id":"uuid|int|null",
"view_mode": "template|instance",
"proposal_mode": false,
"instance_id": "uuid|null",
"as_of": "timestamptz" // query time, for stale-data control
}
3. instance_data (Phase 2 runtime mode — same card, extra block)
"instance_data": {
"instance_id": "uuid",
"assignee_avatar": "url|null",
"assignee_name": "string|null",
"sla_due_at": "timestamptz|null",
"sla_traffic_light":"green|yellow|red",
"progress_ratio": 0.0, // 0.0 → 1.0
"state": "not_started|ready|in_progress|waiting|blocked|overdue|failed|cannot_complete|completed|paused|cancelled"
}
The card's
traffic_lightis 4-state for navigation rollup;instance_data.stateis the 9-state floor + 2 derived (doc 04 / state machine). The UI maps the 9+2 states onto the 6-color chip set (doc 07 token table). These two scales are deliberately distinct: nav rollup answers "is this subtree healthy?", instance state answers "what is this one run doing right now?".
4. Field → backend mapping
| Field | Design-side source | Notes |
|---|---|---|
node_id |
workflows.id / workflow_steps.id / tbl_*.id |
uuid for IU/tbl, int for legacy workflows/steps |
tier |
derived from source table / workflows.level |
T2=workflow, T1=step/task |
code |
workflows.process_code / workflow_steps.step_key / tbl_*.code |
|
title |
*.title |
|
subtitle |
v_mow_design_workflow.owner_name / category |
from governance_registry join |
parent_id |
workflows.parent_workflow_id / workflow_steps.workflow_id / tbl parent |
|
order_index |
workflow_steps.sort_order |
|
child_count |
pre-aggregated step_count |
pre-agg join, not correlated subquery at scale |
child_preview |
top-3 children by order | server-truncated |
status |
workflows.status |
active/draft/retired/deprecated |
traffic_light |
design_health rollup / fn_workflow_rollup_compute |
R1–R6 rollup rules |
automation_level |
derived from step actor_type mix |
full_auto if all system/agent; human_in_loop if any human checkpoint |
owner_gov_code |
workflows.owner_gov_code (+8 EXTEND) |
FK → governance_registry.code |
active_version |
workflows.active_design_version (+8) |
|
freeze_status |
workflows.freeze_active/freeze_reason (+8) |
|
health_summary |
workflows.design_health jsonb (+8) |
owner_set, version_set, bound_pct, dangling refs |
iu_ref |
workflows.design_iu_ref / workflow_steps.step_iu_ref (+8/+7) |
uuid → information_unit |
workflow_ref |
workflows.id |
|
task_ref |
tasks.id via workflow_steps.task_id |
read-only Phase 1 |
dot_refs |
workflow_steps.dot_ref (+7) |
→ dot_iu_command_catalog.command_name |
event_refs |
workflow_steps.event_domain_ref/event_type_ref (+7) |
composite → event_type_registry |
proposal_count |
count(workflow_change_requests WHERE status IN(draft,submitted,review)) |
per node |
permission_actions |
server-computed per role (Điều 37 predicate) | never hardcoded |
last_updated_at |
*.date_updated |
5. Contract rules (carried from Phase 1 doc 04 §B)
- Backend-only visibility: every list is a view + Directus policy + per-tier Điều-37 predicate; the API filters by role before serialization. Action chips are rendered from
permission_actions, not hardcoded. - Stale-data control: every payload carries
as_of+ per-rowlast_updated_at; UI re-fetches 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 + refine guard. - No write path outside DOTs: the UI never issues raw SQL or Directus item writes to design columns; every mutation goes through a named DOT (doc 05) that enforces gate/owner/approval.
- IU bodies via ref:
iu_refis a uuid pointer; the card renders the body throughrender_iu_body/dot_iu_reconstruct_source, never inline-stored prose.
6. Data contract verdict
Concrete + backend-mapped. Every one of the 22 contract fields (plus instance_data) maps to a named view/column/function on the existing+EXTEND substrate. The two traffic-light scales are reconciled. CanvasContext carries the frame state and stale-data control. No implementation performed; this is the single contract both the Design brief (doc 07) and the Code brief (doc 08) consume.