Feat v0.3.7 package audit (#20)
All checks were successful
CI/CD / detect-changes (push) Successful in 4s
CI/CD / test-frontend (push) Successful in 6s
CI/CD / test-go-pg (push) Successful in 2m36s
CI/CD / test-sqlite (push) Successful in 2m41s
CI/CD / build-and-deploy (push) Successful in 1m13s

Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #20.
This commit is contained in:
2026-03-28 21:22:39 +00:00
committed by xcaliber
parent d68451fe8e
commit d91ec02dd7
47 changed files with 2067 additions and 53 deletions

View File

@@ -0,0 +1,50 @@
# Bug Report Triage
Public bug report submission with severity-based routing and SLA timers.
## Features
- **Public entry** — anyone with the link can submit a bug report (no auth)
- **Progressive fieldsets** — two-step form (Report Details + Reproduction)
- **Branch rules** — severity=critical routes to senior queue, otherwise standard queue
- **SLA timer** — critical fixes have a 1-hour SLA (fires `workflow.sla_breach` event)
- **Team assignment** — classify, fix, and verify stages are team-scoped
## Stage Flow
```
[submit] → [classify] → [fix-critical] → [verify]
public team ↘ [fix-normal] ↗ team
team
```
## Installation
```bash
# Via admin API
curl -X POST $BASE/api/v1/admin/packages/install \
-H "Authorization: Bearer $TOKEN" \
-F "file=@packages/bug-report-triage"
```
## API Walkthrough
```bash
# 1. Start public instance (no auth)
curl -X POST $BASE/api/v1/public/workflows/$WF_ID/start \
-H 'Content-Type: application/json' \
-d '{"data": {"reporter_email": "user@example.com", "summary": "Login broken", "component": "ui", "severity": "critical", "steps": "1. Click login", "expected": "Login page", "actual": "500 error"}}'
# 2. Resume (check status)
curl $BASE/api/v1/public/workflows/resume/$ENTRY_TOKEN
# 3. Team claims classify assignment
curl -X POST $BASE/api/v1/teams/$TEAM/assignments/$ASSIGN_ID/claim \
-H "Authorization: Bearer $TOKEN"
# 4. Advance classify (triggers branch rule)
curl -X POST $BASE/api/v1/teams/$TEAM/instances/$INST/advance \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"data": {"severity": "critical", "notes": "Confirmed production issue"}}'
```

View File

@@ -0,0 +1,129 @@
{
"id": "bug-report-triage",
"title": "Bug Report Triage",
"type": "workflow",
"version": "0.1.0",
"tier": "browser",
"icon": "🐛",
"author": "Switchboard Core",
"description": "Public bug report submission with severity-based routing, team assignment, and SLA timers.",
"permissions": [],
"workflow_definition": {
"name": "Bug Report Triage",
"slug": "bug-report-triage",
"entry_mode": "public_link",
"stages": [
{
"name": "submit",
"ordinal": 0,
"stage_mode": "form",
"audience": "public",
"stage_type": "simple",
"auto_transition": false,
"form_template": {
"fieldsets": [
{
"label": "Report Details",
"fields": [
{ "key": "reporter_email", "label": "Your Email", "type": "text", "required": true },
{ "key": "summary", "label": "Summary", "type": "text", "required": true },
{ "key": "component", "label": "Component", "type": "select", "options": ["ui", "api", "database", "auth", "other"], "required": true },
{ "key": "severity", "label": "Severity", "type": "select", "options": ["critical", "high", "medium", "low"], "required": true }
]
},
{
"label": "Reproduction",
"fields": [
{ "key": "steps", "label": "Steps to Reproduce", "type": "textarea", "required": true },
{ "key": "expected", "label": "Expected Behavior", "type": "textarea", "required": true },
{ "key": "actual", "label": "Actual Behavior", "type": "textarea", "required": true }
]
}
]
}
},
{
"name": "classify",
"ordinal": 1,
"stage_mode": "form",
"audience": "team",
"stage_type": "simple",
"auto_transition": false,
"form_template": {
"fieldsets": [
{
"label": "Classification",
"fields": [
{ "key": "severity", "label": "Confirmed Severity", "type": "select", "options": ["critical", "high", "medium", "low"], "required": true },
{ "key": "notes", "label": "Triage Notes", "type": "textarea" }
]
}
]
},
"branch_rules": [
{ "field": "severity", "op": "eq", "value": "critical", "target_stage": "fix-critical" },
{ "field": "severity", "op": "neq", "value": "critical", "target_stage": "fix-normal" }
]
},
{
"name": "fix-critical",
"ordinal": 2,
"stage_mode": "form",
"audience": "team",
"stage_type": "simple",
"auto_transition": false,
"sla_seconds": 3600,
"form_template": {
"fieldsets": [
{
"label": "Critical Fix",
"fields": [
{ "key": "fix_description", "label": "Fix Description", "type": "textarea", "required": true },
{ "key": "commit_ref", "label": "Commit / PR Reference", "type": "text" }
]
}
]
}
},
{
"name": "fix-normal",
"ordinal": 3,
"stage_mode": "form",
"audience": "team",
"stage_type": "simple",
"auto_transition": false,
"form_template": {
"fieldsets": [
{
"label": "Fix",
"fields": [
{ "key": "fix_description", "label": "Fix Description", "type": "textarea", "required": true },
{ "key": "commit_ref", "label": "Commit / PR Reference", "type": "text" }
]
}
]
}
},
{
"name": "verify",
"ordinal": 4,
"stage_mode": "review",
"audience": "team",
"stage_type": "simple",
"auto_transition": false,
"form_template": {
"fieldsets": [
{
"label": "Verification",
"fields": [
{ "key": "verified", "label": "Fix Verified?", "type": "select", "options": ["yes", "no"], "required": true },
{ "key": "verification_notes", "label": "Notes", "type": "textarea" }
]
}
]
}
}
]
}
}

View File

@@ -0,0 +1,33 @@
# Content Approval
Multi-party content review with quorum signoff and revision cycle.
## Features
- **Multi-party signoff** — 2 approvals required (quorum)
- **Rejection reroute** — rejection sends content back for revision
- **Review cycle** — revision stage always routes back to review (loop)
## Stage Flow
```
[draft] → [review] → [publish]
team team ↕ team
[revision]
team
```
## Review Cycle
1. Author submits draft → enters review
2. Reviewer A approves, Reviewer B rejects → routes to revision
3. Author revises → branch rule routes back to review
4. Both reviewers approve → advances to publish
## Installation
```bash
curl -X POST $BASE/api/v1/admin/packages/install \
-H "Authorization: Bearer $TOKEN" \
-F "file=@packages/content-approval"
```

View File

@@ -0,0 +1,95 @@
{
"id": "content-approval",
"title": "Content Approval",
"type": "workflow",
"version": "0.1.0",
"tier": "browser",
"icon": "📝",
"author": "Switchboard Core",
"description": "Multi-party content review with quorum signoff and revision cycle.",
"permissions": [],
"workflow_definition": {
"name": "Content Approval",
"slug": "content-approval",
"entry_mode": "team_only",
"stages": [
{
"name": "draft",
"ordinal": 0,
"stage_mode": "form",
"audience": "team",
"stage_type": "simple",
"auto_transition": false,
"form_template": {
"fieldsets": [
{
"label": "Content Draft",
"fields": [
{ "key": "title", "label": "Title", "type": "text", "required": true },
{ "key": "body", "label": "Body", "type": "textarea", "required": true },
{ "key": "category", "label": "Category", "type": "select", "options": ["blog", "announcement", "documentation", "policy"], "required": true }
]
}
]
}
},
{
"name": "review",
"ordinal": 1,
"stage_mode": "review",
"audience": "team",
"stage_type": "simple",
"auto_transition": false,
"stage_config": {
"validation": {
"required_approvals": 2,
"reject_action": "revision"
}
}
},
{
"name": "revision",
"ordinal": 2,
"stage_mode": "form",
"audience": "team",
"stage_type": "simple",
"auto_transition": false,
"form_template": {
"fieldsets": [
{
"label": "Revision",
"fields": [
{ "key": "title", "label": "Title", "type": "text", "required": true },
{ "key": "body", "label": "Body", "type": "textarea", "required": true },
{ "key": "revision_notes", "label": "What Changed", "type": "textarea", "required": true }
]
}
]
},
"branch_rules": [
{ "field": "title", "op": "exists", "value": "", "target_stage": "review" }
]
},
{
"name": "publish",
"ordinal": 3,
"stage_mode": "form",
"audience": "team",
"stage_type": "simple",
"auto_transition": false,
"form_template": {
"fieldsets": [
{
"label": "Publish Confirmation",
"fields": [
{ "key": "publish_url", "label": "Published URL", "type": "text" },
{ "key": "publish_notes", "label": "Notes", "type": "textarea" }
]
}
]
}
}
]
}
}

View File

@@ -1,6 +1,6 @@
{
"id": "csv-table",
"name": "CSV Table Viewer",
"title": "CSV Table Viewer",
"version": "1.0.0",
"type": "extension",
"tier": "browser",

View File

@@ -6,7 +6,8 @@
"tier": "browser",
"author": "Switchboard Core",
"icon": "📊",
"description": "Project dashboard exercising all SDK primitives",
"description": "Project dashboard exercising all SDK primitives (requires legacy sw.* SDK — dormant until rewritten)",
"requires": ["legacy-sdk"],
"route": "/s/dashboard",
"permissions": [],
"settings": [

View File

@@ -1,6 +1,6 @@
{
"id": "diff-viewer",
"name": "Diff Viewer",
"title": "Diff Viewer",
"version": "1.0.0",
"type": "extension",
"tier": "browser",

View File

@@ -6,7 +6,8 @@
"tier": "browser",
"author": "Switchboard Core",
"icon": "✏️",
"description": "Code editor with workspace management, file tree, and AI assist",
"description": "Code editor with workspace management, file tree, and AI assist (requires legacy sw.* SDK — dormant until rewritten)",
"requires": ["legacy-sdk"],
"route": "/s/editor",
"layout": "editor",
"permissions": [],

View File

@@ -0,0 +1,36 @@
# Employee Onboarding
Automated employee provisioning with manager signoff and welcome notification.
## Features
- **Starlark automated stages** — `db.insert` for provisioning records, `notifications.send` for alerts
- **Signoff gate** — manager approval required (role-based), rejection reroutes to start
- **Progressive fieldsets** — Personal Info + Access Requirements
- **ext_data tables** — `provisions` and `onboarding_log`
## Stage Flow
```
[new-hire-info] → [provision-accounts] → [manager-signoff] → [it-setup] → [welcome]
team automated (Starlark) review + signoff team automated
auto-advances reject → stage 0 auto-advances
```
## Prerequisites
- Team must have a custom **"manager"** role (via Team Admin > Members > Team Roles)
- At least one team member with the "manager" role assigned
## Starlark Hooks
- **`on_provision(ctx)`** — Inserts provisioning record into `provisions` table, notifies workflow starter
- **`on_welcome(ctx)`** — Logs completion to `onboarding_log`, sends welcome notification
## Installation
```bash
curl -X POST $BASE/api/v1/admin/packages/install \
-H "Authorization: Bearer $TOKEN" \
-F "file=@packages/employee-onboarding"
```

View File

@@ -0,0 +1,122 @@
{
"id": "employee-onboarding",
"title": "Employee Onboarding",
"type": "workflow",
"version": "0.1.0",
"tier": "starlark",
"icon": "🏢",
"author": "Switchboard Core",
"description": "Employee onboarding with automated provisioning, manager signoff, and welcome notification.",
"permissions": ["db.write", "notifications.send"],
"db_tables": {
"provisions": {
"columns": {
"employee_name": "text",
"department": "text",
"needs_vpn": "text",
"needs_admin": "text",
"equipment": "text",
"status": "text"
}
},
"onboarding_log": {
"columns": {
"employee_name": "text",
"department": "text",
"completed_by": "text",
"status": "text"
}
}
},
"workflow_definition": {
"name": "Employee Onboarding",
"slug": "employee-onboarding",
"entry_mode": "team_only",
"stages": [
{
"name": "new-hire-info",
"ordinal": 0,
"stage_mode": "form",
"audience": "team",
"stage_type": "simple",
"auto_transition": false,
"form_template": {
"fieldsets": [
{
"label": "Personal Info",
"fields": [
{ "key": "full_name", "label": "Full Name", "type": "text", "required": true },
{ "key": "email", "label": "Email", "type": "text", "required": true },
{ "key": "department", "label": "Department", "type": "select", "options": ["engineering", "sales", "marketing", "hr", "ops", "finance"], "required": true },
{ "key": "start_date", "label": "Start Date", "type": "text", "required": true }
]
},
{
"label": "Access Requirements",
"fields": [
{ "key": "needs_vpn", "label": "VPN Access", "type": "select", "options": ["true", "false"], "required": true },
{ "key": "needs_admin", "label": "Admin Access", "type": "select", "options": ["true", "false"], "required": true },
{ "key": "equipment", "label": "Equipment", "type": "select", "options": ["standard laptop", "developer workstation", "macbook pro"], "required": true }
]
}
]
}
},
{
"name": "provision-accounts",
"ordinal": 1,
"stage_mode": "automated",
"audience": "system",
"stage_type": "automated",
"auto_transition": true,
"starlark_hook": "employee-onboarding:on_provision"
},
{
"name": "manager-signoff",
"ordinal": 2,
"stage_mode": "review",
"audience": "team",
"stage_type": "simple",
"auto_transition": false,
"stage_config": {
"validation": {
"required_approvals": 1,
"required_role": "manager",
"reject_action": "new-hire-info"
}
}
},
{
"name": "it-setup",
"ordinal": 3,
"stage_mode": "form",
"audience": "team",
"stage_type": "simple",
"auto_transition": false,
"form_template": {
"fieldsets": [
{
"label": "IT Setup Confirmation",
"fields": [
{ "key": "hardware_ready", "label": "Hardware Ready?", "type": "select", "options": ["yes", "no"], "required": true },
{ "key": "accounts_created", "label": "Accounts Created?", "type": "select", "options": ["yes", "no"], "required": true },
{ "key": "it_notes", "label": "Notes", "type": "textarea" }
]
}
]
}
},
{
"name": "welcome",
"ordinal": 4,
"stage_mode": "automated",
"audience": "system",
"stage_type": "automated",
"auto_transition": true,
"starlark_hook": "employee-onboarding:on_welcome"
}
]
}
}

View File

@@ -0,0 +1,49 @@
def on_provision(ctx):
"""Automated provisioning: record in ext_data, notify HR."""
data = ctx["stage_data"]
name = data.get("full_name", "unknown")
dept = data.get("department", "general")
# Record provision in ext_data table
db.insert("provisions", {
"employee_name": name,
"department": dept,
"needs_vpn": str(data.get("needs_vpn", "false")),
"needs_admin": str(data.get("needs_admin", "false")),
"equipment": data.get("equipment", "standard laptop"),
"status": "provisioned",
})
# Notify the HR user who started this workflow
notifications.send(
ctx["started_by"],
"Accounts provisioned for " + name,
"Department: " + dept + ". Ready for manager review.",
)
return {"advance": True, "data": {
"provision_status": "complete",
"provisioned_for": name,
}}
def on_welcome(ctx):
"""Completion logging: record onboarding and send welcome."""
data = ctx["stage_data"]
name = data.get("full_name", "")
dept = data.get("department", "")
db.insert("onboarding_log", {
"employee_name": name,
"department": dept,
"completed_by": ctx["started_by"],
"status": "complete",
})
notifications.send(
ctx["started_by"],
"Onboarding complete: " + name,
"All stages finished. Welcome aboard!",
)
return {"advance": True, "data": {"onboarding_complete": True}}

View File

@@ -1,6 +1,7 @@
{
"id": "hello-dashboard",
"icon": "👋",
"type": "surface",
"title": "Hello Dashboard",
"route": "/s/hello-dashboard",
"auth": "authenticated",

View File

@@ -1,6 +1,7 @@
{
"id": "icd-test-runner",
"icon": "🧪",
"type": "surface",
"title": "ICD Test Runner",
"route": "/s/icd-test-runner",
"auth": "authenticated",

View File

@@ -1,6 +1,6 @@
{
"id": "js-sandbox",
"name": "JavaScript Sandbox",
"title": "JavaScript Sandbox",
"version": "1.0.0",
"type": "extension",
"tier": "browser",

View File

@@ -1,6 +1,6 @@
{
"id": "katex-renderer",
"name": "KaTeX Math",
"title": "KaTeX Math",
"version": "1.0.0",
"type": "extension",
"tier": "browser",

View File

@@ -1,6 +1,6 @@
{
"id": "mermaid-renderer",
"name": "Mermaid Diagrams",
"title": "Mermaid Diagrams",
"version": "2.0.0",
"type": "extension",
"tier": "browser",

View File

@@ -1,6 +1,6 @@
{
"id": "regex-tester",
"name": "Regex Tester",
"title": "Regex Tester",
"version": "1.0.0",
"type": "extension",
"tier": "browser",

View File

@@ -1,6 +1,7 @@
{
"id": "sdk-test-runner",
"icon": "🔬",
"type": "surface",
"title": "SDK Test Runner",
"route": "/s/sdk-test-runner",
"auth": "authenticated",

View File

@@ -0,0 +1,30 @@
# Webhook Notifier
Outbound webhook delivery with connection credentials and delivery logging.
## Features
- **Starlark `http.post`** — outbound HTTP with custom headers
- **`connections.get`** — fallback to stored webhook connection credentials
- **Delivery logging** — records URL, status code, event to `webhook_log` ext_data table
- **Result review** — delivery status shown in final review stage
## Stage Flow
```
[configure] → [fire-webhook] → [result]
team automated review
(http.post)
```
## Starlark Hook
- **`on_fire(ctx)`** — Posts JSON payload to webhook URL, falls back to `connections.get("webhook")`, logs delivery to `webhook_log`
## Installation
```bash
curl -X POST $BASE/api/v1/admin/packages/install \
-H "Authorization: Bearer $TOKEN" \
-F "file=@packages/webhook-notifier"
```

View File

@@ -0,0 +1,79 @@
{
"id": "webhook-notifier",
"title": "Webhook Notifier",
"type": "workflow",
"version": "0.1.0",
"tier": "starlark",
"icon": "🔔",
"author": "Switchboard Core",
"description": "Outbound webhook delivery with connection credential support and delivery logging.",
"permissions": ["api.http", "connections.read", "db.write"],
"db_tables": {
"webhook_log": {
"columns": {
"url": "text",
"status_code": "text",
"event": "text",
"instance_id": "text"
}
}
},
"workflow_definition": {
"name": "Webhook Notifier",
"slug": "webhook-notifier",
"entry_mode": "team_only",
"stages": [
{
"name": "configure",
"ordinal": 0,
"stage_mode": "form",
"audience": "team",
"stage_type": "simple",
"auto_transition": false,
"form_template": {
"fieldsets": [
{
"label": "Webhook Configuration",
"fields": [
{ "key": "webhook_url", "label": "Webhook URL", "type": "text", "required": true },
{ "key": "event_name", "label": "Event Name", "type": "text", "required": true },
{ "key": "payload", "label": "Custom Payload (JSON)", "type": "textarea" }
]
}
]
}
},
{
"name": "fire-webhook",
"ordinal": 1,
"stage_mode": "automated",
"audience": "system",
"stage_type": "automated",
"auto_transition": true,
"starlark_hook": "webhook-notifier:on_fire"
},
{
"name": "result",
"ordinal": 2,
"stage_mode": "review",
"audience": "team",
"stage_type": "simple",
"auto_transition": false,
"form_template": {
"fieldsets": [
{
"label": "Delivery Result",
"fields": [
{ "key": "delivery_status", "label": "Status", "type": "text" },
{ "key": "status_code", "label": "HTTP Status Code", "type": "text" },
{ "key": "response_body", "label": "Response", "type": "textarea" }
]
}
]
}
}
]
}
}

View File

@@ -0,0 +1,41 @@
def on_fire(ctx):
"""Fire outbound webhook with optional connection credentials."""
data = ctx["stage_data"]
url = data.get("webhook_url", "")
# Fallback to configured connection
if not url:
conn = connections.get("webhook")
if conn:
url = conn.get("url", "")
if not url:
return {"error": "No webhook URL configured"}
payload = json.encode({
"event": data.get("event_name", "workflow.notify"),
"instance_id": ctx["instance_id"],
"workflow_id": ctx["workflow_id"],
"data": data,
})
resp = http.post(url, body=payload, headers={
"Content-Type": "application/json",
"X-Switchboard-Event": data.get("event_name", "workflow.notify"),
})
# Log the delivery
db.insert("webhook_log", {
"url": url,
"status_code": str(resp["status"]),
"event": data.get("event_name", ""),
"instance_id": ctx["instance_id"],
})
status = "delivered" if int(resp["status"]) < 400 else "failed"
return {"advance": True, "data": {
"delivery_status": status,
"status_code": str(resp["status"]),
"response_body": resp["body"][:500],
}}

View File

@@ -0,0 +1,27 @@
# Workflow Demo
Interactive demo surface for the example workflow packages.
## Features
- **Workflow cards** — one card per example workflow with description and feature badges
- **Stage flow diagrams** — visual representation of each workflow's stage progression
- **Starlark viewer** — collapsible code summary for Starlark-tier packages
- **API curl examples** — collapsible section with copy-ready curl commands
- **"Try It" buttons** — launch workflows directly (public link or team start)
- **Install status** — shows which example workflows are currently installed
## Route
`/s/workflow-demo` — accessible to any authenticated user.
## Installation
Install alongside the example workflow packages. The demo surface detects which
workflows are installed and shows their status accordingly.
```bash
curl -X POST $BASE/api/v1/admin/packages/install \
-H "Authorization: Bearer $TOKEN" \
-F "file=@packages/workflow-demo"
```

View File

@@ -0,0 +1,256 @@
/* ==========================================
Workflow Demo Surface — Styles
Uses the app's theme variables (--bg, --bg-surface, --text, etc.)
defined in variables.css for automatic dark/light support.
========================================== */
.surface-workflow-demo {
max-width: 1100px;
margin: 0 auto;
padding: 0 1.5rem 2rem;
}
/* Topbar */
.demo-topbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 0;
border-bottom: 1px solid var(--border);
margin-bottom: 1rem;
}
.demo-topbar h1 {
font-size: 1.5rem;
font-weight: 700;
margin: 0;
color: var(--text);
}
/* Subtitle */
.demo-subtitle {
color: var(--text-2);
margin: 0 0 1.5rem;
font-size: 0.9rem;
}
/* Grid */
.demo-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1.5rem;
}
@media (min-width: 768px) {
.demo-grid {
grid-template-columns: 1fr 1fr;
}
}
/* Card */
.demo-card {
background: var(--bg-surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 1.25rem;
display: flex;
flex-direction: column;
gap: 0.75rem;
color: var(--text);
}
/* Title row */
.card-title-row {
display: flex;
align-items: center;
gap: 0.5rem;
}
.card-title-row h2 {
font-size: 1.1rem;
font-weight: 600;
margin: 0;
flex: 1;
color: var(--text);
}
.card-icon {
font-size: 1.3rem;
}
.card-tier {
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.05em;
padding: 2px 6px;
border-radius: 4px;
font-weight: 600;
}
.badge-browser { background: var(--success-dim); color: var(--success-light); }
.badge-starlark { background: var(--accent-dim); color: var(--accent-light); }
/* Description */
.card-desc {
color: var(--text-2);
font-size: 0.85rem;
margin: 0;
line-height: 1.4;
}
/* Badges */
.card-badges {
display: flex;
flex-wrap: wrap;
gap: 0.35rem;
}
.demo-card .badge {
font-size: 0.7rem;
padding: 2px 8px;
border-radius: 10px;
background: var(--bg-raised);
color: var(--text-2);
white-space: nowrap;
}
.demo-card .badge-installed { background: var(--success-dim); color: var(--success-light); }
.demo-card .badge-not-installed { background: var(--warning-dim); color: var(--warning-light); }
.demo-card .badge-needs-publish { background: var(--danger-dim); color: var(--danger-light); }
/* Stage flow diagram */
.stage-flow {
display: flex;
align-items: flex-start;
gap: 0.25rem;
flex-wrap: wrap;
padding: 0.75rem;
background: var(--bg-raised);
border-radius: 6px;
overflow-x: auto;
}
.stage-node {
border: 1px solid var(--border);
border-radius: 6px;
padding: 0.4rem 0.6rem;
min-width: 80px;
text-align: center;
background: var(--bg-surface);
font-size: 0.75rem;
color: var(--text);
}
.stage-node.stage-public {
border-color: var(--success);
background: var(--success-dim);
}
.stage-node.stage-system {
border-color: var(--accent);
background: var(--accent-dim);
}
.stage-label {
font-weight: 600;
font-size: 0.8rem;
}
.stage-meta {
color: var(--text-3);
font-size: 0.65rem;
}
.stage-note {
color: var(--warning-light);
font-size: 0.6rem;
font-style: italic;
margin-top: 2px;
}
.stage-arrow {
display: flex;
align-items: center;
font-size: 1.1rem;
color: var(--text-3);
padding: 0 0.15rem;
align-self: center;
}
/* Collapsible sections */
.card-collapsible {
border: 1px solid var(--border);
border-radius: 6px;
font-size: 0.8rem;
}
.card-collapsible summary {
padding: 0.5rem 0.75rem;
cursor: pointer;
font-weight: 600;
user-select: none;
color: var(--text);
}
.card-collapsible summary:hover {
background: var(--bg-hover);
}
.collapsible-content {
padding: 0 0.75rem 0.75rem;
}
.collapsible-content pre {
background: var(--bg-raised);
color: var(--text);
padding: 0.5rem;
border-radius: 4px;
overflow-x: auto;
font-size: 0.72rem;
line-height: 1.4;
margin: 0.25rem 0;
font-family: var(--mono);
}
/* Actions */
.card-actions {
display: flex;
align-items: center;
gap: 0.5rem;
margin-top: auto;
padding-top: 0.5rem;
border-top: 1px solid var(--border);
}
.demo-card .btn {
padding: 0.4rem 1rem;
border: none;
border-radius: 6px;
font-size: 0.8rem;
cursor: pointer;
font-weight: 500;
}
.demo-card .btn-primary {
background: var(--accent);
color: var(--text-on-color);
}
.demo-card .btn-primary:hover {
background: var(--accent-hover);
}
.demo-card .btn-primary:disabled {
opacity: 0.5;
cursor: default;
}
/* Public link row */
.public-link-row {
display: flex;
align-items: center;
gap: 0.5rem;
margin-top: 0.25rem;
}
.public-link-row input {
flex: 1;
font-size: 0.7rem;
padding: 4px 8px;
background: var(--input-bg);
border: 1px solid var(--border);
border-radius: 4px;
color: var(--text);
font-family: var(--mono);
}
.public-link-row .btn-copy {
padding: 4px 10px;
font-size: 0.7rem;
background: var(--bg-raised);
border: 1px solid var(--border);
border-radius: 4px;
color: var(--text-2);
cursor: pointer;
}
.public-link-row .btn-copy:hover {
background: var(--bg-hover);
}

View File

@@ -0,0 +1,355 @@
// ==========================================
// Switchboard Core — Workflow Demo Surface
// ==========================================
// Interactive walkthrough of the four example workflow packages.
// Shows workflow cards with feature badges, stage flow diagrams,
// Starlark code viewer, and API curl examples.
// ==========================================
(function () {
'use strict';
const SURFACE_ID = 'workflow-demo';
if (window.__SURFACE__ !== SURFACE_ID) return;
const base = window.__BASE__ || '';
// ── Workflow catalog ──────────────────────────
const WORKFLOWS = [
{
slug: 'bug-report-triage',
title: 'Bug Report Triage',
icon: '🐛',
description: 'Public bug report submission with severity-based routing, team assignment, and SLA timers.',
badges: ['Public Entry', 'Branch Rules', 'SLA Timer', 'Team Assignment'],
tier: 'browser',
stages: [
{ name: 'submit', mode: 'form', audience: 'public' },
{ name: 'classify', mode: 'form', audience: 'team', note: 'branch rules' },
{ name: 'fix-critical', mode: 'form', audience: 'team', note: 'SLA 1h', branch: true },
{ name: 'fix-normal', mode: 'form', audience: 'team', branch: true },
{ name: 'verify', mode: 'review', audience: 'team' },
],
curl: [
'# 1. Start public instance (no auth required)\ncurl -X POST $BASE/api/v1/public/workflows/$WF_ID/start \\\n -H "Content-Type: application/json" \\\n -d \'{"data": {"reporter_email": "user@example.com", "summary": "Login broken", "component": "ui", "severity": "critical", "steps": "1. Click login", "expected": "Login page", "actual": "500 error"}}\'',
'# 2. Resume (check status)\ncurl $BASE/api/v1/public/workflows/resume/$TOKEN',
'# 3. Team member claims classify assignment\ncurl -X POST $BASE/api/v1/teams/$TEAM/assignments/$ASSIGN_ID/claim \\\n -H "Authorization: Bearer $TOKEN"',
'# 4. Advance classify (triggers branch rule)\ncurl -X POST $BASE/api/v1/teams/$TEAM/instances/$INST/advance \\\n -H "Authorization: Bearer $TOKEN" \\\n -H "Content-Type: application/json" \\\n -d \'{"data": {"severity": "critical", "notes": "Confirmed production issue"}}\'',
],
},
{
slug: 'employee-onboarding',
title: 'Employee Onboarding',
icon: '🏢',
description: 'Automated provisioning with Starlark hooks, manager signoff gate, and welcome notification.',
badges: ['Starlark', 'Signoff Gate', 'Rejection Reroute', 'db.insert', 'notifications.send'],
tier: 'starlark',
stages: [
{ name: 'new-hire-info', mode: 'form', audience: 'team' },
{ name: 'provision-accounts', mode: 'automated', audience: 'system', note: 'Starlark' },
{ name: 'manager-signoff', mode: 'review', audience: 'team', note: 'signoff gate' },
{ name: 'it-setup', mode: 'form', audience: 'team' },
{ name: 'welcome', mode: 'automated', audience: 'system', note: 'Starlark' },
],
starlark: 'on_provision(ctx): db.insert("provisions", ...), notifications.send(...)\non_welcome(ctx): db.insert("onboarding_log", ...), notifications.send(...)',
curl: [
'# 1. Start instance (team auth required)\ncurl -X POST $BASE/api/v1/teams/$TEAM/workflows/$WF_ID/start \\\n -H "Authorization: Bearer $TOKEN" \\\n -H "Content-Type: application/json" \\\n -d \'{"data": {"full_name": "Alice Smith", "department": "engineering", "needs_vpn": "true", "needs_admin": "false", "equipment": "developer workstation"}}\'',
'# 2. After auto-provision, submit manager signoff\ncurl -X POST $BASE/api/v1/instances/$INST/signoffs \\\n -H "Authorization: Bearer $MANAGER_TOKEN" \\\n -H "Content-Type: application/json" \\\n -d \'{"action": "approve"}\'',
],
},
{
slug: 'content-approval',
title: 'Content Approval',
icon: '📝',
description: 'Multi-party review with quorum signoff (2 approvals) and revision cycle loop.',
badges: ['Multi-party Signoff', 'Quorum', 'Rejection Reroute', 'Review Cycle'],
tier: 'browser',
stages: [
{ name: 'draft', mode: 'form', audience: 'team' },
{ name: 'review', mode: 'review', audience: 'team', note: '2 approvals' },
{ name: 'revision', mode: 'form', audience: 'team', note: 'loops to review' },
{ name: 'publish', mode: 'form', audience: 'team' },
],
curl: [
'# 1. Submit draft\ncurl -X POST $BASE/api/v1/teams/$TEAM/workflows/$WF_ID/start \\\n -H "Authorization: Bearer $TOKEN" \\\n -H "Content-Type: application/json" \\\n -d \'{"data": {"title": "Q1 Update", "body": "...", "category": "blog"}}\'',
'# 2. Reviewer A approves\ncurl -X POST $BASE/api/v1/instances/$INST/signoffs \\\n -H "Authorization: Bearer $REVIEWER_A" \\\n -d \'{"action": "approve"}\'',
'# 3. Reviewer B rejects -> routes to revision\ncurl -X POST $BASE/api/v1/instances/$INST/signoffs \\\n -H "Authorization: Bearer $REVIEWER_B" \\\n -d \'{"action": "reject"}\'',
],
},
{
slug: 'webhook-notifier',
title: 'Webhook Notifier',
icon: '🔔',
description: 'Outbound HTTP delivery via Starlark with connection credentials and delivery logging.',
badges: ['Starlark', 'http.post', 'connections.get', 'db.insert'],
tier: 'starlark',
stages: [
{ name: 'configure', mode: 'form', audience: 'team' },
{ name: 'fire-webhook', mode: 'automated', audience: 'system', note: 'http.post' },
{ name: 'result', mode: 'review', audience: 'team' },
],
starlark: 'on_fire(ctx): http.post(url, body=payload), db.insert("webhook_log", ...)',
curl: [
'# 1. Configure and start\ncurl -X POST $BASE/api/v1/teams/$TEAM/workflows/$WF_ID/start \\\n -H "Authorization: Bearer $TOKEN" \\\n -H "Content-Type: application/json" \\\n -d \'{"data": {"webhook_url": "https://webhook.site/test", "event_name": "deploy.complete", "payload": "{}"}}\'',
],
},
];
// ── Init ─────────────────────────────────────
async function _init() {
const mount = document.getElementById('extension-mount');
if (!mount) return;
const surface = document.createElement('div');
surface.className = 'surface-workflow-demo';
mount.appendChild(surface);
// Topbar with user menu
const topbar = document.createElement('div');
topbar.className = 'demo-topbar';
topbar.innerHTML = '<h1>Workflow Demo</h1>';
surface.appendChild(topbar);
sw.userMenu(topbar, { flyout: 'down' });
// Subtitle
const subtitle = document.createElement('p');
subtitle.className = 'demo-subtitle';
subtitle.textContent = 'Four example workflows that prove the engine works end-to-end. Install any package via Admin > Packages.';
surface.appendChild(subtitle);
// Workflow cards
const grid = document.createElement('div');
grid.className = 'demo-grid';
surface.appendChild(grid);
// Fetch installed workflows to check status
// The API returns { data: [...], total, page } — _unwrap keeps the
// full object when siblings exist, so extract .data ourselves.
let installed = {};
try {
const resp = await sw.api.get('/api/v1/workflows');
const list = Array.isArray(resp) ? resp : (resp && resp.data ? resp.data : []);
for (const wf of list) {
if (wf.slug) installed[wf.slug] = wf;
}
} catch (e) { /* ignore — show all as not-installed */ }
for (const wf of WORKFLOWS) {
grid.appendChild(_buildCard(wf, installed[wf.slug]));
}
}
// ── Card builder ─────────────────────────────
function _buildCard(wf, installedWf) {
const card = document.createElement('div');
card.className = 'demo-card';
// Title row
const titleRow = document.createElement('div');
titleRow.className = 'card-title-row';
titleRow.innerHTML = '<span class="card-icon">' + wf.icon + '</span>'
+ '<h2>' + _esc(wf.title) + '</h2>'
+ '<span class="card-tier badge-' + wf.tier + '">' + wf.tier + '</span>';
card.appendChild(titleRow);
// Description
const desc = document.createElement('p');
desc.className = 'card-desc';
desc.textContent = wf.description;
card.appendChild(desc);
// Badges
const badges = document.createElement('div');
badges.className = 'card-badges';
for (const b of wf.badges) {
const span = document.createElement('span');
span.className = 'badge';
span.textContent = b;
badges.appendChild(span);
}
card.appendChild(badges);
// Stage flow diagram
card.appendChild(_buildStageDiagram(wf));
// Starlark viewer (if applicable)
if (wf.starlark) {
card.appendChild(_buildCollapsible('Starlark Hooks', '<pre><code>' + _esc(wf.starlark) + '</code></pre>'));
}
// API examples
if (wf.curl && wf.curl.length) {
const curlHtml = wf.curl.map(function (c) { return '<pre><code>' + _esc(c) + '</code></pre>'; }).join('');
card.appendChild(_buildCollapsible('API Examples', curlHtml));
}
// Status + action
const actions = document.createElement('div');
actions.className = 'card-actions';
if (installedWf) {
const isActive = installedWf.is_active;
const version = installedWf.version || 0;
// version > 1 means published at least once (version increments on edit, publish snapshots it)
const isPublished = version >= 2;
const isReady = isActive && isPublished;
// Installed badge
const status = document.createElement('span');
status.className = 'badge badge-installed';
status.textContent = 'Installed';
actions.appendChild(status);
if (!isActive) {
const hint = document.createElement('span');
hint.className = 'badge badge-needs-publish';
hint.textContent = 'Not Active';
actions.appendChild(hint);
} else if (!isPublished) {
const hint = document.createElement('span');
hint.className = 'badge badge-needs-publish';
hint.textContent = 'Not Published';
actions.appendChild(hint);
}
const tryBtn = document.createElement('button');
tryBtn.className = 'btn btn-primary';
tryBtn.textContent = 'Try It';
tryBtn.disabled = !isReady;
if (!isReady) {
tryBtn.title = 'Activate and publish this workflow in Team Admin first';
}
tryBtn.onclick = function () {
if (!isReady) return;
if (wf.slug === 'bug-report-triage') {
window.open(base + '/api/v1/public/workflows/' + installedWf.id + '/form', '_blank');
} else {
sw.toast('Navigate to your team admin to start a ' + wf.title + ' instance.', 'info');
}
};
actions.appendChild(tryBtn);
// Public link for public_link workflows
if (installedWf.entry_mode === 'public_link' && isReady) {
const linkRow = document.createElement('div');
linkRow.className = 'public-link-row';
const linkInput = document.createElement('input');
linkInput.type = 'text';
linkInput.readOnly = true;
linkInput.value = window.location.origin + base + '/api/v1/public/workflows/' + installedWf.id + '/start';
linkRow.appendChild(linkInput);
const copyBtn = document.createElement('button');
copyBtn.className = 'btn-copy';
copyBtn.textContent = 'Copy';
copyBtn.onclick = function () {
navigator.clipboard.writeText(linkInput.value).then(function () {
copyBtn.textContent = 'Copied!';
setTimeout(function () { copyBtn.textContent = 'Copy'; }, 1500);
});
};
linkRow.appendChild(copyBtn);
card.appendChild(linkRow);
}
} else {
const status = document.createElement('span');
status.className = 'badge badge-not-installed';
status.textContent = 'Not Installed';
actions.appendChild(status);
}
card.appendChild(actions);
return card;
}
// ── Stage flow diagram ───────────────────────
function _buildStageDiagram(wf) {
const container = document.createElement('div');
container.className = 'stage-flow';
const stages = wf.stages;
let hasBranch = false;
for (let i = 0; i < stages.length; i++) {
const s = stages[i];
const node = document.createElement('div');
node.className = 'stage-node stage-' + s.audience;
const label = document.createElement('div');
label.className = 'stage-label';
label.textContent = s.name;
node.appendChild(label);
const meta = document.createElement('div');
meta.className = 'stage-meta';
meta.textContent = s.mode + (s.audience !== 'team' ? ' (' + s.audience + ')' : '');
node.appendChild(meta);
if (s.note) {
const note = document.createElement('div');
note.className = 'stage-note';
note.textContent = s.note;
node.appendChild(note);
}
container.appendChild(node);
// Arrow (except after last stage)
if (i < stages.length - 1 && !stages[i + 1].branch) {
const arrow = document.createElement('div');
arrow.className = 'stage-arrow';
arrow.textContent = '\u2192';
container.appendChild(arrow);
} else if (stages[i + 1] && stages[i + 1].branch) {
if (!hasBranch) {
const split = document.createElement('div');
split.className = 'stage-arrow stage-branch';
split.textContent = '\u2193\u2197';
container.appendChild(split);
hasBranch = true;
}
}
}
return container;
}
// ── Collapsible section ──────────────────────
function _buildCollapsible(title, innerHtml) {
const details = document.createElement('details');
details.className = 'card-collapsible';
const summary = document.createElement('summary');
summary.textContent = title;
details.appendChild(summary);
const content = document.createElement('div');
content.className = 'collapsible-content';
content.innerHTML = innerHtml;
details.appendChild(content);
return details;
}
// ── Helpers ───────────────────────────────────
function _esc(s) {
const d = document.createElement('div');
d.textContent = s;
return d.innerHTML;
}
// ── Boot ──────────────────────────────────────
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', _init);
} else {
_init();
}
})();

View File

@@ -0,0 +1,15 @@
{
"id": "workflow-demo",
"title": "Workflow Demo",
"type": "full",
"version": "0.1.0",
"tier": "browser",
"icon": "🎓",
"author": "Switchboard Core",
"description": "Interactive demo surface for example workflow packages. Cards, stage diagrams, and API walkthroughs.",
"route": "/s/workflow-demo",
"auth": "authenticated",
"layout": "single",
"permissions": [],
"hooks": ["surface"]
}