generated from coulomb/repo-seed
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>
6.7 KiB
6.7 KiB
title
| title |
|---|
| Dependencies |
import {API, POLL} from "./components/config.js";
// Fetch workstreams + topics + summary (summary carries dep edges on open_workstreams)
const depState = (async function*() {
while (true) {
let wsMap = {}, edges = [], ok = false;
try {
const [rw, rto, rr, rs] = await Promise.all([
fetch(`${API}/workstreams/`),
fetch(`${API}/topics/`),
fetch(`${API}/repos/`),
fetch(`${API}/state/summary`),
]);
ok = rw.ok && rto.ok && rr.ok && rs.ok;
if (ok) {
const [wsList, topicList, repoList, summary] = await Promise.all([
rw.json(), rto.json(), rr.json(), rs.json(),
]);
const topicMap = Object.fromEntries(topicList.map(t => [t.id, t]));
const repoMap = Object.fromEntries(repoList.map(r => [r.id, r]));
wsMap = Object.fromEntries(wsList.map(w => [w.id, {
...w,
// Prefer repo→domain (GEMS primary); fall back to topic→domain
domain: repoMap[w.repo_id]?.domain_slug ?? topicMap[w.topic_id]?.domain_slug ?? "unknown",
}]));
// Build directed edge list from open_workstreams depends_on arrays
for (const ow of (summary.open_workstreams ?? [])) {
for (const depId of (ow.depends_on ?? [])) {
edges.push({from_id: ow.id, to_id: depId});
}
}
}
} catch {}
yield {wsMap, edges, ok, ts: new Date()};
await new Promise(res => setTimeout(res, POLL));
}
})();
const wsMap = depState.wsMap ?? {};
const edges = depState.edges ?? [];
const _ok = depState.ok ?? false;
const _ts = depState.ts;
Dependencies
import {injectTocTop} from "./components/toc-sidebar.js";
import {withDocHelp} from "./components/doc-overlay.js";
// ── KPI sidebar card ──────────────────────────────────────────────────────────
const _wsWithDeps = new Set([...edges.map(e => e.from_id), ...edges.map(e => e.to_id)]);
const _kpiBox = html`<div class="kpi-infobox">
<div class="kpi-infobox-title">Dependencies</div>
<div class="kpi-row">
<span class="kpi-row-label">edges</span>
<div class="kpi-row-right"><div class="kpi-row-value">${edges.length}</div></div>
</div>
<div class="kpi-row">
<span class="kpi-row-label">workstreams involved</span>
<div class="kpi-row-right"><div class="kpi-row-value">${_wsWithDeps.size}</div></div>
</div>
</div>`;
const _liveEl = html`<div class="live-indicator">
<span style="color:${_ok ? 'var(--theme-foreground-focus)' : 'red'}">●</span>
${_ok
? `Live · updated ${_ts?.toLocaleTimeString()}`
: html`<span style="color:red">Offline — run: <code>make api</code></span>`}
</div>`;
const _h1 = document.querySelector("#observablehq-main h1");
if (_h1) { _h1.style.position = "relative"; withDocHelp(_h1, "/docs/dependencies"); }
injectTocTop("dep-kpi-box", _kpiBox);
injectTocTop("live-indicator", _liveEl);
Directed edges between active workstreams. An edge A → B means A cannot fully proceed until B reaches a satisfactory state.
if (edges.length === 0) {
display(html`<p class="dim">No dependency edges registered.</p>`);
} else {
const rows = edges.map(e => {
const from = wsMap[e.from_id];
const to = wsMap[e.to_id];
return {
from_domain: from?.domain ?? "—",
from_title: from?.title ?? e.from_id,
from_status: from?.status ?? "—",
to_domain: to?.domain ?? "—",
to_title: to?.title ?? e.to_id,
to_status: to?.status ?? "—",
};
});
display(html`<table class="dep-table">
<thead>
<tr>
<th>Depends-on domain</th>
<th>Depends-on workstream</th>
<th></th>
<th>Blocked-by domain</th>
<th>Blocked-by workstream</th>
<th>Status</th>
</tr>
</thead>
<tbody>${rows.map(r => html`
<tr>
<td class="dep-domain">${r.from_domain}</td>
<td class="dep-title">${r.from_title}</td>
<td class="dep-arrow">→</td>
<td class="dep-domain">${r.to_domain}</td>
<td class="dep-title">${r.to_title}</td>
<td><span class="dep-status dep-status-${r.to_status}">${r.to_status}</span></td>
</tr>
`)}</tbody>
</table>`);
}