169 lines
5.8 KiB
TypeScript
169 lines
5.8 KiB
TypeScript
|
|
import {
|
||
|
|
parseNodeRegistrationEndpointRegistry,
|
||
|
|
type NodeRegistrationEndpointRegistry,
|
||
|
|
} from './node-registration.js';
|
||
|
|
|
||
|
|
const ANCHOR_URL = 'https://guanghulab.com/api/ai/v1/anchor';
|
||
|
|
const CODE_URL = 'https://guanghulab.com/code/bingshuo/guanghu-ice-heart';
|
||
|
|
const REGISTRY_ID = 'GH-AIOS-NODE-REGISTRATION-ENDPOINTS-001';
|
||
|
|
const REGISTRY_PATH = 'routing/node-registration-endpoints.json';
|
||
|
|
const COMMIT_PATTERN = /^[a-f0-9]{40}(?:[a-f0-9]{24})?$/;
|
||
|
|
const VERSION_PATTERN = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/;
|
||
|
|
const registeredSnapshots = new WeakSet<object>();
|
||
|
|
|
||
|
|
export interface NodeRegistrationSnapshotTransport {
|
||
|
|
fetchJson(url: string): Promise<unknown>;
|
||
|
|
}
|
||
|
|
|
||
|
|
export type NodeRegistrationSnapshotReason =
|
||
|
|
| 'ANCHOR_INVALID'
|
||
|
|
| 'REGISTRY_INVALID'
|
||
|
|
| 'REGISTRY_UNREGISTERED'
|
||
|
|
| 'SOURCE_UNAVAILABLE';
|
||
|
|
|
||
|
|
export interface NodeRegistrationSnapshotReceipt {
|
||
|
|
endpointCount: number;
|
||
|
|
reason?: NodeRegistrationSnapshotReason;
|
||
|
|
registryVersion: string | null;
|
||
|
|
sourceCommit: string | null;
|
||
|
|
status: 'CURRENT' | 'DEGRADED_LAST_KNOWN_GOOD' | 'UNAVAILABLE';
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface NodeRegistrationSnapshotResult {
|
||
|
|
receipt: Readonly<NodeRegistrationSnapshotReceipt>;
|
||
|
|
registry: NodeRegistrationEndpointRegistry | null;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface ParsedAnchor {
|
||
|
|
registryVersion: string;
|
||
|
|
sourceCommit: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
class SnapshotLoadError extends Error {
|
||
|
|
constructor(readonly reason: NodeRegistrationSnapshotReason) {
|
||
|
|
super(reason);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||
|
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||
|
|
}
|
||
|
|
|
||
|
|
function parseAnchor(input: unknown): ParsedAnchor {
|
||
|
|
if (!isRecord(input)
|
||
|
|
|| input.schema !== 'guanghu.public-navigation-anchor/v1'
|
||
|
|
|| input.anchor_id !== 'GLW-PUBLIC-NAV-ANCHOR-001'
|
||
|
|
|| input.state !== 'CURRENT_CANONICAL'
|
||
|
|
|| input.repository_id !== 'REPO-012'
|
||
|
|
|| input.branch !== 'main'
|
||
|
|
|| input.public_entry !== ANCHOR_URL
|
||
|
|
|| input.code_entry !== CODE_URL
|
||
|
|
|| !isRecord(input.maps)
|
||
|
|
|| !isRecord(input.navigation_source)
|
||
|
|
|| input.navigation_source.anchor_id !== 'GLW-PUBLIC-NAV-ANCHOR-001'
|
||
|
|
|| input.navigation_source.source_mode !== 'REPO-012_MAIN_GIT_SNAPSHOT'
|
||
|
|
|| input.navigation_source.source_degraded !== false
|
||
|
|
|| typeof input.navigation_source.source_commit !== 'string'
|
||
|
|
|| !COMMIT_PATTERN.test(input.navigation_source.source_commit)) {
|
||
|
|
throw new SnapshotLoadError('ANCHOR_INVALID');
|
||
|
|
}
|
||
|
|
if (!isRecord(input.maps.node_registration_endpoints)) {
|
||
|
|
throw new SnapshotLoadError('REGISTRY_UNREGISTERED');
|
||
|
|
}
|
||
|
|
const map = input.maps.node_registration_endpoints;
|
||
|
|
if (map.path !== REGISTRY_PATH
|
||
|
|
|| map.id !== REGISTRY_ID
|
||
|
|
|| typeof map.version !== 'string'
|
||
|
|
|| !VERSION_PATTERN.test(map.version)) {
|
||
|
|
throw new SnapshotLoadError('ANCHOR_INVALID');
|
||
|
|
}
|
||
|
|
return Object.freeze({
|
||
|
|
registryVersion: map.version,
|
||
|
|
sourceCommit: input.navigation_source.source_commit,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function registryUrl(sourceCommit: string): string {
|
||
|
|
return `${CODE_URL}/raw/commit/${sourceCommit}/${REGISTRY_PATH}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
function receipt(
|
||
|
|
status: NodeRegistrationSnapshotReceipt['status'],
|
||
|
|
registry: NodeRegistrationEndpointRegistry | null,
|
||
|
|
reason?: NodeRegistrationSnapshotReason,
|
||
|
|
): Readonly<NodeRegistrationSnapshotReceipt> {
|
||
|
|
return Object.freeze({
|
||
|
|
endpointCount: registry?.endpoints.length ?? 0,
|
||
|
|
...(reason ? { reason } : {}),
|
||
|
|
registryVersion: registry?.version ?? null,
|
||
|
|
sourceCommit: registry?.source.sourceCommit ?? null,
|
||
|
|
status,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function result(
|
||
|
|
registry: NodeRegistrationEndpointRegistry | null,
|
||
|
|
snapshotReceipt: Readonly<NodeRegistrationSnapshotReceipt>,
|
||
|
|
): NodeRegistrationSnapshotResult {
|
||
|
|
const loaded = Object.freeze({ receipt: snapshotReceipt, registry });
|
||
|
|
registeredSnapshots.add(loaded);
|
||
|
|
return loaded;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function assertLoadedNodeRegistrationSnapshot(
|
||
|
|
snapshot: NodeRegistrationSnapshotResult,
|
||
|
|
): NodeRegistrationSnapshotResult {
|
||
|
|
if (!registeredSnapshots.has(snapshot)) throw new Error('node_registration_snapshot_unregistered');
|
||
|
|
return snapshot;
|
||
|
|
}
|
||
|
|
|
||
|
|
export class NodeRegistrationSnapshotLoader {
|
||
|
|
#inFlight: Promise<NodeRegistrationSnapshotResult> | null = null;
|
||
|
|
#lastKnownGood: NodeRegistrationEndpointRegistry | null = null;
|
||
|
|
|
||
|
|
constructor(private readonly transport: NodeRegistrationSnapshotTransport) {}
|
||
|
|
|
||
|
|
refresh(): Promise<NodeRegistrationSnapshotResult> {
|
||
|
|
if (this.#inFlight) return this.#inFlight;
|
||
|
|
this.#inFlight = this.#load().finally(() => {
|
||
|
|
this.#inFlight = null;
|
||
|
|
});
|
||
|
|
return this.#inFlight;
|
||
|
|
}
|
||
|
|
|
||
|
|
async #load(): Promise<NodeRegistrationSnapshotResult> {
|
||
|
|
try {
|
||
|
|
let rawAnchor: unknown;
|
||
|
|
try {
|
||
|
|
rawAnchor = await this.transport.fetchJson(ANCHOR_URL);
|
||
|
|
} catch {
|
||
|
|
throw new SnapshotLoadError('SOURCE_UNAVAILABLE');
|
||
|
|
}
|
||
|
|
const anchor = parseAnchor(rawAnchor);
|
||
|
|
let rawRegistry: unknown;
|
||
|
|
try {
|
||
|
|
rawRegistry = await this.transport.fetchJson(registryUrl(anchor.sourceCommit));
|
||
|
|
} catch {
|
||
|
|
throw new SnapshotLoadError('SOURCE_UNAVAILABLE');
|
||
|
|
}
|
||
|
|
let registry: NodeRegistrationEndpointRegistry;
|
||
|
|
try {
|
||
|
|
registry = parseNodeRegistrationEndpointRegistry(rawRegistry, {
|
||
|
|
repositoryId: 'REPO-012',
|
||
|
|
sourceCommit: anchor.sourceCommit,
|
||
|
|
sourceUrl: registryUrl(anchor.sourceCommit),
|
||
|
|
});
|
||
|
|
} catch {
|
||
|
|
throw new SnapshotLoadError('REGISTRY_INVALID');
|
||
|
|
}
|
||
|
|
if (registry.version !== anchor.registryVersion) throw new SnapshotLoadError('REGISTRY_INVALID');
|
||
|
|
this.#lastKnownGood = registry;
|
||
|
|
return result(registry, receipt('CURRENT', registry));
|
||
|
|
} catch (error) {
|
||
|
|
const reason = error instanceof SnapshotLoadError ? error.reason : 'SOURCE_UNAVAILABLE';
|
||
|
|
return this.#lastKnownGood
|
||
|
|
? result(this.#lastKnownGood, receipt('DEGRADED_LAST_KNOWN_GOOD', this.#lastKnownGood, reason))
|
||
|
|
: result(null, receipt('UNAVAILABLE', null, reason));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|