generated from coulomb/repo-seed
Compare commits
26 Commits
a8292b639b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 90e1c86af3 | |||
| d840ee3ab6 | |||
| aa7e35a65a | |||
| 2682d0c21c | |||
| 4d04fe591c | |||
| f69916efd6 | |||
| 9b0e43a8d2 | |||
| 4cb9415bc9 | |||
| cb3182e9fd | |||
| ba2d8e1f53 | |||
| 13003e98dc | |||
| 7cee801f58 | |||
| a412f1430b | |||
| f93f8169e2 | |||
| 59b9913d3a | |||
| 81cb73f593 | |||
| b90320e4e7 | |||
| 9582bf5277 | |||
| f27e1551c9 | |||
| e93145966f | |||
| e558d3deba | |||
| dbab7ac1d7 | |||
| be419ddb80 | |||
| ee7948ba5f | |||
| e8e8187946 | |||
| d6b655a5cf |
50
.claude/rules/credential-routing.md
Normal file
50
.claude/rules/credential-routing.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Credential and access routing
|
||||||
|
|
||||||
|
**Audience:** Codex, Claude Code, Grok, and custodian agents that call **llm-connect**
|
||||||
|
for inference. Run this check **before** requesting secrets, API keys, SSH access,
|
||||||
|
login tokens, or database passwords — in any repo, not only `ops-warden`.
|
||||||
|
|
||||||
|
ops-warden **issues SSH certificates only** (`warden sign`, `cert_command`). Every
|
||||||
|
other credential need belongs to another subsystem. **Do not** message
|
||||||
|
`ops-warden` on State Hub expecting a secret value; the reply is a pointer, not a key.
|
||||||
|
|
||||||
|
### Lookup (do this first)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
warden route find "<describe your need>" --json
|
||||||
|
warden route show <catalog-id> --json
|
||||||
|
```
|
||||||
|
|
||||||
|
Requires the `warden` CLI from `~/ops-warden` (`uv tool install .` or `uv run warden`).
|
||||||
|
|
||||||
|
| Agent runtime | How to orient |
|
||||||
|
| --- | --- |
|
||||||
|
| **Codex / Grok** (shell, HTTP State Hub) | `warden route` commands above; inbox `to_agent=inter-hub` is for coordination, not secret vending |
|
||||||
|
| **Claude Code** (MCP when available) | `get_domain_summary("custodian")` for workstreams; **still** use `warden route` for credential ownership |
|
||||||
|
| **llm-connect** (inference service) | Never put secret retrieval in prompts; route custody to OpenBao/operator paths surfaced by `warden route` |
|
||||||
|
|
||||||
|
### Quick routing table
|
||||||
|
|
||||||
|
| I need… | Owner | ops-warden executes? |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| SSH cert (`adm`/`agt`/`atm`) | ops-warden | **Yes** — `warden sign` |
|
||||||
|
| API key, DB password, provider token | OpenBao (`railiance-platform`) | No — route only |
|
||||||
|
| Login / OIDC / MFA | key-cape / Keycloak | No — route only |
|
||||||
|
| Authorization decision | flex-auth | No — route only |
|
||||||
|
| activity-core → issue-core emission | activity-core + issue-core | No — `warden route show activity-core-issue-sink` |
|
||||||
|
| SSH tunnel | ops-bridge (+ `cert_command` from warden) | No — route only |
|
||||||
|
|
||||||
|
### Anti-patterns (do not do these)
|
||||||
|
|
||||||
|
- `POST /messages/` to `ops-warden` asking for `ISSUE_CORE_API_KEY`, `OPENROUTER_API_KEY`, etc.
|
||||||
|
- Inventing `warden secret`, `warden login`, `warden bao`, `warden tunnel` — they do not exist
|
||||||
|
- Pasting secrets into Git, State Hub, workplans, logs, or chat
|
||||||
|
|
||||||
|
### Other capabilities (reuse-surface)
|
||||||
|
|
||||||
|
Non-credential capabilities are usually discovered through **reuse-surface** federation
|
||||||
|
(`reuse-surface` registry / `capability.*` indexes). Credential routing is inlined in
|
||||||
|
every repo's agent instructions because it is high-frequency, high-risk, and easy to
|
||||||
|
get wrong.
|
||||||
|
|
||||||
|
**Canon:** `~/ops-warden/wiki/CredentialRouting.md` · catalog `~/ops-warden/registry/routing/catalog.yaml`
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
## First Session Protocol
|
## First Session Protocol
|
||||||
|
|
||||||
Triggered when `get_domain_summary("custodian")` shows **no workstreams**.
|
Triggered when `get_domain_summary("infotech")` shows **no workstreams**.
|
||||||
The project is registered but work has not yet been structured.
|
The project is registered but work has not yet been structured.
|
||||||
|
|
||||||
**Step 1 — Read, don't write**
|
**Step 1 — Read, don't write**
|
||||||
- `~/the-custodian/canon/projects/custodian/project_charter_v0.1.md` — purpose, scope
|
- `~/the-custodian/canon/projects/infotech/project_charter_v0.1.md` — purpose, scope
|
||||||
- `~/the-custodian/canon/projects/custodian/roadmap_v0.1.md` — planned phases
|
- `~/the-custodian/canon/projects/infotech/roadmap_v0.1.md` — planned phases
|
||||||
- Scan repo root: README, directory structure, existing code or docs
|
- Scan repo root: README, directory structure, existing code or docs
|
||||||
|
|
||||||
**Step 2 — Survey in-progress work**
|
**Step 2 — Survey in-progress work**
|
||||||
@@ -17,7 +17,7 @@ roadmap phase. **Wait for approval before creating.**
|
|||||||
|
|
||||||
**Step 4 — Create workplan file first, then DB record (ADR-001)**
|
**Step 4 — Create workplan file first, then DB record (ADR-001)**
|
||||||
```
|
```
|
||||||
workplans/inter-hub-WP-NNNN-<slug>.md ← write this first
|
workplans/IHUB-WP-NNNN-<slug>.md ← write this first
|
||||||
```
|
```
|
||||||
Then register in the hub:
|
Then register in the hub:
|
||||||
```
|
```
|
||||||
@@ -28,7 +28,7 @@ create_task(workstream_id="<id>", title="...", priority="high|medium|low")
|
|||||||
**Step 5 — Record the setup**
|
**Step 5 — Record the setup**
|
||||||
```
|
```
|
||||||
add_progress_event(
|
add_progress_event(
|
||||||
summary="First session: structured custodian into N workstreams, M tasks",
|
summary="First session: structured infotech into N workstreams, M tasks",
|
||||||
event_type="milestone",
|
event_type="milestone",
|
||||||
topic_id="cee7bedf-2b48-46ef-8601-006474f2ad7a",
|
topic_id="cee7bedf-2b48-46ef-8601-006474f2ad7a",
|
||||||
detail={"workstreams": [...], "tasks_created": M}
|
detail={"workstreams": [...], "tasks_created": M}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
**Purpose:** Governed, observable interaction substrate for hub-based AI-enabled software systems (IHF specification and reference implementation).
|
**Purpose:** Governed, observable interaction substrate for hub-based AI-enabled software systems (IHF specification and reference implementation).
|
||||||
|
|
||||||
**Domain:** custodian
|
**Domain:** infotech
|
||||||
**Repo slug:** inter-hub
|
**Repo slug:** inter-hub
|
||||||
**Topic ID:** cee7bedf-2b48-46ef-8601-006474f2ad7a
|
**Topic ID:** cee7bedf-2b48-46ef-8601-006474f2ad7a
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
## Session Protocol
|
## Session Protocol
|
||||||
|
|
||||||
State Hub: http://127.0.0.1:8000
|
Dev Hub (State Hub API): http://127.0.0.1:8000
|
||||||
|
MCP server name in `~/.claude.json`: `dev-hub`
|
||||||
|
|
||||||
**Step 1 — Orient**
|
**Step 1 — Orient**
|
||||||
|
|
||||||
@@ -10,7 +11,7 @@ cat .custodian-brief.md
|
|||||||
```
|
```
|
||||||
Then call the MCP tool for richer cross-domain context when MCP tools are exposed:
|
Then call the MCP tool for richer cross-domain context when MCP tools are exposed:
|
||||||
```
|
```
|
||||||
get_domain_summary("custodian")
|
get_domain_summary("infotech")
|
||||||
```
|
```
|
||||||
If MCP tools are unavailable in the current agent session, use the REST API:
|
If MCP tools are unavailable in the current agent session, use the REST API:
|
||||||
```bash
|
```bash
|
||||||
@@ -39,11 +40,11 @@ curl -s -X PATCH "http://127.0.0.1:8000/messages/<id>/read" \
|
|||||||
ls workplans/
|
ls workplans/
|
||||||
```
|
```
|
||||||
For each file with `status: ready`, `active`, or `blocked`, note pending
|
For each file with `status: ready`, `active`, or `blocked`, note pending
|
||||||
`todo`/`in_progress` tasks.
|
`wait`/`todo`/`progress` tasks.
|
||||||
|
|
||||||
**Step 4 — Present brief**
|
**Step 4 — Present brief**
|
||||||
|
|
||||||
1. **Active workstreams** for `custodian` — title, task counts, blocking decisions
|
1. **Active workstreams** for `infotech` — title, task counts, blocking decisions
|
||||||
2. **Pending tasks** from `workplans/` + any `[repo:inter-hub]` hub tasks
|
2. **Pending tasks** from `workplans/` + any `[repo:inter-hub]` hub tasks
|
||||||
3. **Goal guidance** — if `goal_guidance` in summary:
|
3. **Goal guidance** — if `goal_guidance` in summary:
|
||||||
- `needs_workplan`: surface as top action — *"Repo goal '{title}' has no workplan yet"*
|
- `needs_workplan`: surface as top action — *"Repo goal '{title}' has no workplan yet"*
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
## Workplan Convention (ADR-001)
|
## Workplan Convention (ADR-001)
|
||||||
|
|
||||||
File location: `workplans/inter-hub-WP-NNNN-<slug>.md`
|
File location: `workplans/IHUB-WP-NNNN-<slug>.md`
|
||||||
ID prefix: `INTER-WP`
|
ID prefix: `IHUB-WP-`
|
||||||
|
|
||||||
Work items originate as files in this repo **before** being registered in the hub.
|
Work items originate as files in this repo **before** being registered in the hub.
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ repo state, and `finished` when implementation is complete. `stalled` and
|
|||||||
`needs_review` are derived health labels, not stored statuses.
|
`needs_review` are derived health labels, not stored statuses.
|
||||||
|
|
||||||
Closed workplans may be moved to `workplans/archived/` with a completion-date
|
Closed workplans may be moved to `workplans/archived/` with a completion-date
|
||||||
prefix: `YYMMDD-inter-hub-WP-NNNN-<slug>.md`. The frontmatter id remains
|
prefix: `YYMMDD-IHUB-WP-NNNN-<slug>.md`. The frontmatter id remains
|
||||||
unchanged; the prefix is only for quick visual reference.
|
unchanged; the prefix is only for quick visual reference.
|
||||||
|
|
||||||
Small opportunistic tasks discovered during another session use **Ad Hoc Tasks**:
|
Small opportunistic tasks discovered during another session use **Ad Hoc Tasks**:
|
||||||
@@ -25,4 +25,16 @@ Ecosystem todos from other agents arrive as `[repo:inter-hub]` hub tasks —
|
|||||||
visible at session start. Pick one up by creating the workplan file, then registering
|
visible at session start. Pick one up by creating the workplan file, then registering
|
||||||
the workstream.
|
the workstream.
|
||||||
|
|
||||||
|
Task blocks use this shape:
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: IHUB-WP-NNNN-T01
|
||||||
|
status: wait | todo | progress | done | cancel
|
||||||
|
priority: high | medium | low
|
||||||
|
state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
|
||||||
|
```
|
||||||
|
|
||||||
|
Status progression is `todo` → `progress` → `done`; use `wait` for waiting or
|
||||||
|
blocked work and `cancel` for stopped work.
|
||||||
|
|
||||||
<!-- Ralph Loop rules and HEUREKA sequence: ~/.claude/CLAUDE.md — do not duplicate here -->
|
<!-- Ralph Loop rules and HEUREKA sequence: ~/.claude/CLAUDE.md — do not duplicate here -->
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<!-- custodian-brief: generated by fix-consistency — do not edit manually -->
|
<!-- custodian-brief: generated by fix-consistency — do not edit manually -->
|
||||||
# Custodian Brief — inter-hub
|
# Custodian Brief — inter-hub
|
||||||
|
|
||||||
**Domain:** inter_hub
|
**Domain:** infotech
|
||||||
**Last synced:** 2026-06-21 14:07 UTC
|
**Last synced:** 2026-07-03 16:58 UTC
|
||||||
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
|
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
|
||||||
|
|
||||||
## Current Goal
|
## Current Goal
|
||||||
@@ -11,38 +11,12 @@ IHF Phase 1 complete — Phase 2 ready to start
|
|||||||
|
|
||||||
## Active Workstreams
|
## Active Workstreams
|
||||||
|
|
||||||
### Personal Dashboard Implementation
|
*(none — repo may need first-session setup)*
|
||||||
Progress: 0/12 done | workstream_id: `79f72176-fb3f-4d59-9678-d42f5ff1e679`
|
|
||||||
|
|
||||||
**Open tasks:**
|
|
||||||
- · T01 - Add personal dashboard schema `bb7366a3`
|
|
||||||
- · T02 - Seed panel types and framework panel vocabulary `d298eab2`
|
|
||||||
- · T03 - Add controller skeleton, routes, and default dashboard helper `8a171c71`
|
|
||||||
- · T04 - Implement first three panel view models/renderers `012dcd2a`
|
|
||||||
- · T05 - Implement dashboard show view and responsive grid `b4c4de39`
|
|
||||||
- · T06 - Implement remaining first-slice panel view models/renderers `8d0bd046`
|
|
||||||
- · T07 - Implement edit flow `51a72b56`
|
|
||||||
- … and 5 more open tasks
|
|
||||||
|
|
||||||
### Ops Hub Evidence Intake for Activity Core
|
|
||||||
Progress: 5/8 done | workstream_id: `bd086c41-287d-4a4e-8ac5-9ab270f14d72`
|
|
||||||
|
|
||||||
**Open tasks:**
|
|
||||||
- ! T03 - Prepare manifest vocabulary and seed widgets `94fc9806`
|
|
||||||
- ! T04 - Provision the runtime API key outside Git `267db6a7`
|
|
||||||
- ! T07 - Run end-to-end Inter-Hub submission smoke `23baee9b`
|
|
||||||
|
|
||||||
### Ad hoc Inter-Hub production fixes
|
|
||||||
Progress: 0/1 done | workstream_id: `9e7a50b4-da7f-4df9-9154-7b89a071f520`
|
|
||||||
|
|
||||||
**Open tasks:**
|
|
||||||
- ! Fix COUNT decode failures in v2 bootstrap endpoints `cceee9f1`
|
|
||||||
*(wait: Protected production acceptance requires an approved operator/runtime key handoff or operator-provided deploy/smoke evidence; no approved key is available in this session and unauthenticated registry manifest checks return 401.)*
|
|
||||||
|
|
||||||
---
|
---
|
||||||
## MCP Orientation (when available)
|
## MCP Orientation (when available)
|
||||||
|
|
||||||
If the state-hub MCP server is reachable, call:
|
If the state-hub MCP server is reachable, call:
|
||||||
`get_domain_summary("inter_hub")`
|
`get_domain_summary("infotech")`
|
||||||
This provides richer cross-domain context.
|
This provides richer cross-domain context.
|
||||||
If the MCP call fails, use this file as your orientation source.
|
If the MCP call fails, use this file as your orientation source.
|
||||||
|
|||||||
27
.repo-classification.yaml
Normal file
27
.repo-classification.yaml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Repo classification (Repo Classification Standard v1.0).
|
||||||
|
|
||||||
|
repo_classification:
|
||||||
|
standard: Repo Classification Standard
|
||||||
|
version: '1.0'
|
||||||
|
classified_at: '2026-06-22'
|
||||||
|
classified_by: human
|
||||||
|
category: research
|
||||||
|
domain: infotech
|
||||||
|
secondary_domains:
|
||||||
|
- agents
|
||||||
|
capability_tags:
|
||||||
|
- governance
|
||||||
|
- observability
|
||||||
|
- platform
|
||||||
|
- coordination
|
||||||
|
- orchestration
|
||||||
|
business_stake:
|
||||||
|
- technology
|
||||||
|
- intelligence
|
||||||
|
- operations
|
||||||
|
business_mechanics:
|
||||||
|
- control
|
||||||
|
- coordination
|
||||||
|
- adaptation
|
||||||
|
notes: Specification + reference implementation of the Interaction Hub Framework (IHF).
|
||||||
|
Core output is the governed framework/substrate, so classified research.
|
||||||
73
AGENTS.md
73
AGENTS.md
@@ -4,10 +4,10 @@
|
|||||||
|
|
||||||
**Purpose:** Governed, observable interaction substrate for hub-based AI-enabled software systems (IHF specification and reference implementation).
|
**Purpose:** Governed, observable interaction substrate for hub-based AI-enabled software systems (IHF specification and reference implementation).
|
||||||
|
|
||||||
**Domain:** custodian
|
**Domain:** infotech
|
||||||
**Repo slug:** inter-hub
|
**Repo slug:** inter-hub
|
||||||
**Topic ID:** `cee7bedf-2b48-46ef-8601-006474f2ad7a`
|
**Topic ID:** `cee7bedf-2b48-46ef-8601-006474f2ad7a`
|
||||||
**Workplan prefix:** `INTER-WP-`
|
**Workplan prefix:** `IHUB-WP-`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -63,8 +63,8 @@ Omit `workstream_id` / `task_id` when not applicable.
|
|||||||
```bash
|
```bash
|
||||||
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
|
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"status": "in_progress"}'
|
-d '{"status": "progress"}'
|
||||||
# values: todo | in_progress | done | blocked
|
# values: wait | todo | progress | done | cancel
|
||||||
```
|
```
|
||||||
|
|
||||||
### Flag a task for human review
|
### Flag a task for human review
|
||||||
@@ -83,7 +83,7 @@ curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
|
|||||||
1. `cat .custodian-brief.md` — domain goal and open workstreams (offline-safe)
|
1. `cat .custodian-brief.md` — domain goal and open workstreams (offline-safe)
|
||||||
2. Check inbox: `GET /messages/?to_agent=inter-hub&unread_only=true`; mark read
|
2. Check inbox: `GET /messages/?to_agent=inter-hub&unread_only=true`; mark read
|
||||||
3. Scan workplans: `ls workplans/` — note `status: ready`, `active`, or `blocked` files and open tasks
|
3. Scan workplans: `ls workplans/` — note `status: ready`, `active`, or `blocked` files and open tasks
|
||||||
4. Check blocked tasks: `GET /tasks/?needs_human=true`
|
4. Check human-needed tasks: `GET /tasks/?needs_human=true`
|
||||||
|
|
||||||
**During work:**
|
**During work:**
|
||||||
- Update task statuses in workplan files as tasks progress
|
- Update task statuses in workplan files as tasks progress
|
||||||
@@ -101,6 +101,63 @@ curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Credential and access routing
|
||||||
|
|
||||||
|
**Audience:** Codex, Claude Code, Grok, and custodian agents that call **llm-connect**
|
||||||
|
for inference. Run this check **before** requesting secrets, API keys, SSH access,
|
||||||
|
login tokens, or database passwords — in any repo, not only `ops-warden`.
|
||||||
|
|
||||||
|
ops-warden **issues SSH certificates only** (`warden sign`, `cert_command`). Every
|
||||||
|
other credential need belongs to another subsystem. **Do not** message
|
||||||
|
`ops-warden` on State Hub expecting a secret value; the reply is a pointer, not a key.
|
||||||
|
|
||||||
|
### Lookup (do this first)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
warden route find "<describe your need>" --json
|
||||||
|
warden route show <catalog-id> --json
|
||||||
|
```
|
||||||
|
|
||||||
|
Requires the `warden` CLI from `~/ops-warden` (`uv tool install .` or `uv run warden`).
|
||||||
|
|
||||||
|
| Agent runtime | How to orient |
|
||||||
|
| --- | --- |
|
||||||
|
| **Codex / Grok** (shell, HTTP State Hub) | `warden route` commands above; inbox `to_agent=inter-hub` is for coordination, not secret vending |
|
||||||
|
| **Claude Code** (MCP when available) | `get_domain_summary("custodian")` for workstreams; **still** use `warden route` for credential ownership |
|
||||||
|
| **llm-connect** (inference service) | Never put secret retrieval in prompts; route custody to OpenBao/operator paths surfaced by `warden route` |
|
||||||
|
|
||||||
|
### Quick routing table
|
||||||
|
|
||||||
|
| I need… | Owner | ops-warden executes? |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| SSH cert (`adm`/`agt`/`atm`) | ops-warden | **Yes** — `warden sign` |
|
||||||
|
| API key, DB password, provider token | OpenBao (`railiance-platform`) | No — route only |
|
||||||
|
| Login / OIDC / MFA | key-cape / Keycloak | No — route only |
|
||||||
|
| Authorization decision | flex-auth | No — route only |
|
||||||
|
| activity-core → issue-core emission | activity-core + issue-core | No — `warden route show activity-core-issue-sink` |
|
||||||
|
| SSH tunnel | ops-bridge (+ `cert_command` from warden) | No — route only |
|
||||||
|
|
||||||
|
### Anti-patterns (do not do these)
|
||||||
|
|
||||||
|
- `POST /messages/` to `ops-warden` asking for `ISSUE_CORE_API_KEY`, `OPENROUTER_API_KEY`, etc.
|
||||||
|
- Inventing `warden secret`, `warden login`, `warden bao`, `warden tunnel` — they do not exist
|
||||||
|
- Pasting secrets into Git, State Hub, workplans, logs, or chat
|
||||||
|
|
||||||
|
### Other capabilities (reuse-surface)
|
||||||
|
|
||||||
|
Non-credential capabilities are usually discovered through **reuse-surface** federation
|
||||||
|
(`reuse-surface` registry / `capability.*` indexes). Credential routing is inlined in
|
||||||
|
every repo's agent instructions because it is high-frequency, high-risk, and easy to
|
||||||
|
get wrong.
|
||||||
|
|
||||||
|
**Canon:** `~/ops-warden/wiki/CredentialRouting.md` · catalog `~/ops-warden/registry/routing/catalog.yaml`
|
||||||
|
|
||||||
|
<!-- REPO-AGENTS-EXTENSIONS -->
|
||||||
|
<!-- Append repo-specific agent instructions below this marker.
|
||||||
|
The state-hub template sync preserves content after this line. -->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Workplan Convention (ADR-001)
|
## Workplan Convention (ADR-001)
|
||||||
|
|
||||||
Work items originate as files in this repo — not in the hub. The hub is a
|
Work items originate as files in this repo — not in the hub. The hub is a
|
||||||
@@ -124,7 +181,7 @@ anything needing analysis, design, approval, dependencies, or multiple phases.
|
|||||||
id: INTER-WP-NNNN
|
id: INTER-WP-NNNN
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "..."
|
title: "..."
|
||||||
domain: custodian
|
domain: infotech
|
||||||
repo: inter-hub
|
repo: inter-hub
|
||||||
status: proposed | ready | active | blocked | backlog | finished | archived
|
status: proposed | ready | active | blocked | backlog | finished | archived
|
||||||
owner: codex
|
owner: codex
|
||||||
@@ -146,7 +203,7 @@ derived health labels, not frontmatter statuses.
|
|||||||
|
|
||||||
` ` `task
|
` ` `task
|
||||||
id: INTER-WP-NNNN-T01
|
id: INTER-WP-NNNN-T01
|
||||||
status: todo | in_progress | done | blocked
|
status: wait | todo | progress | done | cancel
|
||||||
priority: high | medium | low
|
priority: high | medium | low
|
||||||
state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
|
state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
|
||||||
` ` `
|
` ` `
|
||||||
@@ -154,7 +211,7 @@ state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
|
|||||||
Task description text.
|
Task description text.
|
||||||
```
|
```
|
||||||
|
|
||||||
Status progression: `todo` → `in_progress` → `done` (or `blocked`)
|
Status progression: `todo` → `progress` → `done`; use `wait` for waiting/blocked work and `cancel` for stopped work.
|
||||||
|
|
||||||
To create a new workplan:
|
To create a new workplan:
|
||||||
1. Write the file following the format above
|
1. Write the file following the format above
|
||||||
|
|||||||
@@ -8,4 +8,5 @@
|
|||||||
@.claude/rules/stack-and-commands.md
|
@.claude/rules/stack-and-commands.md
|
||||||
@.claude/rules/architecture.md
|
@.claude/rules/architecture.md
|
||||||
@.claude/rules/repo-boundary.md
|
@.claude/rules/repo-boundary.md
|
||||||
|
@.claude/rules/credential-routing.md
|
||||||
@.claude/rules/agents.md
|
@.claude/rules/agents.md
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Ops Hub Activity-Core Event Payloads
|
# Ops Hub Activity-Core Event Payloads
|
||||||
|
|
||||||
Date: 2026-06-15
|
Date: 2026-06-15
|
||||||
|
Updated: 2026-06-27
|
||||||
|
|
||||||
Workplan: `IHUB-WP-0022`
|
Workplan: `IHUB-WP-0022`
|
||||||
|
|
||||||
@@ -33,6 +34,28 @@ Each request body must use the Inter-Hub v2 interaction-event shape:
|
|||||||
Inter-Hub sets `occurredAt` on receipt. Activity-core must send the actual
|
Inter-Hub sets `occurredAt` on receipt. Activity-core must send the actual
|
||||||
probe timestamp as `metadata.attributes.observed_at`.
|
probe timestamp as `metadata.attributes.observed_at`.
|
||||||
|
|
||||||
|
## Live Vocabulary Alignment
|
||||||
|
|
||||||
|
Use event names declared by the live ops-hub manifest:
|
||||||
|
|
||||||
|
- `ops-service-discovered`
|
||||||
|
- `ops-endpoint-verified`
|
||||||
|
- `ops-backup-verified`
|
||||||
|
- `ops-drift-detected`
|
||||||
|
|
||||||
|
Transitional aliases may be accepted in local mapping logic but should not be
|
||||||
|
submitted to Inter-Hub:
|
||||||
|
|
||||||
|
| Earlier activity-core name | Live event name |
|
||||||
|
| --- | --- |
|
||||||
|
| `ops-service-observed` | `ops-service-discovered` |
|
||||||
|
| `ops-inventory-drift` | `ops-drift-detected` |
|
||||||
|
|
||||||
|
`ops-access-path-checked` is deferred because the live manifest has no
|
||||||
|
access-path event type or access-path widget type. Keep State Hub fallback
|
||||||
|
posting for access-path evidence until ops-hub adds that vocabulary or an
|
||||||
|
operator decision maps it to `ops-readiness-gate-updated` or `ops-risk-raised`.
|
||||||
|
|
||||||
## Shared Rules
|
## Shared Rules
|
||||||
|
|
||||||
- `widgetId` must be a UUID for an existing ops-hub widget.
|
- `widgetId` must be a UUID for an existing ops-hub widget.
|
||||||
@@ -41,7 +64,7 @@ probe timestamp as `metadata.attributes.observed_at`.
|
|||||||
be declared by that manifest.
|
be declared by that manifest.
|
||||||
- `viewContext` should be `ops-inventory-probe` unless a more specific context
|
- `viewContext` should be `ops-inventory-probe` unless a more specific context
|
||||||
is useful, such as `ops-inventory-probe/endpoints`.
|
is useful, such as `ops-inventory-probe/endpoints`.
|
||||||
- `metadata.type` must match the Inter-Hub `eventType`.
|
- `metadata.type` must match the Inter-Hub `eventType` after alias translation.
|
||||||
- `metadata.version` must match the activity-core event definition version.
|
- `metadata.version` must match the activity-core event definition version.
|
||||||
- `metadata.publisher` must be `activity-core`.
|
- `metadata.publisher` must be `activity-core`.
|
||||||
- `metadata.attributes.idempotency_key` is required, even though Inter-Hub does
|
- `metadata.attributes.idempotency_key` is required, even though Inter-Hub does
|
||||||
@@ -69,26 +92,27 @@ Use `reason` for compact machine-readable explanations, for example:
|
|||||||
- `backup_probe_not_implemented`
|
- `backup_probe_not_implemented`
|
||||||
- `missing_endpoint`
|
- `missing_endpoint`
|
||||||
|
|
||||||
## Example: Service Observed
|
## Example: Service Discovered
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"widgetId": "<service-widget-uuid>",
|
"widgetId": "<service-or-catalog-widget-uuid>",
|
||||||
"eventType": "ops-service-observed",
|
"eventType": "ops-service-discovered",
|
||||||
"viewContext": "ops-inventory-probe/services",
|
"viewContext": "ops-inventory-probe/services",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"type": "ops-service-observed",
|
"type": "ops-service-discovered",
|
||||||
"version": "1.0",
|
"version": "1.0",
|
||||||
"publisher": "activity-core",
|
"publisher": "activity-core",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"activity_core_run_id": "12345678-aaaa-bbbb-cccc-123456789abc",
|
"activity_core_run_id": "12345678-aaaa-bbbb-cccc-123456789abc",
|
||||||
"idempotency_key": "12345678-aaaa-bbbb-cccc-123456789abc:state-hub:ops-service-observed",
|
"idempotency_key": "12345678-aaaa-bbbb-cccc-123456789abc:state-hub:ops-service-discovered",
|
||||||
"service_id": "state-hub",
|
"service_id": "state-hub",
|
||||||
"service_name": "State Hub",
|
"service_name": "State Hub",
|
||||||
"environment": "local",
|
"environment": "local",
|
||||||
"lifecycle_state": "observed",
|
"lifecycle_state": "discovered",
|
||||||
"observed_status": "ok",
|
"observed_status": "ok",
|
||||||
"observed_at": "2026-06-05T10:15:01Z"
|
"observed_at": "2026-06-05T10:15:01Z",
|
||||||
|
"widget_ref": "ops:service:state-hub"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,37 +148,26 @@ Use `reason` for compact machine-readable explanations, for example:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Example: Access Path Checked
|
## Deferred: Access Path Checked
|
||||||
|
|
||||||
```json
|
Do not submit `ops-access-path-checked` to Inter-Hub while the live ops-hub
|
||||||
{
|
manifest lacks that event type and a matching widget type. Activity-core should
|
||||||
"widgetId": "<access-path-widget-uuid>",
|
continue to include compact access-path detail in State Hub fallback summaries
|
||||||
"eventType": "ops-access-path-checked",
|
and return a non-secret deferred sink result for Inter-Hub.
|
||||||
"viewContext": "ops-inventory-probe/access-paths",
|
|
||||||
"metadata": {
|
If the operator chooses to represent access-path state as readiness or risk,
|
||||||
"type": "ops-access-path-checked",
|
create a separate mapping decision and send the supported event type instead of
|
||||||
"version": "1.0",
|
silently rewriting access-path evidence.
|
||||||
"publisher": "activity-core",
|
|
||||||
"attributes": {
|
|
||||||
"activity_core_run_id": "12345678-aaaa-bbbb-cccc-123456789abc",
|
|
||||||
"idempotency_key": "12345678-aaaa-bbbb-cccc-123456789abc:gitea:gitea-access-1:ops-access-path-checked",
|
|
||||||
"service_id": "gitea",
|
|
||||||
"access_path_id": "gitea-access-1",
|
|
||||||
"access_path_type": "k8s",
|
|
||||||
"declared_status": "unknown",
|
|
||||||
"observed_status": "skipped",
|
|
||||||
"reason": "unsupported_access_path_type",
|
|
||||||
"observed_at": "2026-06-05T10:15:01Z"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Example: Backup Verified
|
## Example: Backup Verified
|
||||||
|
|
||||||
|
This event is declared by the live manifest, but it needs a target
|
||||||
|
`ops-backup-set` widget such as `ops:backup-set:aggregate` before the smoke can
|
||||||
|
submit it.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"widgetId": "<backup-widget-uuid>",
|
"widgetId": "<backup-set-widget-uuid>",
|
||||||
"eventType": "ops-backup-verified",
|
"eventType": "ops-backup-verified",
|
||||||
"viewContext": "ops-inventory-probe/backups",
|
"viewContext": "ops-inventory-probe/backups",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
@@ -170,26 +183,27 @@ Use `reason` for compact machine-readable explanations, for example:
|
|||||||
"restore_verified": false,
|
"restore_verified": false,
|
||||||
"observed_status": "skipped",
|
"observed_status": "skipped",
|
||||||
"reason": "backup_probe_not_implemented",
|
"reason": "backup_probe_not_implemented",
|
||||||
"observed_at": "2026-06-05T10:15:01Z"
|
"observed_at": "2026-06-05T10:15:01Z",
|
||||||
|
"widget_ref": "ops:backup-set:aggregate"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Example: Inventory Drift
|
## Example: Drift Detected
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"widgetId": "<drift-widget-uuid>",
|
"widgetId": "<readiness-or-risk-widget-uuid>",
|
||||||
"eventType": "ops-inventory-drift",
|
"eventType": "ops-drift-detected",
|
||||||
"viewContext": "ops-inventory-probe/drift",
|
"viewContext": "ops-inventory-probe/drift",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"type": "ops-inventory-drift",
|
"type": "ops-drift-detected",
|
||||||
"version": "1.0",
|
"version": "1.0",
|
||||||
"publisher": "activity-core",
|
"publisher": "activity-core",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"activity_core_run_id": "12345678-aaaa-bbbb-cccc-123456789abc",
|
"activity_core_run_id": "12345678-aaaa-bbbb-cccc-123456789abc",
|
||||||
"idempotency_key": "12345678-aaaa-bbbb-cccc-123456789abc:gitea:gitea-oci-registry:ops-inventory-drift",
|
"idempotency_key": "12345678-aaaa-bbbb-cccc-123456789abc:gitea:gitea-oci-registry:ops-drift-detected",
|
||||||
"service_id": "gitea",
|
"service_id": "gitea",
|
||||||
"inventory_object_id": "gitea-oci-registry",
|
"inventory_object_id": "gitea-oci-registry",
|
||||||
"drift_kind": "status_mismatch",
|
"drift_kind": "status_mismatch",
|
||||||
@@ -197,7 +211,8 @@ Use `reason` for compact machine-readable explanations, for example:
|
|||||||
"observed_summary": "status_code=200",
|
"observed_summary": "status_code=200",
|
||||||
"observed_status": "degraded",
|
"observed_status": "degraded",
|
||||||
"reason": "expected_status_mismatch",
|
"reason": "expected_status_mismatch",
|
||||||
"observed_at": "2026-06-05T10:15:01Z"
|
"observed_at": "2026-06-05T10:15:01Z",
|
||||||
|
"widget_ref": "ops:readiness:gitea-registry"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,12 +223,12 @@ Use `reason` for compact machine-readable explanations, for example:
|
|||||||
Activity-core should treat these as configuration or rollout errors:
|
Activity-core should treat these as configuration or rollout errors:
|
||||||
|
|
||||||
| Error | Meaning | Recovery |
|
| Error | Meaning | Recovery |
|
||||||
|---|---|---|
|
| --- | --- | --- |
|
||||||
| `401` | Missing or invalid `OPS_HUB_KEY` | Check Secret provisioning; do not log the key. |
|
| `401` | Missing or invalid `OPS_HUB_KEY` | Check Secret provisioning; do not log the key. |
|
||||||
| `422` with `unregistered_event_type` | Event type not in Inter-Hub registry | Activate the ops-hub manifest vocabulary. |
|
| `422` with `unregistered_event_type` | Event type not in Inter-Hub registry | Use a live manifest event or activate a corrected ops-hub manifest. |
|
||||||
| `422` with `event_type_not_in_manifest` | Runtime consumer manifest does not declare the event | Bind the consumer to the active manifest or activate a corrected manifest. |
|
| `422` with `event_type_not_in_manifest` | Runtime consumer manifest does not declare the event | Bind the consumer to the active manifest or activate a corrected manifest. |
|
||||||
| `422` with `Widget not found` | Mapping points at a missing widget | Refresh `OPS_HUB_WIDGET_MAPPING`. |
|
| `422` with `Widget not found` | Mapping points at a missing widget | Refresh `OPS_HUB_WIDGET_MAPPING` from authenticated widget lookup. |
|
||||||
| `422` with `unregistered_policy_scope` during widget seed | Policy scope is absent | Declare and activate `ops-evidence`. |
|
| `422` with `unregistered_policy_scope` during widget seed | Policy scope is absent | Use one of the declared ops scopes, not the old `ops-evidence` proposal. |
|
||||||
|
|
||||||
For the first activity-core slice, a failed Inter-Hub submission should not
|
For the first activity-core slice, a failed Inter-Hub submission should not
|
||||||
fail the whole probe if State Hub fallback posting succeeds. It should return a
|
fail the whole probe if State Hub fallback posting succeeds. It should return a
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Ops Hub Activity-Core Widget Mapping
|
# Ops Hub Activity-Core Widget Mapping
|
||||||
|
|
||||||
Date: 2026-06-15
|
Date: 2026-06-15
|
||||||
|
Updated: 2026-06-27
|
||||||
|
|
||||||
Workplan: `IHUB-WP-0022`
|
Workplan: `IHUB-WP-0022`
|
||||||
|
|
||||||
@@ -8,8 +9,21 @@ Workplan: `IHUB-WP-0022`
|
|||||||
|
|
||||||
`OPS_HUB_WIDGET_MAPPING` tells activity-core which Inter-Hub widget receives
|
`OPS_HUB_WIDGET_MAPPING` tells activity-core which Inter-Hub widget receives
|
||||||
each ops evidence event. The value must be non-secret JSON. It may contain
|
each ops evidence event. The value must be non-secret JSON. It may contain
|
||||||
Inter-Hub widget UUIDs and logical references, but it must never contain
|
Inter-Hub widget UUIDs and stable logical refs, but it must never contain
|
||||||
`OPS_HUB_KEY` or any operator credential.
|
`OPS_HUB_KEY`, bearer tokens, cookies, or any operator credential.
|
||||||
|
|
||||||
|
This revision aligns the contract to the live `ops-hub` manifest vocabulary
|
||||||
|
observed on 2026-06-27 and to the source seed files in `ops-hub`:
|
||||||
|
|
||||||
|
- event types use the live registry names such as `ops-service-discovered` and
|
||||||
|
`ops-drift-detected`;
|
||||||
|
- widget types use live seed types such as `ops-service`, `ops-endpoint`,
|
||||||
|
`ops-backup-set`, `ops-readiness-gate`, and `ops-risk`;
|
||||||
|
- policy scopes use the declared ops scopes; there is no `ops-evidence` scope
|
||||||
|
in the live manifest;
|
||||||
|
- access-path evidence remains deferred until ops-hub explicitly adds an
|
||||||
|
access-path event and widget type or activity-core maps it to a supported
|
||||||
|
readiness/risk event by decision.
|
||||||
|
|
||||||
Activity-core currently only checks that a mapping value is present before
|
Activity-core currently only checks that a mapping value is present before
|
||||||
returning `inter_hub_sink_deferred`. This document defines the contract that
|
returning `inter_hub_sink_deferred`. This document defines the contract that
|
||||||
@@ -17,6 +31,9 @@ the future Inter-Hub submission implementation should parse.
|
|||||||
|
|
||||||
## Versioned Shape
|
## Versioned Shape
|
||||||
|
|
||||||
|
The shape remains version `v1`; this is a compatibility revision of the
|
||||||
|
vocabulary inside the mapping, not a structural breaking change.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"version": "ops-hub.activity-core.widget-mapping.v1",
|
"version": "ops-hub.activity-core.widget-mapping.v1",
|
||||||
@@ -24,87 +41,109 @@ the future Inter-Hub submission implementation should parse.
|
|||||||
"slug": "ops-hub",
|
"slug": "ops-hub",
|
||||||
"id": "<ops-hub-uuid>"
|
"id": "<ops-hub-uuid>"
|
||||||
},
|
},
|
||||||
"policyScope": "ops-evidence",
|
|
||||||
"defaultViewContext": "ops-inventory-probe",
|
"defaultViewContext": "ops-inventory-probe",
|
||||||
|
"allowedPolicyScopes": [
|
||||||
|
"ops-local",
|
||||||
|
"ops-transitional-prod",
|
||||||
|
"ops-production",
|
||||||
|
"ops-threephoenix",
|
||||||
|
"ops-registry",
|
||||||
|
"ops-secrets",
|
||||||
|
"ops-backup-retention"
|
||||||
|
],
|
||||||
|
"eventAliases": {
|
||||||
|
"ops-service-observed": "ops-service-discovered",
|
||||||
|
"ops-inventory-drift": "ops-drift-detected"
|
||||||
|
},
|
||||||
|
"deferredEvents": {
|
||||||
|
"ops-access-path-checked": {
|
||||||
|
"reason": "not_declared_by_live_ops_hub_manifest",
|
||||||
|
"fallback": "state-hub-progress",
|
||||||
|
"decisionNeeded": "add ops-access-path vocabulary or map access-path evidence to ops-readiness-gate-updated/ops-risk-raised"
|
||||||
|
}
|
||||||
|
},
|
||||||
"events": {
|
"events": {
|
||||||
"ops-service-observed": {
|
"ops-service-discovered": {
|
||||||
"family": "services",
|
"family": "services",
|
||||||
"aggregateWidgetRef": "ops:service:aggregate"
|
"selector": "service_id",
|
||||||
|
"defaultWidgetRef": "ops:service-catalog"
|
||||||
},
|
},
|
||||||
"ops-endpoint-verified": {
|
"ops-endpoint-verified": {
|
||||||
"family": "endpoints",
|
"family": "endpoints",
|
||||||
"aggregateWidgetRef": "ops:endpoint:aggregate"
|
"selector": "<service_id>:<endpoint_id>",
|
||||||
},
|
"defaultWidgetRef": "ops:endpoint:gitea-registry"
|
||||||
"ops-access-path-checked": {
|
|
||||||
"family": "accessPaths",
|
|
||||||
"aggregateWidgetRef": "ops:access-path:aggregate"
|
|
||||||
},
|
},
|
||||||
"ops-backup-verified": {
|
"ops-backup-verified": {
|
||||||
"family": "backups",
|
"family": "backups",
|
||||||
"aggregateWidgetRef": "ops:backup:aggregate"
|
"selector": "<service_id>:<backing_store_ref>",
|
||||||
|
"defaultWidgetRef": "ops:backup-set:aggregate",
|
||||||
|
"requiresSeed": true
|
||||||
},
|
},
|
||||||
"ops-inventory-drift": {
|
"ops-drift-detected": {
|
||||||
"family": "drift",
|
"family": "readiness",
|
||||||
"aggregateWidgetRef": "ops:drift:aggregate"
|
"selector": "<service_id>:<inventory_object_id>",
|
||||||
|
"defaultWidgetRef": "ops:readiness:gitea-registry"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"widgets": {
|
"widgets": {
|
||||||
"aggregate": {
|
"byRef": {
|
||||||
"ops:service:aggregate": {
|
"ops:service-catalog": {
|
||||||
"widgetId": "<uuid>",
|
"widgetId": "<uuid>",
|
||||||
"widgetType": "ops-service-card",
|
"widgetType": "ops-service-catalog",
|
||||||
"name": "Ops Service Evidence Intake"
|
"policyScope": "ops-production",
|
||||||
|
"name": "Operations Service Catalog"
|
||||||
},
|
},
|
||||||
"ops:endpoint:aggregate": {
|
"ops:service:state-hub": {
|
||||||
"widgetId": "<uuid>",
|
"widgetId": "<uuid>",
|
||||||
"widgetType": "ops-endpoint-card",
|
"widgetType": "ops-service",
|
||||||
"name": "Ops Endpoint Evidence Intake"
|
"policyScope": "ops-local",
|
||||||
|
"name": "State Hub Service"
|
||||||
},
|
},
|
||||||
"ops:access-path:aggregate": {
|
"ops:service:gitea": {
|
||||||
"widgetId": "<uuid>",
|
"widgetId": "<uuid>",
|
||||||
"widgetType": "ops-access-path-card",
|
"widgetType": "ops-service",
|
||||||
"name": "Ops Access Path Evidence Intake"
|
"policyScope": "ops-transitional-prod",
|
||||||
|
"name": "Gitea Service"
|
||||||
},
|
},
|
||||||
"ops:backup:aggregate": {
|
"ops:service:inter-hub": {
|
||||||
"widgetId": "<uuid>",
|
"widgetId": "<uuid>",
|
||||||
"widgetType": "ops-backup-card",
|
"widgetType": "ops-service",
|
||||||
"name": "Ops Backup Evidence Intake"
|
"policyScope": "ops-production",
|
||||||
|
"name": "Inter-Hub Service"
|
||||||
},
|
},
|
||||||
"ops:drift:aggregate": {
|
"ops:endpoint:gitea-registry": {
|
||||||
"widgetId": "<uuid>",
|
"widgetId": "<uuid>",
|
||||||
"widgetType": "ops-drift-card",
|
"widgetType": "ops-endpoint",
|
||||||
"name": "Ops Inventory Drift Evidence Intake"
|
"policyScope": "ops-registry",
|
||||||
|
"name": "Gitea Registry Endpoint"
|
||||||
|
},
|
||||||
|
"ops:readiness:gitea-registry": {
|
||||||
|
"widgetId": "<uuid>",
|
||||||
|
"widgetType": "ops-readiness-gate",
|
||||||
|
"policyScope": "ops-registry",
|
||||||
|
"name": "Gitea Registry Readiness"
|
||||||
|
},
|
||||||
|
"ops:backup-set:aggregate": {
|
||||||
|
"widgetId": "<uuid>",
|
||||||
|
"widgetType": "ops-backup-set",
|
||||||
|
"policyScope": "ops-backup-retention",
|
||||||
|
"name": "Ops Backup Evidence Intake",
|
||||||
|
"requiresSeed": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"state-hub": {
|
"state-hub": { "widgetRef": "ops:service:state-hub" },
|
||||||
"widgetRef": "ops:service:state-hub",
|
"gitea": { "widgetRef": "ops:service:gitea" },
|
||||||
"widgetId": "<uuid>"
|
"inter-hub": { "widgetRef": "ops:service:inter-hub" }
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"endpoints": {
|
"endpoints": {
|
||||||
"gitea:gitea-oci-registry": {
|
"gitea:gitea-oci-registry": { "widgetRef": "ops:endpoint:gitea-registry" }
|
||||||
"widgetRef": "ops:endpoint:gitea-registry",
|
|
||||||
"widgetId": "<uuid>"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"accessPaths": {
|
|
||||||
"gitea:gitea-access-1": {
|
|
||||||
"widgetRef": "ops:access-path:gitea-access-1",
|
|
||||||
"widgetId": "<uuid>"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"backups": {
|
"backups": {
|
||||||
"gitea:database:gitea-db": {
|
"gitea:database:gitea-db": { "widgetRef": "ops:backup-set:aggregate" }
|
||||||
"widgetRef": "ops:backup:gitea-db",
|
|
||||||
"widgetId": "<uuid>"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"drift": {
|
"readiness": {
|
||||||
"gitea:gitea-oci-registry": {
|
"gitea:gitea-oci-registry": { "widgetRef": "ops:readiness:gitea-registry" }
|
||||||
"widgetRef": "ops:drift:gitea-oci-registry",
|
|
||||||
"widgetId": "<uuid>"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,44 +154,60 @@ the future Inter-Hub submission implementation should parse.
|
|||||||
Activity-core should choose a widget in this order:
|
Activity-core should choose a widget in this order:
|
||||||
|
|
||||||
1. If the evidence payload carries a `widget_ref` and that reference exists in
|
1. If the evidence payload carries a `widget_ref` and that reference exists in
|
||||||
the mapping, use it.
|
`widgets.byRef`, use it.
|
||||||
2. For `ops-service-observed`, use `services["<service_id>"]`.
|
2. If the event name is present in `eventAliases`, translate it to the live
|
||||||
3. For `ops-endpoint-verified`, use
|
event name before selector lookup and submission.
|
||||||
`endpoints["<service_id>:<endpoint_id>"]`.
|
3. For `ops-service-discovered`, use `services["<service_id>"]`; otherwise use
|
||||||
4. For `ops-access-path-checked`, use
|
`ops:service-catalog`.
|
||||||
`accessPaths["<service_id>:<access_path_id>"]`.
|
4. For `ops-endpoint-verified`, use
|
||||||
|
`endpoints["<service_id>:<endpoint_id>"]`; otherwise skip or use a
|
||||||
|
deliberately seeded endpoint aggregate.
|
||||||
5. For `ops-backup-verified`, use
|
5. For `ops-backup-verified`, use
|
||||||
`backups["<service_id>:<backing_store_ref>"]`.
|
`backups["<service_id>:<backing_store_ref>"]`; otherwise skip until a
|
||||||
6. For `ops-inventory-drift`, use
|
backup-set widget is seeded.
|
||||||
`drift["<service_id>:<inventory_object_id>"]`.
|
6. For `ops-drift-detected`, use
|
||||||
7. If no entity-specific widget exists, use the event's aggregate widget.
|
`readiness["<service_id>:<inventory_object_id>"]`; otherwise use a seeded
|
||||||
8. If neither an entity-specific nor aggregate widget exists, skip Inter-Hub
|
readiness/risk widget chosen by the operator.
|
||||||
submission with a non-secret result that names the missing selector.
|
7. For `ops-access-path-checked`, do not submit to Inter-Hub yet. Keep State
|
||||||
|
Hub fallback evidence unless ops-hub adds access-path vocabulary or an
|
||||||
|
explicit decision maps access-path checks to readiness/risk events.
|
||||||
|
8. If no selector can resolve to a widget id, skip Inter-Hub submission with a
|
||||||
|
non-secret result naming the missing selector.
|
||||||
|
|
||||||
## Bootstrap Widget Names
|
## Bootstrap Widget Names
|
||||||
|
|
||||||
The initial aggregate widgets should be seeded before activity-core is pointed
|
These refs are already present in the current `ops-hub` seed file and should be
|
||||||
at Inter-Hub:
|
looked up in the protected widget registry during the authenticated bootstrap:
|
||||||
|
|
||||||
| Widget ref | Widget type | Suggested name |
|
| Widget ref | Widget type | Name |
|
||||||
|---|---|---|
|
| --- | --- | --- |
|
||||||
| `ops:service:aggregate` | `ops-service-card` | Ops Service Evidence Intake |
|
| `ops:service-catalog` | `ops-service-catalog` | Operations Service Catalog |
|
||||||
| `ops:endpoint:aggregate` | `ops-endpoint-card` | Ops Endpoint Evidence Intake |
|
| `ops:service:gitea` | `ops-service` | Gitea Service |
|
||||||
| `ops:access-path:aggregate` | `ops-access-path-card` | Ops Access Path Evidence Intake |
|
| `ops:service:state-hub` | `ops-service` | State Hub Service |
|
||||||
| `ops:backup:aggregate` | `ops-backup-card` | Ops Backup Evidence Intake |
|
| `ops:service:inter-hub` | `ops-service` | Inter-Hub Service |
|
||||||
| `ops:drift:aggregate` | `ops-drift-card` | Ops Inventory Drift Evidence Intake |
|
| `ops:endpoint:gitea-registry` | `ops-endpoint` | Gitea Registry Endpoint |
|
||||||
|
| `ops:readiness:gitea-registry` | `ops-readiness-gate` | Gitea Registry Readiness |
|
||||||
|
|
||||||
|
Additional seed required before backup evidence can be smoke-tested:
|
||||||
|
|
||||||
|
| Widget ref | Widget type | Suggested name | Policy scope |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `ops:backup-set:aggregate` | `ops-backup-set` | Ops Backup Evidence Intake | `ops-backup-retention` |
|
||||||
|
|
||||||
Per-entity widgets may be seeded later without changing the event contract.
|
Per-entity widgets may be seeded later without changing the event contract.
|
||||||
When a per-entity widget is added, update the mapping and keep the aggregate
|
When a per-entity widget is added, update the mapping and keep a deliberate
|
||||||
widget as the fallback.
|
fallback only when the fallback widget actually exists in the protected widget
|
||||||
|
registry.
|
||||||
|
|
||||||
## Compatibility Rules
|
## Compatibility Rules
|
||||||
|
|
||||||
- `version` is required. Reject unknown major versions.
|
- `version` is required. Reject unknown major versions.
|
||||||
- Consumers must tolerate additional fields.
|
- Consumers must tolerate additional fields.
|
||||||
- Widget UUIDs may rotate, but `widgetRef` values should remain stable.
|
- Widget UUIDs may rotate, but `widgetRef` values should remain stable.
|
||||||
- Removing a widget mapping is a breaking change for that selector unless the
|
- Event aliases are a transition aid only. New activity-core submissions should
|
||||||
aggregate fallback remains present.
|
use live registry event names directly.
|
||||||
|
- Removing a widget mapping is a breaking change for that selector unless a
|
||||||
|
verified fallback widget remains present.
|
||||||
- Mapping updates must be deployed before activity-core starts sending events
|
- Mapping updates must be deployed before activity-core starts sending events
|
||||||
that depend on the new selectors.
|
that depend on the new selectors.
|
||||||
- The mapping is non-secret and may be stored in a ConfigMap or environment
|
- The mapping is non-secret and may be stored in a ConfigMap or environment
|
||||||
@@ -160,7 +215,9 @@ widget as the fallback.
|
|||||||
|
|
||||||
## Minimum Valid Mapping
|
## Minimum Valid Mapping
|
||||||
|
|
||||||
For the first live smoke, an aggregate-only mapping is enough:
|
For the first live smoke, use only live registry events and widgets that are
|
||||||
|
known from the seed contract and can be confirmed through an authenticated
|
||||||
|
widget lookup. Backup and access-path evidence can remain deferred.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -169,37 +226,30 @@ For the first live smoke, an aggregate-only mapping is enough:
|
|||||||
"slug": "ops-hub",
|
"slug": "ops-hub",
|
||||||
"id": "<ops-hub-uuid>"
|
"id": "<ops-hub-uuid>"
|
||||||
},
|
},
|
||||||
"policyScope": "ops-evidence",
|
|
||||||
"defaultViewContext": "ops-inventory-probe",
|
"defaultViewContext": "ops-inventory-probe",
|
||||||
|
"eventAliases": {
|
||||||
|
"ops-service-observed": "ops-service-discovered",
|
||||||
|
"ops-inventory-drift": "ops-drift-detected"
|
||||||
|
},
|
||||||
"events": {
|
"events": {
|
||||||
"ops-service-observed": {
|
"ops-service-discovered": {
|
||||||
"family": "services",
|
"family": "services",
|
||||||
"aggregateWidgetRef": "ops:service:aggregate"
|
"defaultWidgetRef": "ops:service-catalog"
|
||||||
},
|
},
|
||||||
"ops-endpoint-verified": {
|
"ops-endpoint-verified": {
|
||||||
"family": "endpoints",
|
"family": "endpoints",
|
||||||
"aggregateWidgetRef": "ops:endpoint:aggregate"
|
"defaultWidgetRef": "ops:endpoint:gitea-registry"
|
||||||
},
|
},
|
||||||
"ops-access-path-checked": {
|
"ops-drift-detected": {
|
||||||
"family": "accessPaths",
|
"family": "readiness",
|
||||||
"aggregateWidgetRef": "ops:access-path:aggregate"
|
"defaultWidgetRef": "ops:readiness:gitea-registry"
|
||||||
},
|
|
||||||
"ops-backup-verified": {
|
|
||||||
"family": "backups",
|
|
||||||
"aggregateWidgetRef": "ops:backup:aggregate"
|
|
||||||
},
|
|
||||||
"ops-inventory-drift": {
|
|
||||||
"family": "drift",
|
|
||||||
"aggregateWidgetRef": "ops:drift:aggregate"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"widgets": {
|
"widgets": {
|
||||||
"aggregate": {
|
"byRef": {
|
||||||
"ops:service:aggregate": { "widgetId": "<uuid>" },
|
"ops:service-catalog": { "widgetId": "<uuid>" },
|
||||||
"ops:endpoint:aggregate": { "widgetId": "<uuid>" },
|
"ops:endpoint:gitea-registry": { "widgetId": "<uuid>" },
|
||||||
"ops:access-path:aggregate": { "widgetId": "<uuid>" },
|
"ops:readiness:gitea-registry": { "widgetId": "<uuid>" }
|
||||||
"ops:backup:aggregate": { "widgetId": "<uuid>" },
|
|
||||||
"ops:drift:aggregate": { "widgetId": "<uuid>" }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
# Ops Hub Activity-Core Fallback Validation
|
# Ops Hub Activity-Core Fallback Validation
|
||||||
|
|
||||||
Date: 2026-06-15
|
Date: 2026-06-16
|
||||||
|
|
||||||
Workplan: `IHUB-WP-0022`
|
Workplan: `IHUB-WP-0022`
|
||||||
|
|
||||||
## Validation Result
|
## Validation Result
|
||||||
|
|
||||||
The State Hub fallback path is implemented and tested in activity-core, but no
|
The State Hub fallback path is implemented, locally tested in activity-core,
|
||||||
live fallback event exists in State Hub yet.
|
and now verified through a live Railiance01 activity-core manual trigger.
|
||||||
|
|
||||||
Direct query:
|
Direct query:
|
||||||
|
|
||||||
@@ -15,18 +15,41 @@ Direct query:
|
|||||||
GET http://127.0.0.1:8000/progress/?event_type=ops_inventory_probe&limit=20
|
GET http://127.0.0.1:8000/progress/?event_type=ops_inventory_probe&limit=20
|
||||||
```
|
```
|
||||||
|
|
||||||
Observed result on 2026-06-15:
|
Observed result on 2026-06-16:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
[]
|
[
|
||||||
|
{
|
||||||
|
"id": "db408146-0310-4ac3-ac77-f73c5a41e070",
|
||||||
|
"event_type": "ops_inventory_probe",
|
||||||
|
"summary": "Ops inventory probe: 0 ok, 4 degraded, 0 down, 5 skipped",
|
||||||
|
"author": "activity-core",
|
||||||
|
"created_at": "2026-06-16T05:34:02.711888Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
This means Inter-Hub can accept the fallback-first design, but cannot yet cite
|
Railiance also posted verifier evidence note
|
||||||
live `ops_inventory_probe` evidence as a closure artifact.
|
`60256e9a-9d1b-44db-8999-738cf03bca2e`, proving that the progress event was
|
||||||
|
matched to the exact manual activity-core trigger run:
|
||||||
|
|
||||||
|
- manual workflow:
|
||||||
|
`activity-40d15a87-7ff6-4d8e-992c-37df15f95110:manual-d2daa0e4-2d54-430e-a957-dca0ec9f469d`
|
||||||
|
- matched activity-core run id:
|
||||||
|
`90e3b112-d1e3-51af-8fb2-cb61f26add17`
|
||||||
|
- matched fallback progress:
|
||||||
|
`db408146-0310-4ac3-ac77-f73c5a41e070`
|
||||||
|
- immutable runtime evidence:
|
||||||
|
`api_image_id=sha256:5ff92a8217c450ae06075d00862b6e2a92a83ca09eea18b5a5e96b5d2d728b35`
|
||||||
|
|
||||||
|
This means Inter-Hub can cite live fallback evidence as the continuity
|
||||||
|
artifact for activity-core while the governed Inter-Hub widget/API-key path
|
||||||
|
remains explicitly deferred.
|
||||||
|
|
||||||
## What Is Validated
|
## What Is Validated
|
||||||
|
|
||||||
Activity-core local tests validate the fallback sink shape:
|
Activity-core local tests and the Railiance01 verifier now validate the
|
||||||
|
fallback sink shape:
|
||||||
|
|
||||||
- `state-hub-progress` posts one compact `ops_inventory_probe` progress event
|
- `state-hub-progress` posts one compact `ops_inventory_probe` progress event
|
||||||
per run.
|
per run.
|
||||||
@@ -43,11 +66,14 @@ Activity-core local tests validate the fallback sink shape:
|
|||||||
`reason = missing_inter_hub_config`.
|
`reason = missing_inter_hub_config`.
|
||||||
- When config is present but submission is not implemented, the result is a
|
- When config is present but submission is not implemented, the result is a
|
||||||
clean skip with `reason = inter_hub_sink_deferred`.
|
clean skip with `reason = inter_hub_sink_deferred`.
|
||||||
|
- Railiance verifier evidence correlates the State Hub progress event to the
|
||||||
|
exact manual activity-core trigger run id instead of accepting any fresh
|
||||||
|
`ops_inventory_probe`.
|
||||||
|
- The verifier evidence includes immutable runtime identity through the live
|
||||||
|
`actcore-api` container image digest.
|
||||||
|
|
||||||
## What Is Not Yet Validated
|
## What Is Not Yet Validated
|
||||||
|
|
||||||
- No live activity-core worker has posted an `ops_inventory_probe` event to the
|
|
||||||
local State Hub instance.
|
|
||||||
- No live Inter-Hub event has been submitted from activity-core.
|
- No live Inter-Hub event has been submitted from activity-core.
|
||||||
- No production `OPS_HUB_KEY` handoff has been verified.
|
- No production `OPS_HUB_KEY` handoff has been verified.
|
||||||
- No `OPS_HUB_WIDGET_MAPPING` has been deployed into the activity-core runtime.
|
- No `OPS_HUB_WIDGET_MAPPING` has been deployed into the activity-core runtime.
|
||||||
@@ -69,27 +95,24 @@ replacement for Inter-Hub submission:
|
|||||||
|
|
||||||
## Closure Recommendation
|
## Closure Recommendation
|
||||||
|
|
||||||
`ACTIVITY-WP-0007/T06` should not close as fully activated based on the current
|
`ACTIVITY-WP-0007/T06` may remain closed using fallback-deferred closure.
|
||||||
State Hub state, because no live `ops_inventory_probe` progress event exists.
|
The live fallback path now has non-secret State Hub evidence, and the
|
||||||
|
Inter-Hub submission path is explicitly deferred until `IHUB-WP-0022-T03`,
|
||||||
|
`IHUB-WP-0022-T04`, and `IHUB-WP-0022-T07` complete.
|
||||||
|
|
||||||
Two acceptable closure paths remain:
|
This is not full Inter-Hub activation. The remaining full activation path is:
|
||||||
|
|
||||||
1. Fallback-deferred closure: run one disabled/manual activity-core probe,
|
1. Provision `OPS_HUB_KEY`.
|
||||||
confirm a live non-secret `ops_inventory_probe` progress event in State Hub,
|
2. Deploy `OPS_HUB_WIDGET_MAPPING`.
|
||||||
and explicitly record that Inter-Hub submission is deferred until
|
3. Seed and verify the ops-hub widgets.
|
||||||
`IHUB-WP-0022-T03/T04/T07` complete.
|
4. Submit one accepted Inter-Hub event per activity-core event type.
|
||||||
2. Full Inter-Hub closure: provision `OPS_HUB_KEY`, deploy
|
|
||||||
`OPS_HUB_WIDGET_MAPPING`, seed/verify the ops-hub widgets, and submit one
|
|
||||||
accepted Inter-Hub event per activity-core event type.
|
|
||||||
|
|
||||||
Until one of those paths is satisfied, Inter-Hub should keep the activity-core
|
Until that path is satisfied, Inter-Hub should keep its own per-entity intake
|
||||||
closure gate open.
|
tasks open, but it does not need to hold the activity-core closure gate open.
|
||||||
|
|
||||||
## Next Evidence To Capture
|
## Next Evidence To Capture
|
||||||
|
|
||||||
- Progress id for the first live `ops_inventory_probe` event.
|
|
||||||
- Non-secret summary counts from that progress detail.
|
|
||||||
- Confirmation that Inter-Hub sink remains skipped cleanly while config is
|
- Confirmation that Inter-Hub sink remains skipped cleanly while config is
|
||||||
absent or deferred.
|
absent or deferred in the deployed runtime.
|
||||||
- After ops-hub activation, event ids for one accepted Inter-Hub submission per
|
- After ops-hub activation, event ids for one accepted Inter-Hub submission per
|
||||||
event type.
|
event type.
|
||||||
|
|||||||
609
docs/fdd/personal-dashboard-fdd.md
Normal file
609
docs/fdd/personal-dashboard-fdd.md
Normal file
@@ -0,0 +1,609 @@
|
|||||||
|
# Personal Dashboard Framework FDD
|
||||||
|
|
||||||
|
**Workplan:** IHUB-WP-0020
|
||||||
|
**Date:** 2026-06-16
|
||||||
|
**Status:** Functional design for follow-on implementation workplan
|
||||||
|
**Inputs:** `docs/research/personal-dashboard-current-state.md`,
|
||||||
|
`docs/prs/personal-dashboard-prs.md`
|
||||||
|
|
||||||
|
## 1. Summary
|
||||||
|
|
||||||
|
The personal dashboard is an authenticated, per-user landing surface composed of
|
||||||
|
server-rendered, governed panels. It reuses existing inter-hub data and links to
|
||||||
|
existing source dashboards. It does not replace hub dashboards, governance
|
||||||
|
dashboards, API dashboard, marketplace, or learning dashboard.
|
||||||
|
|
||||||
|
First implementation should ship:
|
||||||
|
|
||||||
|
- one default dashboard per user, with schema ready for multiple dashboards;
|
||||||
|
- six first-slice panel types;
|
||||||
|
- persisted panel layout/config;
|
||||||
|
- stable widget identity for each saved panel;
|
||||||
|
- `widgetEnvelope` wrapping for every rendered panel;
|
||||||
|
- simple server-rendered edit forms;
|
||||||
|
- post-login redirect to the personal dashboard.
|
||||||
|
|
||||||
|
## 2. Design Decisions
|
||||||
|
|
||||||
|
| Topic | Decision |
|
||||||
|
|---|---|
|
||||||
|
| Table prefix | Use `personal_dashboards`, `dashboard_panel_types`, and `dashboard_panels` |
|
||||||
|
| Panel type key field | Use `panel_key`, not `key`, to avoid ambiguous SQL/Haskell naming |
|
||||||
|
| Dashboard multiplicity | Schema supports multiple dashboards; first UI exposes only the default dashboard |
|
||||||
|
| Default dashboard | Created idempotently on first dashboard visit |
|
||||||
|
| Role defaults | No `users.role` column in first slice |
|
||||||
|
| Watched hubs | Represented in panel config for first slice, no separate watched-hub table |
|
||||||
|
| Panel widget ownership | Linked panel widgets are owned by the framework hub |
|
||||||
|
| Panel widget type | Use existing framework-level `panel` widget type |
|
||||||
|
| Panel removal | Soft-remove panel row with `removed_at`; archive linked widget |
|
||||||
|
| Rendering model | Controller/helper builds typed panel view models; views render pure HSX |
|
||||||
|
| Refresh model | Wrap the personal dashboard show action in `autoRefresh` initially |
|
||||||
|
| Client runtime | No JS framework and no client-side data fetch loop |
|
||||||
|
|
||||||
|
## 3. Schema
|
||||||
|
|
||||||
|
### 3.1 Migration Tables
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE personal_dashboards (
|
||||||
|
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
|
||||||
|
user_id UUID NOT NULL REFERENCES users(id),
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
is_default BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL,
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX personal_dashboards_one_default_idx
|
||||||
|
ON personal_dashboards (user_id)
|
||||||
|
WHERE is_default = TRUE;
|
||||||
|
|
||||||
|
CREATE INDEX personal_dashboards_user_idx
|
||||||
|
ON personal_dashboards (user_id);
|
||||||
|
|
||||||
|
CREATE TABLE dashboard_panel_types (
|
||||||
|
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
|
||||||
|
panel_key TEXT NOT NULL UNIQUE,
|
||||||
|
label TEXT NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
default_config JSONB NOT NULL DEFAULT '{}',
|
||||||
|
default_col_span INTEGER NOT NULL DEFAULT 4,
|
||||||
|
default_row_span INTEGER NOT NULL DEFAULT 1,
|
||||||
|
live_update BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
status TEXT NOT NULL DEFAULT 'active',
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL,
|
||||||
|
CONSTRAINT dashboard_panel_types_span_check CHECK (
|
||||||
|
default_col_span BETWEEN 1 AND 12
|
||||||
|
AND default_row_span BETWEEN 1 AND 4
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX dashboard_panel_types_status_idx
|
||||||
|
ON dashboard_panel_types (status);
|
||||||
|
|
||||||
|
CREATE TABLE dashboard_panels (
|
||||||
|
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
|
||||||
|
dashboard_id UUID NOT NULL REFERENCES personal_dashboards(id) ON DELETE CASCADE,
|
||||||
|
panel_type_id UUID NOT NULL REFERENCES dashboard_panel_types(id),
|
||||||
|
widget_id UUID NOT NULL REFERENCES widgets(id),
|
||||||
|
title TEXT,
|
||||||
|
config JSONB NOT NULL DEFAULT '{}',
|
||||||
|
col INTEGER NOT NULL DEFAULT 0,
|
||||||
|
row INTEGER NOT NULL DEFAULT 0,
|
||||||
|
col_span INTEGER NOT NULL DEFAULT 4,
|
||||||
|
row_span INTEGER NOT NULL DEFAULT 1,
|
||||||
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL,
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL,
|
||||||
|
removed_at TIMESTAMP WITH TIME ZONE,
|
||||||
|
CONSTRAINT dashboard_panels_layout_check CHECK (
|
||||||
|
col BETWEEN 0 AND 11
|
||||||
|
AND row >= 0
|
||||||
|
AND col_span BETWEEN 1 AND 12
|
||||||
|
AND row_span BETWEEN 1 AND 4
|
||||||
|
AND col + col_span <= 12
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX dashboard_panels_dashboard_idx
|
||||||
|
ON dashboard_panels (dashboard_id, removed_at, row, col, sort_order);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX dashboard_panels_widget_idx
|
||||||
|
ON dashboard_panels (widget_id);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Seed Data
|
||||||
|
|
||||||
|
The implementation migration or seed helper must ensure:
|
||||||
|
|
||||||
|
- a framework hub exists with `hub_kind = 'framework'`;
|
||||||
|
- the framework-level `panel` widget type exists and is active;
|
||||||
|
- six `dashboard_panel_types` exist.
|
||||||
|
|
||||||
|
Seed panel types:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
INSERT INTO dashboard_panel_types
|
||||||
|
(panel_key, label, description, default_config, default_col_span,
|
||||||
|
default_row_span, live_update)
|
||||||
|
VALUES
|
||||||
|
('watched-hubs', 'Watched Hubs',
|
||||||
|
'Hub list with latest health hints',
|
||||||
|
'{"limit":12,"displayMode":"compact"}', 6, 1, FALSE),
|
||||||
|
('recent-interactions', 'Recent Activity',
|
||||||
|
'Latest interaction events with widget and hub context',
|
||||||
|
'{"timeRange":"last24h","limit":25,"displayMode":"compact"}', 6, 1, TRUE),
|
||||||
|
('triage-queue', 'Triage Queue',
|
||||||
|
'Open requirement candidates waiting for triage',
|
||||||
|
'{"status":"open","limit":10,"sort":"oldest"}', 6, 1, TRUE),
|
||||||
|
('recent-decisions', 'Recent Decisions',
|
||||||
|
'Recent governance decisions across visible hubs',
|
||||||
|
'{"timeRange":"last30d","limit":10,"sort":"newest"}', 6, 1, FALSE),
|
||||||
|
('hub-health', 'Hub Health',
|
||||||
|
'Latest health snapshots and active bottleneck counts',
|
||||||
|
'{"limit":12,"displayMode":"compact"}', 6, 1, TRUE),
|
||||||
|
('learning-digest', 'Learning Digest',
|
||||||
|
'Recent learning insights and institutional knowledge highlights',
|
||||||
|
'{"insightLimit":5,"knowledgeLimit":5}', 6, 1, TRUE)
|
||||||
|
ON CONFLICT (panel_key) DO NOTHING;
|
||||||
|
```
|
||||||
|
|
||||||
|
The exact framework hub seed should use existing hub invariants and avoid
|
||||||
|
creating a second framework hub. Recommended slug: `inter-hub`.
|
||||||
|
|
||||||
|
## 4. Haskell Types
|
||||||
|
|
||||||
|
### 4.1 Controller Type
|
||||||
|
|
||||||
|
Add to `Web.Types`:
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
data PersonalDashboardsController
|
||||||
|
= PersonalDashboardAction
|
||||||
|
| EditPersonalDashboardAction
|
||||||
|
| UpdatePersonalDashboardAction
|
||||||
|
| AddDashboardPanelAction
|
||||||
|
| UpdateDashboardPanelAction { dashboardPanelId :: !(Id DashboardPanel) }
|
||||||
|
| RemoveDashboardPanelAction { dashboardPanelId :: !(Id DashboardPanel) }
|
||||||
|
deriving (Eq, Show, Data)
|
||||||
|
```
|
||||||
|
|
||||||
|
Register in:
|
||||||
|
|
||||||
|
- `Web/Routes.hs` with `instance AutoRoute PersonalDashboardsController`
|
||||||
|
- `Web/FrontController.hs` imports and controller list
|
||||||
|
- sidebar navigation as `Dashboard`
|
||||||
|
|
||||||
|
### 4.2 Config ADTs
|
||||||
|
|
||||||
|
Store panel config in JSONB. Decode into explicit Haskell types before querying:
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
data TimeRange
|
||||||
|
= Last24Hours
|
||||||
|
| Last7Days
|
||||||
|
| Last30Days
|
||||||
|
| AllTimeBounded
|
||||||
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
data DisplayMode
|
||||||
|
= Compact
|
||||||
|
| Detailed
|
||||||
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
data SortMode
|
||||||
|
= Newest
|
||||||
|
| Oldest
|
||||||
|
| HighestRisk
|
||||||
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
data DashboardPanelConfig
|
||||||
|
= WatchedHubsConfig
|
||||||
|
{ hubIds :: !(Maybe [Id Hub])
|
||||||
|
, limit :: !Int
|
||||||
|
, displayMode :: !DisplayMode
|
||||||
|
}
|
||||||
|
| RecentInteractionsConfig
|
||||||
|
{ hubIds :: !(Maybe [Id Hub])
|
||||||
|
, timeRange :: !TimeRange
|
||||||
|
, limit :: !Int
|
||||||
|
, displayMode :: !DisplayMode
|
||||||
|
}
|
||||||
|
| TriageQueueConfig
|
||||||
|
{ hubIds :: !(Maybe [Id Hub])
|
||||||
|
, status :: !Text
|
||||||
|
, limit :: !Int
|
||||||
|
, sortMode :: !SortMode
|
||||||
|
}
|
||||||
|
| RecentDecisionsConfig
|
||||||
|
{ hubIds :: !(Maybe [Id Hub])
|
||||||
|
, timeRange :: !TimeRange
|
||||||
|
, limit :: !Int
|
||||||
|
, sortMode :: !SortMode
|
||||||
|
}
|
||||||
|
| HubHealthConfig
|
||||||
|
{ hubIds :: !(Maybe [Id Hub])
|
||||||
|
, limit :: !Int
|
||||||
|
, displayMode :: !DisplayMode
|
||||||
|
}
|
||||||
|
| LearningDigestConfig
|
||||||
|
{ hubIds :: !(Maybe [Id Hub])
|
||||||
|
, insightLimit :: !Int
|
||||||
|
, knowledgeLimit :: !Int
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Implementation can place these in `Application/Helper/PersonalDashboard.hs`.
|
||||||
|
Config decoding should return warnings instead of crashing:
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
data PanelConfigResult
|
||||||
|
= PanelConfigOk DashboardPanelConfig
|
||||||
|
| PanelConfigWithWarnings DashboardPanelConfig [Text]
|
||||||
|
```
|
||||||
|
|
||||||
|
Clamp all limits server-side. Recommended default min/max:
|
||||||
|
|
||||||
|
- list panel limit: 1 to 50;
|
||||||
|
- hub list limit: 1 to 50;
|
||||||
|
- learning insight/knowledge limits: 1 to 20;
|
||||||
|
- column span: 1 to 12;
|
||||||
|
- row span: 1 to 4.
|
||||||
|
|
||||||
|
### 4.3 View Model ADT
|
||||||
|
|
||||||
|
Do not query from HSX views. Build typed view models in the controller/helper:
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
data PersonalDashboardViewModel = PersonalDashboardViewModel
|
||||||
|
{ dashboard :: !PersonalDashboard
|
||||||
|
, panels :: ![DashboardPanelViewModel]
|
||||||
|
, panelTypes :: ![DashboardPanelType]
|
||||||
|
}
|
||||||
|
|
||||||
|
data DashboardPanelViewModel
|
||||||
|
= WatchedHubsPanel DashboardPanel Widget [WatchedHubRow] [Text]
|
||||||
|
| RecentInteractionsPanel DashboardPanel Widget [RecentInteractionRow] [Text]
|
||||||
|
| TriageQueuePanel DashboardPanel Widget [TriageQueueRow] [Text]
|
||||||
|
| RecentDecisionsPanel DashboardPanel Widget [RecentDecisionRow] [Text]
|
||||||
|
| HubHealthPanel DashboardPanel Widget [HubHealthRow] [Text]
|
||||||
|
| LearningDigestPanel DashboardPanel Widget [LearningDigestRow] [Text]
|
||||||
|
| UnsupportedPanel DashboardPanel Widget Text [Text]
|
||||||
|
```
|
||||||
|
|
||||||
|
Each row type should carry exactly the fields the view needs, plus source route
|
||||||
|
ids for link-outs.
|
||||||
|
|
||||||
|
## 5. Controller Flow
|
||||||
|
|
||||||
|
### 5.1 Show
|
||||||
|
|
||||||
|
```text
|
||||||
|
GET /PersonalDashboard
|
||||||
|
-> ensureIsUser
|
||||||
|
-> ensureDefaultDashboard currentUser
|
||||||
|
-> fetch active dashboard panels ordered by row, col, sort_order
|
||||||
|
-> build DashboardPanelViewModel for each panel
|
||||||
|
-> render ShowView
|
||||||
|
```
|
||||||
|
|
||||||
|
The first implementation may wrap `PersonalDashboardAction` in `autoRefresh do`.
|
||||||
|
|
||||||
|
### 5.2 Edit
|
||||||
|
|
||||||
|
```text
|
||||||
|
GET /PersonalDashboard/Edit
|
||||||
|
-> ensureIsUser
|
||||||
|
-> ensureDefaultDashboard currentUser
|
||||||
|
-> fetch active panels and active panel types
|
||||||
|
-> render EditView
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit view should show:
|
||||||
|
|
||||||
|
- panel title;
|
||||||
|
- panel type label;
|
||||||
|
- row, col, col span, row span;
|
||||||
|
- limit/time range/hub filter where supported;
|
||||||
|
- remove button;
|
||||||
|
- add panel form.
|
||||||
|
|
||||||
|
### 5.3 Update Layout/Config
|
||||||
|
|
||||||
|
`UpdatePersonalDashboardAction` should accept a simple form payload for all
|
||||||
|
active panels. It should:
|
||||||
|
|
||||||
|
- authorize that the dashboard belongs to current user;
|
||||||
|
- validate layout bounds;
|
||||||
|
- validate per-panel config;
|
||||||
|
- update dashboard/panel `updated_at`;
|
||||||
|
- redirect back to edit or show with success/error messages.
|
||||||
|
|
||||||
|
### 5.4 Add Panel
|
||||||
|
|
||||||
|
`AddDashboardPanelAction` should:
|
||||||
|
|
||||||
|
1. fetch current user's default dashboard;
|
||||||
|
2. fetch selected active `DashboardPanelType`;
|
||||||
|
3. find/create the framework hub;
|
||||||
|
4. create linked `Widget`;
|
||||||
|
5. create initial `WidgetVersion`;
|
||||||
|
6. create `DashboardPanel` with default config and next layout slot;
|
||||||
|
7. redirect to edit.
|
||||||
|
|
||||||
|
### 5.5 Remove Panel
|
||||||
|
|
||||||
|
`RemoveDashboardPanelAction { dashboardPanelId }` should:
|
||||||
|
|
||||||
|
1. verify the panel belongs to current user's dashboard;
|
||||||
|
2. set `dashboard_panels.removed_at`;
|
||||||
|
3. set linked widget `is_archived = TRUE` and `status = 'deprecated'`;
|
||||||
|
4. keep interaction events and annotations intact;
|
||||||
|
5. redirect to edit.
|
||||||
|
|
||||||
|
## 6. Default Dashboard Seeding
|
||||||
|
|
||||||
|
Recommended helper:
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
ensureDefaultDashboard :: (?modelContext :: ModelContext) => User -> IO PersonalDashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
Behavior:
|
||||||
|
|
||||||
|
1. Query default dashboard for user.
|
||||||
|
2. If found, return it.
|
||||||
|
3. If absent, create `personal_dashboards` row with name `My Dashboard`.
|
||||||
|
4. Fetch active first-slice `DashboardPanelType` rows.
|
||||||
|
5. Create one linked widget and one panel row for each seed panel.
|
||||||
|
6. Return the dashboard.
|
||||||
|
|
||||||
|
Default layout:
|
||||||
|
|
||||||
|
| Panel | col | row | col_span | row_span |
|
||||||
|
|---|---:|---:|---:|---:|
|
||||||
|
| watched-hubs | 0 | 0 | 6 | 1 |
|
||||||
|
| recent-interactions | 6 | 0 | 6 | 1 |
|
||||||
|
| triage-queue | 0 | 1 | 6 | 1 |
|
||||||
|
| recent-decisions | 6 | 1 | 6 | 1 |
|
||||||
|
| hub-health | 0 | 2 | 6 | 1 |
|
||||||
|
| learning-digest | 6 | 2 | 6 | 1 |
|
||||||
|
|
||||||
|
If a user has active stewardship roles matched by email or name, panel config
|
||||||
|
may include those hub ids. If no match exists, config should stay neutral.
|
||||||
|
|
||||||
|
## 7. Panel Renderer Query Shapes
|
||||||
|
|
||||||
|
All panel queries must be bounded and must not expose secrets.
|
||||||
|
|
||||||
|
### Watched Hubs
|
||||||
|
|
||||||
|
Purpose: show hub names, domains/kinds, latest health score if available, and a
|
||||||
|
link to `ShowHubAction`.
|
||||||
|
|
||||||
|
Query shape:
|
||||||
|
|
||||||
|
- fetch hubs matching optional hub filter, order by name, limit N;
|
||||||
|
- fetch latest health snapshots for those hub ids using `DISTINCT ON (hub_id)`
|
||||||
|
or equivalent bounded query.
|
||||||
|
|
||||||
|
### Recent Activity
|
||||||
|
|
||||||
|
Purpose: show recent interaction events with widget and hub context.
|
||||||
|
|
||||||
|
Query shape:
|
||||||
|
|
||||||
|
- filter by optional hub ids through widget join;
|
||||||
|
- filter by time range;
|
||||||
|
- order by `interaction_events.occurred_at DESC`;
|
||||||
|
- limit N;
|
||||||
|
- fetch widget/hub names for display.
|
||||||
|
|
||||||
|
### Triage Queue
|
||||||
|
|
||||||
|
Purpose: show open requirement candidates that need attention.
|
||||||
|
|
||||||
|
Query shape:
|
||||||
|
|
||||||
|
- filter `requirement_candidates.status = 'open'`;
|
||||||
|
- optionally filter by source widget hub;
|
||||||
|
- order oldest first by default;
|
||||||
|
- limit N;
|
||||||
|
- fetch source widget and hub names.
|
||||||
|
|
||||||
|
### Recent Decisions
|
||||||
|
|
||||||
|
Purpose: show governance decisions that changed recently.
|
||||||
|
|
||||||
|
Query shape:
|
||||||
|
|
||||||
|
- filter by time range on `decided_at` or `created_at` fallback;
|
||||||
|
- optionally filter by hub through requirement candidate source widget lineage;
|
||||||
|
- order newest first;
|
||||||
|
- limit N.
|
||||||
|
|
||||||
|
### Hub Health
|
||||||
|
|
||||||
|
Purpose: show latest health score and active bottleneck count.
|
||||||
|
|
||||||
|
Query shape:
|
||||||
|
|
||||||
|
- latest `hub_health_snapshots` per hub;
|
||||||
|
- active `bottleneck_records` count per hub;
|
||||||
|
- limit N hubs by health score ascending or hub name depending config.
|
||||||
|
|
||||||
|
When decoding aggregate counts as `Int`, cast `COUNT(*) AS integer` or decode
|
||||||
|
as `Int64`.
|
||||||
|
|
||||||
|
### Learning Digest
|
||||||
|
|
||||||
|
Purpose: show recent `learning_insights` and
|
||||||
|
`institutional_knowledge_entries`.
|
||||||
|
|
||||||
|
Query shape:
|
||||||
|
|
||||||
|
- optional hub filter;
|
||||||
|
- latest insights ordered by `computed_at DESC`, limit N;
|
||||||
|
- latest knowledge entries ordered by `created_at DESC`, limit N;
|
||||||
|
- link to source knowledge entries when available.
|
||||||
|
|
||||||
|
## 8. Layout
|
||||||
|
|
||||||
|
Desktop layout:
|
||||||
|
|
||||||
|
- 12-column CSS grid.
|
||||||
|
- panel spans come from `dashboard_panels.col_span` and `row_span`.
|
||||||
|
- layout ordering comes from row, col, sort order.
|
||||||
|
- gap should match existing dashboard spacing.
|
||||||
|
|
||||||
|
Mobile/narrow layout:
|
||||||
|
|
||||||
|
- collapse to a single column.
|
||||||
|
- ignore column positions visually.
|
||||||
|
- preserve row/sort ordering.
|
||||||
|
|
||||||
|
Implementation approach:
|
||||||
|
|
||||||
|
- add a small CSS helper in `static/app.css` if inline styles cannot express the
|
||||||
|
responsive collapse cleanly;
|
||||||
|
- keep panel headings at compact dashboard scale;
|
||||||
|
- avoid nested cards;
|
||||||
|
- keep source link and annotate control visible but quiet.
|
||||||
|
|
||||||
|
## 9. Routing and Navigation
|
||||||
|
|
||||||
|
Add:
|
||||||
|
|
||||||
|
- `PersonalDashboardAction`
|
||||||
|
- `EditPersonalDashboardAction`
|
||||||
|
- `UpdatePersonalDashboardAction`
|
||||||
|
- `AddDashboardPanelAction`
|
||||||
|
- `UpdateDashboardPanelAction`
|
||||||
|
- `RemoveDashboardPanelAction`
|
||||||
|
|
||||||
|
Expected user-facing routes with AutoRoute naming are acceptable. If a cleaner
|
||||||
|
path is desired, add explicit `HasPath`/`CanRoute` later. First implementation
|
||||||
|
can use AutoRoute for speed and consistency.
|
||||||
|
|
||||||
|
Update login:
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
login user
|
||||||
|
redirectTo PersonalDashboardAction
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not alter:
|
||||||
|
|
||||||
|
- public `LandingAction`;
|
||||||
|
- docs/tutorial/extension guide routes;
|
||||||
|
- existing `HubsAction` route.
|
||||||
|
|
||||||
|
## 10. Governance Lifecycle
|
||||||
|
|
||||||
|
### Panel Add
|
||||||
|
|
||||||
|
- create `DashboardPanel`;
|
||||||
|
- create linked `Widget`;
|
||||||
|
- create initial `WidgetVersion` snapshot with panel type and config;
|
||||||
|
- render through `widgetEnvelope`.
|
||||||
|
|
||||||
|
### Panel Update
|
||||||
|
|
||||||
|
- update `DashboardPanel.config` and layout fields;
|
||||||
|
- update panel widget name/view context only if needed;
|
||||||
|
- create a new `WidgetVersion` snapshot when config changes materially.
|
||||||
|
|
||||||
|
### Panel Remove
|
||||||
|
|
||||||
|
- set `dashboard_panels.removed_at`;
|
||||||
|
- set widget `is_archived = TRUE`;
|
||||||
|
- set widget `status = 'deprecated'`;
|
||||||
|
- preserve events and annotations.
|
||||||
|
|
||||||
|
### Annotation
|
||||||
|
|
||||||
|
The existing `WidgetAnnotationsAction` should work because panels have stable
|
||||||
|
widget ids.
|
||||||
|
|
||||||
|
### Event Capture
|
||||||
|
|
||||||
|
Existing client-side capture can identify panels via `data-widget-id`. If panel
|
||||||
|
forms submit through normal controller actions, use existing event types where
|
||||||
|
possible (`viewed`, `clicked`, `submitted`, `commented`).
|
||||||
|
|
||||||
|
## 11. Error Handling
|
||||||
|
|
||||||
|
- Missing dashboard: create default.
|
||||||
|
- Missing panel type: render `UnsupportedPanel` with warning.
|
||||||
|
- Invalid config: use defaults and render warning.
|
||||||
|
- Missing linked widget: repair by creating a replacement widget if possible,
|
||||||
|
otherwise render unsupported warning.
|
||||||
|
- Missing framework hub: create the framework hub if absent, honoring unique
|
||||||
|
framework hub constraint.
|
||||||
|
- Empty panel data: render a quiet empty state.
|
||||||
|
|
||||||
|
## 12. Tests and Smoke Checks
|
||||||
|
|
||||||
|
Focused automated checks:
|
||||||
|
|
||||||
|
- `ensureDefaultDashboard` is idempotent.
|
||||||
|
- seeded dashboard contains six active panels.
|
||||||
|
- each seeded panel has a linked widget.
|
||||||
|
- config decoder clamps limits and rejects unknown values safely.
|
||||||
|
- unauthorized user cannot edit another user's dashboard.
|
||||||
|
- remove action soft-removes panel and archives widget.
|
||||||
|
|
||||||
|
Manual smoke:
|
||||||
|
|
||||||
|
1. Log in as the seeded admin user.
|
||||||
|
2. Confirm redirect lands on personal dashboard.
|
||||||
|
3. Confirm all six seeded panels render.
|
||||||
|
4. Click source links from at least three panels.
|
||||||
|
5. Open Annotate for one panel and confirm existing annotation flow loads.
|
||||||
|
6. Edit layout, save, sign out/in, and confirm layout persists.
|
||||||
|
7. Add a panel and remove a panel.
|
||||||
|
8. Confirm `HubsAction`, hub show, Ops Review, Learning, API Dashboard, and
|
||||||
|
Marketplace still load.
|
||||||
|
|
||||||
|
## 13. Implementation File Map
|
||||||
|
|
||||||
|
Expected files for WP-0021:
|
||||||
|
|
||||||
|
- `Application/Migration/<timestamp>-personal-dashboard-framework.sql`
|
||||||
|
- `Application/Helper/PersonalDashboard.hs`
|
||||||
|
- `Web/Controller/PersonalDashboards.hs`
|
||||||
|
- `Web/View/PersonalDashboards/Show.hs`
|
||||||
|
- `Web/View/PersonalDashboards/Edit.hs`
|
||||||
|
- `Web/Types.hs`
|
||||||
|
- `Web/Routes.hs`
|
||||||
|
- `Web/FrontController.hs`
|
||||||
|
- `static/app.css` only if needed for responsive grid helpers
|
||||||
|
- focused tests under `Test/` if the current test harness supports controller or
|
||||||
|
helper tests
|
||||||
|
|
||||||
|
## 14. Open Questions
|
||||||
|
|
||||||
|
These do not block WP-0021, but should be revisited after the first
|
||||||
|
implementation:
|
||||||
|
|
||||||
|
1. Should personal dashboards later support team/shared dashboards?
|
||||||
|
2. Should watched hubs become a first-class table after users start editing
|
||||||
|
dashboards?
|
||||||
|
3. Should per-panel refresh be extracted into fragment routes?
|
||||||
|
4. Should dashboard panel widgets eventually be owned by source hubs instead of
|
||||||
|
the framework hub?
|
||||||
|
5. Should dashboard templates become part of the marketplace?
|
||||||
|
|
||||||
|
## 15. Handoff to WP-0021
|
||||||
|
|
||||||
|
WP-0021 should implement this design in small slices:
|
||||||
|
|
||||||
|
1. schema and seeds;
|
||||||
|
2. controller/route skeleton and default seeding;
|
||||||
|
3. first three panel view models/renderers;
|
||||||
|
4. dashboard show view;
|
||||||
|
5. remaining panel view models/renderers;
|
||||||
|
6. edit flow;
|
||||||
|
7. governance lifecycle;
|
||||||
|
8. login redirect and navigation;
|
||||||
|
9. tests and smoke.
|
||||||
340
docs/prs/personal-dashboard-prs.md
Normal file
340
docs/prs/personal-dashboard-prs.md
Normal file
@@ -0,0 +1,340 @@
|
|||||||
|
# Personal Dashboard Framework PRS
|
||||||
|
|
||||||
|
**Workplan:** IHUB-WP-0020
|
||||||
|
**Date:** 2026-06-16
|
||||||
|
**Status:** Product requirements for follow-on FDD and implementation planning
|
||||||
|
**Research input:** `docs/research/personal-dashboard-current-state.md`
|
||||||
|
|
||||||
|
## 1. Problem Statement
|
||||||
|
|
||||||
|
Authenticated inter-hub users currently land on the Hubs list after login. That
|
||||||
|
list is a useful management table, but it does not answer the daily operating
|
||||||
|
questions users bring to inter-hub:
|
||||||
|
|
||||||
|
- What changed recently?
|
||||||
|
- Which candidates or governance items need attention?
|
||||||
|
- Which hubs are unhealthy or blocked?
|
||||||
|
- Which learning or institutional knowledge signals should I notice today?
|
||||||
|
- Where should I go next?
|
||||||
|
|
||||||
|
Inter-hub already has many specialized dashboards, but they are scattered across
|
||||||
|
hub-level and platform-level routes. Users must know which surface to visit and
|
||||||
|
manually stitch together recent activity, open work, health, governance, and
|
||||||
|
learning signals.
|
||||||
|
|
||||||
|
The personal dashboard should become the authenticated landing surface that
|
||||||
|
summarizes the most relevant existing signals and links users to the source
|
||||||
|
dashboards for detail. It should be persistent, configurable, server-rendered,
|
||||||
|
and governed by the same IHF widget, annotation, and interaction-event model as
|
||||||
|
the rest of the application.
|
||||||
|
|
||||||
|
## 2. Users and Personas
|
||||||
|
|
||||||
|
### Hub Operator
|
||||||
|
|
||||||
|
Owns or watches one or more hubs. Needs quick visibility into recent events,
|
||||||
|
open requirement candidates, hub health, active bottlenecks, and regressions.
|
||||||
|
|
||||||
|
Primary questions:
|
||||||
|
|
||||||
|
- Which hubs need attention now?
|
||||||
|
- What happened since the last session?
|
||||||
|
- Which candidates are still open?
|
||||||
|
- Are any bottlenecks or health drops visible?
|
||||||
|
|
||||||
|
### Governance Reviewer
|
||||||
|
|
||||||
|
Triages candidates, reviews decisions, checks policy coverage, and follows
|
||||||
|
traceability from observations to implementation outcomes.
|
||||||
|
|
||||||
|
Primary questions:
|
||||||
|
|
||||||
|
- Which candidates are waiting for triage?
|
||||||
|
- Which decisions or requirements changed recently?
|
||||||
|
- Which panels need annotation or review?
|
||||||
|
- Are governance signals visible without visiting every hub?
|
||||||
|
|
||||||
|
### AI Orchestrator
|
||||||
|
|
||||||
|
Monitors agent proposals, review outcomes, learning signals, and institutional
|
||||||
|
knowledge that may affect future AI-assisted work.
|
||||||
|
|
||||||
|
Primary questions:
|
||||||
|
|
||||||
|
- Which agent proposals or reviews need attention?
|
||||||
|
- What learning insights were generated recently?
|
||||||
|
- Which knowledge entries should inform the next work session?
|
||||||
|
- Are there patterns of repeated friction or successful reuse?
|
||||||
|
|
||||||
|
### Platform Admin
|
||||||
|
|
||||||
|
Watches the inter-hub platform itself: API consumers, hub registry health,
|
||||||
|
manifests, policy overlays, marketplace activity, and cross-hub propagation.
|
||||||
|
|
||||||
|
Primary questions:
|
||||||
|
|
||||||
|
- Are API consumers active and healthy?
|
||||||
|
- Are hub manifests and registry views coherent?
|
||||||
|
- Are cross-hub governance or propagation signals emerging?
|
||||||
|
- Which operational panels should be promoted into a shared view later?
|
||||||
|
|
||||||
|
## 3. Product Goals
|
||||||
|
|
||||||
|
- Replace the authenticated post-login Hubs table as the daily landing surface.
|
||||||
|
- Provide a compact, configurable overview of existing inter-hub signals.
|
||||||
|
- Preserve the existing specialized dashboards as source-of-truth detail views.
|
||||||
|
- Make every saved panel a governed IHF interaction artifact.
|
||||||
|
- Keep the first implementation simple enough to deliver without a broad
|
||||||
|
dashboard refactor.
|
||||||
|
|
||||||
|
## 4. Non-Goals
|
||||||
|
|
||||||
|
- Do not build a drag-and-drop report builder.
|
||||||
|
- Do not add external datasource connectors.
|
||||||
|
- Do not introduce a client-side data fetching framework.
|
||||||
|
- Do not refactor all existing dashboards into reusable panel modules in the
|
||||||
|
first slice.
|
||||||
|
- Do not add a full role/permission system.
|
||||||
|
- Do not make shared/team dashboards part of the first implementation.
|
||||||
|
- Do not change public root, tutorial, capabilities, or extension-guide routes.
|
||||||
|
|
||||||
|
## 5. Core Requirements
|
||||||
|
|
||||||
|
### Must
|
||||||
|
|
||||||
|
- Provide an authenticated personal dashboard route.
|
||||||
|
- Redirect successful login to the personal dashboard instead of `HubsAction`.
|
||||||
|
- Preserve public root behavior for unauthenticated and documentation users.
|
||||||
|
- Persist at least one dashboard per user.
|
||||||
|
- Seed a default dashboard on first visit.
|
||||||
|
- Persist panel instances, layout position, and panel config.
|
||||||
|
- Render all first-slice panels server-side through IHP/HSX.
|
||||||
|
- Use a server-side panel catalogue with stable panel keys.
|
||||||
|
- Bound every panel query by limit and, where relevant, hub/status/time filters.
|
||||||
|
- Decode JSONB panel config into explicit Haskell config types before querying.
|
||||||
|
- Create or reference stable `Widget` records for saved panel instances.
|
||||||
|
- Wrap rendered panels with `widgetEnvelope`.
|
||||||
|
- Preserve annotation and interaction-event identity across sessions.
|
||||||
|
- Link each panel to its existing source dashboard or source entity list.
|
||||||
|
- Provide a simple edit mode for adding, removing, and reordering panels through
|
||||||
|
normal IHP forms.
|
||||||
|
|
||||||
|
### Should
|
||||||
|
|
||||||
|
- Support hub filters for panels backed by hub-owned data.
|
||||||
|
- Support simple time-range filters where the underlying table has timestamps.
|
||||||
|
- Support limit/display-mode settings for panels with list content.
|
||||||
|
- Refresh recent activity, triage, health, and learning panels using the
|
||||||
|
existing `autoRefresh` style.
|
||||||
|
- Keep forms keyboard accessible and understandable without custom JavaScript.
|
||||||
|
- Render a neutral empty state when a panel has no data.
|
||||||
|
- Provide safe fallback behavior for invalid panel config.
|
||||||
|
- Keep first paint sub-second for a seeded dashboard with default panels.
|
||||||
|
- Use existing Tailwind/card/table visual conventions.
|
||||||
|
|
||||||
|
### Could
|
||||||
|
|
||||||
|
- Add saved watched-hub sets.
|
||||||
|
- Add multiple named dashboards per user.
|
||||||
|
- Add dashboard templates.
|
||||||
|
- Add shared/team dashboards.
|
||||||
|
- Add more panel types after the first framework slice is proven.
|
||||||
|
- Add finer-grained panel refresh routes later.
|
||||||
|
- Add user-selected default landing dashboard later.
|
||||||
|
|
||||||
|
### Won't for First Implementation
|
||||||
|
|
||||||
|
- Drag-and-drop layout editing.
|
||||||
|
- Mobile-native layout editor.
|
||||||
|
- Client-side data fetching.
|
||||||
|
- External dashboard or BI embedding.
|
||||||
|
- External datasource credentials.
|
||||||
|
- Role-based access control beyond existing authenticated controller guards.
|
||||||
|
- Complex visual charting library integration.
|
||||||
|
|
||||||
|
## 6. First-Slice Panel Catalogue
|
||||||
|
|
||||||
|
The first implementation should prove the framework with a small panel set.
|
||||||
|
|
||||||
|
| Panel key | Label | Purpose | Default behavior |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `watched-hubs` | Watched Hubs | Show hub list plus latest health hint | All hubs, limit 12 |
|
||||||
|
| `recent-interactions` | Recent Activity | Show latest interaction events with hub/widget context | Last 24h, limit 25 |
|
||||||
|
| `triage-queue` | Triage Queue | Show open requirement candidates | Open status, oldest first, limit 10 |
|
||||||
|
| `recent-decisions` | Recent Decisions | Show recent governance decisions | Last 30 days, newest first, limit 10 |
|
||||||
|
| `hub-health` | Hub Health | Show latest health snapshot and active blockers | Latest per hub, limit 12 |
|
||||||
|
| `learning-digest` | Learning Digest | Show latest insights and knowledge highlights | Latest insights and entries, limit 5 each |
|
||||||
|
|
||||||
|
Deferred panel catalogue candidates:
|
||||||
|
|
||||||
|
- `agent-proposals`
|
||||||
|
- `api-usage`
|
||||||
|
- `marketplace-trending`
|
||||||
|
- `my-annotations`
|
||||||
|
- `policy-compliance`
|
||||||
|
- `adapter-compatibility`
|
||||||
|
- `cross-hub-propagations`
|
||||||
|
|
||||||
|
## 7. Functional Requirements
|
||||||
|
|
||||||
|
### Dashboard Route
|
||||||
|
|
||||||
|
- Add a `PersonalDashboardsController` or similarly named controller.
|
||||||
|
- Add a show action for the current user's default dashboard.
|
||||||
|
- Add edit/update/add/remove actions for panel management.
|
||||||
|
- Register routes in `Web.Routes` and `Web.FrontController`.
|
||||||
|
- Add a sidebar link labelled `Dashboard`.
|
||||||
|
|
||||||
|
### Dashboard Persistence
|
||||||
|
|
||||||
|
- Store dashboard ownership by `users.id`.
|
||||||
|
- Support at least one default dashboard per user.
|
||||||
|
- Store panel order and grid layout.
|
||||||
|
- Store panel config in JSONB.
|
||||||
|
- Store a linked panel widget id for governance.
|
||||||
|
- Keep panel deletion non-destructive with respect to historical events and
|
||||||
|
annotations.
|
||||||
|
|
||||||
|
### Default Seeding
|
||||||
|
|
||||||
|
- On first visit, create a default dashboard for the authenticated user.
|
||||||
|
- Seed the first-slice panels with safe default config.
|
||||||
|
- Do not require a `users.role` column.
|
||||||
|
- Best-effort hub relevance may use active `stewardship_roles.assigned_to`
|
||||||
|
matching user email or name, but the neutral default must work without it.
|
||||||
|
|
||||||
|
### Panel Rendering
|
||||||
|
|
||||||
|
- Dispatch panels by stable catalogue key.
|
||||||
|
- Decode and validate config before querying.
|
||||||
|
- Use bounded queries.
|
||||||
|
- Render empty states and config warnings without crashing the whole dashboard.
|
||||||
|
- Wrap every rendered panel in `widgetEnvelope`.
|
||||||
|
- Link to source dashboards for deeper work.
|
||||||
|
|
||||||
|
### Edit Mode
|
||||||
|
|
||||||
|
- List current panels in layout order.
|
||||||
|
- Allow adding a panel from active panel types.
|
||||||
|
- Allow removing a panel from the dashboard.
|
||||||
|
- Allow editing column, row, span, limit, hub filter, time range, and display
|
||||||
|
mode where supported.
|
||||||
|
- Validate layout spans and config values server-side.
|
||||||
|
- Keep forms usable without custom JavaScript.
|
||||||
|
|
||||||
|
### Governance and Event Capture
|
||||||
|
|
||||||
|
- Saved panels must use stable `Widget` rows.
|
||||||
|
- Panel widgets should use the existing `panel` widget type.
|
||||||
|
- Panel widget `view_context` must be non-empty.
|
||||||
|
- Panel annotations must attach to the panel widget id.
|
||||||
|
- Panel interaction capture should use existing event types where possible.
|
||||||
|
- Adding/removing/reconfiguring panels should not mutate historical
|
||||||
|
interaction events.
|
||||||
|
|
||||||
|
## 8. Non-Functional Requirements
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
|
||||||
|
- Default dashboard first paint target: under 1 second in local development
|
||||||
|
with seeded fixtures.
|
||||||
|
- Each panel query should have a default row limit.
|
||||||
|
- Any aggregate query decoded as `Int` must cast `COUNT(*)` to integer or
|
||||||
|
decode as `Int64`.
|
||||||
|
- Avoid N+1 patterns where a simple join or batched fetch is practical.
|
||||||
|
- Use existing indexes where available; document new index needs in the FDD.
|
||||||
|
|
||||||
|
### Reliability
|
||||||
|
|
||||||
|
- Invalid panel config should not break the whole dashboard.
|
||||||
|
- Missing source data should render an empty state.
|
||||||
|
- Missing linked widget should be repaired or reported by the controller before
|
||||||
|
rendering.
|
||||||
|
- Dashboard seeding should be idempotent.
|
||||||
|
- Login redirect should fall back gracefully if dashboard seeding fails.
|
||||||
|
|
||||||
|
### Security and Privacy
|
||||||
|
|
||||||
|
- Dashboard routes require authenticated users.
|
||||||
|
- Users can view and edit only their own personal dashboard in the first slice.
|
||||||
|
- No secrets, API keys, or token values may be shown in dashboard panels.
|
||||||
|
- API usage panels must show only non-secret consumer metadata.
|
||||||
|
- Panel config must not become an arbitrary SQL or route execution surface.
|
||||||
|
|
||||||
|
### Accessibility and Usability
|
||||||
|
|
||||||
|
- Use semantic headings for panels.
|
||||||
|
- Use tables/lists for scan-heavy operational data.
|
||||||
|
- Use form labels for all edit inputs.
|
||||||
|
- Keep navigation links clear and predictable.
|
||||||
|
- Do not rely on hover-only controls for essential edits.
|
||||||
|
|
||||||
|
### Maintainability
|
||||||
|
|
||||||
|
- Put renderer dispatch and config decoding in a focused helper/module.
|
||||||
|
- Keep panel renderer functions small and testable.
|
||||||
|
- Avoid moving existing dashboard code unless required.
|
||||||
|
- Prefer additive schema migrations.
|
||||||
|
- Keep first implementation tasks small enough for separate Codex sessions.
|
||||||
|
|
||||||
|
## 9. Acceptance Criteria
|
||||||
|
|
||||||
|
The product design is acceptable when the FDD can specify:
|
||||||
|
|
||||||
|
- Exact schema tables and fields.
|
||||||
|
- Exact controller/action names.
|
||||||
|
- Exact default panel seed set.
|
||||||
|
- Exact panel config types and defaults.
|
||||||
|
- Exact first-slice panel query shapes.
|
||||||
|
- Exact governance identity lifecycle for panel widgets.
|
||||||
|
- Exact smoke tests for login, dashboard seeding, editing, annotation, and
|
||||||
|
source dashboard link-outs.
|
||||||
|
|
||||||
|
The implementation will be acceptable when:
|
||||||
|
|
||||||
|
- A new authenticated user lands on a seeded personal dashboard after login.
|
||||||
|
- The seeded dashboard renders all first-slice panels.
|
||||||
|
- The user can add, remove, and adjust panels through server-rendered forms.
|
||||||
|
- Panel layout persists across sessions.
|
||||||
|
- Each panel is wrapped in `widgetEnvelope`.
|
||||||
|
- Annotating a panel opens the existing widget annotation flow.
|
||||||
|
- Existing Hubs and specialized dashboard routes still load.
|
||||||
|
- Focused tests or smoke checks cover seeding, config validation, route access,
|
||||||
|
and bounded query behavior.
|
||||||
|
|
||||||
|
## 10. Risks and Mitigations
|
||||||
|
|
||||||
|
| Risk | Mitigation |
|
||||||
|
|---|---|
|
||||||
|
| Scope grows into report builder | Limit first slice to six panel types and server-rendered forms |
|
||||||
|
| Existing dashboards are hard-coded | Extract only needed query/render fragments into new panel renderers |
|
||||||
|
| Panel config becomes unsafe JSON | Decode into Haskell ADTs and validate before querying |
|
||||||
|
| Role-aware defaults require missing schema | Use neutral default first; only best-effort stewardship hints |
|
||||||
|
| AutoRefresh refreshes too much | Bound all queries; defer per-panel refresh unless needed |
|
||||||
|
| Panel annotations lack stable identity | Require `dashboard_panels.widget_id` and `widgetEnvelope` |
|
||||||
|
| COUNT decode errors recur | Cast aggregate counts or decode as `Int64` in implementation |
|
||||||
|
|
||||||
|
## 11. Open Questions for FDD
|
||||||
|
|
||||||
|
1. Should table names use `personal_dashboards`/`dashboard_panels`, or a more
|
||||||
|
explicit `user_dashboards` prefix?
|
||||||
|
2. Should removed panels archive their linked widgets or mark them deprecated?
|
||||||
|
3. Should panel widgets be owned by the framework hub, or by the source hub when
|
||||||
|
a panel is hub-specific?
|
||||||
|
4. Should the first implementation allow multiple dashboards per user, or only
|
||||||
|
one default dashboard with schema ready for multiples?
|
||||||
|
5. Should `autoRefresh` wrap the whole dashboard action initially, or should
|
||||||
|
live panel fragments get their own actions?
|
||||||
|
6. Should watched hubs be a separate table in the first slice, or represented
|
||||||
|
as dashboard panel config only?
|
||||||
|
|
||||||
|
## 12. Recommendation
|
||||||
|
|
||||||
|
Proceed to FDD with a small, governed, server-rendered personal dashboard:
|
||||||
|
|
||||||
|
- One default dashboard per user, schema ready for multiples.
|
||||||
|
- Six first-slice panel types.
|
||||||
|
- `dashboard_panels.widget_id` as the governance anchor.
|
||||||
|
- Existing `panel` widget type for saved panel widgets.
|
||||||
|
- Whole-page `autoRefresh` initially, with bounded queries.
|
||||||
|
- Simple edit forms and no custom client runtime.
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
# Ops Hub Evidence Intake - Current State
|
# Ops Hub Evidence Intake - Current State
|
||||||
|
|
||||||
Date: 2026-06-15
|
Date: 2026-06-15
|
||||||
|
Updated: 2026-06-27
|
||||||
|
|
||||||
Workplan: `IHUB-WP-0022`
|
Workplan: `IHUB-WP-0022`
|
||||||
|
|
||||||
@@ -17,6 +18,26 @@ slice is therefore contract-first:
|
|||||||
- wait on live ops-hub manifest/widgets, key provisioning, and production
|
- wait on live ops-hub manifest/widgets, key provisioning, and production
|
||||||
smoke before enabling per-entity Inter-Hub submission.
|
smoke before enabling per-entity Inter-Hub submission.
|
||||||
|
|
||||||
|
|
||||||
|
## 2026-06-27 Live Vocabulary Alignment
|
||||||
|
|
||||||
|
Public production probes now show an `ops-hub` row and public registry
|
||||||
|
vocabulary on `https://hub.coulomb.social`. The live ops-hub seed vocabulary
|
||||||
|
supersedes the early activity-core proposal for several names:
|
||||||
|
|
||||||
|
| Early proposal | Live target |
|
||||||
|
| --- | --- |
|
||||||
|
| `ops-service-observed` | `ops-service-discovered` |
|
||||||
|
| `ops-inventory-drift` | `ops-drift-detected` |
|
||||||
|
| `ops-access-path-checked` | Deferred; no live access-path event or widget type |
|
||||||
|
| `ops-evidence` policy scope | Use declared scopes such as `ops-production`, `ops-registry`, or `ops-backup-retention` |
|
||||||
|
| card-style widget types | Use live widget types such as `ops-service`, `ops-endpoint`, `ops-backup-set`, `ops-readiness-gate`, and `ops-risk` |
|
||||||
|
|
||||||
|
The contract docs have been revised to target the live seed vocabulary and to
|
||||||
|
keep access-path evidence on the State Hub fallback path until ops-hub either
|
||||||
|
adds access-path vocabulary or records an explicit readiness/risk mapping
|
||||||
|
decision.
|
||||||
|
|
||||||
## Inter-Hub API Surface
|
## Inter-Hub API Surface
|
||||||
|
|
||||||
The current repo supports the necessary primitives through `/api/v2`.
|
The current repo supports the necessary primitives through `/api/v2`.
|
||||||
@@ -119,30 +140,39 @@ Known gates before per-entity Inter-Hub submission can be treated as live:
|
|||||||
|
|
||||||
## Recommended Manifest Vocabulary
|
## Recommended Manifest Vocabulary
|
||||||
|
|
||||||
Use one policy scope for the first slice:
|
Use the live ops-hub manifest vocabulary rather than the early proposal names.
|
||||||
|
The current target is:
|
||||||
|
|
||||||
- `ops-evidence`
|
Policy scopes:
|
||||||
|
|
||||||
Use one annotation category:
|
- `ops-local`
|
||||||
|
- `ops-transitional-prod`
|
||||||
|
- `ops-production`
|
||||||
|
- `ops-threephoenix`
|
||||||
|
- `ops-registry`
|
||||||
|
- `ops-secrets`
|
||||||
|
- `ops-backup-retention`
|
||||||
|
|
||||||
- `ops-risk`
|
Widget types for activity-core evidence:
|
||||||
|
|
||||||
Use these widget types unless the operator prefers to keep a smaller aggregate
|
- `ops-service-catalog`
|
||||||
surface:
|
- `ops-service`
|
||||||
|
- `ops-endpoint`
|
||||||
|
- `ops-backup-set`
|
||||||
|
- `ops-readiness-gate`
|
||||||
|
- `ops-risk` when risk-specific drift representation is needed
|
||||||
|
|
||||||
- `ops-service-card`
|
Event types for the first Inter-Hub activity-core slice:
|
||||||
- `ops-endpoint-card`
|
|
||||||
- `ops-access-path-card`
|
|
||||||
- `ops-backup-card`
|
|
||||||
- `ops-drift-card`
|
|
||||||
|
|
||||||
Use the activity-core event types exactly as published:
|
- `ops-service-discovered`
|
||||||
|
|
||||||
- `ops-service-observed`
|
|
||||||
- `ops-endpoint-verified`
|
- `ops-endpoint-verified`
|
||||||
- `ops-access-path-checked`
|
- `ops-backup-verified` after a backup-set widget exists
|
||||||
- `ops-backup-verified`
|
- `ops-drift-detected`
|
||||||
- `ops-inventory-drift`
|
|
||||||
|
Keep `ops-access-path-checked` deferred until ops-hub declares access-path
|
||||||
|
vocabulary or a separate decision maps those checks to readiness/risk events.
|
||||||
|
Do not seed the old `ops-evidence` policy scope unless the manifest is
|
||||||
|
explicitly expanded to include it.
|
||||||
|
|
||||||
## Open Questions
|
## Open Questions
|
||||||
|
|
||||||
|
|||||||
348
docs/research/personal-dashboard-current-state.md
Normal file
348
docs/research/personal-dashboard-current-state.md
Normal file
@@ -0,0 +1,348 @@
|
|||||||
|
# Personal Dashboard Current-State Research
|
||||||
|
|
||||||
|
**Workplan:** IHUB-WP-0020
|
||||||
|
**Date:** 2026-06-16
|
||||||
|
**Status:** Research deliverable for T01
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
This note reviews the current inter-hub implementation before designing a
|
||||||
|
personal dashboard framework. The main finding is that inter-hub already has a
|
||||||
|
large set of server-rendered dashboard surfaces, governed widget identity, type
|
||||||
|
registries, annotations, event capture, hub health, API usage, marketplace, and
|
||||||
|
learning data. The personal dashboard should compose these capabilities instead
|
||||||
|
of inventing a separate dashboard product.
|
||||||
|
|
||||||
|
External dashboard products are used here only for pattern extraction. The
|
||||||
|
implementation direction remains IHP, HSX, Tailwind, server-rendered forms, and
|
||||||
|
existing IHF governance primitives.
|
||||||
|
|
||||||
|
## Evidence Reviewed
|
||||||
|
|
||||||
|
Repo files inspected for this note:
|
||||||
|
|
||||||
|
- `Web/Controller/Hubs.hs`
|
||||||
|
- `Web/Controller/Sessions.hs`
|
||||||
|
- `Web/FrontController.hs`
|
||||||
|
- `Web/Routes.hs`
|
||||||
|
- `Web/Types.hs`
|
||||||
|
- `Application/Schema.sql`
|
||||||
|
- `Application/Helper/View.hs`
|
||||||
|
- `Web/Controller/FederatedGovernance.hs`
|
||||||
|
- `Web/Controller/FederatedPolicyOverlays.hs`
|
||||||
|
- `Web/Controller/ApiDashboard.hs`
|
||||||
|
- `Web/Controller/MarketplaceDashboard.hs`
|
||||||
|
- `Web/Controller/LearningDashboard.hs`
|
||||||
|
- `docs/phase1-summary.md` through `docs/phase8-summary.md`
|
||||||
|
- `docs/ihp-ihf-mapping.md`
|
||||||
|
- `docs/widget-envelope-convention.md`
|
||||||
|
- Workplans IHUB-WP-0001 through IHUB-WP-0015 where dashboard scope was
|
||||||
|
introduced.
|
||||||
|
|
||||||
|
## Current Authenticated Entry Point
|
||||||
|
|
||||||
|
The public root and documentation pages already exist through IHUB-WP-0015 and
|
||||||
|
are registered last in `Web.FrontController`. The authenticated login flow still
|
||||||
|
redirects to `HubsAction`:
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
login user
|
||||||
|
redirectTo HubsAction
|
||||||
|
```
|
||||||
|
|
||||||
|
`HubsAction` renders a table of hubs. It is useful as an admin list, but it is
|
||||||
|
not a personal daily operating surface.
|
||||||
|
|
||||||
|
The sidebar already links to several platform surfaces, including Hubs,
|
||||||
|
Learning, Ops Review, Federation, API Dashboard, Hub Registry, and Marketplace.
|
||||||
|
The personal dashboard should therefore become a new authenticated landing
|
||||||
|
route and a sidebar entry, while public root behavior remains unchanged.
|
||||||
|
|
||||||
|
## Existing Dashboard Inventory
|
||||||
|
|
||||||
|
| Surface | Action | Scope | Live? | Reuse potential |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Hub list | `HubsAction` | Global hub table | No | Source for watched hubs and hub selector |
|
||||||
|
| Hub show | `ShowHubAction` | One hub | Yes | Recent events, annotations, widgets, manifest summary |
|
||||||
|
| Triage dashboard | `TriageDashboardAction` | One hub | Yes | Open candidates, recent escalations, annotation breakdown |
|
||||||
|
| Governance dashboard | `GovernanceDashboardAction` | One hub | Yes | Accepted candidates, requirements, decisions, traceability |
|
||||||
|
| Antifragility dashboard | `AntifragilityDashboardAction` | One hub | Yes | Deployments, outcome signals, recurrence leaderboard |
|
||||||
|
| Agent audit dashboard | `AgentAuditDashboardAction` | One hub context, global data | Yes | Agent proposals and review status |
|
||||||
|
| Adapter compatibility | `AdapterCompatibilityDashboardAction` | One hub | Yes | Adapter and contract compatibility panels |
|
||||||
|
| Friction heatmap | `FrictionHeatmapAction` | One hub | Yes | Widget friction summary |
|
||||||
|
| Bottleneck dashboard | `BottleneckDashboardAction` | One hub | Yes | Open bottlenecks |
|
||||||
|
| Hub health history | `HubHealthHistoryAction` | One hub | Yes | Health snapshot trend |
|
||||||
|
| Operational review board | `OperationalReviewBoardAction` | Global | Yes | Hub health, top friction, bottlenecks, propagations |
|
||||||
|
| Federated governance | `FederatedGovernanceDashboardAction` | Global | Yes | Ownership, routing, policy, stewardship, archive activity |
|
||||||
|
| Policy compliance | `PolicyComplianceDashboardAction` | Global | Yes | Active overlays and policy reference coverage |
|
||||||
|
| API dashboard | `ShowApiDashboardAction` | Global API consumers | Yes | Per-consumer request volume, error rate, last seen |
|
||||||
|
| Marketplace | `MarketplaceDashboardAction` | Global patterns/templates | Yes | Trending patterns, search/filter catalogue |
|
||||||
|
| Learning dashboard | `LearningDashboardAction` | Global learning memory | Yes | Insights, knowledge highlights, pattern rankings |
|
||||||
|
|
||||||
|
The reusable unit today is not a standalone panel renderer. It is a controller
|
||||||
|
query plus an HSX view fragment. WP-0021 should extract only the first small set
|
||||||
|
of renderers needed for the personal dashboard. A broad refactor of existing
|
||||||
|
dashboards is explicitly unnecessary for the first slice.
|
||||||
|
|
||||||
|
## AutoRefresh and Query Patterns
|
||||||
|
|
||||||
|
Most dashboard actions wrap the whole action with `autoRefresh do`. This is
|
||||||
|
simple and consistent with the existing IHP style. The current app does not have
|
||||||
|
a reusable per-panel refresh abstraction.
|
||||||
|
|
||||||
|
Useful bounded patterns already exist:
|
||||||
|
|
||||||
|
- `ShowHubAction` limits recent interaction events to 50 and annotations to 20.
|
||||||
|
- `GovernanceDashboardAction` limits recent decisions to 20.
|
||||||
|
- `LearningDashboardAction` limits correlations, rankings, insights, and
|
||||||
|
knowledge highlights.
|
||||||
|
- `MarketplaceDashboardAction` limits published patterns/templates and casts the
|
||||||
|
trending adoption count to integer.
|
||||||
|
|
||||||
|
Risky or broad patterns to avoid copying directly:
|
||||||
|
|
||||||
|
- Some hub dashboards fetch all related records for a hub and filter in memory.
|
||||||
|
That is acceptable for small scoped screens, but a personal dashboard should
|
||||||
|
bound every panel query by hub, time, status, and limit.
|
||||||
|
- Several global dashboards fetch all hubs or all decision records. A personal
|
||||||
|
view should either limit these or explicitly display only summarized rows.
|
||||||
|
- Raw `COUNT(*)` queries should cast to `integer` or decode as `Int64`. Recent
|
||||||
|
production work exposed PostgreSQL/Haskell decode failures when `COUNT(*)`
|
||||||
|
was decoded as `Int`.
|
||||||
|
|
||||||
|
Recommendation: first implementation can wrap the whole personal dashboard
|
||||||
|
action in `autoRefresh do`. The FDD should leave finer-grained panel refresh as
|
||||||
|
a later optimization unless a simple route-level fragment pattern emerges.
|
||||||
|
|
||||||
|
## Governed Widget and Annotation Constraints
|
||||||
|
|
||||||
|
`Application.Helper.View.widgetEnvelope` wraps a `Widget` record and injects
|
||||||
|
governance metadata:
|
||||||
|
|
||||||
|
- `data-widget-id`
|
||||||
|
- `data-widget-type`
|
||||||
|
- `data-hub-id`
|
||||||
|
- `data-capability-ref`
|
||||||
|
- `data-view-context`
|
||||||
|
- `data-policy-scope`
|
||||||
|
- `data-widget-version`
|
||||||
|
|
||||||
|
It also renders an Annotate link to the widget annotation view. The helper
|
||||||
|
warns when `view_context` is absent. Therefore, a saved personal dashboard panel
|
||||||
|
must not be treated as transient markup. It needs stable widget identity.
|
||||||
|
|
||||||
|
The registry seed includes a framework-level widget type named `panel`. That is
|
||||||
|
the best first choice for saved dashboard panels. A saved panel instance should
|
||||||
|
create or reference a `widgets` row with:
|
||||||
|
|
||||||
|
- `widget_type = 'panel'`
|
||||||
|
- `capability_ref = 'personal-dashboard.<panel-key>'`
|
||||||
|
- `view_context = 'personal-dashboard/<panel-key>'`
|
||||||
|
- `policy_scope = 'internal'`
|
||||||
|
- `status = 'active'`
|
||||||
|
|
||||||
|
The FDD should decide the owning hub rule. Recommended first slice: use the
|
||||||
|
framework hub for personal dashboard panel widgets and store source hub filters
|
||||||
|
in panel config. This keeps the personal dashboard itself governed by inter-hub
|
||||||
|
while still letting panels point at hub-specific data.
|
||||||
|
|
||||||
|
## Schema Available for First-Slice Panels
|
||||||
|
|
||||||
|
Existing tables with direct panel value:
|
||||||
|
|
||||||
|
- `hubs`
|
||||||
|
- `widgets`
|
||||||
|
- `widget_versions`
|
||||||
|
- `interaction_events`
|
||||||
|
- `annotations`
|
||||||
|
- `annotation_threads`
|
||||||
|
- `requirement_candidates`
|
||||||
|
- `triage_states`
|
||||||
|
- `reviewer_assignments`
|
||||||
|
- `requirements`
|
||||||
|
- `decision_records`
|
||||||
|
- `deployment_records`
|
||||||
|
- `outcome_signals`
|
||||||
|
- `friction_scores`
|
||||||
|
- `bottleneck_records`
|
||||||
|
- `hub_health_snapshots`
|
||||||
|
- `cross_hub_propagations`
|
||||||
|
- `widget_ownerships`
|
||||||
|
- `hub_routing_rules`
|
||||||
|
- `federated_policy_overlays`
|
||||||
|
- `stewardship_roles`
|
||||||
|
- `archive_records`
|
||||||
|
- `widget_type_registry`
|
||||||
|
- `event_type_registry`
|
||||||
|
- `annotation_category_registry`
|
||||||
|
- `policy_scope_registry`
|
||||||
|
- `hub_capability_manifests`
|
||||||
|
- `api_consumers`
|
||||||
|
- `api_request_log`
|
||||||
|
- `widget_patterns`
|
||||||
|
- `pattern_adoptions`
|
||||||
|
- `governance_templates`
|
||||||
|
- `governance_template_clones`
|
||||||
|
- `outcome_correlations`
|
||||||
|
- `pattern_performance_records`
|
||||||
|
- `adaptive_threshold_configs`
|
||||||
|
- `institutional_knowledge_entries`
|
||||||
|
- `learning_insights`
|
||||||
|
|
||||||
|
Important gaps:
|
||||||
|
|
||||||
|
- No `personal_dashboards` table.
|
||||||
|
- No `dashboard_panel_types` table.
|
||||||
|
- No `dashboard_panels` table.
|
||||||
|
- No saved watched-hub set or user preference table.
|
||||||
|
- No user role column.
|
||||||
|
- No panel config decoder/validator.
|
||||||
|
- No dedicated panel renderer module.
|
||||||
|
- No explicit default dashboard seeding helper.
|
||||||
|
|
||||||
|
## User and Role Model Findings
|
||||||
|
|
||||||
|
The `users` table has email, password hash, name, lockout fields, and created
|
||||||
|
time. It has no role. `stewardship_roles` stores `assigned_to` as text and is
|
||||||
|
hub-scoped. That can help infer operator relevance, but it is not a reliable
|
||||||
|
role foreign key.
|
||||||
|
|
||||||
|
Recommendation: do not add `users.role` for the first slice. Seed a neutral
|
||||||
|
default dashboard for all authenticated users, then allow the user to edit panel
|
||||||
|
layout and filters. If a default needs hub relevance, match active
|
||||||
|
`stewardship_roles.assigned_to` against user email or name as a best-effort
|
||||||
|
hint, not as an authorization rule.
|
||||||
|
|
||||||
|
## First-Slice Panel Candidates
|
||||||
|
|
||||||
|
The following panels are practical without broad refactoring:
|
||||||
|
|
||||||
|
| Panel key | Source | Default filter | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `watched-hubs` | `hubs`, latest `hub_health_snapshots` | all hubs, limit 12 | First panel can be neutral until watched hubs exist |
|
||||||
|
| `recent-interactions` | `interaction_events`, `widgets`, `hubs` | last 24h, limit 25 | Existing indexes support recent ordering |
|
||||||
|
| `triage-queue` | `requirement_candidates` | `status = 'open'`, limit 10 | Can join source widget/hub for context |
|
||||||
|
| `recent-decisions` | `decision_records` | last 30 days, limit 10 | Good governance reviewer entry point |
|
||||||
|
| `hub-health` | `hub_health_snapshots`, `bottleneck_records` | latest per hub, limit 12 | Needs bounded latest-per-hub query |
|
||||||
|
| `learning-digest` | `learning_insights`, `institutional_knowledge_entries` | latest, limit 5/5 | Already bounded in existing dashboard |
|
||||||
|
|
||||||
|
Panels to defer until after the framework is proven:
|
||||||
|
|
||||||
|
- `agent-proposals`
|
||||||
|
- `api-usage`
|
||||||
|
- `marketplace-trending`
|
||||||
|
- `my-annotations`
|
||||||
|
- `adapter-compatibility`
|
||||||
|
- `policy-compliance`
|
||||||
|
|
||||||
|
The deferred panels are valuable, but they are not needed to prove dashboard
|
||||||
|
persistence, layout, panel renderer dispatch, and governed panel identity.
|
||||||
|
|
||||||
|
## External Pattern Extraction
|
||||||
|
|
||||||
|
| System | Useful pattern | Translation for inter-hub |
|
||||||
|
|---|---|---|
|
||||||
|
| Grafana | Dashboard as saved grid of panels with variables and refresh behavior | Save panel rows plus hub/time filters; keep server-rendered refresh |
|
||||||
|
| Kibana dashboards | Saved searches and time range awareness | Treat panel query config as explicit, bounded, validated config |
|
||||||
|
| Retool/Appsmith | Widget catalogue and data binding | Use a server-side panel catalogue; avoid client runtime/data binding |
|
||||||
|
| Linear home | Personal "my work" aggregation across entities | Make the personal dashboard a daily work queue, not a clone of every dashboard |
|
||||||
|
| Notion linked databases | Multiple saved views over the same records | Let panels define filter/sort/display options against existing tables |
|
||||||
|
| Metabase | Question as a governed reusable unit | Treat panel renderer plus validated config as the reusable unit |
|
||||||
|
| Streamlit | Simple declarative layout vocabulary | Use predictable grid rows/spans and forms rather than drag-and-drop |
|
||||||
|
|
||||||
|
The key pattern across these systems is not visual complexity. It is that a
|
||||||
|
dashboard is a saved composition of bounded questions/panels with explicit
|
||||||
|
parameters. For inter-hub, those questions must remain governed IHF widgets.
|
||||||
|
|
||||||
|
## Answers to WP-0020 Research Questions
|
||||||
|
|
||||||
|
### Which existing fragments can become first-slice renderers?
|
||||||
|
|
||||||
|
Good first-slice candidates:
|
||||||
|
|
||||||
|
- Recent activity from `ShowHubAction`.
|
||||||
|
- Open candidate queue from `TriageDashboardAction`.
|
||||||
|
- Recent decisions from `GovernanceDashboardAction`.
|
||||||
|
- Latest hub health from `OperationalReviewBoardAction` and
|
||||||
|
`HubHealthHistoryAction`.
|
||||||
|
- Learning digest from `LearningDashboardAction`.
|
||||||
|
- Watched hubs from `HubsAction` plus latest health snapshots.
|
||||||
|
|
||||||
|
These can be implemented as new renderer functions that reuse the same model
|
||||||
|
queries and link to existing source dashboards for detail.
|
||||||
|
|
||||||
|
### Which configs are needed on day one?
|
||||||
|
|
||||||
|
Recommended day-one config options:
|
||||||
|
|
||||||
|
- `hubIds :: [Id Hub]` or `hubFilter :: Maybe [Id Hub]`
|
||||||
|
- `timeRange :: Last24Hours | Last7Days | Last30Days | AllTimeBounded`
|
||||||
|
- `limit :: Int`
|
||||||
|
- `sort :: NewestFirst | OldestFirst | HighestRiskFirst`
|
||||||
|
- `displayMode :: Compact | Detailed`
|
||||||
|
|
||||||
|
Config should be stored as JSONB but decoded into a Haskell ADT before use.
|
||||||
|
Invalid config should fall back to panel defaults and surface a non-fatal
|
||||||
|
operator warning.
|
||||||
|
|
||||||
|
### Which panels should live-refresh?
|
||||||
|
|
||||||
|
Live-refresh in the first slice:
|
||||||
|
|
||||||
|
- `recent-interactions`
|
||||||
|
- `triage-queue`
|
||||||
|
- `hub-health`
|
||||||
|
- `learning-digest`
|
||||||
|
|
||||||
|
Static per request in the first slice:
|
||||||
|
|
||||||
|
- `watched-hubs`
|
||||||
|
- `recent-decisions`
|
||||||
|
|
||||||
|
If the first implementation wraps the entire personal dashboard in
|
||||||
|
`autoRefresh`, all panels will refresh together. That is acceptable initially if
|
||||||
|
queries are bounded.
|
||||||
|
|
||||||
|
### How should saved panels map to governed widgets?
|
||||||
|
|
||||||
|
Each saved dashboard panel should own a `widgets` row and store the id on
|
||||||
|
`dashboard_panels.widget_id`. The panel renderer should call `widgetEnvelope`
|
||||||
|
with that widget. This gives stable annotation and interaction capture identity.
|
||||||
|
|
||||||
|
Panel lifecycle:
|
||||||
|
|
||||||
|
1. User adds a panel.
|
||||||
|
2. Controller creates `dashboard_panels` row.
|
||||||
|
3. Controller creates linked `widgets` row with `widget_type = 'panel'`.
|
||||||
|
4. Controller creates a `widget_versions` snapshot for the panel widget.
|
||||||
|
5. Show view renders the panel through `widgetEnvelope`.
|
||||||
|
6. Removing a panel should mark the widget archived or deprecated, not delete
|
||||||
|
interaction history.
|
||||||
|
|
||||||
|
### What should be deferred?
|
||||||
|
|
||||||
|
Defer:
|
||||||
|
|
||||||
|
- Drag-and-drop layout.
|
||||||
|
- Shared dashboards.
|
||||||
|
- Team dashboards.
|
||||||
|
- External datasource connectors.
|
||||||
|
- Client-side data fetching.
|
||||||
|
- Per-panel WebSocket channels.
|
||||||
|
- Full refactor of existing dashboard views.
|
||||||
|
- Complex role model.
|
||||||
|
- Dashboard marketplace/templates beyond one seeded default.
|
||||||
|
|
||||||
|
## Recommendations for T02/T03
|
||||||
|
|
||||||
|
1. Define the personal dashboard as the authenticated landing page, not a
|
||||||
|
replacement for existing source dashboards.
|
||||||
|
2. Use a small panel catalogue for the first implementation.
|
||||||
|
3. Persist dashboard/panel rows in relational tables and panel config in JSONB.
|
||||||
|
4. Decode panel config into explicit Haskell ADTs before querying.
|
||||||
|
5. Give every saved panel stable `Widget` identity.
|
||||||
|
6. Use the existing `panel` widget type.
|
||||||
|
7. Keep the default dashboard neutral and editable.
|
||||||
|
8. Bound every panel query.
|
||||||
|
9. Cast SQL aggregate counts to integer when decoding as `Int`.
|
||||||
|
10. Keep implementation tasks small enough to avoid a cross-dashboard refactor.
|
||||||
@@ -9,6 +9,7 @@ owner: codex
|
|||||||
topic_slug: inter_hub
|
topic_slug: inter_hub
|
||||||
created: "2026-06-06"
|
created: "2026-06-06"
|
||||||
updated: "2026-06-06"
|
updated: "2026-06-06"
|
||||||
|
state_hub_workstream_id: "302b4e09-a03c-4afe-939f-4c89c84bc2c9"
|
||||||
---
|
---
|
||||||
|
|
||||||
# ADHOC-2026-06-06 - Ad hoc fixes for 2026-06-06
|
# ADHOC-2026-06-06 - Ad hoc fixes for 2026-06-06
|
||||||
@@ -19,6 +20,7 @@ updated: "2026-06-06"
|
|||||||
id: ADHOC-2026-06-06-T01
|
id: ADHOC-2026-06-06-T01
|
||||||
status: done
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
|
state_hub_task_id: "153f02de-803c-4a78-9cd7-0f6483c787ea"
|
||||||
```
|
```
|
||||||
|
|
||||||
Added default `make` help plus `install`, `install-nix`, `doctor`, and `ui`
|
Added default `make` help plus `install`, `install-nix`, `doctor`, and `ui`
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
id: IHUB-WP-0001
|
id: IHUB-WP-0001
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "IHF Phase 1 — Minimal Interaction Core"
|
title: "IHF Phase 1 — Minimal Interaction Core"
|
||||||
domain: inter_hub
|
domain: infotech
|
||||||
repo: inter-hub
|
repo: inter-hub
|
||||||
status: done
|
status: done
|
||||||
owner: custodian
|
owner: custodian
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
id: IHUB-WP-0002
|
id: IHUB-WP-0002
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "IHF Phase 2 — Structured Feedback and Triage"
|
title: "IHF Phase 2 — Structured Feedback and Triage"
|
||||||
domain: custodian
|
domain: infotech
|
||||||
repo: inter-hub
|
repo: inter-hub
|
||||||
status: done
|
status: done
|
||||||
owner: custodian
|
owner: custodian
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
id: IHUB-WP-0003
|
id: IHUB-WP-0003
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "IHF Phase 3 — Governance and Decision Linkage"
|
title: "IHF Phase 3 — Governance and Decision Linkage"
|
||||||
domain: inter_hub
|
domain: infotech
|
||||||
repo: inter-hub
|
repo: inter-hub
|
||||||
status: done
|
status: done
|
||||||
owner: custodian
|
owner: custodian
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
id: IHUB-WP-0004
|
id: IHUB-WP-0004
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "IHF Phase 4 — Outcome Observation and Antifragility Loop"
|
title: "IHF Phase 4 — Outcome Observation and Antifragility Loop"
|
||||||
domain: inter_hub
|
domain: infotech
|
||||||
repo: inter-hub
|
repo: inter-hub
|
||||||
status: done
|
status: done
|
||||||
owner: custodian
|
owner: custodian
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
id: IHUB-WP-0005
|
id: IHUB-WP-0005
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "IHF Phase 5 — Agent-Assisted Distillation and Suggestion"
|
title: "IHF Phase 5 — Agent-Assisted Distillation and Suggestion"
|
||||||
domain: inter_hub
|
domain: infotech
|
||||||
repo: inter-hub
|
repo: inter-hub
|
||||||
status: done
|
status: done
|
||||||
owner: custodian
|
owner: custodian
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
id: IHUB-WP-0006
|
id: IHUB-WP-0006
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "IHF Phase 6 — Cross-Framework UI Adaptation Layer"
|
title: "IHF Phase 6 — Cross-Framework UI Adaptation Layer"
|
||||||
domain: inter_hub
|
domain: infotech
|
||||||
repo: inter-hub
|
repo: inter-hub
|
||||||
status: done
|
status: done
|
||||||
owner: custodian
|
owner: custodian
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
id: IHUB-WP-0007
|
id: IHUB-WP-0007
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "IHF Phase 7 — Advanced Observability and Operational Integration"
|
title: "IHF Phase 7 — Advanced Observability and Operational Integration"
|
||||||
domain: inter_hub
|
domain: infotech
|
||||||
repo: inter-hub
|
repo: inter-hub
|
||||||
status: done
|
status: done
|
||||||
owner: custodian
|
owner: custodian
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
id: IHUB-WP-0008
|
id: IHUB-WP-0008
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "IHF Phase 8 — Federated Hub Maturity"
|
title: "IHF Phase 8 — Federated Hub Maturity"
|
||||||
domain: inter_hub
|
domain: infotech
|
||||||
repo: inter-hub
|
repo: inter-hub
|
||||||
status: done
|
status: done
|
||||||
owner: custodian
|
owner: custodian
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
id: IHUB-WP-0009
|
id: IHUB-WP-0009
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "IHF GAAF Compliance Foundation — Type Registries, Extension Manifests, and Architectural Contracts"
|
title: "IHF GAAF Compliance Foundation — Type Registries, Extension Manifests, and Architectural Contracts"
|
||||||
domain: inter_hub
|
domain: infotech
|
||||||
repo: inter-hub
|
repo: inter-hub
|
||||||
status: done
|
status: done
|
||||||
owner: custodian
|
owner: custodian
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
id: IHUB-WP-0010
|
id: IHUB-WP-0010
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "IHF Phase 9 — External API Surface and Consumer SDKs"
|
title: "IHF Phase 9 — External API Surface and Consumer SDKs"
|
||||||
domain: inter_hub
|
domain: infotech
|
||||||
repo: inter-hub
|
repo: inter-hub
|
||||||
status: done
|
status: done
|
||||||
owner: custodian
|
owner: custodian
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
id: IHUB-WP-0011
|
id: IHUB-WP-0011
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "IHF Phase 10 — Hub Registry and Widget Marketplace"
|
title: "IHF Phase 10 — Hub Registry and Widget Marketplace"
|
||||||
domain: inter_hub
|
domain: infotech
|
||||||
repo: inter-hub
|
repo: inter-hub
|
||||||
status: done
|
status: done
|
||||||
owner: custodian
|
owner: custodian
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
id: IHUB-WP-0012
|
id: IHUB-WP-0012
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "IHF Phase 11 — Advanced AI Federation"
|
title: "IHF Phase 11 — Advanced AI Federation"
|
||||||
domain: inter_hub
|
domain: infotech
|
||||||
repo: inter-hub
|
repo: inter-hub
|
||||||
status: done
|
status: done
|
||||||
owner: custodian
|
owner: custodian
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
id: IHUB-WP-0013
|
id: IHUB-WP-0013
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "IHF Phase 12 — Platform Memory and Continuous Learning"
|
title: "IHF Phase 12 — Platform Memory and Continuous Learning"
|
||||||
domain: inter_hub
|
domain: infotech
|
||||||
repo: inter-hub
|
repo: inter-hub
|
||||||
status: done
|
status: done
|
||||||
owner: custodian
|
owner: custodian
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
id: IHUB-WP-0014
|
id: IHUB-WP-0014
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "Pre-flight: Close Deployment Gaps"
|
title: "Pre-flight: Close Deployment Gaps"
|
||||||
domain: inter_hub
|
domain: infotech
|
||||||
repo: inter-hub
|
repo: inter-hub
|
||||||
status: done
|
status: done
|
||||||
owner: custodian
|
owner: custodian
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
id: IHUB-WP-0015
|
id: IHUB-WP-0015
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "Local Deployment — Intro and Tutorial Web UI"
|
title: "Local Deployment — Intro and Tutorial Web UI"
|
||||||
domain: inter_hub
|
domain: infotech
|
||||||
repo: inter-hub
|
repo: inter-hub
|
||||||
status: done
|
status: done
|
||||||
owner: custodian
|
owner: custodian
|
||||||
@@ -11,8 +11,7 @@ created: "2026-04-03"
|
|||||||
updated: "2026-04-03"
|
updated: "2026-04-03"
|
||||||
state_hub_sync: done
|
state_hub_sync: done
|
||||||
state_hub_workstream_id: "946d50b8-441c-4c0a-b1a0-2a4fb3340d16"
|
state_hub_workstream_id: "946d50b8-441c-4c0a-b1a0-2a4fb3340d16"
|
||||||
depends_on: IHUB-WP-0014
|
depends_on: IHUB-WP-0014---
|
||||||
---
|
|
||||||
|
|
||||||
# IHUB-WP-0015 — Local Deployment: Intro and Tutorial Web UI
|
# IHUB-WP-0015 — Local Deployment: Intro and Tutorial Web UI
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
id: IHUB-WP-0016
|
id: IHUB-WP-0016
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "Build Infrastructure: Incremental Compilation and Autonomous Error-Fix Loop"
|
title: "Build Infrastructure: Incremental Compilation and Autonomous Error-Fix Loop"
|
||||||
domain: inter_hub
|
domain: infotech
|
||||||
repo: inter-hub
|
repo: inter-hub
|
||||||
status: done
|
status: done
|
||||||
owner: custodian
|
owner: custodian
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
id: IHUB-WP-0017
|
id: IHUB-WP-0017
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "Autonomous Error-Fix Loop: Reach Clean Build"
|
title: "Autonomous Error-Fix Loop: Reach Clean Build"
|
||||||
domain: inter_hub
|
domain: infotech
|
||||||
repo: inter-hub
|
repo: inter-hub
|
||||||
status: done
|
status: done
|
||||||
owner: custodian
|
owner: custodian
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
id: IHUB-WP-0018
|
id: IHUB-WP-0018
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "Railiance01 Deployment — Production Operations Scaffold"
|
title: "Railiance01 Deployment — Production Operations Scaffold"
|
||||||
domain: inter_hub
|
domain: infotech
|
||||||
repo: inter-hub
|
repo: inter-hub
|
||||||
status: finished
|
status: finished
|
||||||
owner: custodian
|
owner: custodian
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
id: IHUB-WP-0019
|
id: IHUB-WP-0019
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "VSM Hub Bootstrap API Hardening"
|
title: "VSM Hub Bootstrap API Hardening"
|
||||||
domain: inter_hub
|
domain: infotech
|
||||||
repo: inter-hub
|
repo: inter-hub
|
||||||
status: finished
|
status: finished
|
||||||
owner: codex
|
owner: codex
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
id: IHUB-WP-0020
|
id: IHUB-WP-0020
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "Personal Dashboard Framework"
|
title: "Personal Dashboard Framework"
|
||||||
domain: inter_hub
|
domain: infotech
|
||||||
repo: inter-hub
|
repo: inter-hub
|
||||||
status: backlog
|
status: finished
|
||||||
owner: tegwick
|
owner: tegwick
|
||||||
topic_slug: inter_hub
|
topic_slug: inter_hub
|
||||||
created: "2026-05-03"
|
created: "2026-05-03"
|
||||||
updated: "2026-06-07"
|
updated: "2026-06-16"
|
||||||
phase: 13
|
phase: 13
|
||||||
state_hub_workstream_id: "72fc022b-0196-492a-aaba-3475f8768f06"
|
state_hub_workstream_id: "72fc022b-0196-492a-aaba-3475f8768f06"
|
||||||
---
|
---
|
||||||
@@ -17,283 +17,399 @@ state_hub_workstream_id: "72fc022b-0196-492a-aaba-3475f8768f06"
|
|||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
|
|
||||||
Design and implement a personal dashboard framework that allows individual users to
|
Design the first personal dashboard layer for inter-hub: an authenticated,
|
||||||
compose, configure, and persist a view of the inter-hub platform tailored to their
|
per-user landing surface that composes the most important existing hub,
|
||||||
role and focus. The dashboard is the post-login landing page and the primary daily
|
governance, API, marketplace, and learning signals into a configurable daily
|
||||||
driver surface for hub operators, governance reviewers, and AI orchestrators.
|
operator view.
|
||||||
|
|
||||||
## Motivation
|
This workplan is now a design and implementation-planning workplan. It should
|
||||||
|
produce the current-state audit, product requirements, functional design, and
|
||||||
|
follow-on implementation workplan needed to build the feature safely.
|
||||||
|
|
||||||
The current post-login experience drops the user on a raw Hubs list. As inter-hub
|
## Review Update: 2026-06-15
|
||||||
grows to 12+ phases of functionality, users need a curated, role-aware entry point
|
|
||||||
that surfaces the signals that matter to them without requiring manual navigation.
|
|
||||||
The dashboard is also the natural home for cross-cutting observability (recent
|
|
||||||
decisions, open candidates, outcome signals) that cuts across the current
|
|
||||||
controller-per-entity navigation.
|
|
||||||
|
|
||||||
---
|
This workplan was reviewed against the current repository state and updated
|
||||||
|
from `backlog` to `ready`.
|
||||||
|
|
||||||
|
The original version assumed inter-hub mainly had a raw Hubs list and needed a
|
||||||
|
greenfield dashboard framework. That assumption is outdated. The repo now has
|
||||||
|
many dashboard-like surfaces and governed interaction primitives that should be
|
||||||
|
reused instead of bypassed:
|
||||||
|
|
||||||
|
- Public root/intro pages exist from IHUB-WP-0015; the authenticated login flow
|
||||||
|
still redirects to `HubsAction`.
|
||||||
|
- Hub-level dashboard actions already exist in `Web.Controller.Hubs`, including
|
||||||
|
hub show, triage, governance, antifragility, agent audit, adapter
|
||||||
|
compatibility, friction heatmap, bottleneck, hub health history, and the
|
||||||
|
operational review board.
|
||||||
|
- Cross-hub and platform dashboards already exist: federated governance, policy
|
||||||
|
compliance, API usage, marketplace, and learning dashboard.
|
||||||
|
- The governed interaction substrate is mature: `widgets`, `widget_versions`,
|
||||||
|
`interaction_events`, `annotations`, type registries, hub manifests,
|
||||||
|
ownership/routing, API request logs, hub health snapshots, learning insights,
|
||||||
|
and institutional knowledge are all present.
|
||||||
|
- There is no personal dashboard schema, controller, saved panel catalogue,
|
||||||
|
user preference model, or role-aware default layout yet.
|
||||||
|
- Existing dashboards are mostly hard-coded controller queries plus HSX view
|
||||||
|
fragments. They are useful source material, but they are not yet reusable
|
||||||
|
panel renderers.
|
||||||
|
- The `users` table has no role column. `stewardship_roles.assigned_to` is text
|
||||||
|
and hub-scoped, so role-aware defaults must be designed carefully instead of
|
||||||
|
assuming a user-role foreign key exists.
|
||||||
|
|
||||||
|
The updated scope is therefore integration-first: define a personal dashboard
|
||||||
|
contract that reuses existing data sources and view patterns, then introduce a
|
||||||
|
small panel renderer abstraction only where it removes real duplication.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
### In Scope
|
||||||
|
|
||||||
|
- Authenticated personal dashboard route and post-login redirect design.
|
||||||
|
- Per-user saved dashboard record with ordered panel instances.
|
||||||
|
- A server-rendered panel catalogue backed by existing inter-hub models.
|
||||||
|
- Simple layout editing through IHP forms; no drag-and-drop in the first slice.
|
||||||
|
- Hub/time filters for panels where the underlying queries already support
|
||||||
|
bounded data.
|
||||||
|
- Panel-level governance: each rendered saved panel must be annotatable and
|
||||||
|
event-capturable through the existing `widgetEnvelope` convention.
|
||||||
|
- A migration path that reuses current dashboard queries before attempting broad
|
||||||
|
refactors.
|
||||||
|
|
||||||
|
### Out of Scope for the First Implementation Workplan
|
||||||
|
|
||||||
|
- Client-side dashboard frameworks or client-side data fetching.
|
||||||
|
- External datasource connectors.
|
||||||
|
- Shared/team dashboards.
|
||||||
|
- Mobile-native layout editing.
|
||||||
|
- Drag-and-drop layout editing.
|
||||||
|
- A general purpose report builder.
|
||||||
|
- Rewriting every existing dashboard into panel renderers.
|
||||||
|
|
||||||
|
## Current Design Constraints
|
||||||
|
|
||||||
|
- Server-rendered IHP views remain the default. `autoRefresh` is acceptable for
|
||||||
|
panels that already use live refresh patterns.
|
||||||
|
- Tailwind and existing HSX view conventions should be reused.
|
||||||
|
- Runtime panel config may be stored as JSONB, but renderer code should decode
|
||||||
|
into explicit Haskell config types before use.
|
||||||
|
- Do not create an ungoverned visual component layer. A saved dashboard panel
|
||||||
|
must either reference or create a `Widget` row, most likely using the existing
|
||||||
|
framework-level `panel` widget type, so annotations and interaction events
|
||||||
|
remain first-class IHF artifacts.
|
||||||
|
- Avoid adding a `users.role` column unless the PRS/FDD proves it is needed.
|
||||||
|
Prefer defaults derived from current user identity, stewardship assignments,
|
||||||
|
selected watched hubs, or explicit dashboard template choice.
|
||||||
|
|
||||||
|
## Proposed First-Slice Panel Catalogue
|
||||||
|
|
||||||
|
The initial catalogue should be limited to panels that can be built from
|
||||||
|
existing tables and controllers:
|
||||||
|
|
||||||
|
| Panel key | Label | Source surface/data | Live? |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `watched-hubs` | Watched Hubs | `hubs`, `hub_health_snapshots`, optional saved hub filter | No |
|
||||||
|
| `recent-interactions` | Recent Activity | `interaction_events` plus `widgets` and `hubs` | Yes |
|
||||||
|
| `triage-queue` | Triage Queue | open `requirement_candidates` | Yes |
|
||||||
|
| `recent-decisions` | Recent Decisions | `decision_records`, requirements, candidates | No |
|
||||||
|
| `hub-health` | Hub Health | latest `hub_health_snapshots`, bottlenecks | Yes |
|
||||||
|
| `agent-proposals` | Agent Proposals | `agent_proposals`, `agent_review_records` | No |
|
||||||
|
| `api-usage` | API Usage | `api_consumers`, `api_request_log` | Yes |
|
||||||
|
| `marketplace-trending` | Marketplace Trending | `widget_patterns`, adoptions, templates | No |
|
||||||
|
| `learning-digest` | Learning Digest | `learning_insights`, `institutional_knowledge_entries` | Yes |
|
||||||
|
| `my-annotations` | My Annotations | `annotations` filtered by current user when available | No |
|
||||||
|
|
||||||
|
The implementation workplan should start with a smaller subset if needed:
|
||||||
|
`watched-hubs`, `recent-interactions`, `triage-queue`, `recent-decisions`,
|
||||||
|
`hub-health`, and `learning-digest` are enough to prove the framework.
|
||||||
|
|
||||||
## Tasks
|
## Tasks
|
||||||
|
|
||||||
### T01 — Research: Dashboard frameworks and patterns for inspiration
|
### T01 - Current-state audit and dashboard pattern research
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: IHUB-WP-0020-T01
|
id: IHUB-WP-0020-T01
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "6074f195-636b-4517-b6d1-eb3c57394a82"
|
state_hub_task_id: "6074f195-636b-4517-b6d1-eb3c57394a82"
|
||||||
```
|
```
|
||||||
|
|
||||||
Survey existing dashboard systems to extract patterns that are re-implementable in
|
Produce a short research note that starts with the current inter-hub codebase,
|
||||||
Haskell / IHP / Tailwind under inter-hub's design constraints (server-rendered,
|
then uses external dashboard systems only for secondary inspiration.
|
||||||
type-safe, governed).
|
|
||||||
|
|
||||||
**Research targets:**
|
Required current-state inventory:
|
||||||
|
|
||||||
|
- Existing routes and views that behave like dashboards.
|
||||||
|
- Existing `autoRefresh` usage and query patterns that are safe to reuse.
|
||||||
|
- Existing type registry, `Widget`, `widgetEnvelope`, annotation, and event
|
||||||
|
capture constraints.
|
||||||
|
- Existing tables that can power first-slice personal panels.
|
||||||
|
- Gaps: personal dashboard persistence, panel catalogue, saved filters, layout
|
||||||
|
model, and user preference/defaulting model.
|
||||||
|
|
||||||
|
External systems may still be sampled, but the output should focus on patterns
|
||||||
|
that are practical in IHP/HSX/Tailwind:
|
||||||
|
|
||||||
| System | What to extract |
|
| System | What to extract |
|
||||||
|---|---|
|
|---|---|
|
||||||
| **Grafana** | Panel/grid layout model; datasource abstraction; variable-driven filtering |
|
| Grafana | Panel/grid layout model, dashboard variables, bounded refresh |
|
||||||
| **Kibana (dashboards)** | Saved-search panels; time-range awareness; role-based visibility |
|
| Kibana dashboards | Saved-search panels, time range filters, role visibility |
|
||||||
| **Retool / Appsmith** | Widget catalogue approach; drag-grid layout; data binding model |
|
| Retool/Appsmith | Widget catalogue and data binding concepts, not their client runtime |
|
||||||
| **Linear (home view)** | Flat "My Work" aggregation across entities; priority surfacing |
|
| Linear home view | Flat "my work" aggregation across entities |
|
||||||
| **Notion (linked databases)** | Filter/sort persistence per user; view types (table, board, calendar) |
|
| Notion linked databases | Saved filters/sorts as user-facing views |
|
||||||
| **Observable Framework** | Reactive cell model; markdown + code co-location |
|
| Metabase | Question-as-unit model and governed saved queries |
|
||||||
| **Metabase** | Question-as-unit; dashboard as ordered collection of questions |
|
| Streamlit | Declarative layout vocabulary suitable for server rendering |
|
||||||
| **Streamlit** | Declarative layout (columns, expanders); pure functional rendering loop |
|
|
||||||
|
|
||||||
**Questions to answer per system:**
|
Questions to answer:
|
||||||
1. How is a dashboard persisted? (JSON blob, relational rows, code-as-config?)
|
|
||||||
2. How is a widget/panel parameterised? (datasource, filter, display options)
|
|
||||||
3. How is layout described? (fixed grid, CSS grid, drag-and-drop, responsive breakpoints)
|
|
||||||
4. How is per-user state separated from shared/team state?
|
|
||||||
5. What is the update model? (full-page reload, WebSocket push, polling, partial HTMX swap)
|
|
||||||
6. How are access controls expressed at panel level?
|
|
||||||
|
|
||||||
**IHP/Haskell-specific constraints to keep in mind:**
|
1. Which existing inter-hub dashboard fragments can become first-slice panel
|
||||||
- Server-rendered by default; AutoRefresh (WebSocket) available for live data
|
renderers without broad refactoring?
|
||||||
- No client-side state management library; JS must be minimal
|
2. Which panel configs must exist on day one: hub filter, time range, limit,
|
||||||
- Type safety from DB schema → view layer is a first-class constraint
|
display mode, or sort order?
|
||||||
- Tailwind CSS; no component library
|
3. Which panels need live refresh, and which should stay static per request?
|
||||||
|
4. How should each saved panel map to a governed `Widget` row?
|
||||||
|
5. What should be explicitly deferred to avoid building a report builder?
|
||||||
|
|
||||||
**Exit criteria:** Research notes written in `docs/research/dashboard-frameworks.md`.
|
Exit criteria: `docs/research/personal-dashboard-current-state.md` exists and
|
||||||
|
has enough evidence to drive the PRS.
|
||||||
|
|
||||||
|
Completion note (2026-06-16): added
|
||||||
|
`docs/research/personal-dashboard-current-state.md`, covering the current
|
||||||
|
dashboard inventory, AutoRefresh/query patterns, governed widget constraints,
|
||||||
|
first-slice panel candidates, external pattern extraction, and T02/T03
|
||||||
|
recommendations.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### T02 — Product Requirements Specification (PRS)
|
### T02 - Product Requirements Specification
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: IHUB-WP-0020-T02
|
id: IHUB-WP-0020-T02
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
depends_on: T01
|
depends_on: T01
|
||||||
state_hub_task_id: "698304bc-b91a-42e2-a617-b3ddbf749174"
|
state_hub_task_id: "698304bc-b91a-42e2-a617-b3ddbf749174"
|
||||||
```
|
```
|
||||||
|
|
||||||
Produce a formal PRS for the Personal Dashboard Framework based on T01 findings and
|
Produce a formal PRS based on T01 and the current implementation.
|
||||||
inter-hub's design principles.
|
|
||||||
|
|
||||||
**Required sections:**
|
Required sections:
|
||||||
|
|
||||||
1. **Problem statement** — who uses the dashboard, what decisions they make with it,
|
1. Problem statement: authenticated users currently land on the Hubs list and
|
||||||
what pain the current flat-nav approach causes
|
must manually navigate to specialized dashboards to answer daily operating
|
||||||
|
questions.
|
||||||
|
2. Personas:
|
||||||
|
- Hub operator: watches hub health, recent events, candidates, and
|
||||||
|
bottlenecks.
|
||||||
|
- Governance reviewer: triages candidates, decisions, policy coverage, and
|
||||||
|
annotations.
|
||||||
|
- AI orchestrator: watches agent proposals, review outcomes, and learning
|
||||||
|
signals.
|
||||||
|
- Platform admin: watches API usage, hub registry health, manifests, and
|
||||||
|
cross-hub propagation.
|
||||||
|
3. Core requirements using MoSCoW:
|
||||||
|
- Must: per-user saved dashboard, seeded default dashboard, panel catalogue,
|
||||||
|
server-rendered panels, persisted layout, governed panel widget identity,
|
||||||
|
post-login route design, bounded panel queries.
|
||||||
|
- Should: hub/time filters, simple edit mode, live refresh on selected
|
||||||
|
panels, keyboard-accessible forms, link-outs to existing source
|
||||||
|
dashboards.
|
||||||
|
- Could: dashboard templates, saved watched-hub sets, shared dashboards,
|
||||||
|
richer display modes.
|
||||||
|
- Won't: drag-and-drop, external datasources, client-side fetching, mobile
|
||||||
|
layout editor, complete refactor of existing dashboards.
|
||||||
|
4. Non-functional requirements:
|
||||||
|
- First paint target remains sub-second for seeded dashboards with bounded
|
||||||
|
panel queries.
|
||||||
|
- Panel queries must use limits and existing indexes or propose new indexes.
|
||||||
|
- Dashboard save/load must be simple transactional IHP controller work.
|
||||||
|
- No new JS framework.
|
||||||
|
5. Governance fit:
|
||||||
|
- Saved panel instances are governed IHF widgets or reference governed
|
||||||
|
widgets.
|
||||||
|
- Panel views use `widgetEnvelope`.
|
||||||
|
- Panel interactions emit existing event types where possible.
|
||||||
|
- Annotations attach to the panel widget identity, not to a transient DOM
|
||||||
|
block.
|
||||||
|
|
||||||
2. **User personas**
|
Exit criteria: `docs/prs/personal-dashboard-prs.md` exists and is ready for
|
||||||
- Hub Operator: monitors activity within their hub; wants recent events, open candidates
|
FDD work.
|
||||||
- Governance Reviewer: triages candidates, reviews decisions; needs queue and signal views
|
|
||||||
- AI Orchestrator: monitors agent proposals, outcome correlations; needs performance panels
|
|
||||||
- Platform Admin: watches system health, API usage, learning throughput
|
|
||||||
|
|
||||||
3. **Core requirements (MoSCoW)**
|
Completion note (2026-06-16): added
|
||||||
- Must: per-user dashboard persisted in DB; selectable panels from a catalogue;
|
`docs/prs/personal-dashboard-prs.md`, defining the problem statement,
|
||||||
layout preserved across sessions; role-aware default layout on first login
|
personas, MoSCoW requirements, first-slice panel catalogue, governance
|
||||||
- Should: panel-level filtering (by hub, by time range); live-update via AutoRefresh
|
requirements, acceptance criteria, risks, and FDD open questions.
|
||||||
for signal panels; keyboard-navigable
|
|
||||||
- Could: drag-and-drop layout editing; shared/team dashboards; dashboard templates
|
|
||||||
- Won't (Phase 13): mobile-native layout; client-side data fetching; external datasources
|
|
||||||
|
|
||||||
4. **Non-functional requirements**
|
|
||||||
- First paint < 500 ms (server-rendered, no JS data fetching)
|
|
||||||
- Dashboard save/load < 100 ms
|
|
||||||
- Each panel query < 200 ms (indexed, bounded result sets)
|
|
||||||
- Zero JS frameworks; AutoRefresh WebSocket for live panels only
|
|
||||||
|
|
||||||
5. **Governance fit** — dashboard widgets are themselves `Widget` records in the IHF
|
|
||||||
sense; `InteractionEvent`s recorded on dashboard interactions; annotations possible
|
|
||||||
on any panel
|
|
||||||
|
|
||||||
**Exit criteria:** `docs/prs/dashboard-framework-prs.md` reviewed and accepted.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### T03 — Functional Design Document (FDD)
|
### T03 - Functional Design Document
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: IHUB-WP-0020-T03
|
id: IHUB-WP-0020-T03
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
depends_on: T02
|
depends_on: T02
|
||||||
state_hub_task_id: "438e5771-a043-4f26-a1ce-994ed478a760"
|
state_hub_task_id: "438e5771-a043-4f26-a1ce-994ed478a760"
|
||||||
```
|
```
|
||||||
|
|
||||||
Translate the PRS into a concrete functional design covering schema, component model,
|
Translate the PRS into a concrete FDD covering schema, controller actions,
|
||||||
rendering pipeline, and layout system. This is the authoritative reference for implementation.
|
panel renderer contract, layout, seed/default behavior, and migration strategy.
|
||||||
|
|
||||||
**Required sections:**
|
The FDD must update the old greenfield schema sketch. A likely shape is:
|
||||||
|
|
||||||
#### 3.1 Data model
|
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- A user's named dashboard
|
CREATE TABLE personal_dashboards (
|
||||||
CREATE TABLE dashboards (
|
|
||||||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
|
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
|
||||||
user_id UUID NOT NULL REFERENCES users(id),
|
user_id UUID NOT NULL REFERENCES users(id),
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
is_default BOOLEAN NOT NULL DEFAULT FALSE,
|
is_default BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
);
|
);
|
||||||
|
|
||||||
-- An instance of a panel type on a dashboard, with position
|
CREATE TABLE dashboard_panel_types (
|
||||||
CREATE TABLE dashboard_panels (
|
|
||||||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
|
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
|
||||||
dashboard_id UUID NOT NULL REFERENCES dashboards(id) ON DELETE CASCADE,
|
key TEXT NOT NULL UNIQUE,
|
||||||
panel_type TEXT NOT NULL, -- FK to panel_type_registry
|
|
||||||
config JSONB NOT NULL DEFAULT '{}',
|
|
||||||
col INT NOT NULL DEFAULT 0,
|
|
||||||
row INT NOT NULL DEFAULT 0,
|
|
||||||
col_span INT NOT NULL DEFAULT 1,
|
|
||||||
row_span INT NOT NULL DEFAULT 1,
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Registry of available panel types
|
|
||||||
CREATE TABLE panel_type_registry (
|
|
||||||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
|
|
||||||
name TEXT NOT NULL UNIQUE,
|
|
||||||
label TEXT NOT NULL,
|
label TEXT NOT NULL,
|
||||||
description TEXT,
|
description TEXT,
|
||||||
default_config JSONB NOT NULL DEFAULT '{}',
|
default_config JSONB NOT NULL DEFAULT '{}',
|
||||||
requires_hub BOOLEAN NOT NULL DEFAULT FALSE,
|
default_col_span INT NOT NULL DEFAULT 4,
|
||||||
live_update BOOLEAN NOT NULL DEFAULT FALSE
|
default_row_span INT NOT NULL DEFAULT 1,
|
||||||
|
live_update BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
status TEXT NOT NULL DEFAULT 'active'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE dashboard_panels (
|
||||||
|
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
|
||||||
|
dashboard_id UUID NOT NULL REFERENCES personal_dashboards(id) ON DELETE CASCADE,
|
||||||
|
panel_type_id UUID NOT NULL REFERENCES dashboard_panel_types(id),
|
||||||
|
widget_id UUID NOT NULL REFERENCES widgets(id),
|
||||||
|
config JSONB NOT NULL DEFAULT '{}',
|
||||||
|
col INT NOT NULL DEFAULT 0,
|
||||||
|
row INT NOT NULL DEFAULT 0,
|
||||||
|
col_span INT NOT NULL DEFAULT 4,
|
||||||
|
row_span INT NOT NULL DEFAULT 1,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 3.2 Panel catalogue (Phase 13 scope)
|
The FDD must also resolve:
|
||||||
|
|
||||||
| Panel type name | Label | Description | Live? |
|
- Naming: whether tables should use `personal_dashboards` or another prefix to
|
||||||
|---|---|---|---|
|
avoid confusing them with existing dashboard actions.
|
||||||
| `recent-interactions` | Recent Activity | Latest interaction events across watched hubs | Yes |
|
- Panel config: JSONB storage plus explicit Haskell ADT decoding and validation.
|
||||||
| `open-candidates` | Open Candidates | Requirement candidates awaiting triage | No |
|
- Governance identity: how `dashboard_panels.widget_id` is created, versioned,
|
||||||
| `decision-queue` | Decision Queue | Decisions pending review | No |
|
and named.
|
||||||
| `outcome-signals` | Outcome Signals | Recent outcome signal summary | Yes |
|
- Renderer contract:
|
||||||
| `hub-health` | Hub Health | Health snapshot per hub | No |
|
|
||||||
| `agent-proposals` | Agent Proposals | Open AI agent proposals | No |
|
|
||||||
| `learning-digest` | Learning Digest | Latest institutional knowledge entries | No |
|
|
||||||
| `my-annotations` | My Annotations | Annotations by the current user | No |
|
|
||||||
|
|
||||||
#### 3.3 Layout system
|
|
||||||
|
|
||||||
12-column CSS grid; panels occupy `col_span` columns and `row_span` rows.
|
|
||||||
Row height fixed at 240px. No drag-and-drop in Phase 13; layout edited via
|
|
||||||
form fields (col, row, span). Responsive: collapse to single column below 768px.
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────┐
|
|
||||||
│ [Recent Activity ×6] [Decision Queue ×3] [Hub ×3]│
|
|
||||||
│ │
|
|
||||||
├─────────────────────────────────────────────────────┤
|
|
||||||
│ [Open Candidates ×4] [Outcome Signals ×4] [My ×4] │
|
|
||||||
└─────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.4 Rendering pipeline
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /Dashboard
|
|
||||||
→ DashboardController#show
|
|
||||||
→ fetch dashboard + panels (ordered by row, col)
|
|
||||||
→ for each panel: dispatch to panel renderer (panelHtml panelType config)
|
|
||||||
→ embed in DashboardView with CSS grid layout
|
|
||||||
→ AutoRefresh wraps live panels only
|
|
||||||
```
|
|
||||||
|
|
||||||
Each panel renderer is a Haskell function:
|
|
||||||
```haskell
|
```haskell
|
||||||
type PanelRenderer = PanelConfig -> ModelContext -> IO Html
|
data DashboardPanelConfig
|
||||||
renderPanel :: Text -> PanelConfig -> ModelContext -> IO Html
|
= WatchedHubsConfig WatchedHubsOptions
|
||||||
renderPanel "recent-interactions" = renderRecentInteractions
|
| RecentInteractionsConfig RecentInteractionsOptions
|
||||||
renderPanel "open-candidates" = renderOpenCandidates
|
| TriageQueueConfig TriageQueueOptions
|
||||||
...
|
| RecentDecisionsConfig RecentDecisionsOptions
|
||||||
|
| HubHealthConfig HubHealthOptions
|
||||||
|
| LearningDigestConfig LearningDigestOptions
|
||||||
|
|
||||||
|
renderDashboardPanel
|
||||||
|
:: DashboardPanelType
|
||||||
|
-> DashboardPanel
|
||||||
|
-> DashboardPanelConfig
|
||||||
|
-> ModelContext
|
||||||
|
-> IO Html
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 3.5 Edit flow
|
- Layout: 12-column grid on desktop, single-column below the existing Tailwind
|
||||||
|
breakpoint, stable row/span constraints, no drag-and-drop in the first slice.
|
||||||
|
- Routes/actions:
|
||||||
|
- `PersonalDashboardAction`
|
||||||
|
- `EditPersonalDashboardAction`
|
||||||
|
- `UpdatePersonalDashboardAction`
|
||||||
|
- `AddDashboardPanelAction`
|
||||||
|
- `UpdateDashboardPanelAction`
|
||||||
|
- `RemoveDashboardPanelAction`
|
||||||
|
- Login behavior: `CreateSessionAction` should redirect to the personal
|
||||||
|
dashboard after authentication, while public root pages remain unchanged.
|
||||||
|
- Defaulting model: seed a default dashboard on first visit without requiring a
|
||||||
|
`users.role` column.
|
||||||
|
- Query safety: each panel query must be bounded, indexed, and compatible with
|
||||||
|
current PostgreSQL type decoding practices such as casting `COUNT(*)` to
|
||||||
|
integer when read as `Int`.
|
||||||
|
- Tests and smoke checks needed for the follow-on implementation workplan.
|
||||||
|
|
||||||
`GET /Dashboard/Edit` → grid with inline forms per panel (col/row/span inputs) +
|
Exit criteria: `docs/fdd/personal-dashboard-fdd.md` exists, schema decisions
|
||||||
"Add Panel" dropdown from `panel_type_registry`. `POST /Dashboard` saves layout.
|
are concrete enough to implement, and open questions are explicitly listed.
|
||||||
No JavaScript needed for basic edit; optional HTMX for panel preview.
|
|
||||||
|
|
||||||
#### 3.6 Default dashboard on first login
|
Completion note (2026-06-16): added
|
||||||
|
`docs/fdd/personal-dashboard-fdd.md`, resolving schema names, panel config
|
||||||
`afterLoginRedirectPath` (in `SessionsControllerConfig`) redirects to `/Dashboard`.
|
typing, renderer/view-model shape, default seeding, governed panel widget
|
||||||
On first visit, `DashboardController` checks for an existing dashboard; if absent,
|
lifecycle, query constraints, routes, layout, tests, and handoff shape for
|
||||||
creates a default one seeded from the user's role (determined by a `role` field
|
IHUB-WP-0021.
|
||||||
to be added to `users`, or a simple heuristic based on existing data).
|
|
||||||
|
|
||||||
**Exit criteria:** FDD reviewed; schema migrations drafted; panel catalogue agreed.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### T04 — Implementation workplan
|
### T04 - Implementation workplan
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: IHUB-WP-0020-T04
|
id: IHUB-WP-0020-T04
|
||||||
status: todo
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
depends_on: T03
|
depends_on: T03
|
||||||
state_hub_task_id: "970aa221-7e17-4500-8b37-9c98676280b1"
|
state_hub_task_id: "970aa221-7e17-4500-8b37-9c98676280b1"
|
||||||
```
|
```
|
||||||
|
|
||||||
Break T03's FDD into a detailed, sequenced task list suitable for execution as a new
|
Create the execution workplan for implementation as `IHUB-WP-0021`.
|
||||||
workplan (IHUB-WP-0021). Each task must have a clear entry/exit criterion and fit within
|
|
||||||
the 8k token soft budget.
|
|
||||||
|
|
||||||
**Expected task structure of IHUB-WP-0021:**
|
Expected task structure for `IHUB-WP-0021`:
|
||||||
|
|
||||||
```
|
| Task | Focus |
|
||||||
T01 Schema migration: dashboards, dashboard_panels, panel_type_registry
|
|---|---|
|
||||||
T02 Seed: default panel types in panel_type_registry
|
| T01 | Schema migration for personal dashboards, panel types, and panel instances |
|
||||||
T03 DashboardController — show action (fetch + render)
|
| T02 | Seed dashboard panel types and any required framework `panel` widgets/type vocabulary |
|
||||||
T04 Panel renderers — first 3 panels (recent-interactions, open-candidates, decision-queue)
|
| T03 | Add controller/action/route skeleton and default dashboard lookup/seed helper |
|
||||||
T05 DashboardView — CSS grid layout
|
| T04 | Implement first three renderers: watched hubs, recent interactions, triage queue |
|
||||||
T06 Panel renderers — remaining 5 panels
|
| T05 | Implement dashboard show view and responsive CSS grid |
|
||||||
T07 Dashboard edit flow (layout form, add/remove panels)
|
| T06 | Implement remaining first-slice renderers: recent decisions, hub health, learning digest |
|
||||||
T08 Default dashboard seeding on first login
|
| T07 | Implement edit flow: reorder/update layout, add/remove panels, validate config |
|
||||||
T09 afterLoginRedirectPath → /Dashboard
|
| T08 | Add governed widget identity creation and `widgetEnvelope` wrapping for panels |
|
||||||
T10 AutoRefresh for live panels (recent-interactions, outcome-signals)
|
| T09 | Redirect successful login to the personal dashboard |
|
||||||
T11 Role-aware default layout
|
| T10 | Add `autoRefresh` only around selected live panels or the whole page if finer wrapping is not practical |
|
||||||
T12 Smoke tests
|
| T11 | Add focused tests for seeding, panel config validation, route access, and bounded queries |
|
||||||
```
|
| T12 | Manual smoke: login, seeded dashboard, edit layout, annotate a panel, verify source dashboards still load |
|
||||||
|
|
||||||
**Exit criteria:** IHUB-WP-0021 workplan file committed; T01–T12 each have
|
Each task must have entry criteria, exit criteria, rollback notes, and the
|
||||||
entry/exit criteria; ready for execution.
|
smallest reasonable test/smoke requirement. Keep implementation slices small
|
||||||
|
enough for Codex sessions to finish without broad refactors.
|
||||||
|
|
||||||
---
|
Exit criteria: `workplans/IHUB-WP-0021-personal-dashboard-implementation.md`
|
||||||
|
exists with all tasks in `todo` state and enough detail to start implementation.
|
||||||
|
|
||||||
|
Completion note (2026-06-16): added
|
||||||
|
`workplans/IHUB-WP-0021-personal-dashboard-implementation.md` with twelve
|
||||||
|
sequenced implementation tasks covering schema, seeds, controller skeleton,
|
||||||
|
panel renderers, show/edit views, governed panel widget lifecycle, login
|
||||||
|
redirect, AutoRefresh/query hardening, tests, and manual smoke.
|
||||||
|
|
||||||
## Exit Criteria Summary
|
## Exit Criteria Summary
|
||||||
|
|
||||||
| Task | Deliverable | Status |
|
| Task | Deliverable | Status |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| T01 | `docs/research/dashboard-frameworks.md` | todo |
|
| T01 | `docs/research/personal-dashboard-current-state.md` | done |
|
||||||
| T02 | `docs/prs/dashboard-framework-prs.md` | todo |
|
| T02 | `docs/prs/personal-dashboard-prs.md` | done |
|
||||||
| T03 | `docs/fdd/dashboard-framework-fdd.md` | todo |
|
| T03 | `docs/fdd/personal-dashboard-fdd.md` | done |
|
||||||
| T04 | `workplans/IHUB-WP-0021-personal-dashboard-impl.md` | todo |
|
| T04 | `workplans/IHUB-WP-0021-personal-dashboard-implementation.md` | done |
|
||||||
|
|
||||||
## Design Principles (binding throughout)
|
## Binding Design Principles
|
||||||
|
|
||||||
- **Server-first**: every panel renders in a single round-trip. No client-side data fetching.
|
- Server-first: every panel renders on the server in the normal IHP request
|
||||||
- **Type-safe config**: `PanelConfig` is a Haskell ADT, not an opaque JSON blob at runtime.
|
lifecycle.
|
||||||
- **IHF governed**: each rendered panel is a `Widget` with a `widgetEnvelope`; interactions
|
- Integration-first: reuse current dashboard query patterns before extracting
|
||||||
are recorded; annotations can be attached.
|
shared abstractions.
|
||||||
- **Tailwind only**: no external component library. Layout via CSS Grid with inline style for
|
- Governed panels: saved panel instances have stable IHF widget identity and
|
||||||
structural properties (lessons learned from sidebar nav).
|
use `widgetEnvelope`.
|
||||||
- **Minimal JS**: AutoRefresh WebSocket for live panels; vanilla JS for any UX enhancement.
|
- Type-safe runtime config: JSONB is storage, not the unchecked runtime API.
|
||||||
No framework, no bundler beyond the existing IHP asset pipeline.
|
- Bounded queries: every panel limits rows and uses existing indexes or proposes
|
||||||
|
a specific migration.
|
||||||
|
- Minimal JS: no framework and no client-side data fetch loop.
|
||||||
|
- Tailwind only: use existing view style and responsive grid conventions.
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ type: workplan
|
|||||||
title: "Ad hoc Inter-Hub production fixes"
|
title: "Ad hoc Inter-Hub production fixes"
|
||||||
domain: custodian
|
domain: custodian
|
||||||
repo: inter-hub
|
repo: inter-hub
|
||||||
status: active
|
status: archived
|
||||||
owner: codex
|
owner: codex
|
||||||
created: "2026-06-15"
|
created: "2026-06-15"
|
||||||
updated: "2026-06-15"
|
updated: "2026-07-03"
|
||||||
state_hub_workstream_id: "9e7a50b4-da7f-4df9-9154-7b89a071f520"
|
state_hub_workstream_id: "9e7a50b4-da7f-4df9-9154-7b89a071f520"
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ state_hub_workstream_id: "9e7a50b4-da7f-4df9-9154-7b89a071f520"
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: ADHOC-2026-06-15-T01
|
id: ADHOC-2026-06-15-T01
|
||||||
status: wait
|
status: cancel
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "cceee9f1-56af-44bc-898d-21c4508df07c"
|
state_hub_task_id: "cceee9f1-56af-44bc-898d-21c4508df07c"
|
||||||
```
|
```
|
||||||
@@ -70,3 +70,35 @@ requires authenticated Gitea Actions workflow dispatch or inspection of the
|
|||||||
self-hosted `haskelseed` runner path. The normal workflow needs haskelseed as
|
self-hosted `haskelseed` runner path. The normal workflow needs haskelseed as
|
||||||
build runner; an equivalent operator-controlled build host with Nix, registry
|
build runner; an equivalent operator-controlled build host with Nix, registry
|
||||||
push credentials, and Railiance deploy credentials could substitute.
|
push credentials, and Railiance deploy credentials could substitute.
|
||||||
|
|
||||||
|
Recheck on 2026-06-16:
|
||||||
|
|
||||||
|
- The local source fix is still present:
|
||||||
|
`Application/Helper/TypeRegistry.hs` casts registry validation counts with
|
||||||
|
`COUNT(*)::int`, and `Application/Helper/ApiRateLimit.hs` casts API request
|
||||||
|
log counts with `COUNT(*)::int`.
|
||||||
|
- A source-wide `COUNT` search found the targeted v2 bootstrap helpers fixed.
|
||||||
|
Other raw aggregate counts remain in non-bootstrap dashboard/marketplace/API
|
||||||
|
surfaces and are outside this ad hoc task's acceptance path unless they are
|
||||||
|
separately reproduced as decode failures.
|
||||||
|
- Live public `GET https://hub.coulomb.social/api/v2/hubs` returns `200` and
|
||||||
|
lists `ops-hub`, confirming the public API and ops-hub route surface are
|
||||||
|
present.
|
||||||
|
- Live unauthenticated `GET /api/v2/widgets` and `GET /api/v2/hub-registry`
|
||||||
|
return `401`, confirming the protected routes exist and authentication is
|
||||||
|
enforced before the code path that previously failed.
|
||||||
|
- Unauthenticated registry manifest checks for tags `68c66b9` and `5101eb5`
|
||||||
|
now return `401`, not the earlier unauthenticated `manifest unknown`; this
|
||||||
|
session cannot prove image publication from the public registry endpoint.
|
||||||
|
- The previously documented local temp key
|
||||||
|
`/tmp/ops-hub-runtime-key-gb5nxg92` is absent. No approved runtime key or
|
||||||
|
operator key is available in this session, so the protected widget-create and
|
||||||
|
hub-registry smoke checks could not be run without a secret handoff.
|
||||||
|
|
||||||
|
Closure on 2026-07-03:
|
||||||
|
|
||||||
|
- Obsolete after migration from inter-hub to core-hub; no further inter-hub
|
||||||
|
production follow-up is planned.
|
||||||
|
- Source-side COUNT decode fix remains in inter-hub history but production
|
||||||
|
acceptance was never completed.
|
||||||
|
- Workstream archived and task cancelled per operator direction.
|
||||||
@@ -0,0 +1,632 @@
|
|||||||
|
---
|
||||||
|
id: IHUB-WP-0021
|
||||||
|
type: workplan
|
||||||
|
title: "Personal Dashboard Implementation"
|
||||||
|
domain: infotech
|
||||||
|
repo: inter-hub
|
||||||
|
status: archived
|
||||||
|
owner: codex
|
||||||
|
topic_slug: inter_hub
|
||||||
|
created: "2026-06-16"
|
||||||
|
updated: "2026-07-03"
|
||||||
|
phase: 13
|
||||||
|
depends_on: IHUB-WP-0020
|
||||||
|
related_docs:
|
||||||
|
- docs/research/personal-dashboard-current-state.md
|
||||||
|
- docs/prs/personal-dashboard-prs.md
|
||||||
|
- docs/fdd/personal-dashboard-fdd.md
|
||||||
|
state_hub_workstream_id: "79f72176-fb3f-4d59-9678-d42f5ff1e679"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Personal Dashboard Implementation
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Implement the personal dashboard framework designed in IHUB-WP-0020: a
|
||||||
|
server-rendered authenticated landing page with persisted per-user panels,
|
||||||
|
governed panel widget identity, default dashboard seeding, simple edit forms,
|
||||||
|
and six first-slice panel renderers.
|
||||||
|
|
||||||
|
## Inputs
|
||||||
|
|
||||||
|
- `docs/research/personal-dashboard-current-state.md`
|
||||||
|
- `docs/prs/personal-dashboard-prs.md`
|
||||||
|
- `docs/fdd/personal-dashboard-fdd.md`
|
||||||
|
- Existing dashboard surfaces in `Web/Controller/Hubs.hs`,
|
||||||
|
`Web/Controller/LearningDashboard.hs`, `Web/Controller/ApiDashboard.hs`,
|
||||||
|
`Web/Controller/MarketplaceDashboard.hs`, and federated governance
|
||||||
|
controllers.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
- Keep implementation additive.
|
||||||
|
- Preserve public root/static routes.
|
||||||
|
- Do not refactor all existing dashboards.
|
||||||
|
- No client-side data fetching framework.
|
||||||
|
- No drag-and-drop layout in this workplan.
|
||||||
|
- Every saved dashboard panel must have stable `Widget` identity and render
|
||||||
|
through `widgetEnvelope`.
|
||||||
|
- Bound every panel query.
|
||||||
|
- Cast aggregate `COUNT(*)` queries when decoding as `Int`, or decode as
|
||||||
|
`Int64`.
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
### T01 - Add personal dashboard schema
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: IHUB-WP-0021-T01
|
||||||
|
status: cancel
|
||||||
|
priority: high
|
||||||
|
state_hub_task_id: "bb7366a3-78ec-42d8-9f16-b7ed4979ec53"
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the schema from the FDD:
|
||||||
|
|
||||||
|
- `personal_dashboards`
|
||||||
|
- `dashboard_panel_types`
|
||||||
|
- `dashboard_panels`
|
||||||
|
|
||||||
|
Implementation notes:
|
||||||
|
|
||||||
|
- Add an `Application/Migration/<timestamp>-personal-dashboard-framework.sql`
|
||||||
|
migration.
|
||||||
|
- Update `Application/Schema.sql` consistently with the migration.
|
||||||
|
- Use `panel_key`, not `key`, on `dashboard_panel_types`.
|
||||||
|
- Include `removed_at` on `dashboard_panels`.
|
||||||
|
- Include indexes and layout CHECK constraints from the FDD.
|
||||||
|
|
||||||
|
Entry criteria:
|
||||||
|
|
||||||
|
- IHUB-WP-0020 is done.
|
||||||
|
- FDD exists and is reviewed enough for implementation.
|
||||||
|
|
||||||
|
Exit criteria:
|
||||||
|
|
||||||
|
- Schema files contain the three new tables and indexes.
|
||||||
|
- `rg "personal_dashboards|dashboard_panel_types|dashboard_panels" Application`
|
||||||
|
finds the expected migration/schema entries.
|
||||||
|
- No existing table or route behavior is changed.
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
|
||||||
|
- Run `git diff --check`.
|
||||||
|
- If the IHP dev environment is available, run the repo compile/schema check
|
||||||
|
used by prior inter-hub workplans.
|
||||||
|
|
||||||
|
Rollback notes:
|
||||||
|
|
||||||
|
- Before production data exists, rollback is removing the migration/schema
|
||||||
|
additions.
|
||||||
|
- After production data exists, rollback requires preserving linked `widgets`
|
||||||
|
and `interaction_events`; do not delete panel widgets casually.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### T02 - Seed panel types and framework panel vocabulary
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: IHUB-WP-0021-T02
|
||||||
|
status: cancel
|
||||||
|
priority: high
|
||||||
|
depends_on: T01
|
||||||
|
state_hub_task_id: "d298eab2-736d-48ed-b6d4-84afa1604de9"
|
||||||
|
```
|
||||||
|
|
||||||
|
Add idempotent seed data for:
|
||||||
|
|
||||||
|
- framework hub with slug `inter-hub` and `hub_kind = 'framework'` if absent;
|
||||||
|
- active widget type `panel` if absent;
|
||||||
|
- six first-slice `dashboard_panel_types`:
|
||||||
|
- `watched-hubs`
|
||||||
|
- `recent-interactions`
|
||||||
|
- `triage-queue`
|
||||||
|
- `recent-decisions`
|
||||||
|
- `hub-health`
|
||||||
|
- `learning-digest`
|
||||||
|
|
||||||
|
Entry criteria:
|
||||||
|
|
||||||
|
- T01 schema exists.
|
||||||
|
|
||||||
|
Exit criteria:
|
||||||
|
|
||||||
|
- Seed SQL or helper is idempotent.
|
||||||
|
- Re-running seeds does not create duplicate framework hubs, widget types, or
|
||||||
|
panel types.
|
||||||
|
- Default configs match the FDD.
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
|
||||||
|
- Inspect seed SQL for `ON CONFLICT DO NOTHING` or equivalent idempotency.
|
||||||
|
- If DB is available, run a local seed twice and confirm row counts stay stable.
|
||||||
|
|
||||||
|
Rollback notes:
|
||||||
|
|
||||||
|
- Panel type seed rows are additive. If rollback is needed before use, remove
|
||||||
|
only the new dashboard panel type rows and any framework hub created solely
|
||||||
|
for this feature.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### T03 - Add controller skeleton, routes, and default dashboard helper
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: IHUB-WP-0021-T03
|
||||||
|
status: cancel
|
||||||
|
priority: high
|
||||||
|
depends_on: T02
|
||||||
|
state_hub_task_id: "8a171c71-3762-46c7-88d7-10ffb87fc78a"
|
||||||
|
```
|
||||||
|
|
||||||
|
Add:
|
||||||
|
|
||||||
|
- `PersonalDashboardsController` to `Web/Types.hs`;
|
||||||
|
- `Web/Controller/PersonalDashboards.hs`;
|
||||||
|
- `Web/View/PersonalDashboards/Show.hs`;
|
||||||
|
- `Web/View/PersonalDashboards/Edit.hs` placeholder or minimal views;
|
||||||
|
- route registration in `Web/Routes.hs`;
|
||||||
|
- controller import and parser registration in `Web/FrontController.hs`;
|
||||||
|
- helper module `Application/Helper/PersonalDashboard.hs`.
|
||||||
|
|
||||||
|
Implement:
|
||||||
|
|
||||||
|
- `ensureDefaultDashboard :: User -> IO PersonalDashboard`;
|
||||||
|
- dashboard lookup scoped to current user;
|
||||||
|
- idempotent default seeding with six panel rows;
|
||||||
|
- linked `Widget` creation for each seeded panel;
|
||||||
|
- initial `WidgetVersion` creation for panel widgets.
|
||||||
|
|
||||||
|
Entry criteria:
|
||||||
|
|
||||||
|
- T01/T02 complete.
|
||||||
|
|
||||||
|
Exit criteria:
|
||||||
|
|
||||||
|
- Authenticated user can hit `PersonalDashboardAction`.
|
||||||
|
- First visit creates a default dashboard with six active panels.
|
||||||
|
- A second visit does not duplicate panels.
|
||||||
|
- Controller denies unauthenticated access through `ensureIsUser`.
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
|
||||||
|
- Compile if environment is available.
|
||||||
|
- Add or run focused helper tests if existing test harness supports it.
|
||||||
|
|
||||||
|
Rollback notes:
|
||||||
|
|
||||||
|
- Remove route/controller/helper additions if skeleton must be reverted.
|
||||||
|
- Keep seeded widgets/events if any user interactions already happened.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### T04 - Implement first three panel view models/renderers
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: IHUB-WP-0021-T04
|
||||||
|
status: cancel
|
||||||
|
priority: high
|
||||||
|
depends_on: T03
|
||||||
|
state_hub_task_id: "012dcd2a-d3e0-48ba-966b-f4c7afa51dad"
|
||||||
|
```
|
||||||
|
|
||||||
|
Implement typed config decoding and view-model builders for:
|
||||||
|
|
||||||
|
- `watched-hubs`
|
||||||
|
- `recent-interactions`
|
||||||
|
- `triage-queue`
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
|
||||||
|
- Query from controller/helper, not from HSX views.
|
||||||
|
- Clamp configured limits.
|
||||||
|
- Apply optional hub filters.
|
||||||
|
- Render empty states.
|
||||||
|
- Include source links:
|
||||||
|
- watched hub rows link to `ShowHubAction`;
|
||||||
|
- recent interaction rows link to widget or hub context where available;
|
||||||
|
- triage rows link to `ShowRequirementCandidateAction`.
|
||||||
|
|
||||||
|
Entry criteria:
|
||||||
|
|
||||||
|
- T03 controller/helper skeleton exists.
|
||||||
|
|
||||||
|
Exit criteria:
|
||||||
|
|
||||||
|
- The three panels render in the dashboard show action.
|
||||||
|
- Invalid config falls back to defaults and records a warning in the panel view
|
||||||
|
model.
|
||||||
|
- Queries are bounded.
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
|
||||||
|
- Compile if environment is available.
|
||||||
|
- Manual smoke with empty DB and seeded fixture data if possible.
|
||||||
|
|
||||||
|
Rollback notes:
|
||||||
|
|
||||||
|
- Renderer additions are isolated to helper/view modules and can be reverted
|
||||||
|
without dropping schema.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### T05 - Implement dashboard show view and responsive grid
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: IHUB-WP-0021-T05
|
||||||
|
status: cancel
|
||||||
|
priority: high
|
||||||
|
depends_on: T04
|
||||||
|
state_hub_task_id: "b4c4de39-147b-45a0-954d-9bafad4aafa1"
|
||||||
|
```
|
||||||
|
|
||||||
|
Build the dashboard show view:
|
||||||
|
|
||||||
|
- title and edit link;
|
||||||
|
- responsive grid;
|
||||||
|
- panel cards using row/col/span config;
|
||||||
|
- panel title fallback to panel type label;
|
||||||
|
- warning display for invalid config or unsupported panel;
|
||||||
|
- source link area;
|
||||||
|
- `widgetEnvelope` around every panel.
|
||||||
|
|
||||||
|
Implementation notes:
|
||||||
|
|
||||||
|
- Use current Tailwind and HSX conventions.
|
||||||
|
- Add a small CSS helper in `static/app.css` only if needed for responsive
|
||||||
|
collapse.
|
||||||
|
- Keep text compact and operational.
|
||||||
|
|
||||||
|
Entry criteria:
|
||||||
|
|
||||||
|
- At least three panel view models exist.
|
||||||
|
|
||||||
|
Exit criteria:
|
||||||
|
|
||||||
|
- Seeded dashboard is usable as an authenticated landing surface.
|
||||||
|
- Panels do not overlap at desktop or narrow widths.
|
||||||
|
- Panel layout persists in the order defined by `row`, `col`, and `sort_order`.
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
|
||||||
|
- Compile if environment is available.
|
||||||
|
- Browser/manual smoke if a dev server is running.
|
||||||
|
|
||||||
|
Rollback notes:
|
||||||
|
|
||||||
|
- Show view changes can be reverted independently of schema/controller work.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### T06 - Implement remaining first-slice panel view models/renderers
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: IHUB-WP-0021-T06
|
||||||
|
status: cancel
|
||||||
|
priority: high
|
||||||
|
depends_on: T05
|
||||||
|
state_hub_task_id: "8d0bd046-17b3-48d9-a945-8b2e9c001123"
|
||||||
|
```
|
||||||
|
|
||||||
|
Implement:
|
||||||
|
|
||||||
|
- `recent-decisions`
|
||||||
|
- `hub-health`
|
||||||
|
- `learning-digest`
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
|
||||||
|
- Recent decisions: bounded by time range and limit; link to
|
||||||
|
`ShowDecisionRecordAction`.
|
||||||
|
- Hub health: latest snapshot per hub plus active bottleneck count; use
|
||||||
|
aggregate count casting or `Int64`.
|
||||||
|
- Learning digest: recent `learning_insights` and
|
||||||
|
`institutional_knowledge_entries`; link to knowledge entries where possible.
|
||||||
|
|
||||||
|
Entry criteria:
|
||||||
|
|
||||||
|
- T05 show view can render panel view models.
|
||||||
|
|
||||||
|
Exit criteria:
|
||||||
|
|
||||||
|
- All six first-slice panels render.
|
||||||
|
- All panel queries are bounded.
|
||||||
|
- Empty states are sane for all six panels.
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
|
||||||
|
- Compile if environment is available.
|
||||||
|
- `git diff --check`.
|
||||||
|
|
||||||
|
Rollback notes:
|
||||||
|
|
||||||
|
- Each renderer should be separable so a single broken panel can be reverted
|
||||||
|
without removing the framework.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### T07 - Implement edit flow
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: IHUB-WP-0021-T07
|
||||||
|
status: cancel
|
||||||
|
priority: high
|
||||||
|
depends_on: T06
|
||||||
|
state_hub_task_id: "51a72b56-5c23-4baa-892f-4ab89fd8495c"
|
||||||
|
```
|
||||||
|
|
||||||
|
Implement:
|
||||||
|
|
||||||
|
- `EditPersonalDashboardAction`;
|
||||||
|
- `UpdatePersonalDashboardAction`;
|
||||||
|
- `AddDashboardPanelAction`;
|
||||||
|
- `UpdateDashboardPanelAction`;
|
||||||
|
- `RemoveDashboardPanelAction`.
|
||||||
|
|
||||||
|
Edit view capabilities:
|
||||||
|
|
||||||
|
- show existing panels in layout order;
|
||||||
|
- edit row, col, col span, row span, title, and sort order;
|
||||||
|
- edit supported config fields such as limit, time range, display mode, and
|
||||||
|
hub filter;
|
||||||
|
- add active panel type;
|
||||||
|
- remove panel.
|
||||||
|
|
||||||
|
Entry criteria:
|
||||||
|
|
||||||
|
- All six panels render on show view.
|
||||||
|
|
||||||
|
Exit criteria:
|
||||||
|
|
||||||
|
- User can modify layout and config through server-rendered forms.
|
||||||
|
- User can add a panel and remove a panel.
|
||||||
|
- Invalid layout/config re-renders edit view with an error.
|
||||||
|
- A user cannot edit another user's dashboard/panel.
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
|
||||||
|
- Manual edit smoke.
|
||||||
|
- Focused authorization/config tests if available.
|
||||||
|
|
||||||
|
Rollback notes:
|
||||||
|
|
||||||
|
- If edit flow is unstable, keep show-only dashboard and disable edit links
|
||||||
|
until fixed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### T08 - Complete governed panel widget lifecycle
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: IHUB-WP-0021-T08
|
||||||
|
status: cancel
|
||||||
|
priority: high
|
||||||
|
depends_on: T07
|
||||||
|
state_hub_task_id: "ca70b76d-766f-4e6e-84f1-19943c0a347c"
|
||||||
|
```
|
||||||
|
|
||||||
|
Harden widget lifecycle behavior:
|
||||||
|
|
||||||
|
- create panel widget on panel add/seed;
|
||||||
|
- create initial `WidgetVersion` snapshot;
|
||||||
|
- create a new `WidgetVersion` snapshot when material panel config changes;
|
||||||
|
- render every panel through `widgetEnvelope`;
|
||||||
|
- preserve annotations/events when panels are removed;
|
||||||
|
- archive/deprecate linked widget on panel removal.
|
||||||
|
|
||||||
|
Entry criteria:
|
||||||
|
|
||||||
|
- Edit flow can add/remove/update panels.
|
||||||
|
|
||||||
|
Exit criteria:
|
||||||
|
|
||||||
|
- Every active dashboard panel has a linked active `Widget`.
|
||||||
|
- Every linked widget has non-empty `view_context`.
|
||||||
|
- Annotate link opens the existing widget annotation flow.
|
||||||
|
- Removing a panel does not delete widget history.
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
|
||||||
|
- Manual smoke: add panel, annotate panel, remove panel, confirm widget/event
|
||||||
|
history is not deleted.
|
||||||
|
- Inspect generated HTML for expected `data-widget-*` attributes.
|
||||||
|
|
||||||
|
Rollback notes:
|
||||||
|
|
||||||
|
- Do not delete existing `widgets`, `annotations`, or `interaction_events`.
|
||||||
|
Disable dashboard rendering if needed while preserving history.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### T09 - Redirect login and add navigation
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: IHUB-WP-0021-T09
|
||||||
|
status: cancel
|
||||||
|
priority: medium
|
||||||
|
depends_on: T08
|
||||||
|
state_hub_task_id: "2fd1041b-9135-49e4-a9d1-e5d0b67d8fd7"
|
||||||
|
```
|
||||||
|
|
||||||
|
Update:
|
||||||
|
|
||||||
|
- `Web/Controller/Sessions.hs` to redirect successful login to
|
||||||
|
`PersonalDashboardAction`;
|
||||||
|
- `Web/FrontController.hs` sidebar to include `Dashboard`;
|
||||||
|
- any relevant public page management links only if they should point to the
|
||||||
|
dashboard rather than Hubs.
|
||||||
|
|
||||||
|
Do not change:
|
||||||
|
|
||||||
|
- public root route;
|
||||||
|
- `LandingAction`;
|
||||||
|
- capabilities/tutorial/extension guide pages;
|
||||||
|
- `HubsAction` availability.
|
||||||
|
|
||||||
|
Entry criteria:
|
||||||
|
|
||||||
|
- Dashboard show route is stable and governed panel lifecycle is complete.
|
||||||
|
|
||||||
|
Exit criteria:
|
||||||
|
|
||||||
|
- Successful login lands on personal dashboard.
|
||||||
|
- Hubs remain reachable from sidebar.
|
||||||
|
- Public pages still render without login.
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
|
||||||
|
- Manual login smoke.
|
||||||
|
- Route smoke for `/`, Hubs, dashboard, Learning, API Dashboard, Marketplace.
|
||||||
|
|
||||||
|
Rollback notes:
|
||||||
|
|
||||||
|
- If dashboard redirect fails, revert only the login redirect and keep
|
||||||
|
dashboard accessible from sidebar.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### T10 - Add AutoRefresh and query hardening pass
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: IHUB-WP-0021-T10
|
||||||
|
status: cancel
|
||||||
|
priority: medium
|
||||||
|
depends_on: T09
|
||||||
|
state_hub_task_id: "e19c06e7-ec95-40ca-8087-df669b575f86"
|
||||||
|
```
|
||||||
|
|
||||||
|
Wrap `PersonalDashboardAction` in `autoRefresh do` and audit all six panel
|
||||||
|
queries:
|
||||||
|
|
||||||
|
- every query is bounded;
|
||||||
|
- optional hub filter is applied before broad fetches where practical;
|
||||||
|
- aggregate counts decode safely;
|
||||||
|
- no secrets are selected or displayed;
|
||||||
|
- dashboard refresh remains acceptable with default seed data.
|
||||||
|
|
||||||
|
Entry criteria:
|
||||||
|
|
||||||
|
- Dashboard route, renderers, edit flow, and login redirect exist.
|
||||||
|
|
||||||
|
Exit criteria:
|
||||||
|
|
||||||
|
- Dashboard updates using existing IHP AutoRefresh behavior.
|
||||||
|
- Query review notes are either captured in code comments or tests where useful.
|
||||||
|
- No known `COUNT(*)` as `Int` decode hazard remains in dashboard code.
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
|
||||||
|
- Compile if environment is available.
|
||||||
|
- Manual refresh smoke by adding an interaction/candidate and observing the
|
||||||
|
dashboard update, when a dev DB is available.
|
||||||
|
|
||||||
|
Rollback notes:
|
||||||
|
|
||||||
|
- Remove `autoRefresh` wrapper if it causes unacceptable behavior; keep static
|
||||||
|
dashboard route.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### T11 - Add focused tests
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: IHUB-WP-0021-T11
|
||||||
|
status: cancel
|
||||||
|
priority: medium
|
||||||
|
depends_on: T10
|
||||||
|
state_hub_task_id: "32b4f55e-ede6-4830-a171-b0785afe88e1"
|
||||||
|
```
|
||||||
|
|
||||||
|
Add focused tests where the current harness supports them:
|
||||||
|
|
||||||
|
- default dashboard seeding is idempotent;
|
||||||
|
- seeded dashboard has six active panels;
|
||||||
|
- each active panel has linked widget identity;
|
||||||
|
- config decoder clamps limits and rejects unknown values safely;
|
||||||
|
- remove action soft-removes panel and archives widget;
|
||||||
|
- users cannot edit another user's dashboard;
|
||||||
|
- aggregate counts in dashboard helpers decode safely.
|
||||||
|
|
||||||
|
Entry criteria:
|
||||||
|
|
||||||
|
- T10 implementation is stable enough to test.
|
||||||
|
|
||||||
|
Exit criteria:
|
||||||
|
|
||||||
|
- Relevant test files exist or a documented reason explains why the current
|
||||||
|
harness cannot cover a case.
|
||||||
|
- Tests pass where runnable in the local environment.
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
|
||||||
|
- Run available test command.
|
||||||
|
- If unavailable, record exact blocker in this workplan before closing T11.
|
||||||
|
|
||||||
|
Rollback notes:
|
||||||
|
|
||||||
|
- Do not weaken production behavior to satisfy a brittle test; adjust the test
|
||||||
|
to match the intended FDD contract.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### T12 - Manual smoke and closeout
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: IHUB-WP-0021-T12
|
||||||
|
status: cancel
|
||||||
|
priority: high
|
||||||
|
depends_on: T11
|
||||||
|
state_hub_task_id: "8c6648ae-d33e-48f6-9d56-ee557f367d80"
|
||||||
|
```
|
||||||
|
|
||||||
|
Run a manual smoke pass:
|
||||||
|
|
||||||
|
1. Log in as an existing admin user.
|
||||||
|
2. Confirm redirect lands on personal dashboard.
|
||||||
|
3. Confirm all six seeded panels render.
|
||||||
|
4. Click source links from watched hubs, triage queue, and learning digest.
|
||||||
|
5. Open Annotate for one panel.
|
||||||
|
6. Edit layout and save.
|
||||||
|
7. Sign out/in and confirm layout persists.
|
||||||
|
8. Add and remove a panel.
|
||||||
|
9. Confirm Hubs, Hub show, Ops Review, Federation, Learning, API Dashboard,
|
||||||
|
Hub Registry, and Marketplace still load.
|
||||||
|
10. Run `git diff --check`.
|
||||||
|
|
||||||
|
Entry criteria:
|
||||||
|
|
||||||
|
- T01 through T11 complete or have explicit accepted caveats.
|
||||||
|
|
||||||
|
Exit criteria:
|
||||||
|
|
||||||
|
- Smoke evidence is recorded in this workplan or a short docs/evidence note.
|
||||||
|
- WP-0021 tasks reflect final status.
|
||||||
|
- State Hub progress note is logged.
|
||||||
|
- Operator is reminded to run `make fix-consistency REPO=inter-hub` from
|
||||||
|
`~/state-hub` after workplan/status changes.
|
||||||
|
|
||||||
|
Rollback notes:
|
||||||
|
|
||||||
|
- If smoke fails after login redirect, first rollback is reverting the login
|
||||||
|
redirect while keeping dashboard route available for debugging.
|
||||||
|
|
||||||
|
## Workplan Exit Criteria
|
||||||
|
|
||||||
|
- Personal dashboard schema, seeds, controller, views, helper, and route are
|
||||||
|
implemented.
|
||||||
|
- Successful login reaches the personal dashboard.
|
||||||
|
- Default dashboard seeding is idempotent.
|
||||||
|
- Six first-slice panels render with bounded queries.
|
||||||
|
- Panel edit flow works.
|
||||||
|
- Every panel has governed widget identity.
|
||||||
|
- Existing source dashboards remain functional.
|
||||||
|
- Checks/smoke evidence is recorded.
|
||||||
|
|
||||||
|
## Closure on 2026-07-03
|
||||||
|
|
||||||
|
- Obsolete after migration from inter-hub to core-hub; no further inter-hub
|
||||||
|
personal-dashboard implementation is planned.
|
||||||
|
- IHUB-WP-0020 design artefacts remain in-repo for reference; execution
|
||||||
|
work in this workplan is cancelled rather than transferred.
|
||||||
|
- Workstream archived and all tasks cancelled per operator direction.
|
||||||
|
|
||||||
@@ -2,13 +2,13 @@
|
|||||||
id: IHUB-WP-0022
|
id: IHUB-WP-0022
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "Ops Hub Evidence Intake for Activity Core"
|
title: "Ops Hub Evidence Intake for Activity Core"
|
||||||
domain: inter_hub
|
domain: infotech
|
||||||
repo: inter-hub
|
repo: inter-hub
|
||||||
status: active
|
status: archived
|
||||||
owner: codex
|
owner: codex
|
||||||
topic_slug: inter_hub
|
topic_slug: inter_hub
|
||||||
created: "2026-06-15"
|
created: "2026-06-15"
|
||||||
updated: "2026-06-15"
|
updated: "2026-07-03"
|
||||||
planning_priority: high
|
planning_priority: high
|
||||||
planning_order: 22
|
planning_order: 22
|
||||||
related_repos:
|
related_repos:
|
||||||
@@ -94,20 +94,22 @@ closed.
|
|||||||
|
|
||||||
## Proposed Evidence Vocabulary
|
## Proposed Evidence Vocabulary
|
||||||
|
|
||||||
Activity-core has already declared the event contracts it wants to send:
|
The original activity-core suggestion used five evidence names. Production
|
||||||
|
ops-hub now exposes a live seed vocabulary, so the Inter-Hub activation target
|
||||||
|
is the compatibility mapping below rather than the early names verbatim:
|
||||||
|
|
||||||
| Event type | Suggested widget family | Purpose |
|
| Original evidence intent | Live Inter-Hub target | Widget family | Purpose |
|
||||||
|---|---|---|
|
|---|---|---|---|
|
||||||
| `ops-service-observed` | service inventory | Record that a service exists and was observed. |
|
| `ops-service-observed` | `ops-service-discovered` | `ops-service` / `ops-service-catalog` | Record that a service exists and was discovered. |
|
||||||
| `ops-endpoint-verified` | endpoint inventory | Record endpoint reachability, auth challenge, or health verification. |
|
| `ops-endpoint-verified` | `ops-endpoint-verified` | `ops-endpoint` | Record endpoint reachability, auth challenge, or health verification. |
|
||||||
| `ops-access-path-checked` | access path inventory | Record operator or service access path verification. |
|
| `ops-access-path-checked` | deferred | State Hub fallback until ops-hub adds access-path vocabulary or maps it to readiness/risk | Record operator or service access path verification without inventing unsupported registry names. |
|
||||||
| `ops-backup-verified` | backup inventory | Record backup presence, recency, or restore-drill evidence. |
|
| `ops-backup-verified` | `ops-backup-verified` | `ops-backup-set` | Record backup presence, recency, or restore-drill evidence after a backup-set widget exists. |
|
||||||
| `ops-inventory-drift` | drift inventory | Record drift between expected and observed operations inventory. |
|
| `ops-inventory-drift` | `ops-drift-detected` | `ops-readiness-gate` / `ops-risk` | Record drift between expected and observed operations inventory. |
|
||||||
|
|
||||||
The first implementation should keep one stable widget per entity and evidence
|
The first implementation should use existing seeded widgets where possible and
|
||||||
family where possible. If activity-core cannot know entity identity reliably,
|
seed only the missing backup/risk widgets that are needed for the attended
|
||||||
use one aggregate intake widget per family as a conservative first slice, then
|
smoke. If activity-core cannot know entity identity reliably, keep State Hub
|
||||||
split into per-entity widgets after payload evidence proves stable.
|
fallback evidence rather than submitting to a made-up aggregate widget.
|
||||||
|
|
||||||
## Tasks
|
## Tasks
|
||||||
|
|
||||||
@@ -179,13 +181,20 @@ versioned non-secret `OPS_HUB_WIDGET_MAPPING` JSON shape, aggregate-first
|
|||||||
fallback widgets, per-entity selector rules, stable `widgetRef` values, and
|
fallback widgets, per-entity selector rules, stable `widgetRef` values, and
|
||||||
Secret-only handling for `OPS_HUB_KEY`.
|
Secret-only handling for `OPS_HUB_KEY`.
|
||||||
|
|
||||||
|
Compatibility note (2026-06-27): revised the mapping contract to match the
|
||||||
|
live ops-hub seed vocabulary. The mapping now aliases `ops-service-observed`
|
||||||
|
to `ops-service-discovered`, aliases `ops-inventory-drift` to
|
||||||
|
`ops-drift-detected`, uses declared ops policy scopes instead of the old
|
||||||
|
`ops-evidence` proposal, and defers access-path evidence until ops-hub has a
|
||||||
|
supported event/widget target.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### T03 - Prepare manifest vocabulary and seed widgets
|
### T03 - Prepare manifest vocabulary and seed widgets
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: IHUB-WP-0022-T03
|
id: IHUB-WP-0022-T03
|
||||||
status: wait
|
status: cancel
|
||||||
priority: high
|
priority: high
|
||||||
depends_on: T02
|
depends_on: T02
|
||||||
state_hub_task_id: "94fc9806-781c-45f6-a43c-a6bce13da47b"
|
state_hub_task_id: "94fc9806-781c-45f6-a43c-a6bce13da47b"
|
||||||
@@ -218,13 +227,19 @@ environment with the `5101eb5` COUNT decode fix live and an authenticated
|
|||||||
operator/runtime key path. The required vocabulary is documented, but no live
|
operator/runtime key path. The required vocabulary is documented, but no live
|
||||||
manifest or widget seed was performed in this implementation slice.
|
manifest or widget seed was performed in this implementation slice.
|
||||||
|
|
||||||
|
Progress note (2026-06-27): public production probes show an `ops-hub` row and
|
||||||
|
the live seed registry vocabulary, and the contract docs now target that live
|
||||||
|
vocabulary. T03 remains waiting because protected widget lookup, widget ids,
|
||||||
|
any missing backup/risk seed widgets, and authenticated smoke evidence still
|
||||||
|
require the operator/runtime key path.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### T04 - Provision the runtime API key outside Git
|
### T04 - Provision the runtime API key outside Git
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: IHUB-WP-0022-T04
|
id: IHUB-WP-0022-T04
|
||||||
status: wait
|
status: cancel
|
||||||
priority: high
|
priority: high
|
||||||
depends_on: T03
|
depends_on: T03
|
||||||
state_hub_task_id: "267db6a7-67d2-48af-b3e8-7588f8684957"
|
state_hub_task_id: "267db6a7-67d2-48af-b3e8-7588f8684957"
|
||||||
@@ -289,13 +304,18 @@ Inter-Hub request envelope, shared validation rules, idempotency expectations,
|
|||||||
forbidden payload material, expected API errors, and one example for each
|
forbidden payload material, expected API errors, and one example for each
|
||||||
activity-core event type.
|
activity-core event type.
|
||||||
|
|
||||||
|
Compatibility note (2026-06-27): revised payload examples to submit only live
|
||||||
|
ops-hub event types. Access-path payloads are documented as deferred fallback
|
||||||
|
evidence, and old event names are treated as aliases that should not be posted
|
||||||
|
to Inter-Hub.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### T06 - Validate fallback-first intake
|
### T06 - Validate fallback-first intake
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: IHUB-WP-0022-T06
|
id: IHUB-WP-0022-T06
|
||||||
status: wait
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
depends_on: T01
|
depends_on: T01
|
||||||
state_hub_task_id: "38b54991-bed2-4f9d-bede-bea35821b1ef"
|
state_hub_task_id: "38b54991-bed2-4f9d-bede-bea35821b1ef"
|
||||||
@@ -325,13 +345,24 @@ for `event_type=ops_inventory_probe` returned no live events. This task remains
|
|||||||
waiting on a disabled/manual activity-core probe or other live fallback
|
waiting on a disabled/manual activity-core probe or other live fallback
|
||||||
evidence before it can close.
|
evidence before it can close.
|
||||||
|
|
||||||
|
Implementation note (2026-06-16): completed fallback-first validation using
|
||||||
|
Railiance cluster-owned verifier evidence. State Hub progress
|
||||||
|
`db408146-0310-4ac3-ac77-f73c5a41e070` records a live
|
||||||
|
`ops_inventory_probe` summary from activity-core:
|
||||||
|
`0 ok, 4 degraded, 0 down, 5 skipped`. Railiance evidence note
|
||||||
|
`60256e9a-9d1b-44db-8999-738cf03bca2e` proves the progress event matched the
|
||||||
|
manual trigger run id `90e3b112-d1e3-51af-8fb2-cb61f26add17` and includes the
|
||||||
|
live `actcore-api` image digest. Updated the validation document with the
|
||||||
|
evidence, gaps, and closure recommendation. Inter-Hub per-entity submission
|
||||||
|
remains deferred to T03/T04/T07.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### T07 - Run end-to-end Inter-Hub submission smoke
|
### T07 - Run end-to-end Inter-Hub submission smoke
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: IHUB-WP-0022-T07
|
id: IHUB-WP-0022-T07
|
||||||
status: wait
|
status: cancel
|
||||||
priority: high
|
priority: high
|
||||||
depends_on: T03,T04,T05
|
depends_on: T03,T04,T05
|
||||||
state_hub_task_id: "23baee9b-d710-42c8-9a19-f936bd237444"
|
state_hub_task_id: "23baee9b-d710-42c8-9a19-f936bd237444"
|
||||||
@@ -367,9 +398,9 @@ Inter-Hub submission beyond its current deferred sink.
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: IHUB-WP-0022-T08
|
id: IHUB-WP-0022-T08
|
||||||
status: wait
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
depends_on: T06,T07
|
depends_on: T06
|
||||||
state_hub_task_id: "4a7ed0ed-552e-42d3-a90f-1efd52b8851e"
|
state_hub_task_id: "4a7ed0ed-552e-42d3-a90f-1efd52b8851e"
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -393,18 +424,41 @@ Current wait reason (2026-06-15): closure handoff depends on either a live
|
|||||||
State Hub fallback event plus an explicit Inter-Hub deferral decision, or a
|
State Hub fallback event plus an explicit Inter-Hub deferral decision, or a
|
||||||
successful Inter-Hub submission smoke.
|
successful Inter-Hub submission smoke.
|
||||||
|
|
||||||
|
Implementation note (2026-06-16): completed the activity-core closure handoff
|
||||||
|
on the fallback-deferred path. `ACTIVITY-WP-0007/T06` is already closed in
|
||||||
|
activity-core and State Hub. Inter-Hub accepts that closure on live State Hub
|
||||||
|
fallback evidence (`ops_inventory_probe`
|
||||||
|
`db408146-0310-4ac3-ac77-f73c5a41e070`) with explicit deferral of governed
|
||||||
|
Inter-Hub submissions until the ops-hub manifest/widget path, runtime key, and
|
||||||
|
end-to-end smoke are complete under T03, T04, and T07. No secret values or
|
||||||
|
runtime key material are required for this handoff.
|
||||||
|
|
||||||
## Exit Criteria Summary
|
## Exit Criteria Summary
|
||||||
|
|
||||||
| Task | Deliverable | Status |
|
| Task | Deliverable | Status |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| T01 | `docs/research/ops-hub-evidence-intake-current-state.md` | done |
|
| T01 | `docs/research/ops-hub-evidence-intake-current-state.md` | done |
|
||||||
| T02 | `docs/contracts/ops-hub-activity-core-mapping.md` | done |
|
| T02 | `docs/contracts/ops-hub-activity-core-mapping.md` | done |
|
||||||
| T03 | Active ops-hub manifest and seed widgets | wait |
|
| T03 | Active ops-hub manifest and seed widgets | cancel |
|
||||||
| T04 | `OPS_HUB_KEY` stored outside Git and smokeable | wait |
|
| T04 | `OPS_HUB_KEY` stored outside Git and smokeable | cancel |
|
||||||
| T05 | `docs/contracts/ops-hub-activity-core-event-payloads.md` | done |
|
| T05 | `docs/contracts/ops-hub-activity-core-event-payloads.md` | done |
|
||||||
| T06 | `docs/evidence/ops-hub-activity-core-fallback-validation.md` | wait |
|
| T06 | `docs/evidence/ops-hub-activity-core-fallback-validation.md` | done |
|
||||||
| T07 | End-to-end Inter-Hub submission smoke evidence | wait |
|
| T07 | End-to-end Inter-Hub submission smoke evidence | cancel |
|
||||||
| T08 | activity-core closure handoff | wait |
|
| T08 | activity-core closure handoff | done |
|
||||||
|
|
||||||
|
## Closure on 2026-07-03
|
||||||
|
|
||||||
|
- Obsolete after migration from inter-hub to core-hub; no further Inter-Hub
|
||||||
|
ops-hub activation work is planned in this repo.
|
||||||
|
- Contract and evidence artefacts (`docs/research/`,
|
||||||
|
`docs/contracts/`, `docs/evidence/`) remain in-repo for reference.
|
||||||
|
- Live manifest/widget seeding, `OPS_HUB_KEY` custody, and end-to-end Inter-Hub
|
||||||
|
submission smoke are superseded by `CORE-WP-0008` in core-hub per
|
||||||
|
`CUST-WP-0052`.
|
||||||
|
- `ACTIVITY-WP-0007/T06` remains closed on the fallback-deferred path from
|
||||||
|
T06/T08; Core Hub consumer proof is tracked in core-hub, not here.
|
||||||
|
- T03, T04, and T07 cancelled rather than transferred; workstream archived per
|
||||||
|
operator direction.
|
||||||
|
|
||||||
## Binding Principles
|
## Binding Principles
|
||||||
|
|
||||||
Reference in New Issue
Block a user