Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Archive failed epidemiology strains to museum hospice with SQLite persistence, operator/AI/court triggers, breeding and graft guards, topology museum nodes, and Seer plus oath ledger accountability.
130 lines
3.5 KiB
TypeScript
130 lines
3.5 KiB
TypeScript
/**
|
|
* Onion timeline fork/merge helpers for Path Tracer UI + Seer/AI context.
|
|
*/
|
|
|
|
import { spreadStrainFromJoinLane } from './fleetTopologyEpidemiology';
|
|
|
|
export type TimelineBranchStatus =
|
|
| 'canonical'
|
|
| 'running'
|
|
| 'won'
|
|
| 'lost'
|
|
| 'merged'
|
|
| 'failed'
|
|
| 'aborted';
|
|
|
|
export interface PathTraceTimelineBranch {
|
|
id: string;
|
|
parent_id?: string;
|
|
fork_hop_index: number;
|
|
target_agent_id?: string;
|
|
target_agent_name?: string;
|
|
persona?: string;
|
|
spread_lanes?: string[];
|
|
status: TimelineBranchStatus;
|
|
mining_linked?: boolean;
|
|
hashrate?: number;
|
|
active_tier?: string;
|
|
error?: string;
|
|
is_ghost: boolean;
|
|
created_at?: string;
|
|
merged_at?: string;
|
|
}
|
|
|
|
export interface PathTraceTimelineWS {
|
|
session_id: string;
|
|
event: string;
|
|
branch?: PathTraceTimelineBranch;
|
|
branches?: PathTraceTimelineBranch[];
|
|
mermaid?: string;
|
|
target_agent_id?: string;
|
|
}
|
|
|
|
export function branchStatusLabel(status: TimelineBranchStatus): string {
|
|
switch (status) {
|
|
case 'canonical':
|
|
return 'canonical';
|
|
case 'running':
|
|
return 'exploring';
|
|
case 'won':
|
|
return 'mining linked';
|
|
case 'merged':
|
|
return 'merged';
|
|
case 'lost':
|
|
return 'lost';
|
|
case 'failed':
|
|
return 'failed';
|
|
case 'aborted':
|
|
return 'aborted';
|
|
default:
|
|
return status;
|
|
}
|
|
}
|
|
|
|
export function branchStatusClass(status: TimelineBranchStatus): string {
|
|
switch (status) {
|
|
case 'won':
|
|
case 'merged':
|
|
return 'won';
|
|
case 'running':
|
|
return 'running';
|
|
case 'failed':
|
|
case 'lost':
|
|
case 'aborted':
|
|
return 'failed';
|
|
case 'canonical':
|
|
return 'canonical';
|
|
default:
|
|
return 'ghost';
|
|
}
|
|
}
|
|
|
|
/** Ghost branches grouped under fork hop index for tree rendering. */
|
|
export function ghostBranchesByHop(branches: PathTraceTimelineBranch[]): Map<number, PathTraceTimelineBranch[]> {
|
|
const map = new Map<number, PathTraceTimelineBranch[]>();
|
|
for (const b of branches) {
|
|
if (!b.is_ghost) continue;
|
|
const list = map.get(b.fork_hop_index) ?? [];
|
|
list.push(b);
|
|
map.set(b.fork_hop_index, list);
|
|
}
|
|
return map;
|
|
}
|
|
|
|
function branchPrimaryLane(branch: PathTraceTimelineBranch): string {
|
|
return branch.spread_lanes?.[0]?.trim() ?? '';
|
|
}
|
|
|
|
/** Spread strain from join lane — mirrors Go SpreadStrainFromJoinLane for hospice guards. */
|
|
export function spreadStrainFromPersonaLane(lane: string): string {
|
|
if (!lane) return '';
|
|
return spreadStrainFromJoinLane(lane).toLowerCase();
|
|
}
|
|
|
|
export function pickMergeCandidate(
|
|
branches: PathTraceTimelineBranch[],
|
|
hospiceStrains?: Set<string>,
|
|
): PathTraceTimelineBranch | null {
|
|
const hospice = hospiceStrains ?? new Set<string>();
|
|
const eligible = (b: PathTraceTimelineBranch) => {
|
|
const lane = branchPrimaryLane(b);
|
|
if (!lane || hospice.size === 0) return true;
|
|
const strain = spreadStrainFromPersonaLane(lane);
|
|
return strain === '' || !hospice.has(strain);
|
|
};
|
|
const ghosts = branches.filter((b) => b.is_ghost && eligible(b));
|
|
const won = ghosts.find((b) => b.status === 'won' || b.mining_linked);
|
|
if (won) return won;
|
|
const running = ghosts.find((b) => b.status === 'running');
|
|
return running ?? null;
|
|
}
|
|
|
|
export function mergeMermaidStyles(mermaid: string): string {
|
|
const styles = `classDef won fill:#0a3d2e,stroke:#00ffaa,color:#00ffaa
|
|
classDef running fill:#3d320a,stroke:#ffc800,color:#ffc800
|
|
classDef failed fill:#3d0a0a,stroke:#ff5050,color:#ff5050
|
|
classDef ghost fill:#1a1a2e,stroke:#888,color:#ccc`;
|
|
if (mermaid.includes('classDef won')) return mermaid;
|
|
return `${mermaid.trim()}\n${styles}`;
|
|
}
|