mirror of
https://github.com/gadgethd/ukmesh.git
synced 2026-10-23 06:12:27 +00:00
fix(pathing): expose canonical path metadata in public DTOs
This commit is contained in:
1 parent
b2c46f7103
commit
457eb77cbb
2 files changed
+126
-2
No files matched your search
@@ -414,11 +414,27 @@ function findSharedPathPrefix(pathsByObserver: readonly string[][]): string[] {
|
||||
return first.slice(0, length);
|
||||
}
|
||||
|
||||
export type BetaCanonicalPathNode = {
|
||||
position: number;
|
||||
hash: string;
|
||||
nodeId: string | null;
|
||||
name: string | null;
|
||||
lat: number | null;
|
||||
lon: number | null;
|
||||
ambiguous: boolean;
|
||||
confidence: number | null;
|
||||
};
|
||||
|
||||
export type BetaPathObserver = { observerId: string };
|
||||
|
||||
export type BetaResolvedPayload = {
|
||||
ok: boolean;
|
||||
packetHash: string;
|
||||
mode: 'resolved' | 'none';
|
||||
confidence: number | null;
|
||||
canonicalPath: BetaCanonicalPathNode[];
|
||||
observers: BetaPathObserver[];
|
||||
network: string;
|
||||
permutationCount: number;
|
||||
remainingHops: number | null;
|
||||
purplePath: [number, number][] | null;
|
||||
@@ -492,6 +508,25 @@ function maskPath(
|
||||
return path.map((point) => hiddenCoordMask.get(hiddenCoordKey(point[0], point[1])) ?? point);
|
||||
}
|
||||
|
||||
function buildCanonicalPath(decoded: BetaSharedDecode): BetaCanonicalPathNode[] {
|
||||
const nodes: BetaCanonicalPathNode[] = [];
|
||||
for (let position = 0; position < decoded.canonicalHashes.length; position++) {
|
||||
const hop = decoded.hops.get(position);
|
||||
const confidence = decoded.hopConfidences.get(position) ?? null;
|
||||
nodes.push({
|
||||
position,
|
||||
hash: decoded.canonicalHashes[position] ?? hop?.hash ?? '',
|
||||
nodeId: hop?.nodeId ?? null,
|
||||
name: hop?.name ?? null,
|
||||
lat: hop?.lat ?? null,
|
||||
lon: hop?.lon ?? null,
|
||||
ambiguous: hop?.ambiguous ?? (confidence == null || confidence < BETA_PURPLE_THRESHOLD),
|
||||
confidence,
|
||||
});
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
function maskResolvedPayload(
|
||||
payload: BetaResolvedPayload,
|
||||
hiddenCoordMask: ReadonlyMap<string, [number, number]>,
|
||||
@@ -539,6 +574,9 @@ function emptyPayload(
|
||||
packetHash,
|
||||
mode: 'none',
|
||||
confidence: null,
|
||||
canonicalPath: [],
|
||||
observers: [],
|
||||
network: '',
|
||||
permutationCount: 0,
|
||||
remainingHops,
|
||||
purplePath: null,
|
||||
@@ -561,6 +599,7 @@ function payloadFromProjection(
|
||||
packetHash: string,
|
||||
entry: BetaObserverEntry,
|
||||
projection: BetaPathProjection,
|
||||
canonical: { canonicalPath: BetaCanonicalPathNode[]; observers: BetaPathObserver[]; network: string },
|
||||
): BetaResolvedPayload {
|
||||
if (!projection.purplePath) {
|
||||
return emptyPayload(packetHash, entry.packet, entry.hashes.length, projection.remainingHops);
|
||||
@@ -570,6 +609,9 @@ function payloadFromProjection(
|
||||
packetHash,
|
||||
mode: 'resolved',
|
||||
confidence: projection.confidence,
|
||||
canonicalPath: canonical.canonicalPath,
|
||||
observers: canonical.observers,
|
||||
network: canonical.network,
|
||||
permutationCount: 0,
|
||||
remainingHops: projection.remainingHops,
|
||||
purplePath: projection.purplePath,
|
||||
@@ -670,7 +712,11 @@ export async function resolveBetaPathForPacketHash(
|
||||
? context.nodesById.get(selectedEntry.packet.src_node_id) ?? null
|
||||
: null;
|
||||
const projection = projectCanonicalPathForObserver(selectedEntry, decoded, context, source);
|
||||
const payload = payloadFromProjection(packetHash, selectedEntry, projection);
|
||||
const payload = payloadFromProjection(packetHash, selectedEntry, projection, {
|
||||
canonicalPath: buildCanonicalPath(decoded),
|
||||
observers: [{ observerId: selectedEntry.observerId }],
|
||||
network,
|
||||
});
|
||||
if (payload.mode === 'resolved' && shouldTouchPredictedOnline(options)) {
|
||||
await recordPredictedOnline(projection.nodeIds);
|
||||
}
|
||||
@@ -692,8 +738,12 @@ export type RegionLink = {
|
||||
export type MultiObserverResolvedPayload = {
|
||||
ok: boolean;
|
||||
packetHash: string;
|
||||
network: string;
|
||||
observerCount: number;
|
||||
sharedPrefixLength: number;
|
||||
canonicalPath: BetaCanonicalPathNode[];
|
||||
observers: BetaPathObserver[];
|
||||
confidence: number | null;
|
||||
results: BetaResolvedPayload[];
|
||||
regionLinks?: RegionLink[];
|
||||
stickyUpdates?: Record<string, string>;
|
||||
@@ -821,6 +871,7 @@ export async function resolveMultiObserverBetaPath(
|
||||
const groups = groupCompatibleObservations(entries);
|
||||
const hiddenCoordMask = buildHiddenCoordMask(context.nodesById);
|
||||
const results: BetaResolvedPayload[] = [];
|
||||
let firstCanonicalPath: BetaCanonicalPathNode[] = [];
|
||||
const predictedOnlineNodeIds = new Set<string>();
|
||||
const stickyUpdates: Record<string, string> = {};
|
||||
const stickyConflicts = new Set<string>();
|
||||
@@ -829,6 +880,7 @@ export async function resolveMultiObserverBetaPath(
|
||||
for (const group of groups) {
|
||||
throwIfResolutionAborted(options);
|
||||
const decoded = decodeBetaCanonicalGroup(group, context, stickyMap, stickyAgeFraction);
|
||||
if (firstCanonicalPath.length === 0) firstCanonicalPath = buildCanonicalPath(decoded);
|
||||
for (let position = 0; position < decoded.canonicalHashes.length; position++) {
|
||||
const hop = decoded.hops.get(position);
|
||||
const confidence = decoded.hopConfidences.get(position) ?? 0;
|
||||
@@ -842,7 +894,11 @@ export async function resolveMultiObserverBetaPath(
|
||||
? context.nodesById.get(entry.packet.src_node_id) ?? null
|
||||
: null;
|
||||
const projection = projectCanonicalPathForObserver(entry, decoded, context, source);
|
||||
const payload = payloadFromProjection(packetHash, entry, projection);
|
||||
const payload = payloadFromProjection(packetHash, entry, projection, {
|
||||
canonicalPath: buildCanonicalPath(decoded),
|
||||
observers: group.members.map((member) => ({ observerId: member.observerId })),
|
||||
network,
|
||||
});
|
||||
results.push(maskResolvedPayload(payload, hiddenCoordMask));
|
||||
if (payload.mode === 'resolved') {
|
||||
for (const nodeId of projection.nodeIds) predictedOnlineNodeIds.add(nodeId);
|
||||
@@ -865,8 +921,12 @@ export async function resolveMultiObserverBetaPath(
|
||||
return {
|
||||
ok: true,
|
||||
packetHash,
|
||||
network,
|
||||
observerCount: entries.length,
|
||||
sharedPrefixLength,
|
||||
canonicalPath: firstCanonicalPath,
|
||||
observers: entries.map((entry) => ({ observerId: entry.observerId })),
|
||||
confidence: results[0]?.confidence ?? null,
|
||||
results,
|
||||
...(regionLinks.length > 0 ? { regionLinks } : {}),
|
||||
...(Object.keys(stickyUpdates).length > 0 ? { stickyUpdates } : {}),
|
||||
|
||||
@@ -9,11 +9,27 @@ export type PublicPathExplanation = {
|
||||
limitations?: string[];
|
||||
};
|
||||
|
||||
export type PublicCanonicalPathNode = {
|
||||
position: number;
|
||||
hash: string;
|
||||
nodeId: string | null;
|
||||
name: string | null;
|
||||
lat: number | null;
|
||||
lon: number | null;
|
||||
ambiguous: boolean;
|
||||
confidence: number | null;
|
||||
};
|
||||
|
||||
export type PublicPathObserver = { observerId: string };
|
||||
|
||||
export type PublicBetaResultDto = {
|
||||
ok: boolean;
|
||||
packetHash: string;
|
||||
mode: 'resolved' | 'fallback' | 'none';
|
||||
confidence: number | null;
|
||||
canonicalPath: PublicCanonicalPathNode[];
|
||||
observers: PublicPathObserver[];
|
||||
network: string;
|
||||
permutationCount: number;
|
||||
remainingHops: number | null;
|
||||
purplePath: PublicPathPoint[] | null;
|
||||
@@ -27,8 +43,12 @@ export type PublicBetaResultDto = {
|
||||
export type PublicMultiObserverDto = {
|
||||
ok: boolean;
|
||||
packetHash: string;
|
||||
network: string;
|
||||
observerCount: number;
|
||||
sharedPrefixLength: number;
|
||||
canonicalPath: PublicCanonicalPathNode[];
|
||||
observers: PublicPathObserver[];
|
||||
confidence: number | null;
|
||||
results: PublicBetaResultDto[];
|
||||
};
|
||||
|
||||
@@ -108,6 +128,41 @@ function projectExplanation(value: unknown): PublicPathExplanation | undefined {
|
||||
};
|
||||
}
|
||||
|
||||
function projectCanonicalPath(value: unknown): PublicCanonicalPathNode[] {
|
||||
if (!Array.isArray(value)) return [];
|
||||
const nodes: PublicCanonicalPathNode[] = [];
|
||||
for (const item of value) {
|
||||
if (!isPlainRecord(item)) continue;
|
||||
const hash = ownValue(item, 'hash');
|
||||
const nodeId = ownValue(item, 'nodeId');
|
||||
const name = ownValue(item, 'name');
|
||||
if (typeof hash !== 'string') continue;
|
||||
nodes.push({
|
||||
position: finiteNumber(ownValue(item, 'position'), -1),
|
||||
hash,
|
||||
nodeId: typeof nodeId === 'string' ? nodeId : null,
|
||||
name: typeof name === 'string' ? name : null,
|
||||
lat: nullableNumber(ownValue(item, 'lat')),
|
||||
lon: nullableNumber(ownValue(item, 'lon')),
|
||||
ambiguous: ownValue(item, 'ambiguous') === true,
|
||||
confidence: nullableNumber(ownValue(item, 'confidence')),
|
||||
});
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
function projectObservers(value: unknown): PublicPathObserver[] {
|
||||
if (!Array.isArray(value)) return [];
|
||||
const observers: PublicPathObserver[] = [];
|
||||
for (const item of value) {
|
||||
if (isPlainRecord(item)) {
|
||||
const observerId = ownValue(item, 'observerId');
|
||||
if (typeof observerId === 'string') observers.push({ observerId });
|
||||
}
|
||||
}
|
||||
return observers;
|
||||
}
|
||||
|
||||
export function toPublicBetaResultDto(value: unknown): PublicBetaResultDto {
|
||||
if (!isPlainRecord(value)) throw new Error('INVALID_PATH_RESULT');
|
||||
const packetHash = ownValue(value, 'packetHash');
|
||||
@@ -117,11 +172,15 @@ export function toPublicBetaResultDto(value: unknown): PublicBetaResultDto {
|
||||
throw new Error('INVALID_PATH_RESULT');
|
||||
}
|
||||
const explanation = projectExplanation(ownValue(value, 'explanation'));
|
||||
const network = ownValue(value, 'network');
|
||||
return {
|
||||
ok: ownValue(value, 'ok') === true,
|
||||
packetHash,
|
||||
mode,
|
||||
confidence: nullableNumber(ownValue(value, 'confidence')),
|
||||
canonicalPath: projectCanonicalPath(ownValue(value, 'canonicalPath')),
|
||||
observers: projectObservers(ownValue(value, 'observers')),
|
||||
network: typeof network === 'string' ? network : '',
|
||||
permutationCount: finiteNumber(ownValue(value, 'permutationCount')),
|
||||
remainingHops: nullableNumber(ownValue(value, 'remainingHops')),
|
||||
purplePath: projectPath(ownValue(value, 'purplePath')),
|
||||
@@ -138,11 +197,16 @@ export function toPublicMultiObserverDto(value: unknown): PublicMultiObserverDto
|
||||
const packetHash = ownValue(value, 'packetHash');
|
||||
const results = ownValue(value, 'results');
|
||||
if (typeof packetHash !== 'string' || !Array.isArray(results)) throw new Error('INVALID_PATH_RESULT');
|
||||
const network = ownValue(value, 'network');
|
||||
return {
|
||||
ok: ownValue(value, 'ok') === true,
|
||||
packetHash,
|
||||
network: typeof network === 'string' ? network : '',
|
||||
observerCount: finiteNumber(ownValue(value, 'observerCount')),
|
||||
sharedPrefixLength: finiteNumber(ownValue(value, 'sharedPrefixLength')),
|
||||
canonicalPath: projectCanonicalPath(ownValue(value, 'canonicalPath')),
|
||||
observers: projectObservers(ownValue(value, 'observers')),
|
||||
confidence: nullableNumber(ownValue(value, 'confidence')),
|
||||
results: results.map(toPublicBetaResultDto),
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user