From 9994d81fd29501c1fcdf7580cb6e1649d98e5fb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Mon, 10 Aug 2026 09:04:58 +0800 Subject: [PATCH] feat: anchor domain runtime handoff transport --- .../domain-runtime-handoff-registry.test.ts | 66 +++++++ .../server/domain-runtime-handoff-registry.ts | 161 +++++++++++++++++ .../domain-runtime-handoff-snapshot.test.ts | 78 ++++++++ .../server/domain-runtime-handoff-snapshot.ts | 152 ++++++++++++++++ .../domain-runtime-handoff-source.test.ts | 169 ++++++++++++++++-- .../server/domain-runtime-handoff-source.ts | 97 +++++++++- .../hololake-desktop/electron/main.ts | 34 +++- ...AKE-NODE-REGISTRATION-CONTRACT-20260810.md | 19 ++ .../hololake-platform/architecture/README.md | 2 +- 9 files changed, 759 insertions(+), 19 deletions(-) create mode 100644 product-source/guanghu-knowledge-base/server/domain-runtime-handoff-registry.test.ts create mode 100644 product-source/guanghu-knowledge-base/server/domain-runtime-handoff-registry.ts create mode 100644 product-source/guanghu-knowledge-base/server/domain-runtime-handoff-snapshot.test.ts create mode 100644 product-source/guanghu-knowledge-base/server/domain-runtime-handoff-snapshot.ts diff --git a/product-source/guanghu-knowledge-base/server/domain-runtime-handoff-registry.test.ts b/product-source/guanghu-knowledge-base/server/domain-runtime-handoff-registry.test.ts new file mode 100644 index 0000000..f0a0c99 --- /dev/null +++ b/product-source/guanghu-knowledge-base/server/domain-runtime-handoff-registry.test.ts @@ -0,0 +1,66 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { + assertRegisteredDomainRuntimeHandoffEndpoint, + parseDomainRuntimeHandoffEndpointRegistry, + resolveDomainRuntimeHandoffEndpoint, +} from './domain-runtime-handoff-registry.js'; + +const COMMIT = 'a'.repeat(40); +const SOURCE = { + repositoryId: 'REPO-012' as const, + sourceCommit: COMMIT, + sourceUrl: `https://guanghulab.com/code/bingshuo/guanghu-ice-heart/raw/commit/${COMMIT}/routing/domain-runtime-handoff-endpoints.json`, +}; + +function endpoint(overrides: Record = {}) { + return { + domainIds: ['DOM-FIFTH-0001'], endpointId: 'GH-DOMAIN-HANDOFF-001', + nodeTypes: ['cloud-resident'], status: 'ACTIVE', + url: 'https://guanghulab.com/api/ai/v1/domain-runtime/handoff', ...overrides, + }; +} + +function registry(endpoints: unknown[] = [endpoint()]) { + return { + endpoints, registryId: 'GH-AIOS-DOMAIN-RUNTIME-HANDOFF-ENDPOINTS-001', + schema: 'gh-aios.domain-runtime-handoff-endpoints/v1', state: 'CURRENT', version: '1.0.0', + }; +} + +test('parses and resolves one anchored endpoint for the exact scope', () => { + const parsed = parseDomainRuntimeHandoffEndpointRegistry(registry(), SOURCE); + const resolved = resolveDomainRuntimeHandoffEndpoint(parsed, { + domainId: 'DOM-FIFTH-0001', nodeType: 'cloud-resident', + }); + assert.ok(resolved); + assert.equal(resolved.url, 'https://guanghulab.com/api/ai/v1/domain-runtime/handoff'); + assert.equal(resolveDomainRuntimeHandoffEndpoint(parsed, { + domainId: 'DOM-FIFTH-0001', nodeType: 'local-terminal', + }), null); +}); + +test('rejects unsafe URLs, unknown fields and duplicate active scopes', () => { + for (const unsafe of [ + 'http://guanghulab.com/api/ai/v1/handoff', + 'https://example.com/api/ai/v1/handoff', + 'https://user@guanghulab.com/api/ai/v1/handoff', + 'https://guanghulab.com:8443/api/ai/v1/handoff', + 'https://guanghulab.com/api/ai/v1/handoff?target=other', + ]) { + assert.throws(() => parseDomainRuntimeHandoffEndpointRegistry(registry([endpoint({ url: unsafe })]), SOURCE)); + } + assert.throws(() => parseDomainRuntimeHandoffEndpointRegistry(registry([endpoint({ extra: true })]), SOURCE)); + assert.throws(() => parseDomainRuntimeHandoffEndpointRegistry(registry([ + endpoint(), endpoint({ endpointId: 'GH-DOMAIN-HANDOFF-002' }), + ]), SOURCE)); +}); + +test('rejects caller-created and copied endpoint objects', () => { + const parsed = parseDomainRuntimeHandoffEndpointRegistry(registry(), SOURCE); + const resolved = resolveDomainRuntimeHandoffEndpoint(parsed, { + domainId: 'DOM-FIFTH-0001', nodeType: 'cloud-resident', + }); + assert.ok(resolved); + assert.throws(() => assertRegisteredDomainRuntimeHandoffEndpoint({ ...resolved }), /endpoint_unregistered/); +}); diff --git a/product-source/guanghu-knowledge-base/server/domain-runtime-handoff-registry.ts b/product-source/guanghu-knowledge-base/server/domain-runtime-handoff-registry.ts new file mode 100644 index 0000000..58584e7 --- /dev/null +++ b/product-source/guanghu-knowledge-base/server/domain-runtime-handoff-registry.ts @@ -0,0 +1,161 @@ +import type { DomainNodeType } from './domain-access.js'; + +const REGISTRY_SCHEMA = 'gh-aios.domain-runtime-handoff-endpoints/v1' as const; +const IDENTIFIER_PATTERN = /^[A-Z0-9][A-Z0-9._:-]{1,159}$/; +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 registeredEndpoints = new WeakSet(); + +export interface DomainRuntimeHandoffEndpoint { + domainIds: readonly string[]; + endpointId: string; + nodeTypes: readonly DomainNodeType[]; + status: 'ACTIVE' | 'REVOKED'; + url: string; +} + +export interface DomainRuntimeHandoffEndpointRegistrySource { + repositoryId: 'REPO-012'; + sourceCommit: string; + sourceUrl: string; +} + +export interface DomainRuntimeHandoffEndpointRegistry { + endpoints: readonly DomainRuntimeHandoffEndpoint[]; + registryId: 'GH-AIOS-DOMAIN-RUNTIME-HANDOFF-ENDPOINTS-001'; + schema: typeof REGISTRY_SCHEMA; + source: Readonly; + state: 'CURRENT'; + version: string; +} + +export interface DomainRuntimeHandoffEndpointLookup { + domainId: string; + nodeType: DomainNodeType; +} + +function invalidRegistry(): never { + throw new Error('domain_runtime_handoff_endpoint_registry_invalid'); +} + +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +function hasExactKeys(record: Record, keys: string[]): boolean { + const expected = [...keys].sort(); + const actual = Object.keys(record).sort(); + return actual.length === expected.length && actual.every((key, index) => key === expected[index]); +} + +function isIdentifier(value: unknown): value is string { + return typeof value === 'string' && IDENTIFIER_PATTERN.test(value); +} + +function parseEndpointUrl(value: unknown): string { + if (typeof value !== 'string') invalidRegistry(); + let url: URL; + try { + url = new URL(value); + } catch { + invalidRegistry(); + } + if (url.protocol !== 'https:' + || url.hostname !== 'guanghulab.com' + || url.port + || url.username + || url.password + || url.search + || url.hash + || !url.pathname.startsWith('/api/ai/v1/')) invalidRegistry(); + return url.toString(); +} + +function parseEndpoint(input: unknown): DomainRuntimeHandoffEndpoint { + if (!isRecord(input) + || !hasExactKeys(input, ['domainIds', 'endpointId', 'nodeTypes', 'status', 'url']) + || !isIdentifier(input.endpointId) + || (input.status !== 'ACTIVE' && input.status !== 'REVOKED') + || !Array.isArray(input.domainIds) + || input.domainIds.length === 0 + || input.domainIds.some(domainId => !isIdentifier(domainId)) + || new Set(input.domainIds).size !== input.domainIds.length + || !Array.isArray(input.nodeTypes) + || input.nodeTypes.length === 0 + || input.nodeTypes.some(nodeType => nodeType !== 'local-terminal' && nodeType !== 'cloud-resident') + || new Set(input.nodeTypes).size !== input.nodeTypes.length) invalidRegistry(); + const endpoint = Object.freeze({ + domainIds: Object.freeze([...input.domainIds] as string[]), + endpointId: input.endpointId, + nodeTypes: Object.freeze([...input.nodeTypes] as DomainNodeType[]), + status: input.status, + url: parseEndpointUrl(input.url), + }); + registeredEndpoints.add(endpoint); + return endpoint; +} + +function parseSource( + source: DomainRuntimeHandoffEndpointRegistrySource, +): Readonly { + if (!isRecord(source) + || !hasExactKeys(source, ['repositoryId', 'sourceCommit', 'sourceUrl']) + || source.repositoryId !== 'REPO-012' + || typeof source.sourceCommit !== 'string' + || !COMMIT_PATTERN.test(source.sourceCommit)) invalidRegistry(); + const expected = `https://guanghulab.com/code/bingshuo/guanghu-ice-heart/raw/commit/${source.sourceCommit}/routing/domain-runtime-handoff-endpoints.json`; + if (source.sourceUrl !== expected) invalidRegistry(); + return Object.freeze({ ...source }); +} + +export function parseDomainRuntimeHandoffEndpointRegistry( + input: unknown, + source: DomainRuntimeHandoffEndpointRegistrySource, +): DomainRuntimeHandoffEndpointRegistry { + if (!isRecord(input) + || !hasExactKeys(input, ['endpoints', 'registryId', 'schema', 'state', 'version']) + || input.registryId !== 'GH-AIOS-DOMAIN-RUNTIME-HANDOFF-ENDPOINTS-001' + || input.schema !== REGISTRY_SCHEMA + || input.state !== 'CURRENT' + || typeof input.version !== 'string' + || !VERSION_PATTERN.test(input.version) + || !Array.isArray(input.endpoints)) invalidRegistry(); + const endpoints = input.endpoints.map(parseEndpoint); + if (new Set(endpoints.map(endpoint => endpoint.endpointId)).size !== endpoints.length) invalidRegistry(); + const activeScopes = new Set(); + for (const endpoint of endpoints.filter(candidate => candidate.status === 'ACTIVE')) { + for (const domainId of endpoint.domainIds) { + for (const nodeType of endpoint.nodeTypes) { + const scope = `${domainId}\0${nodeType}`; + if (activeScopes.has(scope)) invalidRegistry(); + activeScopes.add(scope); + } + } + } + return Object.freeze({ + endpoints: Object.freeze(endpoints), + registryId: input.registryId, + schema: input.schema, + source: parseSource(source), + state: input.state, + version: input.version, + }); +} + +export function resolveDomainRuntimeHandoffEndpoint( + registry: DomainRuntimeHandoffEndpointRegistry, + lookup: DomainRuntimeHandoffEndpointLookup, +): DomainRuntimeHandoffEndpoint | null { + const endpoint = registry.endpoints.find(candidate => candidate.status === 'ACTIVE' + && candidate.domainIds.includes(lookup.domainId) + && candidate.nodeTypes.includes(lookup.nodeType)); + if (!endpoint) return null; + return assertRegisteredDomainRuntimeHandoffEndpoint(endpoint); +} + +export function assertRegisteredDomainRuntimeHandoffEndpoint( + endpoint: DomainRuntimeHandoffEndpoint, +): DomainRuntimeHandoffEndpoint { + if (!registeredEndpoints.has(endpoint)) throw new Error('domain_runtime_handoff_endpoint_unregistered'); + return endpoint; +} diff --git a/product-source/guanghu-knowledge-base/server/domain-runtime-handoff-snapshot.test.ts b/product-source/guanghu-knowledge-base/server/domain-runtime-handoff-snapshot.test.ts new file mode 100644 index 0000000..7ff3e43 --- /dev/null +++ b/product-source/guanghu-knowledge-base/server/domain-runtime-handoff-snapshot.test.ts @@ -0,0 +1,78 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { + assertLoadedDomainRuntimeHandoffSnapshot, + DomainRuntimeHandoffSnapshotLoader, +} from './domain-runtime-handoff-snapshot.js'; + +const COMMIT = 'a'.repeat(40); +const ANCHOR_URL = 'https://guanghulab.com/api/ai/v1/anchor'; +const REGISTRY_URL = `https://guanghulab.com/code/bingshuo/guanghu-ice-heart/raw/commit/${COMMIT}/routing/domain-runtime-handoff-endpoints.json`; + +function anchor(includeMap = true) { + return { + schema: 'guanghu.public-navigation-anchor/v1', anchor_id: 'GLW-PUBLIC-NAV-ANCHOR-001', + state: 'CURRENT_CANONICAL', repository_id: 'REPO-012', branch: 'main', public_entry: ANCHOR_URL, + code_entry: 'https://guanghulab.com/code/bingshuo/guanghu-ice-heart', + maps: includeMap ? { domain_runtime_handoff_endpoints: { + path: 'routing/domain-runtime-handoff-endpoints.json', + id: 'GH-AIOS-DOMAIN-RUNTIME-HANDOFF-ENDPOINTS-001', version: '1.0.0', + } } : {}, + navigation_source: { anchor_id: 'GLW-PUBLIC-NAV-ANCHOR-001', source_commit: COMMIT, + source_mode: 'REPO-012_MAIN_GIT_SNAPSHOT', source_degraded: false }, + }; +} + +function registry(version = '1.0.0') { + return { + endpoints: [{ domainIds: ['DOM-FIFTH-0001'], endpointId: 'GH-DOMAIN-HANDOFF-001', + nodeTypes: ['cloud-resident'], status: 'ACTIVE', + url: 'https://guanghulab.com/api/ai/v1/domain-runtime/handoff' }], + registryId: 'GH-AIOS-DOMAIN-RUNTIME-HANDOFF-ENDPOINTS-001', + schema: 'gh-aios.domain-runtime-handoff-endpoints/v1', state: 'CURRENT', version, + }; +} + +function loader(readAnchor: () => unknown, readRegistry: () => unknown) { + return new DomainRuntimeHandoffSnapshotLoader({ + async fetchJson(url) { + if (url === ANCHOR_URL) return readAnchor(); + if (url === REGISTRY_URL) return readRegistry(); + throw new Error('unexpected_url'); + }, + }); +} + +test('loads only the registry at the exact anchor source commit', async () => { + const snapshot = await loader(() => anchor(), () => registry()).refresh(); + assert.equal(snapshot.receipt.status, 'CURRENT'); + assert.equal(snapshot.receipt.sourceCommit, COMMIT); + assert.equal(snapshot.receipt.endpointCount, 1); + assert.equal(assertLoadedDomainRuntimeHandoffSnapshot(snapshot), snapshot); +}); + +test('current anchor without the handoff map is explicitly unavailable', async () => { + const snapshot = await loader(() => anchor(false), () => registry()).refresh(); + assert.deepEqual(snapshot.receipt, { endpointCount: 0, reason: 'REGISTRY_UNREGISTERED', + registryVersion: null, sourceCommit: null, status: 'UNAVAILABLE' }); + assert.equal(snapshot.registry, null); +}); + +test('invalid and degraded sources remain unusable as current authority', async () => { + const invalid = await loader(() => anchor(), () => registry('2.0.0')).refresh(); + assert.equal(invalid.receipt.reason, 'REGISTRY_INVALID'); + let currentAnchor: unknown = anchor(); + const snapshots = loader(() => currentAnchor, () => registry()); + await snapshots.refresh(); + currentAnchor = anchor(false); + const degraded = await snapshots.refresh(); + assert.equal(degraded.receipt.status, 'DEGRADED_LAST_KNOWN_GOOD'); + assert.equal(degraded.receipt.reason, 'REGISTRY_UNREGISTERED'); +}); + +test('caller-created snapshots are rejected', () => { + assert.throws(() => assertLoadedDomainRuntimeHandoffSnapshot({ + receipt: { endpointCount: 0, registryVersion: null, sourceCommit: null, status: 'UNAVAILABLE' }, + registry: null, + }), /snapshot_unregistered/); +}); diff --git a/product-source/guanghu-knowledge-base/server/domain-runtime-handoff-snapshot.ts b/product-source/guanghu-knowledge-base/server/domain-runtime-handoff-snapshot.ts new file mode 100644 index 0000000..06629c0 --- /dev/null +++ b/product-source/guanghu-knowledge-base/server/domain-runtime-handoff-snapshot.ts @@ -0,0 +1,152 @@ +import { + parseDomainRuntimeHandoffEndpointRegistry, + type DomainRuntimeHandoffEndpointRegistry, +} from './domain-runtime-handoff-registry.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-DOMAIN-RUNTIME-HANDOFF-ENDPOINTS-001'; +const REGISTRY_PATH = 'routing/domain-runtime-handoff-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(); + +export interface DomainRuntimeHandoffSnapshotTransport { + fetchJson(url: string): Promise; +} + +export type DomainRuntimeHandoffSnapshotReason = + | 'ANCHOR_INVALID' + | 'REGISTRY_INVALID' + | 'REGISTRY_UNREGISTERED' + | 'SOURCE_UNAVAILABLE'; + +export interface DomainRuntimeHandoffSnapshotReceipt { + endpointCount: number; + reason?: DomainRuntimeHandoffSnapshotReason; + registryVersion: string | null; + sourceCommit: string | null; + status: 'CURRENT' | 'DEGRADED_LAST_KNOWN_GOOD' | 'UNAVAILABLE'; +} + +export interface DomainRuntimeHandoffSnapshotResult { + receipt: Readonly; + registry: DomainRuntimeHandoffEndpointRegistry | null; +} + +interface ParsedAnchor { registryVersion: string; sourceCommit: string } + +class SnapshotLoadError extends Error { + constructor(readonly reason: DomainRuntimeHandoffSnapshotReason) { super(reason); } +} + +function isRecord(value: unknown): value is Record { + 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.domain_runtime_handoff_endpoints)) { + throw new SnapshotLoadError('REGISTRY_UNREGISTERED'); + } + const map = input.maps.domain_runtime_handoff_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: DomainRuntimeHandoffSnapshotReceipt['status'], + registry: DomainRuntimeHandoffEndpointRegistry | null, + reason?: DomainRuntimeHandoffSnapshotReason, +): Readonly { + return Object.freeze({ + endpointCount: registry?.endpoints.length ?? 0, + ...(reason ? { reason } : {}), + registryVersion: registry?.version ?? null, + sourceCommit: registry?.source.sourceCommit ?? null, + status, + }); +} + +function result( + registry: DomainRuntimeHandoffEndpointRegistry | null, + snapshotReceipt: Readonly, +): DomainRuntimeHandoffSnapshotResult { + const loaded = Object.freeze({ receipt: snapshotReceipt, registry }); + registeredSnapshots.add(loaded); + return loaded; +} + +export function assertLoadedDomainRuntimeHandoffSnapshot( + snapshot: DomainRuntimeHandoffSnapshotResult, +): DomainRuntimeHandoffSnapshotResult { + if (!registeredSnapshots.has(snapshot)) throw new Error('domain_runtime_handoff_snapshot_unregistered'); + return snapshot; +} + +export class DomainRuntimeHandoffSnapshotLoader { + #inFlight: Promise | null = null; + #lastKnownGood: DomainRuntimeHandoffEndpointRegistry | null = null; + + constructor(private readonly transport: DomainRuntimeHandoffSnapshotTransport) {} + + refresh(): Promise { + if (this.#inFlight) return this.#inFlight; + this.#inFlight = this.#load().finally(() => { this.#inFlight = null; }); + return this.#inFlight; + } + + async #load(): Promise { + 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: DomainRuntimeHandoffEndpointRegistry; + try { + registry = parseDomainRuntimeHandoffEndpointRegistry(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)); + } + } +} diff --git a/product-source/guanghu-knowledge-base/server/domain-runtime-handoff-source.test.ts b/product-source/guanghu-knowledge-base/server/domain-runtime-handoff-source.test.ts index ddd1503..6f9cf80 100644 --- a/product-source/guanghu-knowledge-base/server/domain-runtime-handoff-source.test.ts +++ b/product-source/guanghu-knowledge-base/server/domain-runtime-handoff-source.test.ts @@ -1,7 +1,16 @@ import test from 'node:test'; import assert from 'node:assert/strict'; import { createHash, generateKeyPairSync, sign } from 'node:crypto'; -import { StrictDomainRuntimeHandoffSource } from './domain-runtime-handoff-source.js'; +import { + AnchoredDomainRuntimeHandoffSource, + HttpDomainRuntimeHandoffTransport, + StrictDomainRuntimeHandoffSource, +} from './domain-runtime-handoff-source.js'; +import { DomainRuntimeHandoffSnapshotLoader } from './domain-runtime-handoff-snapshot.js'; +import { + parseDomainRuntimeHandoffEndpointRegistry, + resolveDomainRuntimeHandoffEndpoint, +} from './domain-runtime-handoff-registry.js'; import { createNodePossessionChallenge, nodePossessionChallengeSigningBytes, @@ -25,6 +34,29 @@ const fingerprint = createHash('sha256') .update(nodeKeys.publicKey.export({ format: 'der', type: 'spki' })) .digest('hex'); +function handoffEndpoint() { + const registry = parseDomainRuntimeHandoffEndpointRegistry({ + endpoints: [{ + domainIds: [DOMAIN_ID], + endpointId: 'GH-DOMAIN-HANDOFF-001', + nodeTypes: [NODE_TYPE], + status: 'ACTIVE', + url: 'https://guanghulab.com/api/ai/v1/domain-runtime/handoff', + }], + registryId: 'GH-AIOS-DOMAIN-RUNTIME-HANDOFF-ENDPOINTS-001', + schema: 'gh-aios.domain-runtime-handoff-endpoints/v1', + state: 'CURRENT', + version: '1.0.0', + }, { + repositoryId: 'REPO-012', + sourceCommit: 'b'.repeat(40), + sourceUrl: `https://guanghulab.com/code/bingshuo/guanghu-ice-heart/raw/commit/${'b'.repeat(40)}/routing/domain-runtime-handoff-endpoints.json`, + }); + const endpoint = resolveDomainRuntimeHandoffEndpoint(registry, { domainId: DOMAIN_ID, nodeType: NODE_TYPE }); + assert.ok(endpoint); + return endpoint; +} + function verifiedPossession() { const registry = parseNodeRegistrationEndpointRegistry({ endpoints: [{ @@ -101,8 +133,8 @@ function response(requestId: string, overrides: Record = {}) { test('forwards one exact cryptographic authorization bundle without private material', async () => { const requests: unknown[] = []; - const source = new StrictDomainRuntimeHandoffSource({ - async request(input) { + const source = new StrictDomainRuntimeHandoffSource(handoffEndpoint(), { + async request(_endpoint, input) { requests.push(input); return response(input.requestId); }, @@ -112,7 +144,7 @@ test('forwards one exact cryptographic authorization bundle without private mate assert.equal(requests.length, 1); assert.deepEqual(Object.keys(requests[0] as object).sort(), [ - 'accountId', 'challenge', 'domainId', 'issuedAt', 'nodeId', 'nodeType', + 'accountId', 'challenge', 'domainId', 'endpointId', 'issuedAt', 'nodeId', 'nodeType', 'registration', 'requestId', 'response', 'schema', ]); assert.equal(JSON.stringify(requests[0]).includes('PRIVATE KEY'), false); @@ -134,7 +166,7 @@ test('binds the response to the generated request and rejects malformed envelope response('HANDOFF-REQ-001', { handoff: null, signer_lookup: null, status: 'NOT_AUTHORIZED' }), ]; for (const sample of samples) { - const source = new StrictDomainRuntimeHandoffSource({ async request() { return sample; } }, () => NOW + 2, () => 'HANDOFF-REQ-001'); + const source = new StrictDomainRuntimeHandoffSource(handoffEndpoint(), { async request() { return sample; } }, () => NOW + 2, () => 'HANDOFF-REQ-001'); assert.equal(await source.read(verifiedPossession()), null); } }); @@ -142,22 +174,139 @@ test('binds the response to the generated request and rejects malformed envelope test('rejects copied, expired and replayed possession authorizations before transport', async () => { let requests = 0; const possession = verifiedPossession(); - const source = new StrictDomainRuntimeHandoffSource({ - async request(input) { requests += 1; return response(input.requestId); }, + const source = new StrictDomainRuntimeHandoffSource(handoffEndpoint(), { + async request(_endpoint, input) { requests += 1; return response(input.requestId); }, }, () => NOW + 2, () => 'HANDOFF-REQ-001'); assert.ok(await source.read(possession)); assert.equal(await source.read(possession), null); assert.equal(await source.read({ ...verifiedPossession() }), null); - const expired = new StrictDomainRuntimeHandoffSource({ - async request(input) { requests += 1; return response(input.requestId); }, + const expired = new StrictDomainRuntimeHandoffSource(handoffEndpoint(), { + async request(_endpoint, input) { requests += 1; return response(input.requestId); }, }, () => NOW + 60_000, () => 'HANDOFF-REQ-002'); assert.equal(await expired.read(verifiedPossession()), null); assert.equal(requests, 1); }); test('fails closed on transport errors without exposing remote details', async () => { - const source = new StrictDomainRuntimeHandoffSource({ + const source = new StrictDomainRuntimeHandoffSource(handoffEndpoint(), { async request() { throw new Error('secret remote detail'); }, }, () => NOW + 2, () => 'HANDOFF-REQ-001'); assert.equal(await source.read(verifiedPossession()), null); }); + +function handoffAnchor(includeMap = true) { + return { + schema: 'guanghu.public-navigation-anchor/v1', + anchor_id: 'GLW-PUBLIC-NAV-ANCHOR-001', + state: 'CURRENT_CANONICAL', + repository_id: 'REPO-012', + branch: 'main', + public_entry: 'https://guanghulab.com/api/ai/v1/anchor', + code_entry: 'https://guanghulab.com/code/bingshuo/guanghu-ice-heart', + maps: includeMap ? { + domain_runtime_handoff_endpoints: { + path: 'routing/domain-runtime-handoff-endpoints.json', + id: 'GH-AIOS-DOMAIN-RUNTIME-HANDOFF-ENDPOINTS-001', + version: '1.0.0', + }, + } : {}, + navigation_source: { + anchor_id: 'GLW-PUBLIC-NAV-ANCHOR-001', + source_commit: 'b'.repeat(40), + source_mode: 'REPO-012_MAIN_GIT_SNAPSHOT', + source_degraded: false, + }, + }; +} + +function handoffRegistry() { + return { + endpoints: [{ + domainIds: [DOMAIN_ID], endpointId: 'GH-DOMAIN-HANDOFF-001', nodeTypes: [NODE_TYPE], + status: 'ACTIVE', url: 'https://guanghulab.com/api/ai/v1/domain-runtime/handoff', + }], + registryId: 'GH-AIOS-DOMAIN-RUNTIME-HANDOFF-ENDPOINTS-001', + schema: 'gh-aios.domain-runtime-handoff-endpoints/v1', + state: 'CURRENT', version: '1.0.0', + }; +} + +function anchoredLoader(includeMap = true) { + return new DomainRuntimeHandoffSnapshotLoader({ + async fetchJson(url) { + if (url.endsWith('/api/ai/v1/anchor')) return handoffAnchor(includeMap); + if (url.includes('/raw/commit/')) return handoffRegistry(); + throw new Error('unexpected_url'); + }, + }); +} + +test('anchored source resolves the exact endpoint before consuming possession', async () => { + const requests: Array<{ endpoint: string; endpointId: string }> = []; + const source = new AnchoredDomainRuntimeHandoffSource(anchoredLoader(), { + async request(endpoint, input) { + requests.push({ endpoint: endpoint.url, endpointId: input.endpointId }); + return response(input.requestId); + }, + }, () => NOW + 2, () => 'HANDOFF-REQ-001'); + assert.ok(await source.read(verifiedPossession())); + assert.deepEqual(requests, [{ + endpoint: 'https://guanghulab.com/api/ai/v1/domain-runtime/handoff', + endpointId: 'GH-DOMAIN-HANDOFF-001', + }]); +}); + +test('missing anchored registry fails before network and does not consume possession', async () => { + let requests = 0; + const possession = verifiedPossession(); + const unavailable = new AnchoredDomainRuntimeHandoffSource(anchoredLoader(false), { + async request() { requests += 1; return null; }, + }, () => NOW + 2, () => 'HANDOFF-REQ-001'); + assert.equal(await unavailable.read(possession), null); + assert.equal(requests, 0); + const current = new AnchoredDomainRuntimeHandoffSource(anchoredLoader(), { + async request(_endpoint, input) { requests += 1; return response(input.requestId); }, + }, () => NOW + 2, () => 'HANDOFF-REQ-002'); + assert.ok(await current.read(possession)); + assert.equal(requests, 1); +}); + +test('HTTP transport posts only to the registered endpoint without credentials', async () => { + const endpoint = handoffEndpoint(); + let observedUrl = ''; + let observedInit: RequestInit | undefined; + const transport = new HttpDomainRuntimeHandoffTransport(async (url, init) => { + observedUrl = String(url); + observedInit = init; + return new Response(JSON.stringify(response('HANDOFF-REQ-001')), { + headers: { 'Content-Type': 'application/json' }, status: 200, + }); + }); + const source = new StrictDomainRuntimeHandoffSource(endpoint, transport, () => NOW + 2, () => 'HANDOFF-REQ-001'); + assert.ok(await source.read(verifiedPossession())); + assert.equal(observedUrl, endpoint.url); + assert.equal(observedInit?.method, 'POST'); + assert.equal(observedInit?.credentials, 'omit'); + assert.equal(observedInit?.redirect, 'manual'); + assert.equal(observedInit?.cache, 'no-store'); + assert.equal(observedInit?.referrerPolicy, 'no-referrer'); + assert.equal(JSON.stringify(observedInit?.headers).includes('Authorization'), false); +}); + +test('HTTP transport rejects redirects, non-JSON and oversized responses', async () => { + const endpoint = handoffEndpoint(); + const request = Object.freeze({ + accountId: 'bingshuo', challenge: {} as never, domainId: DOMAIN_ID, + endpointId: endpoint.endpointId, issuedAt: NOW, nodeId: NODE_ID, nodeType: NODE_TYPE, + registration: {} as never, requestId: 'HANDOFF-REQ-001', response: {} as never, + schema: 'gh-aios.domain-runtime-handoff-request/v1' as const, + }); + for (const reply of [ + new Response('', { status: 302, headers: { Location: 'https://example.com/' } }), + new Response('plain', { status: 200, headers: { 'Content-Type': 'text/plain' } }), + new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json', 'Content-Length': '262145' } }), + ]) { + const transport = new HttpDomainRuntimeHandoffTransport(async () => reply); + await assert.rejects(() => transport.request(endpoint, request)); + } +}); diff --git a/product-source/guanghu-knowledge-base/server/domain-runtime-handoff-source.ts b/product-source/guanghu-knowledge-base/server/domain-runtime-handoff-source.ts index 1d36698..cb10c95 100644 --- a/product-source/guanghu-knowledge-base/server/domain-runtime-handoff-source.ts +++ b/product-source/guanghu-knowledge-base/server/domain-runtime-handoff-source.ts @@ -10,16 +10,27 @@ import { } from './node-possession-proof.js'; import type { VerifiedNodeRegistration } from './node-registration.js'; import { randomUUID } from 'node:crypto'; +import { + assertRegisteredDomainRuntimeHandoffEndpoint, + resolveDomainRuntimeHandoffEndpoint, + type DomainRuntimeHandoffEndpoint, +} from './domain-runtime-handoff-registry.js'; +import { + assertLoadedDomainRuntimeHandoffSnapshot, + type DomainRuntimeHandoffSnapshotLoader, +} from './domain-runtime-handoff-snapshot.js'; const IDENTIFIER_PATTERN = /^[A-Z0-9][A-Z0-9._:-]{1,159}$/; const REQUEST_SCHEMA = 'gh-aios.domain-runtime-handoff-request/v1' as const; const RESPONSE_SCHEMA = 'gh-aios.domain-runtime-handoff-response/v1' as const; const consumedPossessions = new WeakSet(); +const MAX_RESPONSE_BYTES = 256 * 1024; export interface DomainRuntimeHandoffRequest { accountId: string; challenge: Readonly; domainId: string; + endpointId: string; issuedAt: number; nodeId: string; nodeType: VerifiedNodeRegistration['nodeType']; @@ -30,7 +41,10 @@ export interface DomainRuntimeHandoffRequest { } export interface DomainRuntimeHandoffTransport { - request(input: Readonly): Promise; + request( + endpoint: Readonly, + input: Readonly, + ): Promise; } function isRecord(value: unknown): value is Record { @@ -76,6 +90,7 @@ function parseResponse( export class StrictDomainRuntimeHandoffSource implements DomainRuntimeHandoffSource { constructor( + private readonly endpoint: DomainRuntimeHandoffEndpoint, private readonly transport: DomainRuntimeHandoffTransport, private readonly clock: () => number = Date.now, private readonly requestId: () => string = () => `HANDOFF-REQ-${randomUUID().toUpperCase()}`, @@ -85,6 +100,10 @@ export class StrictDomainRuntimeHandoffSource implements DomainRuntimeHandoffSou try { const verified = assertVerifiedNodePossession(possession); if (consumedPossessions.has(verified)) return null; + const endpoint = assertRegisteredDomainRuntimeHandoffEndpoint(this.endpoint); + if (endpoint.status !== 'ACTIVE' + || !endpoint.domainIds.includes(verified.registration.domainId) + || !endpoint.nodeTypes.includes(verified.registration.nodeType)) return null; const issuedAt = this.clock(); const requestId = this.requestId(); if (!Number.isSafeInteger(issuedAt) @@ -96,6 +115,7 @@ export class StrictDomainRuntimeHandoffSource implements DomainRuntimeHandoffSou accountId: verified.registration.accountId, challenge: verified.challenge, domainId: verified.registration.domainId, + endpointId: endpoint.endpointId, issuedAt, nodeId: verified.registration.nodeId, nodeType: verified.registration.nodeType, @@ -105,9 +125,82 @@ export class StrictDomainRuntimeHandoffSource implements DomainRuntimeHandoffSou schema: REQUEST_SCHEMA, }); consumedPossessions.add(verified); - return parseResponse(await this.transport.request(request), request); + return parseResponse(await this.transport.request(endpoint, request), request); } catch { return null; } } } + +export class AnchoredDomainRuntimeHandoffSource implements DomainRuntimeHandoffSource { + constructor( + private readonly snapshots: DomainRuntimeHandoffSnapshotLoader, + private readonly transport: DomainRuntimeHandoffTransport, + private readonly clock: () => number = Date.now, + private readonly requestId: () => string = () => `HANDOFF-REQ-${randomUUID().toUpperCase()}`, + ) {} + + async read(possession: VerifiedNodePossession): Promise | null> { + try { + const verified = assertVerifiedNodePossession(possession); + const snapshot = assertLoadedDomainRuntimeHandoffSnapshot(await this.snapshots.refresh()); + if (snapshot.receipt.status !== 'CURRENT' || !snapshot.registry) return null; + const endpoint = resolveDomainRuntimeHandoffEndpoint(snapshot.registry, { + domainId: verified.registration.domainId, + nodeType: verified.registration.nodeType, + }); + if (!endpoint) return null; + return new StrictDomainRuntimeHandoffSource( + endpoint, + this.transport, + this.clock, + this.requestId, + ).read(verified); + } catch { + return null; + } + } +} + +export class HttpDomainRuntimeHandoffTransport implements DomainRuntimeHandoffTransport { + constructor(private readonly fetcher: typeof fetch = fetch) {} + + async request( + inputEndpoint: Readonly, + input: Readonly, + ): Promise { + const endpoint = assertRegisteredDomainRuntimeHandoffEndpoint(inputEndpoint); + if (endpoint.status !== 'ACTIVE' + || input.endpointId !== endpoint.endpointId + || !endpoint.domainIds.includes(input.domainId) + || !endpoint.nodeTypes.includes(input.nodeType)) { + throw new Error('domain_runtime_handoff_endpoint_scope_invalid'); + } + const response = await this.fetcher(endpoint.url, { + body: JSON.stringify(input), + cache: 'no-store', + credentials: 'omit', + headers: { Accept: 'application/json', 'Content-Type': 'application/json' }, + method: 'POST', + redirect: 'manual', + referrerPolicy: 'no-referrer', + signal: AbortSignal.timeout(5000), + }); + if (!response.ok || response.status < 200 || response.status >= 300 + || response.type === 'opaqueredirect') throw new Error('domain_runtime_handoff_http_rejected'); + const contentType = response.headers.get('content-type')?.split(';', 1)[0].trim().toLowerCase(); + if (contentType !== 'application/json') throw new Error('domain_runtime_handoff_content_type_invalid'); + const declaredLength = response.headers.get('content-length'); + if (declaredLength !== null + && (!/^\d+$/.test(declaredLength) || Number(declaredLength) > MAX_RESPONSE_BYTES)) { + throw new Error('domain_runtime_handoff_response_too_large'); + } + const bytes = new Uint8Array(await response.arrayBuffer()); + if (bytes.byteLength > MAX_RESPONSE_BYTES) throw new Error('domain_runtime_handoff_response_too_large'); + let text: string; + try { text = new TextDecoder('utf-8', { fatal: true }).decode(bytes); } + catch { throw new Error('domain_runtime_handoff_response_encoding_invalid'); } + try { return JSON.parse(text); } + catch { throw new Error('domain_runtime_handoff_response_json_invalid'); } + } +} diff --git a/product-source/hololake-desktop/electron/main.ts b/product-source/hololake-desktop/electron/main.ts index 9de9395..2665092 100644 --- a/product-source/hololake-desktop/electron/main.ts +++ b/product-source/hololake-desktop/electron/main.ts @@ -22,6 +22,11 @@ import { DomainAccessOrchestrator } from '../../guanghu-knowledge-base/server/do import { TrustedSignerSnapshotLoader } from '../../guanghu-knowledge-base/server/trusted-signer-snapshot.js'; import { NodeRegistrationSnapshotLoader } from '../../guanghu-knowledge-base/server/node-registration-snapshot.js'; import { HttpNodeRegistrationClaimSource } from '../../guanghu-knowledge-base/server/node-registration-client.js'; +import { DomainRuntimeHandoffSnapshotLoader } from '../../guanghu-knowledge-base/server/domain-runtime-handoff-snapshot.js'; +import { + AnchoredDomainRuntimeHandoffSource, + HttpDomainRuntimeHandoffTransport, +} from '../../guanghu-knowledge-base/server/domain-runtime-handoff-source.js'; // ─── 配置 ─── @@ -339,6 +344,28 @@ const nodeRegistrationSnapshots = new NodeRegistrationSnapshotLoader({ const nodeRegistrationClaims = new HttpNodeRegistrationClaimSource(); +const domainRuntimeHandoffSnapshots = new DomainRuntimeHandoffSnapshotLoader({ + async fetchJson(url: string): Promise { + const response = await fetch(url, { + cache: 'no-store', + credentials: 'omit', + headers: { Accept: 'application/json' }, + redirect: 'manual', + referrerPolicy: 'no-referrer', + signal: AbortSignal.timeout(5000), + }); + if (!response.ok || response.type === 'opaqueredirect') { + throw new Error(`domain_runtime_handoff_source_http_${response.status}`); + } + return response.json(); + }, +}); + +const domainRuntimeHandoffs = new AnchoredDomainRuntimeHandoffSource( + domainRuntimeHandoffSnapshots, + new HttpDomainRuntimeHandoffTransport(), +); + const domainAccessOrchestrator = new DomainAccessOrchestrator( trustedSignerSnapshots, nodeRegistrationSnapshots, @@ -366,12 +393,7 @@ const domainAccessOrchestrator = new DomainAccessOrchestrator( // proof keeps the domain gate closed even if a future signed claim appears. async prove() { return null; }, }, - { - async read() { - // 当前灯塔尚未发布签名运行体交接端点。保持关闭,不从账号身份推导运行体权限。 - return null; - }, - }, + domainRuntimeHandoffs, ); function startServer(): Promise { diff --git a/product-source/hololake-platform/architecture/HOLOLAKE-NODE-REGISTRATION-CONTRACT-20260810.md b/product-source/hololake-platform/architecture/HOLOLAKE-NODE-REGISTRATION-CONTRACT-20260810.md index 0a660c3..247434c 100644 --- a/product-source/hololake-platform/architecture/HOLOLAKE-NODE-REGISTRATION-CONTRACT-20260810.md +++ b/product-source/hololake-platform/architecture/HOLOLAKE-NODE-REGISTRATION-CONTRACT-20260810.md @@ -99,3 +99,22 @@ REPO-012 信任快照独立验证 handoff 中的域清单签名、短期会话 本阶段闭合的是“证明能够被带到交接边界”的客户端证据结构,不是生产 HTTP 端点。当前公共 锚点没有域交接端点注册表,桌面运行源仍返回空结果,因此生产会话签发、域运行体连接与在线 回执继续为 `NOT_EXISTS_0`。 + +## 域运行体交接端点锚定与严格传输 · 阶段 005 + +本阶段先固定以下验收标准,再进入实现: + +1. 域交接 URL 只能来自 REPO-012 当前公共锚点同一提交声明的 + `GH-AIOS-DOMAIN-RUNTIME-HANDOFF-ENDPOINTS-001`,账号响应、调用方参数、本地配置和复制对象 + 都不能指定或替换 URL。 +2. 注册表只允许无凭据的 `https://guanghulab.com/api/ai/v1/...` 端点;禁止端口、用户名、密码、 + 查询串、片段和重定向。同一域与节点类型最多一个活跃端点。 +3. 只有来源状态为 `CURRENT` 的已登记快照和已登记端点才能发起请求;注册表缺失、来源降级、 + 范围不匹配、端点撤销或调用方伪造对象时必须在网络请求前失败关闭。 +4. 交接传输固定为无凭据 `POST application/json`,5 秒超时、`redirect=manual`、 + `credentials=omit`、`cache=no-store`、`referrerPolicy=no-referrer`;响应必须是 JSON 且不超过 + 256 KiB。HTTP 错误、重定向、非 JSON、超限和响应绑定错误均返回空结果。 +5. 请求包必须增加并绑定 `endpointId`,已验证节点证明只有在解析到当前活跃端点后才被消耗, + 且一次证明仍最多发起一次网络请求。 +6. 当前公共锚点尚未登记域交接端点映射,生产桌面必须不发送交接请求并继续返回 + `NOT_EXISTS_0`;代码完成不得冒充生产端点、会话签发、域运行体连接或在线健康已经存在。 diff --git a/product-source/hololake-platform/architecture/README.md b/product-source/hololake-platform/architecture/README.md index b59f51e..1349f01 100644 --- a/product-source/hololake-platform/architecture/README.md +++ b/product-source/hololake-platform/architecture/README.md @@ -8,7 +8,7 @@ - [`HOLOLAKE-INTENT-REASONING-MAP-20260809.md`](HOLOLAKE-INTENT-REASONING-MAP-20260809.md):当前产品意图、纠正、思维推理与因果链。 - [`HOLOLAKE-SYSTEM-ARCHITECTURE-20260809.md`](HOLOLAKE-SYSTEM-ARCHITECTURE-20260809.md):`HLP-CURRENT-ARCH-001` 当前完整系统蓝图,覆盖 GH-AIOS 灯塔、五域独立入口、光湖本源域、节点登录与光湖桥。 - [`HOLOLAKE-DESKTOP-0.8.0-CURRENT-CAPABILITY-GAP-20260809.md`](HOLOLAKE-DESKTOP-0.8.0-CURRENT-CAPABILITY-GAP-20260809.md):桌面 0.8.0 已观察能力与最终规划的逐项差距;同时登记线上产品源码仍为 0.4.6 的分叉事实。 -- [`HOLOLAKE-NODE-REGISTRATION-CONTRACT-20260810.md`](HOLOLAKE-NODE-REGISTRATION-CONTRACT-20260810.md):账号、签名节点登记与当前节点私钥持有证明分离后的端点注册表、无凭据声明读取、60 秒一次性 Ed25519 挑战、不可伪造交接证据包、失败关闭和当前生产缺口验收基线。 +- [`HOLOLAKE-NODE-REGISTRATION-CONTRACT-20260810.md`](HOLOLAKE-NODE-REGISTRATION-CONTRACT-20260810.md):账号、签名节点登记与当前节点私钥持有证明分离后的端点注册表、无凭据声明读取、60 秒一次性 Ed25519 挑战、不可伪造交接证据包,以及只能由 REPO-012 当前公共锚点登记 URL 的严格域交接传输;当前锚点未登记映射,生产继续失败关闭。 - [`HOLOLAKE-LANGUAGE-PERSONA-OS.md`](HOLOLAKE-LANGUAGE-PERSONA-OS.md):HoloLake 作为 AI 语言人格驱动操作系统的当前正式产品定位、操作系统映射、原生应用边界与实现缺口。 - [`HOLOLAKE-INTENT-REASONING-MAP-20260807.md`](HOLOLAKE-INTENT-REASONING-MAP-20260807.md):2026-08-07 历史意图层,继续保存形成过程;不再作为当前默认入口。