diff --git a/state-hub/dashboard/observablehq.config.js b/state-hub/dashboard/observablehq.config.js index 3d994ff..28ff4f4 100644 --- a/state-hub/dashboard/observablehq.config.js +++ b/state-hub/dashboard/observablehq.config.js @@ -10,6 +10,8 @@ export default { name: "Reference", pages: [ { name: "Live Data", path: "/docs/live-data" }, + { name: "Workstreams", path: "/docs/workstreams" }, + { name: "Decisions", path: "/docs/decisions" }, { name: "Decision Health", path: "/docs/decisions-kpi" }, { name: "Progress Log", path: "/docs/progress-log" }, ], diff --git a/state-hub/dashboard/src/decisions.md b/state-hub/dashboard/src/decisions.md index c9dba84..971ca0f 100644 --- a/state-hub/dashboard/src/decisions.md +++ b/state-hub/dashboard/src/decisions.md @@ -160,6 +160,9 @@ const _liveEl = html`
`; withDocHelp(_liveEl, "/docs/live-data"); +const _h1 = document.querySelector("#observablehq-main h1"); +if (_h1) { _h1.style.position = "relative"; withDocHelp(_h1, "/docs/decisions"); } + // ── Inject into TOC sidebar: KPI first (prepend → bottom), live last (→ top) ─ const _toc = document.querySelector("#observablehq-toc"); if (_toc) { diff --git a/state-hub/dashboard/src/docs/decisions.md b/state-hub/dashboard/src/docs/decisions.md new file mode 100644 index 0000000..a55ae35 --- /dev/null +++ b/state-hub/dashboard/src/docs/decisions.md @@ -0,0 +1,138 @@ +--- +title: Decisions — Reference +--- + +# Decisions — Reference + +The Decisions page tracks every choice that matters: things pending a verdict, things already resolved, and things that have been escalated for human approval before action can proceed. + +--- + +## Decision types + +| Type | Meaning | +|---|---| +| **pending** | A question or fork that still needs an answer — actively blocking or influencing work | +| **made** | A resolved choice; kept for historical context and to explain why things are the way they are | + +--- + +## Decision statuses + +| Status | Border | Meaning | +|---|---|---| +| **open** | blue | Pending, no urgent flag | +| **escalated** | amber | Requires human approval before any action (constitution §4) | +| **resolved** | green | Decision has been made and recorded | +| **superseded** | gray | Replaced by a newer decision | + +Decisions are sorted by status (escalated → open → resolved → superseded), then by deadline (earliest first within the same status group). + +--- + +## Resolution History chart + +An area + step chart showing cumulative decisions over time. Each dot marks a period where at least one decision was recorded or resolved. + +**Period selector** — choose the time window: + +| Period | Bucket | Use for | +|---|---|---| +| day | hour | Today's activity | +| week | day | Rolling 7 days | +| month | week | Current calendar month | +| quarter | month | Current quarter | +| YTD | month | Year to date | +| year | month | Rolling 12 months | +| all | month | Full history | + +The chart respects the active filter — changing type, status, or search narrows the data plotted. + +--- + +## Filter bar + +| Filter | Effect | +|---|---| +| **Type** | Show only `pending` or `made` decisions | +| **Status** | Show one or more of open / escalated / resolved / superseded | +| **Search** | Case-insensitive substring match on the decision title | + +All filters are applied client-side on the last 500 decisions fetched from the API. + +--- + +## Decision cards + +Each card shows: + +- **Type badge** — amber `pending` or indigo `made` +- **Status badge** — colour-coded (see table above) +- **Domain** — which of the six tracked domains this decision belongs to (if linked to a topic) +- **Due date** — shown in red with ⚠ if the deadline has passed +- **Age badge** — `open Xd` for unresolved decisions; `took Xd` for resolved ones. Turns amber when an open decision has been open longer than the current average resolution time. +- **Created date** — when the decision was first recorded +- **Title** — the decision question or statement +- **Description / rationale** — first 200 characters, truncated with `…` +- **Resolved by** — who resolved it and when (resolved decisions only) +- **Escalation note** — amber panel explaining why human approval is required (escalated decisions only) + +--- + +## Decision Health card + +The **Decision Health** widget in the right margin shows two KPIs computed from the full (unfiltered) dataset: + +| KPI | How it's calculated | +|---|---| +| **avg resolve** | Mean time from creation to resolution, computed over the last 5 resolved decisions | +| **avg open age** | Mean age of all currently open or escalated decisions | + +**Color coding for avg open age:** + +| Color | Condition | +|---|---| +| black (inherit) | All open decisions younger than the avg resolve time | +| amber | At least one open decision older than the avg resolve time | +| red | Mean open age exceeds the avg resolve time | + +--- + +## Escalation + +Any `pending` decision whose title or rationale contains financial or legal keywords is automatically escalated by the API at creation time. Escalated decisions: + +- Sort to the top of the list +- Carry an amber escalation note explaining the reason +- Trigger the warning box at the bottom of the page listing all escalated items +- **Must be reviewed and approved by Bernd before any related action is taken (constitution §4)** + +To clear an escalation, resolve the decision via `resolve_decision()` in the MCP server or the REST API. + +--- + +## Adding decisions + +Via MCP: + +``` +record_decision( + title = "Should we use Redis or Postgres for session storage?", + decision_type = "pending", + workstream_id = "", + rationale = "Postgres is already in the stack; Redis adds a dependency", + deadline = "2026-03-01" +) +``` + +Via REST: + +```bash +curl -X POST http://127.0.0.1:8000/decisions/ \ + -H "Content-Type: application/json" \ + -d '{"title": "…", "decision_type": "pending", "workstream_id": ""}' +``` + +--- + +*Decisions are never deleted — only resolved or superseded (constitution §5).* diff --git a/state-hub/dashboard/src/docs/workstreams.md b/state-hub/dashboard/src/docs/workstreams.md new file mode 100644 index 0000000..4e3920d --- /dev/null +++ b/state-hub/dashboard/src/docs/workstreams.md @@ -0,0 +1,110 @@ +--- +title: Workstreams — Reference +--- + +# Workstreams — Reference + +A workstream is a bounded unit of work within a topic. It carries a status, an optional owner and due date, and belongs to exactly one of the six project domains. The Workstreams page gives you a filtered, visual overview of all active work and the dependency graph between workstreams. + +--- + +## Status Distribution chart + +A horizontal bar chart showing the count of workstreams in each status for the current filter selection. Updates immediately as filters change. + +| Status | Meaning | +|---|---| +| **active** | Work in progress or ready to start | +| **blocked** | Waiting on something outside the workstream — see Dependencies | +| **completed** | Done | +| **archived** | Closed without completion; no longer relevant | + +--- + +## Filter bar + +| Filter | Effect | +|---|---| +| **Domain** | Multi-select — show only workstreams from selected domains | +| **Status** | Multi-select — show only workstreams with selected statuses | +| **Owner** | Text substring match on the owner field (case-insensitive) | + +Leaving a filter empty means "show all". All three filters combine with AND logic. Filters persist across polls — selections are not lost when the page refreshes live data. + +The six domains are: `custodian`, `railiance`, `markitect`, `coulomb_social`, `personhood`, `foerster_capabilities`. + +--- + +## All Workstreams table + +| Column | Source | +|---|---| +| Title | Workstream title | +| Domain | Derived from the parent topic | +| Status | Current workstream status | +| Owner | Assigned person (or `—` if unset) | +| Due | Target completion date (or `—`) | +| Updated | Last modification timestamp | + +Up to 20 rows displayed; paginate for more. + +--- + +## Dependencies + +The Dependencies section shows workstreams that have at least one `depends_on` or `blocks` relationship. Each card displays: + +- **Workstream title** and current status badge +- **↳ depends on** — workstreams that must complete before this one can proceed +- **⊳ blocks** — workstreams that are waiting on this one + +Dependencies are created via the MCP server: + +``` +create_dependency( + from_workstream_id = "", # the one that depends + to_workstream_id = "", # the prerequisite + description = "needs auth before API can be built" +) +``` + +If no dependency edges exist for the current filter, the section shows an empty-state message. + +--- + +## Creating workstreams + +Via MCP: + +``` +create_workstream( + topic_id = "", + title = "Build user authentication", + description = "JWT-based auth, refresh tokens, middleware", + status = "active", + owner = "Bernd", + due_date = "2026-04-01" +) +``` + +Via REST: + +```bash +curl -X POST http://127.0.0.1:8000/workstreams/ \ + -H "Content-Type: application/json" \ + -d '{"topic_id": "", "title": "…", "status": "active"}' +``` + +--- + +## Updating workstream status + +``` +update_workstream_status(workstream_id="", status="completed") +``` + +Valid transitions: `active` → `blocked` / `completed` / `archived`; `blocked` → `active`; `completed` → `archived`. + +--- + +*Workstreams are never hard-deleted — use `archived` to close them without losing history.* diff --git a/state-hub/dashboard/src/progress.md b/state-hub/dashboard/src/progress.md index 9587976..3b5e5ed 100644 --- a/state-hub/dashboard/src/progress.md +++ b/state-hub/dashboard/src/progress.md @@ -30,8 +30,6 @@ const _ts = progState.ts; # Progress Log -*Append-only per constitution §5 — no deletions.* - ```js import {injectTocTop} from "./components/toc-sidebar.js"; import {withDocHelp} from "./components/doc-overlay.js"; diff --git a/state-hub/dashboard/src/workstreams.md b/state-hub/dashboard/src/workstreams.md index e3c7d02..20856a7 100644 --- a/state-hub/dashboard/src/workstreams.md +++ b/state-hub/dashboard/src/workstreams.md @@ -58,6 +58,9 @@ const _liveEl = html`
`; withDocHelp(_liveEl, "/docs/live-data"); injectTocTop("live-indicator", _liveEl); + +const _h1 = document.querySelector("#observablehq-main h1"); +if (_h1) { _h1.style.position = "relative"; withDocHelp(_h1, "/docs/workstreams"); } ``` ```js