Changeset 0.35.0 (#209)
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit is contained in:
148
docs/DESIGN-0.35.0.md
Normal file
148
docs/DESIGN-0.35.0.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# DESIGN-0.35.0 — Workflow Product
|
||||
|
||||
Transforms the workflow engine from a linear stage-runner into a
|
||||
product-grade automation tool. Seven changesets covering conditional
|
||||
routing, AI-triggered routing, data pipeline hooks, progressive forms,
|
||||
conditional fields, structured review, and a monitoring dashboard.
|
||||
|
||||
Depends on: v0.34.0 (Data Portability), v0.31.2 (Team Workflows),
|
||||
v0.29.0 (Starlark Sandbox).
|
||||
|
||||
## Schema Changes
|
||||
|
||||
Migrations modified in-place (DB rebuild required):
|
||||
|
||||
**005_channels.sql** — add `stage_entered_at` (PG: `TIMESTAMPTZ`,
|
||||
SQLite: `TEXT`). Tracks when the current stage was entered for SLA
|
||||
computation. Also add index `idx_channels_workflow_active` on
|
||||
`(workflow_id, workflow_status)` for monitoring queries.
|
||||
|
||||
**018_workflows.sql** — add `sla_seconds INTEGER` to `workflow_stages`.
|
||||
Add `review_comments` (PG: `JSONB DEFAULT '[]'`, SQLite: `TEXT
|
||||
DEFAULT '[]'`) to `workflow_assignments`.
|
||||
|
||||
No new migration files. No new tables.
|
||||
|
||||
## Conditional Routing Engine
|
||||
|
||||
**File:** `server/workflow/routing.go`
|
||||
|
||||
The existing advance logic hardcoded `nextStage = currentStage + 1`.
|
||||
The routing engine replaces this with condition evaluation:
|
||||
|
||||
1. Parse `transition_rules.conditions[]` from the departing stage
|
||||
2. Evaluate each condition against accumulated `stage_data`
|
||||
3. First match → resolve `target_stage` (by name or ordinal)
|
||||
4. No match → fallback to `ordinal + 1`
|
||||
|
||||
**Expression format:** `{field, op, value, target_stage}`. Operators:
|
||||
`eq`, `neq`, `gt`, `lt`, `gte`, `lte`, `in`, `contains`, `exists`,
|
||||
`not_exists`. Loose equality via `fmt.Sprintf("%v")` comparison.
|
||||
Numeric comparison extracts `float64` from both sides.
|
||||
|
||||
**Wiring:** `ResolveNextStage()` called in all 4 advance sites:
|
||||
handler (`workflow_instances.go`), tool (`tools/workflow.go`), forms
|
||||
auto-advance (`workflow_forms.go`), Starlark module
|
||||
(`sandbox/workflow_module.go`).
|
||||
|
||||
**Backward compatible:** no conditions = `ordinal + 1`.
|
||||
|
||||
## AI-Triggered Routing
|
||||
|
||||
**Tool:** `workflow_route` — persona calls with `{target_stage, reason}`.
|
||||
Uses `ResolveStageByName()` (case-insensitive). Records decision in
|
||||
`stage_data._route_history_latest`. Allows forward and backward jumps
|
||||
(loop stages for iterative correction).
|
||||
|
||||
**Starlark:** `workflow.route(channel_id, target_stage, reason)` for
|
||||
extension-driven escalation patterns. Requires `workflow.access`.
|
||||
|
||||
## on_advance Hook
|
||||
|
||||
**File:** `server/handlers/workflow_hooks.go`
|
||||
|
||||
Fires synchronously after `AdvanceWorkflowStage` succeeds. Configured
|
||||
per-stage in `transition_rules.on_advance: {package_id, entry_point}`.
|
||||
|
||||
Hook receives: `{stage_data, previous_stage, current_stage, channel_id}`.
|
||||
Returns: `{stage_data: {...}}` (enriched), `None` (no change), or
|
||||
`{error: "msg"}` (logged, not rolled back — future: rollback support).
|
||||
|
||||
The existing `http` Starlark module (v0.29.1) is available in hooks,
|
||||
enabling external API enrichment without new infrastructure.
|
||||
|
||||
## Progressive Forms
|
||||
|
||||
**Model:** `TypedFormTemplate.Fieldsets []FormFieldset` — optional
|
||||
array of `{label, fields[]}`. When present, top-level `fields` is
|
||||
replaced by flattened fieldset fields in `ParseTypedFormTemplate()`.
|
||||
|
||||
**Frontend:** `workflow.html` renders one fieldset at a time with
|
||||
step indicator, next/back navigation. All fields submitted together
|
||||
on the final step.
|
||||
|
||||
## Conditional Fields
|
||||
|
||||
**Model:** `FormField.Condition *FieldCondition` — `{when, op, value}`.
|
||||
Operators: `eq` (default), `neq`, `in`, `exists`.
|
||||
|
||||
**Server-side:** `ValidateFormData()` calls `evaluateFieldCondition()`
|
||||
before each field. Hidden fields are skipped (no required check, no
|
||||
type validation).
|
||||
|
||||
**Client-side:** `workflow.html` attaches `change`/`input` listeners
|
||||
to source fields. Target fields toggle `cond-hidden` CSS class.
|
||||
|
||||
## Structured Review
|
||||
|
||||
**Review comments:** `review_comments JSONB` on `workflow_assignments`.
|
||||
`POST /workflow-assignments/:id/comment` appends `{text, user_id,
|
||||
created_at}`. `GET /workflow-assignments/:id` returns full assignment.
|
||||
|
||||
**Review surface:** Side-by-side layout in `workflow.html` — left panel
|
||||
shows structured `stage_data` table (internal `_`-prefixed keys hidden),
|
||||
right panel has comment textarea + approve/reject buttons. Keyboard
|
||||
shortcuts: `Ctrl+Enter` approve, `Ctrl+Shift+Enter` reject.
|
||||
|
||||
## Monitoring Dashboard
|
||||
|
||||
**File:** `server/handlers/workflow_monitor.go`
|
||||
|
||||
**Endpoints:**
|
||||
- `GET /admin/workflows/monitor/instances` — all active instances
|
||||
- `GET /admin/workflows/monitor/funnel/:id` — stage counts
|
||||
- `GET /admin/workflows/monitor/stale?threshold_hours=N`
|
||||
- `GET /teams/:teamId/workflows/monitor/instances` — team-scoped
|
||||
|
||||
**SLA computation:** `stage_entered_at + sla_seconds` vs `NOW()`.
|
||||
Returned as `sla_remaining_seconds` (negative = breached) and
|
||||
`sla_breached` boolean. Computed at query time, not stored.
|
||||
|
||||
**Store:** New `ListByType(ctx, "workflow")` method on `ChannelStore`
|
||||
(both PG and SQLite). Monitoring queries iterate active channels,
|
||||
joining workflow definitions and stage metadata.
|
||||
|
||||
**Frontend:** `src/js/workflow-monitor.js` — auto-refresh every 30s,
|
||||
SLA color indicators (green > 50%, yellow > 20%, red), stale instance
|
||||
alert section.
|
||||
|
||||
## Workflow Branding
|
||||
|
||||
`WorkflowPageData.BrandingJSON` passes the workflow's `branding` JSON
|
||||
to the visitor template. Applied as:
|
||||
- `--accent` CSS custom property (from `accent_color`)
|
||||
- Logo image in header (from `logo_url`)
|
||||
- Tagline text below header (from `tagline`)
|
||||
|
||||
Previously branding was only on the landing page; now also on the
|
||||
active workflow surface.
|
||||
|
||||
## New Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `server/workflow/routing.go` | Conditional routing engine |
|
||||
| `server/handlers/workflow_hooks.go` | on_advance hook dispatch |
|
||||
| `server/handlers/workflow_monitor.go` | Monitoring dashboard |
|
||||
| `src/js/workflow-monitor.js` | Admin monitoring frontend |
|
||||
| `packages/icd-test-runner/js/crud/workflow-product.js` | E2E tests |
|
||||
Reference in New Issue
Block a user