Always call display() for the warning element so Observable Framework
replaces it on each poll re-run. Previously the conditional display()
call left the warning rendered indefinitely once shown.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
MCP server is now a persistent SSE service on :8001 (make mcp-http),
independent of the Claude Code session. Re-registration is a single
claude mcp add-json command; no patch_mcp_cwd.py needed.
- Makefile: mcp-http is primary transport, add fuser restart + updated comment
- state-hub/README.md: stack table, MCP section, troubleshooting note updated
- CLAUDE.md (project): registration instructions rewritten for SSE
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The old bare `api` target (uvicorn only) is subsumed into the new `api`
target (db + postgres-wait + migrate + fuser-restart + uvicorn). Updated
all doc references and cleaned up duplicate entries left by the rename.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
pkill -f matched the shell subprocess's own argv (which contains the
pattern as a -c argument), causing make to receive SIGTERM and abort.
fuser -k 8000/tcp / 3000/tcp targets only the process bound to the
port — no self-kill risk.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- `make backend` replaces `make start`; polls postgres with nc (up to 10s)
instead of fixed sleep, kills any running uvicorn before starting fresh
- `make dashboard` kills any running observable preview before restarting
- Update all references in CLAUDE.md, README.md, SCOPE.md, state-hub/README.md,
and dashboard/src/docs/live-data.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Agents had no way to look up task UUIDs by workstream; they were stuck
unable to call update_task_status without already knowing the UUID.
list_tasks() wraps GET /tasks with workstream_id filter, returning
[{id, title, status, priority}] for all matching tasks.
FR raised by kaizen-agentic worker on COULOMBCORE while syncing
KAIZEN-WP-0002 task IDs. Marked merged in contributions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- parse_task_blocks() now injects the nearest preceding ### heading
text as `title` — tasks no longer stored with bare IDs as their title
- C-11 fix skips creating tasks when workstream is completed/archived
(prevents duplicate task creation on repeated fix-consistency runs)
- C-12 is now fixable: auto-cancels open orphan DB tasks when the
backing workstream is finished (completed/archived)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
FastMCP validates dict | None strictly, rejecting a JSON string even if
parseable. Broaden to dict | str | None and coerce in the function body
so callers don't need to pre-parse the detail payload.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
T01: Restructure nav — "Repos" → collapsible "Repository" section with
Repo Sync, SBOM, Debt as sub-pages; Debt moved out of Workstreams
T02: workstream-dod.md migrated from inline const API to config.js import
T03: todo.md suggestion filter (done in previous commit)
Closes CUST-WP-0019. Resolves UI suggestion c2fc284a.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Consolidates all open technical debt into three workplans:
- CUST-WP-0018: API hardening & code quality (TD-006–019 medium/high items)
- CUST-WP-0019: Dashboard UX polish (Repos nav restructure, config.js cleanup,
todo filter fix for new suggestion workflow statuses)
- CUST-WP-0020: pytest test suite with real DB (TD-014)
Also fixes todo.md Suggestions filter: was checking status===open but new
suggestions enter with status=submitted; now excludes terminal statuses only.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Shift+click now works on #observablehq-toc links, KPI boxes, and [id] elements
- _inferWidgetName detects TOC context and labels suggestions accordingly
- Click handler adds inToc branch alongside existing inSidebar
- _updateMode: 1-second setTimeout before activating highlight mode
so normal Shift+typing doesn't flicker the UI; clears immediately on
Shift release or window blur
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Click handler: sidebar <a> and <summary> are no longer excluded;
e.preventDefault() stops navigation while shift is held
- _inferWidgetName: sidebar-first branch returns nav link text,
section heading text, or "Navigation" fallback
- pageName is "Navigation" for sidebar clicks (not the current page title)
- CSS: sidebar a and summary highlighted (dashed indigo outline + tint)
when shift is held, same as main content widgets
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The card titled "SBOM" was displaying contribution type counts (FR/BR/EP/UPR)
which is unrelated to SBOM data. Added a sbomSnapState generator that fetches
/sbom/snapshots/ and shows: total tracked packages (sum of entry_count across
all snapshots), repos tracked, and copyleft risk count from the existing
licence_risk_count in the summary. Card turns orange if licenceRisk > 0.
Resolves suggestion b6775727.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- improvement-modal.js: API expects `domain` not `domain_slug` (422 fix)
- todo.md: section heading and KPI label renamed to "Suggestions"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Observable Framework proxies all src/*.js files through its own bundler —
<script type="module"> imports from <head> resolve to circular re-export
shims and never execute. The fix: read improvement-modal.js at config load
time in Node.js, strip the ES module export keyword, and interpolate the
content as a plain <script> block in the head config. It runs on every page.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_footer.md is not a supported special file in Observable Framework 1.13.3
and was silently ignored. The preview server does serve src/*.js files at
their root-relative path, so the correct approach is a <script type="module">
in the head config — runs once on page load, persists across SPA navigation.
Removed _footer.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- updateMode() now subscribes to keydown, keyup AND mousemove so the
body class stays in sync regardless of where focus is (mirrors the
pattern from the working modifier-click demo)
- cursor: copy replaces crosshair (matches copy-affordance semantics)
- figure, h2–h4 and [data-widget-name] elements get a dashed indigo
outline + subtle background tint when shift is held, so the user
can see which elements are annotatable before clicking
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
improvement-modal.js:
- Replace contextmenu handler with click+shiftKey check — browser
context menu is no longer intercepted
- Add keydown/keyup/blur listeners: holding Shift applies
.impr-shift-mode to <body>, switching cursor to crosshair
across the entire page as a visual affordance
- Update hint text to "Ctrl + Enter to submit · Escape to cancel"
todo.md:
- New "Improvements" section shows open dashboard-improvement TD items
with a "review →" link to the UI Feedback page
- KPI sidebar row added for open improvement count (indigo when > 0)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- improvement-modal.js: global contextmenu handler that opens a modal
showing page/widget context; submits suggestions as TD items with
debt_type="dashboard-improvement" to POST /technical-debt/
- _footer.md: shared Observable footer that auto-initialises the modal
on every dashboard page
- ui-feedback.md: review/approval page — lists open suggestions with
resolve / won't-fix / in-progress action buttons; archived items shown
below; live-polled
- observablehq.config.js: "UI Feedback" added under Workstreams group
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause analysis: calling create_workstream() before writing the workplan
file creates a ghost workstream with repo_id=null. When fix-consistency later
runs on the file, it creates a second workstream and writes its ID into the
file — leaving the ghost permanently active and showing false partial progress
in the dashboard.
C-14: after checking file-backed workstreams, query active workstreams on the
same topic with repo_id=null. Flag any whose title matches a file-backed
workstream as a probable ghost duplicate.
CLAUDE.md: add explicit "workplan ↔ DB sync rule" prohibiting create_workstream()
for file-backed work. Write file first, then make fix-consistency.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
T01: copy agent-scope-analyst.md to the-custodian/agents/
T02: add scope.template, prepend @SCOPE.md to claude-md.template,
update register_project.sh to write SCOPE.md stub on new registration,
add scope-analyst row to TOOLS.md
T03: SCOPE.md for the-custodian itself
Workplan: CUST-WP-0017 registered in state-hub
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix /domains/{slug}/ 500: EP/TD queries now use domain_id FK (not string column)
- Remove dead cascade-slug code in rename_domain (FK handles it)
- MCP: list_kaizen_agents(category?) + get_kaizen_agent(name) via resolve_repo_path()
- TOOLS.md: Kaizen Agents section with discovery/load pattern
- agents.template: new project rule for consumer repos
- claude-md.template + register_project.sh: include agents.md in new-project scaffolding
- agents/: direct install of 6 curated agents for hub sessions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the monolithic project_claude_md.template with a directory of
7 focused rule files in scripts/project_rules/. register_project.sh now
generates .claude/rules/*.md + a thin CLAUDE.md index of @-imports,
matching the pattern established in ops-bridge.
Template files:
claude-md.template — 9-line @-import index
repo-identity.template — purpose, domain, slug, topic ID (machine-gen)
session-protocol.template — orient/inbox/workplans/brief/close (machine-gen)
first-session.template — bootstrap flow; delete once past FSP
workplan-convention.template— prefix, location; delegates to global CLAUDE.md
stack-and-commands.template — language/deps/commands (stub, manual)
architecture.template — design overview (stub, manual)
repo-boundary.template — what this repo does NOT own (stub, manual)
register_project.sh changes:
- Generates .claude/rules/ from templates with variable substitution
- Writes thin CLAUDE.md if none exists; appends suggestion comment if one does
- Step 7: auto-registers this machine's local path via POST /repos/{slug}/paths/
- project_claude_md.template deprecated to a redirect notice
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a JSONB column `host_paths` to managed_repos mapping
hostname → absolute local path. Fixes the consistency-checker
failure when the same repo lives at different paths on different
machines (e.g. /home/worsch/marki-docx on the workstation vs
/home/tegwick/marki-docx on custodiancore).
Changes:
- Migration g4b5c6d7e8f9: adds host_paths JSONB (default {})
- Model: host_paths Mapped[dict] column
- Schemas: host_paths in RepoRead; new RepoPathRegister schema
- Router: POST /repos/{slug}/paths/ — merges one host entry
- consistency_check.py: resolve_repo_path() prefers host_paths
[hostname] over local_path; --repo-path CLI override added
- MCP: update_repo_path(slug, path, host?) tool
- Makefile: register-path target; REPO_PATH passthrough on
check-consistency and fix-consistency targets
- TOOLS.md: documents update_repo_path
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Detects when all DB tasks are done/cancelled but the workstream status
is still 'active' — the pattern where a worker completes tasks via MCP
but forgets to call update_workstream_status(). Auto-fixable via --fix.
Also extends the C-04/C-05 fix path to handle C-13 (same PATCH logic).
Motivated by marki-docx WP-0001/WP-0002 visibility gap (2026-03-16).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers installation, usage, workplan file format, task status lifecycle,
custodian naming conventions, COULOMBCORE usage, and manual cancellation.
Registered in Reference nav + reference.md index.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers local setup, remote (COULOMBCORE) one-liner registration,
ops-bridge tunnel config, bridge states, MCP transport modes, and
adding new remote hosts. Registered in Reference nav + reference.md index.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
server.py: MCP_TRANSPORT and MCP_PORT env vars select transport at startup
(default: stdio — no behaviour change for local use)
Makefile: `make mcp-http` starts SSE server on 127.0.0.1:8001
Remote registration (one-liner on COULOMBCORE after tunnel is up):
claude mcp add-json -s user state-hub \
'{"type":"sse","url":"http://127.0.0.1:18001/sse"}'
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause: workplan files use "done" (task vocabulary) but the DB workstream
API only accepts "completed". The PATCH was silently failing with 422.
Fixes:
- Add FILE_TO_DB_WORKSTREAM_STATUS map and normalise_workstream_status()
- Normalise file status before C-04 comparison: done↔completed is no longer
spurious drift
- Normalise file status before PATCHing: always send DB-valid "completed"
- _api_patch now returns {"_error": ...} instead of None on failure, so the
fix loop reports FAILED entries rather than silently dropping them
- 9 new tests in TestNormaliseWorkstreamStatus (42 total)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
33 offline tests covering: parse_frontmatter, parse_task_blocks,
get_tasks_from_workplan, ConsistencyReport severity filtering,
render_text output, and report_to_dict serialisation.
Closes the DoD automated-tests gap for the Consistency Engine workstream.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PATCH /decisions/{id}/ is a blind field-setter with no decided_at logic.
POST /decisions/{id}/resolve is the correct endpoint — it auto-sets
decided_at and emits a decision_resolved progress event.
Fixes: resolved decisions showing last in the sorted list because
decided_at was never populated.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previous fix applied the decided_at branch to all status groups,
causing open decisions without decided_at (e.g. COULOMBCORE decision)
to sort last behind any open decision that had decided_at set.
Now: decided_at desc only for resolved/superseded; open/escalated
use deadline asc → created_at desc.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Within resolved/superseded: most recently decided_at first.
Within open/escalated: soonest deadline first, then most recently
created_at (previously had no created_at fallback).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
EP catalogue (all domains):
- EP-RAIL-001 ep_id patched (schema fix: add ep_id to EPUpdate)
- EP-RAIL-003 (git bare-repo mirrors) and EP-RAIL-004 (offsite secondary
backup) registered from railiance-cluster/docs/backup-restore.md
- EP-CUST-003..007 ep_ids assigned to existing custodian EPs
- EP-CUST-008 (State Hub API auth) and EP-CUST-009 (update_workstream MCP
tool) registered as new custodian extension points
TD catalogue (railiance — first 5 items):
- TD-RAIL-001: backup cron runs as root without audit trail (high/security)
- TD-RAIL-002: k3s kubeconfig world-readable mode 644 (medium/security)
- TD-RAIL-003: no Ansible role unit tests (medium/test)
- TD-RAIL-004: age key extracted via awk — fragile (medium/impl)
- TD-RAIL-005: etcd snapshot retention uncoordinated (low/impl)
Dashboard (T08 + T10):
- Extract API URL and POLL to src/components/config.js; all 15 pages
now import from the shared module (contributions/goals keep custom POLL)
- Shared .kpi-infobox, .filter-bar, .filter-search/.filter-owner CSS
moved to observablehq.config.js head <style> block; removed from 9 pages
- Build: 0 errors, 0 warnings
API (T09):
- progress.py: limit param now Query(100, le=1000) — prevents unbounded
list requests; closes TD-CUST-004 for the only endpoint that had limit
CUST-WP-0004 marked completed (all 10 tasks done).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
EPUpdate was missing the ep_id field, making it impossible to assign a
human-readable ID to an existing EP via PATCH. The router already uses
model_dump(exclude_unset=True) + setattr so no router change needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The tunnel command belongs here — it opens a reverse SSH tunnel so that
a remote host can reach the local State Hub at 127.0.0.1:8000.
Usage: make tunnel HOST=user@hostname
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Migration c5d6e7f8a9b0: domain_goals and repo_goals tables, repo_goal_id FK on workstreams
- DomainGoal: one active per domain (partial unique index), status active/archived/superseded
- RepoGoal: integer priority, status active/paused/completed/archived, optional domain_goal_id link
- WorkstreamUpdate schema and router extended with repo_goal_id and repo_goal_id filter
- 6 new MCP goal tools: create_domain_goal, get_domain_goals, activate_domain_goal, create_repo_goal, get_repo_goals, update_repo_goal
- update_workstream MCP tool: patch any subset of workstream fields (title, description, owner, due_date, repo_goal_id, status)
- get_domain_summary extended with goal_guidance (needs_workplan, alignment_warnings) signals
- Dashboard goals.md page and docs/goals.md reference page
- CLAUDE.md template updated to act on goal_guidance signals at session start
- CUST-WP-0010 workplan for this feature
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>