feat(rnetwork): enhanced trust flow viz + rename authorities to gov/fin/dev-ops
- Compute delegatedWeight per node from delegation edges (both directions) - Animated directional particles on delegation edges (count/size ~ weight) - Wider delegation edges (1+weight*8) with 0.15 curvature - "All" authority overlay mode with per-authority colored edges - Rename 5 authorities (voting/moderation/curation/treasury/membership) to 3 verticals: gov-ops, fin-ops, dev-ops - DB migration: update CHECK constraint + migrate existing data - Update all frontend components + backend defaults Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f2d575d1a2
commit
4c44eb9941
|
|
@ -1,7 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* <folk-delegation-manager> — per-vertical delegation management UI.
|
* <folk-delegation-manager> — per-vertical delegation management UI.
|
||||||
*
|
*
|
||||||
* Shows bars for each authority vertical (voting, moderation, curation, treasury, membership)
|
* Shows bars for each authority vertical (gov-ops, fin-ops, dev-ops)
|
||||||
* with percentage allocated and delegate avatars. Supports create, edit, revoke.
|
* with percentage allocated and delegate avatars. Supports create, edit, revoke.
|
||||||
* Weight sum validated client-side before submission.
|
* Weight sum validated client-side before submission.
|
||||||
*/
|
*/
|
||||||
|
|
@ -30,13 +30,11 @@ interface SpaceUser {
|
||||||
role: string;
|
role: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AUTHORITIES = ["voting", "moderation", "curation", "treasury", "membership"] as const;
|
const AUTHORITIES = ["gov-ops", "fin-ops", "dev-ops"] as const;
|
||||||
const AUTHORITY_ICONS: Record<string, string> = {
|
const AUTHORITY_ICONS: Record<string, string> = {
|
||||||
voting: "\u{1F5F3}\uFE0F",
|
"gov-ops": "\u{1F3DB}\uFE0F",
|
||||||
moderation: "\u{1F6E1}\uFE0F",
|
"fin-ops": "\u{1F4B0}",
|
||||||
curation: "\u2728",
|
"dev-ops": "\u{1F528}",
|
||||||
treasury: "\u{1F4B0}",
|
|
||||||
membership: "\u{1F465}",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class FolkDelegationManager extends HTMLElement {
|
class FolkDelegationManager extends HTMLElement {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ interface GraphNode {
|
||||||
location?: string;
|
location?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
trustScore?: number;
|
trustScore?: number;
|
||||||
|
delegatedWeight?: number; // 0-1 normalized, computed from delegation edges
|
||||||
// 3d-force-graph internal properties
|
// 3d-force-graph internal properties
|
||||||
x?: number;
|
x?: number;
|
||||||
y?: number;
|
y?: number;
|
||||||
|
|
@ -27,10 +28,19 @@ interface GraphEdge {
|
||||||
type: string;
|
type: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
weight?: number;
|
weight?: number;
|
||||||
|
authority?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DELEGATION_AUTHORITIES = ["voting", "moderation", "curation", "treasury", "membership"] as const;
|
const DELEGATION_AUTHORITIES = ["gov-ops", "fin-ops", "dev-ops"] as const;
|
||||||
type DelegationAuthority = typeof DELEGATION_AUTHORITIES[number];
|
type DelegationAuthority = typeof DELEGATION_AUTHORITIES[number];
|
||||||
|
type AuthoritySelection = "all" | DelegationAuthority;
|
||||||
|
|
||||||
|
// Per-authority edge colors for "all" overlay mode (governance, economics, technology)
|
||||||
|
const AUTHORITY_COLORS: Record<string, string> = {
|
||||||
|
"gov-ops": "#a78bfa", // purple — governance decisions
|
||||||
|
"fin-ops": "#fbbf24", // amber — economic/financial decisions
|
||||||
|
"dev-ops": "#34d399", // green — technical decisions
|
||||||
|
};
|
||||||
|
|
||||||
// Node colors by type
|
// Node colors by type
|
||||||
const NODE_COLORS: Record<string, number> = {
|
const NODE_COLORS: Record<string, number> = {
|
||||||
|
|
@ -63,7 +73,7 @@ class FolkGraphViewer extends HTMLElement {
|
||||||
private error = "";
|
private error = "";
|
||||||
private selectedNode: GraphNode | null = null;
|
private selectedNode: GraphNode | null = null;
|
||||||
private trustMode = false;
|
private trustMode = false;
|
||||||
private authority: DelegationAuthority = "voting";
|
private authority: AuthoritySelection = "gov-ops";
|
||||||
|
|
||||||
// 3D graph instance
|
// 3D graph instance
|
||||||
private graph: any = null;
|
private graph: any = null;
|
||||||
|
|
@ -102,7 +112,7 @@ class FolkGraphViewer extends HTMLElement {
|
||||||
private async loadData() {
|
private async loadData() {
|
||||||
const base = this.getApiBase();
|
const base = this.getApiBase();
|
||||||
try {
|
try {
|
||||||
const trustParam = this.trustMode ? `?trust=true&authority=${this.authority}` : "";
|
const trustParam = this.trustMode ? `?trust=true&authority=${encodeURIComponent(this.authority)}` : "";
|
||||||
const [wsRes, infoRes, graphRes] = await Promise.all([
|
const [wsRes, infoRes, graphRes] = await Promise.all([
|
||||||
fetch(`${base}/api/workspaces`),
|
fetch(`${base}/api/workspaces`),
|
||||||
fetch(`${base}/api/info`),
|
fetch(`${base}/api/info`),
|
||||||
|
|
@ -121,7 +131,7 @@ class FolkGraphViewer extends HTMLElement {
|
||||||
this.updateGraphData();
|
this.updateGraphData();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async reloadWithAuthority(authority: DelegationAuthority) {
|
private async reloadWithAuthority(authority: AuthoritySelection) {
|
||||||
this.authority = authority;
|
this.authority = authority;
|
||||||
this.trustMode = true;
|
this.trustMode = true;
|
||||||
await this.loadData();
|
await this.loadData();
|
||||||
|
|
@ -169,8 +179,27 @@ class FolkGraphViewer extends HTMLElement {
|
||||||
type: edgeTypeMap[e.type] || e.type,
|
type: edgeTypeMap[e.type] || e.type,
|
||||||
label: e.label,
|
label: e.label,
|
||||||
weight: e.weight,
|
weight: e.weight,
|
||||||
|
authority: e.authority,
|
||||||
} as GraphEdge));
|
} as GraphEdge));
|
||||||
|
|
||||||
|
// Compute delegatedWeight for every node from delegation edges
|
||||||
|
const nodeWeights = new Map<string, number>();
|
||||||
|
for (const e of this.edges) {
|
||||||
|
if (e.type !== "delegates_to") continue;
|
||||||
|
const sid = typeof e.source === "string" ? e.source : e.source.id;
|
||||||
|
const tid = typeof e.target === "string" ? e.target : e.target.id;
|
||||||
|
const w = e.weight || 0.5;
|
||||||
|
nodeWeights.set(sid, (nodeWeights.get(sid) || 0) + w);
|
||||||
|
nodeWeights.set(tid, (nodeWeights.get(tid) || 0) + w);
|
||||||
|
}
|
||||||
|
const maxWeight = Math.max(...nodeWeights.values(), 1);
|
||||||
|
for (const node of this.nodes) {
|
||||||
|
const w = nodeWeights.get(node.id);
|
||||||
|
if (w != null) {
|
||||||
|
node.delegatedWeight = w / maxWeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Assign company colors
|
// Assign company colors
|
||||||
const companies = this.nodes.filter(n => n.type === "company");
|
const companies = this.nodes.filter(n => n.type === "company");
|
||||||
this.companyColors.clear();
|
this.companyColors.clear();
|
||||||
|
|
@ -215,8 +244,14 @@ class FolkGraphViewer extends HTMLElement {
|
||||||
|
|
||||||
private getNodeRadius(node: GraphNode): number {
|
private getNodeRadius(node: GraphNode): number {
|
||||||
if (node.type === "company") return 22;
|
if (node.type === "company") return 22;
|
||||||
if (node.trustScore != null && this.trustMode) {
|
if (this.trustMode) {
|
||||||
return 8 + (node.trustScore * 22);
|
// Prefer edge-computed delegatedWeight, fall back to trustScore
|
||||||
|
if (node.delegatedWeight != null) {
|
||||||
|
return 6 + node.delegatedWeight * 24;
|
||||||
|
}
|
||||||
|
if (node.trustScore != null) {
|
||||||
|
return 6 + node.trustScore * 24;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 12;
|
return 12;
|
||||||
}
|
}
|
||||||
|
|
@ -308,7 +343,7 @@ class FolkGraphViewer extends HTMLElement {
|
||||||
.authority-btn:hover { border-color: var(--rs-border-strong); }
|
.authority-btn:hover { border-color: var(--rs-border-strong); }
|
||||||
.authority-btn.active { border-color: #a78bfa; color: #a78bfa; background: rgba(167, 139, 250, 0.1); }
|
.authority-btn.active { border-color: #a78bfa; color: #a78bfa; background: rgba(167, 139, 250, 0.1); }
|
||||||
|
|
||||||
.legend { display: flex; gap: 16px; margin-top: 12px; }
|
.legend { display: flex; gap: 16px; margin-top: 12px; flex-wrap: wrap; }
|
||||||
.legend-item { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--rs-text-muted); }
|
.legend-item { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--rs-text-muted); }
|
||||||
.legend-dot { width: 10px; height: 10px; border-radius: 50%; }
|
.legend-dot { width: 10px; height: 10px; border-radius: 50%; }
|
||||||
.dot-person { background: #3b82f6; }
|
.dot-person { background: #3b82f6; }
|
||||||
|
|
@ -387,6 +422,7 @@ class FolkGraphViewer extends HTMLElement {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="authority-bar" id="authority-bar">
|
<div class="authority-bar" id="authority-bar">
|
||||||
|
<button class="authority-btn" data-authority="all">All</button>
|
||||||
${DELEGATION_AUTHORITIES.map(a => `<button class="authority-btn" data-authority="${a}">${a}</button>`).join("")}
|
${DELEGATION_AUTHORITIES.map(a => `<button class="authority-btn" data-authority="${a}">${a}</button>`).join("")}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -409,6 +445,11 @@ class FolkGraphViewer extends HTMLElement {
|
||||||
<div class="legend-item"><svg width="20" height="10"><line x1="0" y1="5" x2="20" y2="5" stroke="#888" stroke-width="2"></line></svg> Works at</div>
|
<div class="legend-item"><svg width="20" height="10"><line x1="0" y1="5" x2="20" y2="5" stroke="#888" stroke-width="2"></line></svg> Works at</div>
|
||||||
<div class="legend-item"><svg width="20" height="10"><line x1="0" y1="5" x2="20" y2="5" stroke="#c084fc" stroke-width="2" stroke-dasharray="4 2"></line></svg> Point of contact</div>
|
<div class="legend-item"><svg width="20" height="10"><line x1="0" y1="5" x2="20" y2="5" stroke="#c084fc" stroke-width="2" stroke-dasharray="4 2"></line></svg> Point of contact</div>
|
||||||
<div class="legend-item" id="legend-delegates" style="display:none"><svg width="20" height="10"><line x1="0" y1="5" x2="20" y2="5" stroke="#a78bfa" stroke-width="2"></line></svg> Delegates to</div>
|
<div class="legend-item" id="legend-delegates" style="display:none"><svg width="20" height="10"><line x1="0" y1="5" x2="20" y2="5" stroke="#a78bfa" stroke-width="2"></line></svg> Delegates to</div>
|
||||||
|
<span id="legend-authority-colors" style="display:none">
|
||||||
|
<div class="legend-item"><span class="legend-dot" style="background:#a78bfa"></span> Gov-Ops</div>
|
||||||
|
<div class="legend-item"><span class="legend-dot" style="background:#fbbf24"></span> Fin-Ops</div>
|
||||||
|
<div class="legend-item"><span class="legend-dot" style="background:#34d399"></span> Dev-Ops</div>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="workspace-section"></div>
|
<div id="workspace-section"></div>
|
||||||
|
|
@ -449,7 +490,7 @@ class FolkGraphViewer extends HTMLElement {
|
||||||
// Authority buttons
|
// Authority buttons
|
||||||
this.shadow.querySelectorAll("[data-authority]").forEach(el => {
|
this.shadow.querySelectorAll("[data-authority]").forEach(el => {
|
||||||
el.addEventListener("click", () => {
|
el.addEventListener("click", () => {
|
||||||
const authority = (el as HTMLElement).dataset.authority as DelegationAuthority;
|
const authority = (el as HTMLElement).dataset.authority as AuthoritySelection;
|
||||||
this.reloadWithAuthority(authority);
|
this.reloadWithAuthority(authority);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -514,21 +555,45 @@ class FolkGraphViewer extends HTMLElement {
|
||||||
.linkSource("source")
|
.linkSource("source")
|
||||||
.linkTarget("target")
|
.linkTarget("target")
|
||||||
.linkColor((link: GraphEdge) => {
|
.linkColor((link: GraphEdge) => {
|
||||||
|
if (link.type === "delegates_to") {
|
||||||
|
if (this.authority === "all" && link.authority) {
|
||||||
|
return AUTHORITY_COLORS[link.authority] || EDGE_STYLES.delegates_to.color;
|
||||||
|
}
|
||||||
|
return EDGE_STYLES.delegates_to.color;
|
||||||
|
}
|
||||||
const style = EDGE_STYLES[link.type] || EDGE_STYLES.default;
|
const style = EDGE_STYLES[link.type] || EDGE_STYLES.default;
|
||||||
return style.color;
|
return style.color;
|
||||||
})
|
})
|
||||||
.linkWidth((link: GraphEdge) => {
|
.linkWidth((link: GraphEdge) => {
|
||||||
if (link.type === "delegates_to") {
|
if (link.type === "delegates_to") {
|
||||||
return 1 + (link.weight || 0.5) * 3;
|
return 1 + (link.weight || 0.5) * 8;
|
||||||
}
|
}
|
||||||
const style = EDGE_STYLES[link.type] || EDGE_STYLES.default;
|
const style = EDGE_STYLES[link.type] || EDGE_STYLES.default;
|
||||||
return style.width;
|
return style.width;
|
||||||
})
|
})
|
||||||
|
.linkCurvature((link: GraphEdge) =>
|
||||||
|
link.type === "delegates_to" ? 0.15 : 0
|
||||||
|
)
|
||||||
|
.linkCurveRotation("rotation")
|
||||||
.linkOpacity(0.6)
|
.linkOpacity(0.6)
|
||||||
.linkDirectionalArrowLength((link: GraphEdge) =>
|
.linkDirectionalArrowLength((link: GraphEdge) =>
|
||||||
link.type === "delegates_to" ? 4 : 0
|
link.type === "delegates_to" ? 4 : 0
|
||||||
)
|
)
|
||||||
.linkDirectionalArrowRelPos(1)
|
.linkDirectionalArrowRelPos(1)
|
||||||
|
.linkDirectionalParticles((link: GraphEdge) =>
|
||||||
|
link.type === "delegates_to" ? Math.ceil((link.weight || 0.5) * 4) : 0
|
||||||
|
)
|
||||||
|
.linkDirectionalParticleSpeed(0.004)
|
||||||
|
.linkDirectionalParticleWidth((link: GraphEdge) =>
|
||||||
|
link.type === "delegates_to" ? 1 + (link.weight || 0.5) * 2 : 0
|
||||||
|
)
|
||||||
|
.linkDirectionalParticleColor((link: GraphEdge) => {
|
||||||
|
if (link.type !== "delegates_to") return null;
|
||||||
|
if (this.authority === "all" && link.authority) {
|
||||||
|
return AUTHORITY_COLORS[link.authority] || "#c4b5fd";
|
||||||
|
}
|
||||||
|
return "#c4b5fd";
|
||||||
|
})
|
||||||
.linkLineDash((link: GraphEdge) => {
|
.linkLineDash((link: GraphEdge) => {
|
||||||
const style = EDGE_STYLES[link.type] || EDGE_STYLES.default;
|
const style = EDGE_STYLES[link.type] || EDGE_STYLES.default;
|
||||||
return style.dashed ? [4, 2] : null;
|
return style.dashed ? [4, 2] : null;
|
||||||
|
|
@ -745,8 +810,10 @@ class FolkGraphViewer extends HTMLElement {
|
||||||
// Update legend visibility for trust mode
|
// Update legend visibility for trust mode
|
||||||
const membersLegend = this.shadow.getElementById("legend-members");
|
const membersLegend = this.shadow.getElementById("legend-members");
|
||||||
const delegatesLegend = this.shadow.getElementById("legend-delegates");
|
const delegatesLegend = this.shadow.getElementById("legend-delegates");
|
||||||
|
const authorityColors = this.shadow.getElementById("legend-authority-colors");
|
||||||
if (membersLegend) membersLegend.style.display = this.trustMode ? "" : "none";
|
if (membersLegend) membersLegend.style.display = this.trustMode ? "" : "none";
|
||||||
if (delegatesLegend) delegatesLegend.style.display = this.trustMode ? "" : "none";
|
if (delegatesLegend) delegatesLegend.style.display = (this.trustMode && this.authority !== "all") ? "" : "none";
|
||||||
|
if (authorityColors) authorityColors.style.display = (this.trustMode && this.authority === "all") ? "" : "none";
|
||||||
|
|
||||||
// Fit view after data settles
|
// Fit view after data settles
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
|
||||||
|
|
@ -29,13 +29,13 @@ interface TrustEvent {
|
||||||
createdAt: number;
|
createdAt: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SANKEY_AUTHORITIES = ["voting", "moderation", "curation", "treasury", "membership"] as const;
|
const SANKEY_AUTHORITIES = ["gov-ops", "fin-ops", "dev-ops"] as const;
|
||||||
const FLOW_COLOR = "#a78bfa";
|
const FLOW_COLOR = "#a78bfa";
|
||||||
|
|
||||||
class FolkTrustSankey extends HTMLElement {
|
class FolkTrustSankey extends HTMLElement {
|
||||||
private shadow: ShadowRoot;
|
private shadow: ShadowRoot;
|
||||||
private space = "";
|
private space = "";
|
||||||
private authority = "voting";
|
private authority = "gov-ops";
|
||||||
private flows: DelegationFlow[] = [];
|
private flows: DelegationFlow[] = [];
|
||||||
private events: TrustEvent[] = [];
|
private events: TrustEvent[] = [];
|
||||||
private loading = true;
|
private loading = true;
|
||||||
|
|
@ -50,7 +50,7 @@ class FolkTrustSankey extends HTMLElement {
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
this.space = this.getAttribute("space") || "demo";
|
this.space = this.getAttribute("space") || "demo";
|
||||||
this.authority = this.getAttribute("authority") || "voting";
|
this.authority = this.getAttribute("authority") || "gov-ops";
|
||||||
this.render();
|
this.render();
|
||||||
this.loadData();
|
this.loadData();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1727,7 +1727,7 @@ export async function updatePushSubscriptionLastUsed(id: string): Promise<void>
|
||||||
// DELEGATIONS (person-to-person liquid democracy)
|
// DELEGATIONS (person-to-person liquid democracy)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
export type DelegationAuthority = 'voting' | 'moderation' | 'curation' | 'treasury' | 'membership' | 'custom';
|
export type DelegationAuthority = 'gov-ops' | 'fin-ops' | 'dev-ops' | 'custom';
|
||||||
export type DelegationState = 'active' | 'paused' | 'revoked';
|
export type DelegationState = 'active' | 'paused' | 'revoked';
|
||||||
|
|
||||||
export interface StoredDelegation {
|
export interface StoredDelegation {
|
||||||
|
|
|
||||||
|
|
@ -376,7 +376,7 @@ CREATE TABLE IF NOT EXISTS delegations (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
delegator_did TEXT NOT NULL,
|
delegator_did TEXT NOT NULL,
|
||||||
delegate_did TEXT NOT NULL,
|
delegate_did TEXT NOT NULL,
|
||||||
authority TEXT NOT NULL CHECK (authority IN ('voting', 'moderation', 'curation', 'treasury', 'membership', 'custom')),
|
authority TEXT NOT NULL CHECK (authority IN ('gov-ops', 'fin-ops', 'dev-ops', 'custom')),
|
||||||
weight REAL NOT NULL CHECK (weight > 0 AND weight <= 1),
|
weight REAL NOT NULL CHECK (weight > 0 AND weight <= 1),
|
||||||
max_depth INTEGER NOT NULL DEFAULT 3,
|
max_depth INTEGER NOT NULL DEFAULT 3,
|
||||||
retain_authority BOOLEAN NOT NULL DEFAULT TRUE,
|
retain_authority BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
|
|
@ -452,3 +452,26 @@ CREATE TABLE IF NOT EXISTS legacy_identities (
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_legacy_identities_user ON legacy_identities(user_id);
|
CREATE INDEX IF NOT EXISTS idx_legacy_identities_user ON legacy_identities(user_id);
|
||||||
CREATE INDEX IF NOT EXISTS idx_legacy_identities_pubkey ON legacy_identities(legacy_public_key_hash);
|
CREATE INDEX IF NOT EXISTS idx_legacy_identities_pubkey ON legacy_identities(legacy_public_key_hash);
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- MIGRATION: Rename authority verticals (voting/moderation/curation/treasury/membership → gov-ops/fin-ops/dev-ops)
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- Migrate existing delegation data
|
||||||
|
UPDATE delegations SET authority = 'gov-ops' WHERE authority IN ('voting', 'moderation');
|
||||||
|
UPDATE delegations SET authority = 'fin-ops' WHERE authority = 'treasury';
|
||||||
|
UPDATE delegations SET authority = 'dev-ops' WHERE authority IN ('curation', 'membership');
|
||||||
|
|
||||||
|
-- Migrate existing trust_scores data
|
||||||
|
UPDATE trust_scores SET authority = 'gov-ops' WHERE authority IN ('voting', 'moderation');
|
||||||
|
UPDATE trust_scores SET authority = 'fin-ops' WHERE authority = 'treasury';
|
||||||
|
UPDATE trust_scores SET authority = 'dev-ops' WHERE authority IN ('curation', 'membership');
|
||||||
|
|
||||||
|
-- Migrate existing trust_events data
|
||||||
|
UPDATE trust_events SET authority = 'gov-ops' WHERE authority IN ('voting', 'moderation');
|
||||||
|
UPDATE trust_events SET authority = 'fin-ops' WHERE authority = 'treasury';
|
||||||
|
UPDATE trust_events SET authority = 'dev-ops' WHERE authority IN ('curation', 'membership');
|
||||||
|
|
||||||
|
-- Update CHECK constraint on delegations table
|
||||||
|
ALTER TABLE delegations DROP CONSTRAINT IF EXISTS delegations_authority_check;
|
||||||
|
ALTER TABLE delegations ADD CONSTRAINT delegations_authority_check CHECK (authority IN ('gov-ops', 'fin-ops', 'dev-ops', 'custom'));
|
||||||
|
|
|
||||||
|
|
@ -7122,7 +7122,7 @@ app.get('/', (c) => {
|
||||||
// DELEGATION ROUTES (person-to-person liquid democracy)
|
// DELEGATION ROUTES (person-to-person liquid democracy)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
const VALID_AUTHORITIES = ['voting', 'moderation', 'curation', 'treasury', 'membership', 'custom'];
|
const VALID_AUTHORITIES = ['gov-ops', 'fin-ops', 'dev-ops', 'custom'];
|
||||||
|
|
||||||
// POST /api/delegations — create a new delegation
|
// POST /api/delegations — create a new delegation
|
||||||
app.post('/api/delegations', async (c) => {
|
app.post('/api/delegations', async (c) => {
|
||||||
|
|
@ -7359,7 +7359,7 @@ app.get('/api/delegations/space', async (c) => {
|
||||||
|
|
||||||
// GET /api/trust/scores — aggregated trust scores for visualization
|
// GET /api/trust/scores — aggregated trust scores for visualization
|
||||||
app.get('/api/trust/scores', async (c) => {
|
app.get('/api/trust/scores', async (c) => {
|
||||||
const authority = c.req.query('authority') || 'voting';
|
const authority = c.req.query('authority') || 'gov-ops';
|
||||||
const spaceSlug = c.req.query('space');
|
const spaceSlug = c.req.query('space');
|
||||||
if (!spaceSlug) return c.json({ error: 'space query parameter required' }, 400);
|
if (!spaceSlug) return c.json({ error: 'space query parameter required' }, 400);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,7 @@ export function computeTrustScores(
|
||||||
|
|
||||||
// ── Background Recomputation ──
|
// ── Background Recomputation ──
|
||||||
|
|
||||||
const AUTHORITIES: DelegationAuthority[] = ['voting', 'moderation', 'curation', 'treasury', 'membership', 'custom'];
|
const AUTHORITIES: DelegationAuthority[] = ['gov-ops', 'fin-ops', 'dev-ops', 'custom'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recompute all trust scores for a single space.
|
* Recompute all trust scores for a single space.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue