Compare commits

...
Author SHA1 Message Date
b8680e82e1 Record node registration contract delivery 2026-08-10 07:11:31 +08:00
2bfede2376 Verify signed node registration claims 2026-08-10 07:01:36 +08:00
10 changed files with 1024 additions and 54 deletions

View file

@ -0,0 +1,59 @@
{
"schema": "hololake.node-registration-contract-receipt/v1",
"receipt_id": "GH-HOLOLAKE-NODE-REGISTRATION-CONTRACT-20260810-001",
"development_id": "DEV-20260809-007",
"repository_id": "REPO-014",
"branch": "main",
"source_base_commit": "f45113b86161aeb67f42741d8dcf6a28c5fe0cd1",
"implementation_commit": "2bfede237605067ee01023b7da2d4a6418583268",
"implementation_tree": "bd3d2b12a8163fee316e807742870b91f9025554",
"receipt_commit": "SELF",
"acceptance": {
"account_source_cannot_assert_registration": "PASS: the account projection has no nodeRegistrationVerified field",
"anchored_endpoint_registry": "PASS: only GH-AIOS-NODE-REGISTRATION-ENDPOINTS-001 from one exact REPO-012 anchor commit is accepted",
"signed_claim_binding": "PASS: Ed25519 claim binds account, domain, node id, node type, node key fingerprint, endpoint, signer and expiry",
"degraded_or_invalid_fails_closed": "PASS: missing, degraded, revoked, ambiguous, expired, extended, mismatched or invalidly signed evidence cannot contact the domain handoff route",
"current_public_reality": "PASS: the live public anchor projects REGISTRY_UNREGISTERED and UNAVAILABLE without claiming production node registration"
},
"verification": {
"new_contract_tests": "PASS 17 OF 17",
"desktop_tests": "PASS 72 OF 72",
"desktop_build": "PASS",
"ghnqg_frontend": "PASS 4993 OF 4993",
"ghnqg_rust": "PASS 1142 OF 1142",
"ghnqg_result": "GHNQG_PASS_100",
"ghnqg_external_receipt": "/Users/bingshuolingdianyuanhe/Documents/Codex/2026-08-09/new-chat/outputs/quality/GHNQG-NODE-REGISTRATION-CONTRACT-20260810.hldp",
"ghnqg_external_receipt_sha256": "d8192e4b0d13311e78d02279b3a7b865f28f037812e6737ca58573431f5871ff",
"live_anchor_probe": {
"endpointCount": 0,
"reason": "REGISTRY_UNREGISTERED",
"registryVersion": null,
"sourceCommit": null,
"status": "UNAVAILABLE"
}
},
"desktop_delivery": {
"target": "/Users/bingshuolingdianyuanhe/Desktop/HoloLake Era.app",
"architecture": "arm64",
"codesign": "PASS_DEEP_STRICT_DESIGNATED_REQUIREMENT",
"notarization": "NOT_RUN_OPTIONS_UNAVAILABLE",
"app_asar_sha256": "badbec3c60f63eb9592d18a5c4d39b2c41907961a0c3fdc9053ac1754b1dd7a9",
"executable_sha256": "d3ee775b75fb2d47e66a6342b93119d891a50c27d896a69d6f74dee2208e121c",
"candidate_installed_hash_match": true,
"runtime_process_observed": true,
"previous_bundle_backup": "/Users/bingshuolingdianyuanhe/Documents/Codex/2026-08-09/new-chat/work/DEV-20260809-007/desktop-backup-node-registration-20260810-0711/HoloLake Era.app"
},
"fact_boundaries": {
"code_complete": true,
"desktop_installed": true,
"repository_published": false,
"production_server_deployed": false,
"public_endpoint_registry_registered": false,
"node_registration_claim_endpoint_implemented": false,
"production_registration_signer_exists": false,
"node_key_challenge_implemented": false,
"real_domain_runtime_ready": false,
"statement": "This stage defines and enforces the independent verification contract. It does not create or register the production endpoint, signer, node claim service, node private key proof, domain session or online runtime."
},
"state": "READY_FOR_PUBLICATION"
}

View file

@ -3,16 +3,21 @@ import assert from 'node:assert/strict';
import { createHash, generateKeyPairSync, sign } from 'node:crypto';
import { domainManifestSigningBytes } from './domain-access.js';
import { DomainAccessOrchestrator } from './domain-access-orchestrator.js';
import { nodeRegistrationClaimSigningBytes } from './node-registration.js';
import { NodeRegistrationSnapshotLoader } from './node-registration-snapshot.js';
import { TrustedSignerSnapshotLoader } from './trusted-signer-snapshot.js';
const NOW = 1_786_291_200_000;
const DOMAIN_ID = 'DOM-FIFTH-0001';
const NODE_ID = 'LOCAL-001';
const NODE_TYPE = 'local-terminal' as const;
const ACCOUNT_ID = 'bingshuo';
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/trusted-domain-manifest-signers.json`;
const { privateKey, publicKey } = generateKeyPairSync('ed25519');
const SIGNER_REGISTRY_URL = `https://guanghulab.com/code/bingshuo/guanghu-ice-heart/raw/commit/${COMMIT}/routing/trusted-domain-manifest-signers.json`;
const NODE_REGISTRY_URL = `https://guanghulab.com/code/bingshuo/guanghu-ice-heart/raw/commit/${COMMIT}/routing/node-registration-endpoints.json`;
const manifestKeys = generateKeyPairSync('ed25519');
const registrationKeys = generateKeyPairSync('ed25519');
function anchor() {
return {
@ -29,6 +34,11 @@ function anchor() {
id: 'GH-AIOS-TRUSTED-DOMAIN-MANIFEST-SIGNERS-001',
version: '1.0.0',
},
node_registration_endpoints: {
path: 'routing/node-registration-endpoints.json',
id: 'GH-AIOS-NODE-REGISTRATION-ENDPOINTS-001',
version: '1.0.0',
},
},
navigation_source: {
anchor_id: 'GLW-PUBLIC-NAV-ANCHOR-001',
@ -39,7 +49,7 @@ function anchor() {
};
}
function registry(signers: unknown[] = []) {
function signerRegistry(signers: unknown[] = []) {
return {
registryId: 'GH-AIOS-TRUSTED-DOMAIN-MANIFEST-SIGNERS-001',
schema: 'gh-aios.trusted-domain-manifest-signers/v1',
@ -53,23 +63,72 @@ function activeSigner() {
return {
algorithm: 'Ed25519',
domainIds: [DOMAIN_ID],
publicKeyPem: publicKey.export({ format: 'pem', type: 'spki' }).toString(),
publicKeyPem: manifestKeys.publicKey.export({ format: 'pem', type: 'spki' }).toString(),
repositoryId: 'REPO-014',
signerId: 'GH-LIGHTHOUSE-001',
status: 'ACTIVE',
};
}
function loader(readRegistry: () => unknown = () => registry()) {
function nodeRegistry(version = '1.0.0') {
return {
endpoints: [{
algorithm: 'Ed25519',
domainIds: [DOMAIN_ID],
endpointId: 'GH-NODE-REG-001',
nodeTypes: [NODE_TYPE],
publicKeyPem: registrationKeys.publicKey.export({ format: 'pem', type: 'spki' }).toString(),
signerId: 'GH-NODE-REG-SIGNER-001',
status: 'ACTIVE',
url: 'https://guanghulab.com/api/ai/v1/node-registrations/claims',
}],
registryId: 'GH-AIOS-NODE-REGISTRATION-ENDPOINTS-001',
schema: 'gh-aios.node-registration-endpoints/v1',
state: 'CURRENT',
version,
};
}
function signerLoader(readRegistry: () => unknown = () => signerRegistry()) {
return new TrustedSignerSnapshotLoader({
async fetchJson(url) {
if (url === ANCHOR_URL) return anchor();
if (url === REGISTRY_URL) return readRegistry();
if (url === SIGNER_REGISTRY_URL) return readRegistry();
throw new Error('unexpected_url');
},
});
}
function registrationLoader(readRegistry: () => unknown = () => nodeRegistry()) {
return new NodeRegistrationSnapshotLoader({
async fetchJson(url) {
if (url === ANCHOR_URL) return anchor();
if (url === NODE_REGISTRY_URL) return readRegistry();
throw new Error('unexpected_url');
},
});
}
function validRegistrationClaim() {
const payload = {
accountId: ACCOUNT_ID,
claimId: 'NODE-CLAIM-001',
domainId: DOMAIN_ID,
endpointId: 'GH-NODE-REG-001',
expiresAt: NOW + 60_000,
issuedAt: NOW - 1_000,
nodeId: NODE_ID,
nodeKeyFingerprint: 'c'.repeat(64),
nodeType: NODE_TYPE,
schema: 'gh-aios.node-registration-claim/v1' as const,
signerId: 'GH-NODE-REG-SIGNER-001',
};
return {
...payload,
signature: sign(null, nodeRegistrationClaimSigningBytes(payload), registrationKeys.privateKey).toString('base64'),
};
}
function validHandoff() {
const payload = {
domainId: DOMAIN_ID,
@ -93,7 +152,7 @@ function validHandoff() {
manifest: {
digest,
...payload,
signature: sign(null, bytes, privateKey).toString('base64'),
signature: sign(null, bytes, manifestKeys.privateKey).toString('base64'),
},
sessionCapability: {
capabilityId: 'CAP-001',
@ -106,32 +165,37 @@ function validHandoff() {
};
}
test('projects public trust health without asking for a handoff before login', async () => {
const loggedOutIdentity = { async read() { return { accountId: '', accountVerified: false, nodeId: '' }; } };
const loggedInIdentity = { async read() { return { accountId: ACCOUNT_ID, accountVerified: true, nodeId: NODE_ID }; } };
const validRegistrationSource = { async read() { return validRegistrationClaim(); } };
test('projects both trust-source states without asking for claims or handoff before login', async () => {
let claimReads = 0;
let handoffReads = 0;
const orchestrator = new DomainAccessOrchestrator(
loader(),
{ async read() { return { accountVerified: false, nodeId: '', nodeRegistrationVerified: false }; } },
signerLoader(),
registrationLoader(),
loggedOutIdentity,
{ async read() { claimReads += 1; return null; } },
{ async read() { handoffReads += 1; return null; } },
);
const status = await orchestrator.domainAccess(DOMAIN_ID, NODE_TYPE, NOW);
assert.equal(claimReads, 0);
assert.equal(handoffReads, 0);
assert.equal(status.stage, 'login-required');
assert.equal(status.runtimeReady, false);
assert.equal(status.localWorkspaceAllowed, true);
assert.deepEqual(status.trustSource, {
status: 'CURRENT',
sourceCommit: COMMIT,
registryVersion: '1.0.0',
signerCount: 0,
});
assert.equal(status.nodeRegistrationSource.status, 'CURRENT');
assert.equal(status.trustSource.status, 'CURRENT');
});
test('an authenticated node without a verified handoff remains identity-only', async () => {
test('a signed node registration without a handoff remains identity-only', async () => {
const orchestrator = new DomainAccessOrchestrator(
loader(),
{ async read() { return { accountVerified: true, nodeId: NODE_ID, nodeRegistrationVerified: true }; } },
signerLoader(),
registrationLoader(),
loggedInIdentity,
validRegistrationSource,
{ async read() { return null; } },
);
@ -139,14 +203,17 @@ test('an authenticated node without a verified handoff remains identity-only', a
assert.equal(status.stage, 'identity-verified');
assert.equal(status.runtimeReady, false);
assert.equal(status.blockers.includes('verified_node_registration_missing'), false);
assert.ok(status.blockers.includes('verified_domain_manifest_missing'));
});
test('a code-channel account without node registration never contacts a domain handoff route', async () => {
test('a code-channel account cannot supply registration and never contacts a handoff route', async () => {
let handoffReads = 0;
const orchestrator = new DomainAccessOrchestrator(
loader(),
{ async read() { return { accountVerified: true, nodeId: NODE_ID, nodeRegistrationVerified: false }; } },
signerLoader(),
registrationLoader(),
loggedInIdentity,
{ async read() { return null; } },
{ async read() { handoffReads += 1; return validHandoff() as never; } },
);
@ -157,10 +224,12 @@ test('a code-channel account without node registration never contacts a domain h
assert.ok(status.blockers.includes('verified_node_registration_missing'));
});
test('a loaded signer snapshot and verified handoff project runtime-ready', async () => {
test('current registries, signed node claim and verified handoff project runtime-ready', async () => {
const orchestrator = new DomainAccessOrchestrator(
loader(() => registry([activeSigner()])),
{ async read() { return { accountVerified: true, nodeId: NODE_ID, nodeRegistrationVerified: true }; } },
signerLoader(() => signerRegistry([activeSigner()])),
registrationLoader(),
loggedInIdentity,
validRegistrationSource,
{ async read() {
return {
handoff: validHandoff(),
@ -174,33 +243,38 @@ test('a loaded signer snapshot and verified handoff project runtime-ready', asyn
assert.equal(status.stage, 'runtime-ready');
assert.equal(status.runtimeReady, true);
assert.deepEqual(status.blockers, []);
assert.equal(status.trustSource.status, 'CURRENT');
});
test('source and handoff failures are reduced to safe status without raw details', async () => {
test('source failures are reduced to safe status without raw details', async () => {
const unavailableSigner = new TrustedSignerSnapshotLoader({ async fetchJson() { throw new Error('secret signer detail'); } });
const unavailableRegistration = new NodeRegistrationSnapshotLoader({ async fetchJson() { throw new Error('secret registration detail'); } });
const orchestrator = new DomainAccessOrchestrator(
new TrustedSignerSnapshotLoader({ async fetchJson() { throw new Error('secret source detail'); } }),
{ async read() { return { accountVerified: true, nodeId: NODE_ID, nodeRegistrationVerified: true }; } },
unavailableSigner,
unavailableRegistration,
loggedInIdentity,
{ async read() { throw new Error('secret claim detail'); } },
{ async read() { throw new Error('secret handoff detail'); } },
);
const status = await orchestrator.domainAccess(DOMAIN_ID, NODE_TYPE, NOW);
assert.equal(status.stage, 'identity-verified');
assert.equal(status.stage, 'login-required');
assert.equal(status.runtimeReady, false);
assert.equal(status.nodeRegistrationSource.status, 'UNAVAILABLE');
assert.equal(status.trustSource.status, 'UNAVAILABLE');
assert.equal(status.trustSource.reason, 'SOURCE_UNAVAILABLE');
assert.equal(JSON.stringify(status).includes('secret'), false);
});
test('a last-known-good signer can project access while preserving degraded health', async () => {
let currentRegistry: unknown = registry([activeSigner()]);
const signerLoader = loader(() => currentRegistry);
await signerLoader.refresh();
currentRegistry = { ...registry([activeSigner()]), version: 'invalid' };
test('a degraded signer may preserve handoff verification while node registration must be current', async () => {
let currentSignerRegistry: unknown = signerRegistry([activeSigner()]);
const currentSignerLoader = signerLoader(() => currentSignerRegistry);
await currentSignerLoader.refresh();
currentSignerRegistry = { ...signerRegistry([activeSigner()]), version: 'invalid' };
const orchestrator = new DomainAccessOrchestrator(
signerLoader,
{ async read() { return { accountVerified: true, nodeId: NODE_ID, nodeRegistrationVerified: true }; } },
currentSignerLoader,
registrationLoader(),
loggedInIdentity,
validRegistrationSource,
{ async read() {
return {
handoff: validHandoff(),
@ -210,8 +284,30 @@ test('a last-known-good signer can project access while preserving degraded heal
);
const status = await orchestrator.domainAccess(DOMAIN_ID, NODE_TYPE, NOW);
assert.equal(status.runtimeReady, true);
assert.equal(status.trustSource.status, 'DEGRADED_LAST_KNOWN_GOOD');
assert.equal(status.trustSource.reason, 'REGISTRY_INVALID');
assert.equal(status.nodeRegistrationSource.status, 'CURRENT');
});
test('a degraded node-registration registry cannot authorize a claim', async () => {
let currentNodeRegistry: unknown = nodeRegistry();
const currentNodeLoader = registrationLoader(() => currentNodeRegistry);
await currentNodeLoader.refresh();
currentNodeRegistry = nodeRegistry('2.0.0');
let claimReads = 0;
let handoffReads = 0;
const orchestrator = new DomainAccessOrchestrator(
signerLoader(() => signerRegistry([activeSigner()])),
currentNodeLoader,
loggedInIdentity,
{ async read() { claimReads += 1; return validRegistrationClaim(); } },
{ async read() { handoffReads += 1; return null; } },
);
const status = await orchestrator.domainAccess(DOMAIN_ID, NODE_TYPE, NOW);
assert.equal(status.nodeRegistrationSource.status, 'DEGRADED_LAST_KNOWN_GOOD');
assert.equal(status.runtimeReady, false);
assert.ok(status.blockers.includes('verified_node_registration_missing'));
assert.equal(claimReads, 0);
assert.equal(handoffReads, 0);
});

View file

@ -9,19 +9,41 @@ import {
TrustedSignerSnapshotLoader,
type TrustedSignerSnapshotReceipt,
} from './trusted-signer-snapshot.js';
import {
resolveNodeRegistrationEndpoint,
verifyNodeRegistrationClaim,
} from './node-registration.js';
import {
assertLoadedNodeRegistrationSnapshot,
NodeRegistrationSnapshotLoader,
type NodeRegistrationSnapshotReceipt,
} from './node-registration-snapshot.js';
const IDENTIFIER_PATTERN = /^[A-Z0-9][A-Z0-9._:-]{1,159}$/;
export interface DomainIdentityProjection {
accountId: string;
accountVerified: boolean;
nodeId: string;
nodeRegistrationVerified: boolean;
}
export interface DomainIdentitySource {
read(domainId: string, nodeType: DomainNodeType): Promise<DomainIdentityProjection>;
}
export interface NodeRegistrationClaimRequest {
accountId: string;
domainId: string;
endpointId: string;
endpointUrl: string;
nodeId: string;
nodeType: DomainNodeType;
}
export interface NodeRegistrationClaimSource {
read(request: Readonly<NodeRegistrationClaimRequest>): Promise<unknown | null>;
}
export interface DomainRuntimeHandoffCandidate {
handoff: unknown;
signerLookup: {
@ -40,6 +62,7 @@ export interface DomainAccessProjection {
localWorkspaceAllowed: true;
nodeId: string;
nodeType: DomainNodeType;
nodeRegistrationSource: Readonly<NodeRegistrationSnapshotReceipt>;
runtimeReady: boolean;
stage: DomainAccessStatus['stage'];
trustSource: Readonly<TrustedSignerSnapshotReceipt>;
@ -47,42 +70,77 @@ export interface DomainAccessProjection {
function safeIdentity(input: unknown): DomainIdentityProjection {
if (typeof input !== 'object' || input === null || Array.isArray(input)) {
return { accountVerified: false, nodeId: '', nodeRegistrationVerified: false };
return { accountId: '', accountVerified: false, nodeId: '' };
}
const candidate = input as Record<string, unknown>;
const accountId = typeof candidate.accountId === 'string'
&& /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/.test(candidate.accountId)
? candidate.accountId
: '';
const nodeId = typeof candidate.nodeId === 'string' && IDENTIFIER_PATTERN.test(candidate.nodeId)
? candidate.nodeId
: '';
return {
accountVerified: candidate.accountVerified === true && Boolean(nodeId),
accountId,
accountVerified: candidate.accountVerified === true && Boolean(accountId) && Boolean(nodeId),
nodeId,
nodeRegistrationVerified: candidate.nodeRegistrationVerified === true && Boolean(nodeId),
};
}
export class DomainAccessOrchestrator {
constructor(
private readonly signerSnapshots: TrustedSignerSnapshotLoader,
private readonly nodeRegistrationSnapshots: NodeRegistrationSnapshotLoader,
private readonly identities: DomainIdentitySource,
private readonly nodeRegistrations: NodeRegistrationClaimSource,
private readonly handoffs: DomainRuntimeHandoffSource,
) {}
async domainAccess(domainId: string, nodeType: DomainNodeType, now = Date.now()): Promise<Readonly<DomainAccessProjection>> {
if (!IDENTIFIER_PATTERN.test(domainId)) throw new Error('domain_access_domain_invalid');
if (nodeType !== 'local-terminal' && nodeType !== 'cloud-resident') throw new Error('domain_access_node_type_invalid');
const [snapshot, identity] = await Promise.all([
const [snapshot, nodeRegistrationSnapshot, identity] = await Promise.all([
this.signerSnapshots.refresh(),
this.nodeRegistrationSnapshots.refresh(),
this.identities.read(domainId, nodeType).then(safeIdentity, () => safeIdentity(null)),
]);
let nodeRegistrationVerified = false;
if (identity.accountVerified && nodeRegistrationSnapshot.receipt.status === 'CURRENT') {
try {
const loaded = assertLoadedNodeRegistrationSnapshot(nodeRegistrationSnapshot);
const endpoint = loaded.registry && resolveNodeRegistrationEndpoint(loaded.registry, { domainId, nodeType });
if (endpoint) {
const claim = await this.nodeRegistrations.read(Object.freeze({
accountId: identity.accountId,
domainId,
endpointId: endpoint.endpointId,
endpointUrl: endpoint.url,
nodeId: identity.nodeId,
nodeType,
}));
if (claim) {
verifyNodeRegistrationClaim(claim, {
accountId: identity.accountId,
domainId,
nodeId: identity.nodeId,
nodeType,
}, endpoint, now);
nodeRegistrationVerified = true;
}
}
} catch {
nodeRegistrationVerified = false;
}
}
const evidence: DomainAccessEvidence = {
accountVerified: identity.accountVerified,
domainId,
nodeId: identity.nodeId,
nodeRegistrationVerified: identity.nodeRegistrationVerified,
nodeRegistrationVerified,
nodeType,
};
if (identity.accountVerified && identity.nodeRegistrationVerified) {
if (identity.accountVerified && nodeRegistrationVerified) {
let candidate: DomainRuntimeHandoffCandidate | null = null;
try {
candidate = await this.handoffs.read(domainId, identity.nodeId, nodeType);
@ -111,6 +169,7 @@ export class DomainAccessOrchestrator {
return Object.freeze({
...status,
blockers: Object.freeze([...status.blockers]),
nodeRegistrationSource: nodeRegistrationSnapshot.receipt,
trustSource: snapshot.receipt,
});
}

View file

@ -0,0 +1,111 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { generateKeyPairSync } from 'node:crypto';
import {
assertLoadedNodeRegistrationSnapshot,
NodeRegistrationSnapshotLoader,
} from './node-registration-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/node-registration-endpoints.json`;
const { publicKey } = generateKeyPairSync('ed25519');
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 ? {
node_registration_endpoints: {
path: 'routing/node-registration-endpoints.json',
id: 'GH-AIOS-NODE-REGISTRATION-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: [{
algorithm: 'Ed25519',
domainIds: ['DOM-FIFTH-0001'],
endpointId: 'GH-NODE-REG-001',
nodeTypes: ['local-terminal'],
publicKeyPem: publicKey.export({ format: 'pem', type: 'spki' }).toString(),
signerId: 'GH-NODE-REG-SIGNER-001',
status: 'ACTIVE',
url: 'https://guanghulab.com/api/ai/v1/node-registrations/claims',
}],
registryId: 'GH-AIOS-NODE-REGISTRATION-ENDPOINTS-001',
schema: 'gh-aios.node-registration-endpoints/v1',
state: 'CURRENT',
version,
};
}
function loader(readAnchor: () => unknown, readRegistry: () => unknown) {
return new NodeRegistrationSnapshotLoader({
async fetchJson(url) {
if (url === ANCHOR_URL) return readAnchor();
if (url === REGISTRY_URL) return readRegistry();
throw new Error('unexpected_url');
},
});
}
test('loads one exact registry from the 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(assertLoadedNodeRegistrationSnapshot(snapshot), snapshot);
});
test('the current public anchor without a registration 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('source and registry mismatches fail closed', async () => {
const unavailable = await loader(() => { throw new Error('offline'); }, () => registry()).refresh();
assert.equal(unavailable.receipt.reason, 'SOURCE_UNAVAILABLE');
const invalid = await loader(() => anchor(), () => registry('2.0.0')).refresh();
assert.equal(invalid.receipt.reason, 'REGISTRY_INVALID');
});
test('last-known-good state is reported degraded and cannot be mistaken for current', async () => {
let currentAnchor: unknown = anchor();
const snapshotLoader = loader(() => currentAnchor, () => registry());
await snapshotLoader.refresh();
currentAnchor = anchor(false);
const degraded = await snapshotLoader.refresh();
assert.equal(degraded.receipt.status, 'DEGRADED_LAST_KNOWN_GOOD');
assert.equal(degraded.receipt.reason, 'REGISTRY_UNREGISTERED');
assert.equal(degraded.receipt.sourceCommit, COMMIT);
});
test('caller-created snapshots are rejected', () => {
assert.throws(() => assertLoadedNodeRegistrationSnapshot({
receipt: { endpointCount: 0, registryVersion: null, sourceCommit: null, status: 'UNAVAILABLE' },
registry: null,
}), /snapshot_unregistered/);
});

View file

@ -0,0 +1,169 @@
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));
}
}
}

View file

@ -0,0 +1,136 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { generateKeyPairSync, sign } from 'node:crypto';
import {
nodeRegistrationClaimSigningBytes,
parseNodeRegistrationEndpointRegistry,
resolveNodeRegistrationEndpoint,
verifyNodeRegistrationClaim,
} from './node-registration.js';
const NOW = 1_786_291_200_000;
const COMMIT = 'a'.repeat(40);
const { privateKey, publicKey } = generateKeyPairSync('ed25519');
const source = {
repositoryId: 'REPO-012' as const,
sourceCommit: COMMIT,
sourceUrl: `https://guanghulab.com/code/bingshuo/guanghu-ice-heart/raw/commit/${COMMIT}/routing/node-registration-endpoints.json`,
};
function endpoint(overrides: Record<string, unknown> = {}) {
return {
algorithm: 'Ed25519',
domainIds: ['DOM-FIFTH-0001'],
endpointId: 'GH-NODE-REG-001',
nodeTypes: ['local-terminal', 'cloud-resident'],
publicKeyPem: publicKey.export({ format: 'pem', type: 'spki' }).toString(),
signerId: 'GH-NODE-REG-SIGNER-001',
status: 'ACTIVE',
url: 'https://guanghulab.com/api/ai/v1/node-registrations/claims',
...overrides,
};
}
function registry(endpoints: unknown[] = [endpoint()]) {
return parseNodeRegistrationEndpointRegistry({
endpoints,
registryId: 'GH-AIOS-NODE-REGISTRATION-ENDPOINTS-001',
schema: 'gh-aios.node-registration-endpoints/v1',
state: 'CURRENT',
version: '1.0.0',
}, source);
}
function signedClaim(overrides: Record<string, unknown> = {}) {
const payload = {
accountId: 'bingshuo',
claimId: 'NODE-CLAIM-001',
domainId: 'DOM-FIFTH-0001',
endpointId: 'GH-NODE-REG-001',
expiresAt: NOW + 60_000,
issuedAt: NOW - 1_000,
nodeId: 'JD-FD-PRIMARY',
nodeKeyFingerprint: 'b'.repeat(64),
nodeType: 'local-terminal' as const,
schema: 'gh-aios.node-registration-claim/v1' as const,
signerId: 'GH-NODE-REG-SIGNER-001',
...overrides,
};
return {
...payload,
signature: sign(null, nodeRegistrationClaimSigningBytes(payload as never), privateKey).toString('base64'),
};
}
test('resolves one active endpoint for a domain and node type', () => {
const resolved = resolveNodeRegistrationEndpoint(registry(), {
domainId: 'DOM-FIFTH-0001',
nodeType: 'local-terminal',
});
assert.equal(resolved?.endpointId, 'GH-NODE-REG-001');
});
test('rejects credentials, arbitrary origins and ambiguous active scopes', () => {
assert.throws(() => registry([endpoint({ url: 'https://user:secret@guanghulab.com/api/ai/v1/node-registrations' })]), /registry_invalid/);
assert.throws(() => registry([endpoint({ url: 'https://example.com/api/ai/v1/node-registrations' })]), /registry_invalid/);
assert.throws(() => registry([
endpoint(),
endpoint({ endpointId: 'GH-NODE-REG-002', signerId: 'GH-NODE-REG-SIGNER-002' }),
]), /registry_invalid/);
});
test('a signed registration binds account, domain, node, type, key and endpoint', () => {
const resolved = resolveNodeRegistrationEndpoint(registry(), {
domainId: 'DOM-FIFTH-0001',
nodeType: 'local-terminal',
});
assert.ok(resolved);
const verified = verifyNodeRegistrationClaim(signedClaim(), {
accountId: 'bingshuo',
domainId: 'DOM-FIFTH-0001',
nodeId: 'JD-FD-PRIMARY',
nodeType: 'local-terminal',
}, resolved, NOW);
assert.equal(verified.nodeKeyFingerprint, 'b'.repeat(64));
assert.equal(verified.verifiedAt, NOW);
});
test('copied booleans, unknown fields, mismatches and expired claims fail closed', () => {
const resolved = resolveNodeRegistrationEndpoint(registry(), {
domainId: 'DOM-FIFTH-0001',
nodeType: 'local-terminal',
});
assert.ok(resolved);
const expected = {
accountId: 'bingshuo',
domainId: 'DOM-FIFTH-0001',
nodeId: 'JD-FD-PRIMARY',
nodeType: 'local-terminal' as const,
};
assert.throws(() => verifyNodeRegistrationClaim({ ...signedClaim(), verified: true }, expected, resolved, NOW), /claim_invalid/);
assert.throws(() => verifyNodeRegistrationClaim(signedClaim(), { ...expected, accountId: 'other' }, resolved, NOW), /claim_invalid/);
assert.throws(() => verifyNodeRegistrationClaim(signedClaim(), { ...expected, nodeType: 'cloud-resident' }, resolved, NOW), /claim_invalid/);
assert.throws(() => verifyNodeRegistrationClaim(signedClaim({ expiresAt: NOW }), expected, resolved, NOW), /claim_invalid/);
});
test('tampering or signing with another key fails closed', () => {
const resolved = resolveNodeRegistrationEndpoint(registry(), {
domainId: 'DOM-FIFTH-0001',
nodeType: 'local-terminal',
});
assert.ok(resolved);
const expected = {
accountId: 'bingshuo',
domainId: 'DOM-FIFTH-0001',
nodeId: 'JD-FD-PRIMARY',
nodeType: 'local-terminal' as const,
};
assert.throws(() => verifyNodeRegistrationClaim({ ...signedClaim(), nodeKeyFingerprint: 'c'.repeat(64) }, expected, resolved, NOW), /claim_invalid/);
const other = generateKeyPairSync('ed25519');
const claim = signedClaim();
const forged = {
...claim,
signature: sign(null, nodeRegistrationClaimSigningBytes(claim), other.privateKey).toString('base64'),
};
assert.throws(() => verifyNodeRegistrationClaim(forged, expected, resolved, NOW), /claim_invalid/);
});

View file

@ -0,0 +1,269 @@
import { createPublicKey, verify as verifySignature } from 'node:crypto';
import type { DomainNodeType } from './domain-access.js';
const REGISTRY_SCHEMA = 'gh-aios.node-registration-endpoints/v1' as const;
const CLAIM_SCHEMA = 'gh-aios.node-registration-claim/v1' as const;
const IDENTIFIER_PATTERN = /^[A-Z0-9][A-Z0-9._:-]{1,159}$/;
const ACCOUNT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/;
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 SHA256_PATTERN = /^[a-f0-9]{64}$/;
const MAX_CLAIM_LIFETIME_MS = 24 * 60 * 60 * 1000;
const MAX_CLOCK_SKEW_MS = 5 * 60 * 1000;
const registeredEndpoints = new WeakSet<object>();
export interface NodeRegistrationEndpoint {
algorithm: 'Ed25519';
domainIds: readonly string[];
endpointId: string;
nodeTypes: readonly DomainNodeType[];
publicKeyPem: string;
signerId: string;
status: 'ACTIVE' | 'REVOKED';
url: string;
}
export interface NodeRegistrationEndpointRegistrySource {
repositoryId: 'REPO-012';
sourceCommit: string;
sourceUrl: string;
}
export interface NodeRegistrationEndpointRegistry {
endpoints: readonly NodeRegistrationEndpoint[];
registryId: 'GH-AIOS-NODE-REGISTRATION-ENDPOINTS-001';
schema: typeof REGISTRY_SCHEMA;
source: Readonly<NodeRegistrationEndpointRegistrySource>;
state: 'CURRENT';
version: string;
}
export interface NodeRegistrationClaimPayload {
accountId: string;
claimId: string;
domainId: string;
endpointId: string;
expiresAt: number;
issuedAt: number;
nodeId: string;
nodeKeyFingerprint: string;
nodeType: DomainNodeType;
schema: typeof CLAIM_SCHEMA;
signerId: string;
}
export interface VerifiedNodeRegistration extends NodeRegistrationClaimPayload {
signature: string;
verifiedAt: number;
}
export interface NodeRegistrationLookup {
domainId: string;
nodeType: DomainNodeType;
}
function invalidRegistry(): never {
throw new Error('node_registration_endpoint_registry_invalid');
}
function invalidClaim(): never {
throw new Error('node_registration_claim_invalid');
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function hasExactKeys(record: Record<string, unknown>, 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 parsePublicKey(value: unknown): string {
if (typeof value !== 'string'
|| value.length > 4096
|| value.includes('PRIVATE KEY')
|| !value.startsWith('-----BEGIN PUBLIC KEY-----\n')
|| !value.endsWith('-----END PUBLIC KEY-----\n')) invalidRegistry();
try {
const key = createPublicKey({ format: 'pem', key: value });
if (key.asymmetricKeyType !== 'ed25519') invalidRegistry();
} catch {
invalidRegistry();
}
return 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): NodeRegistrationEndpoint {
if (!isRecord(input)
|| !hasExactKeys(input, ['algorithm', 'domainIds', 'endpointId', 'nodeTypes', 'publicKeyPem', 'signerId', 'status', 'url'])
|| input.algorithm !== 'Ed25519'
|| !isIdentifier(input.endpointId)
|| !isIdentifier(input.signerId)
|| (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({
algorithm: 'Ed25519' as const,
domainIds: Object.freeze([...input.domainIds] as string[]),
endpointId: input.endpointId,
nodeTypes: Object.freeze([...input.nodeTypes] as DomainNodeType[]),
publicKeyPem: parsePublicKey(input.publicKeyPem),
signerId: input.signerId,
status: input.status,
url: parseEndpointUrl(input.url),
});
registeredEndpoints.add(endpoint);
return endpoint;
}
function parseSource(source: NodeRegistrationEndpointRegistrySource): Readonly<NodeRegistrationEndpointRegistrySource> {
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/node-registration-endpoints.json`;
if (source.sourceUrl !== expected) invalidRegistry();
return Object.freeze({ ...source });
}
export function parseNodeRegistrationEndpointRegistry(
input: unknown,
source: NodeRegistrationEndpointRegistrySource,
): NodeRegistrationEndpointRegistry {
if (!isRecord(input)
|| !hasExactKeys(input, ['endpoints', 'registryId', 'schema', 'state', 'version'])
|| input.registryId !== 'GH-AIOS-NODE-REGISTRATION-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<string>();
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 resolveNodeRegistrationEndpoint(
registry: NodeRegistrationEndpointRegistry,
lookup: NodeRegistrationLookup,
): NodeRegistrationEndpoint | null {
const endpoint = registry.endpoints.find(candidate => candidate.status === 'ACTIVE'
&& candidate.domainIds.includes(lookup.domainId)
&& candidate.nodeTypes.includes(lookup.nodeType));
if (!endpoint) return null;
if (!registeredEndpoints.has(endpoint)) throw new Error('node_registration_endpoint_unregistered');
return endpoint;
}
export function nodeRegistrationClaimSigningBytes(payload: NodeRegistrationClaimPayload): Buffer {
return Buffer.from(JSON.stringify({
accountId: payload.accountId,
claimId: payload.claimId,
domainId: payload.domainId,
endpointId: payload.endpointId,
expiresAt: payload.expiresAt,
issuedAt: payload.issuedAt,
nodeId: payload.nodeId,
nodeKeyFingerprint: payload.nodeKeyFingerprint,
nodeType: payload.nodeType,
schema: payload.schema,
signerId: payload.signerId,
}), 'utf8');
}
export function verifyNodeRegistrationClaim(
input: unknown,
expected: Readonly<{ accountId: string; domainId: string; nodeId: string; nodeType: DomainNodeType }>,
endpoint: NodeRegistrationEndpoint,
now = Date.now(),
): Readonly<VerifiedNodeRegistration> {
if (!registeredEndpoints.has(endpoint) || endpoint.status !== 'ACTIVE') invalidClaim();
if (!isRecord(input)
|| !hasExactKeys(input, ['accountId', 'claimId', 'domainId', 'endpointId', 'expiresAt', 'issuedAt', 'nodeId', 'nodeKeyFingerprint', 'nodeType', 'schema', 'signature', 'signerId'])) invalidClaim();
if (input.schema !== CLAIM_SCHEMA
|| typeof input.accountId !== 'string'
|| !ACCOUNT_ID_PATTERN.test(input.accountId)
|| input.accountId !== expected.accountId
|| !isIdentifier(input.claimId)
|| input.domainId !== expected.domainId
|| input.nodeId !== expected.nodeId
|| input.nodeType !== expected.nodeType
|| input.endpointId !== endpoint.endpointId
|| input.signerId !== endpoint.signerId
|| !endpoint.domainIds.includes(expected.domainId)
|| !endpoint.nodeTypes.includes(expected.nodeType)
|| typeof input.nodeKeyFingerprint !== 'string'
|| !SHA256_PATTERN.test(input.nodeKeyFingerprint)
|| typeof input.issuedAt !== 'number'
|| !Number.isSafeInteger(input.issuedAt)
|| typeof input.expiresAt !== 'number'
|| !Number.isSafeInteger(input.expiresAt)
|| input.issuedAt > now + MAX_CLOCK_SKEW_MS
|| input.expiresAt <= now
|| input.expiresAt <= input.issuedAt
|| input.expiresAt - input.issuedAt > MAX_CLAIM_LIFETIME_MS
|| typeof input.signature !== 'string') invalidClaim();
const payload = input as unknown as NodeRegistrationClaimPayload;
let signature: Buffer;
try {
signature = Buffer.from(input.signature, 'base64');
if (signature.length !== 64 || signature.toString('base64') !== input.signature) invalidClaim();
if (!verifySignature(null, nodeRegistrationClaimSigningBytes(payload), endpoint.publicKeyPem, signature)) invalidClaim();
} catch {
invalidClaim();
}
return Object.freeze({
...payload,
signature: input.signature,
verifiedAt: now,
});
}

View file

@ -20,6 +20,7 @@ import fs from 'fs';
import { importKnowledgeFolder } from './folder-import.js';
import { DomainAccessOrchestrator } from '../../guanghu-knowledge-base/server/domain-access-orchestrator.js';
import { TrustedSignerSnapshotLoader } from '../../guanghu-knowledge-base/server/trusted-signer-snapshot.js';
import { NodeRegistrationSnapshotLoader } from '../../guanghu-knowledge-base/server/node-registration-snapshot.js';
// ─── 配置 ───
@ -324,22 +325,45 @@ const trustedSignerSnapshots = new TrustedSignerSnapshotLoader({
},
});
const nodeRegistrationSnapshots = new NodeRegistrationSnapshotLoader({
async fetchJson(url: string): Promise<unknown> {
const response = await fetch(url, {
headers: { Accept: 'application/json' },
signal: AbortSignal.timeout(5000),
});
if (!response.ok) throw new Error(`node_registration_source_http_${response.status}`);
return response.json();
},
});
const domainAccessOrchestrator = new DomainAccessOrchestrator(
trustedSignerSnapshots,
nodeRegistrationSnapshots,
{
async read() {
const stored = applyStoredServerAuth();
if (!stored) return { accountVerified: false, nodeId: '', nodeRegistrationVerified: false };
if (!stored) return { accountId: '', accountVerified: false, nodeId: '' };
try {
const { response } = await forgejoRequest(stored.nodeId, '/api/v1/user', {}, { token: stored.token });
// Forgejo proves only the account part. A dedicated node-registration
// verifier must independently supply nodeRegistrationVerified.
return { accountVerified: response.ok, nodeId: stored.nodeId, nodeRegistrationVerified: false };
const { response, data } = await forgejoRequest(stored.nodeId, '/api/v1/user', {}, { token: stored.token });
// Forgejo proves only the account part. It cannot submit a node-registration
// boolean; a separately anchored signed claim is required below.
return {
accountId: response.ok && typeof data?.login === 'string' ? data.login : '',
accountVerified: response.ok,
nodeId: stored.nodeId,
};
} catch {
return { accountVerified: false, nodeId: stored.nodeId, nodeRegistrationVerified: false };
return { accountId: '', accountVerified: false, nodeId: stored.nodeId };
}
},
},
{
async read() {
// Current REPO-012 does not yet register a node-registration endpoint.
// Keep this transport absent until that public registry and service exist.
return null;
},
},
{
async read() {
// 当前灯塔尚未发布签名运行体交接端点。保持关闭,不从账号身份推导运行体权限。

View file

@ -0,0 +1,46 @@
# HoloLake 节点登记与验证来源契约 · 2026-08-10
> 阶段:`DEV-20260809-007 / NODE-REGISTRATION-CONTRACT-001`
>
> 上游:`HLP-CURRENT-ARCH-001` 第 5 节“节点与登录协议”
>
> 状态:`IMPLEMENTATION_STAGE_ACCEPTANCE_BASELINE`
## 验收标准
1. 账号验证源只能返回账号身份与用户选择的节点编号,不能直接声明节点已经登记。
2. HoloLake 只读取 REPO-012 当前公共锚点同一提交声明的
`GH-AIOS-NODE-REGISTRATION-ENDPOINTS-001`;任意本地配置、账号响应或未登记 URL 都不能成为
节点登记权威。
3. 节点登记声明使用 Ed25519 签名,并精确绑定账号、域、节点编号、节点类型、节点公钥
SHA-256 指纹、登记端点、签发者、签发时间与过期时间。
4. 注册表缺失、来源降级、端点撤销、端点范围不匹配、声明过期、未知字段、签名错误或任一
绑定字段不匹配时,`nodeRegistrationVerified` 必须保持 `false`,且不得请求域运行体交接。
5. 当前 REPO-012 锚点尚未登记此端点注册表,因此当前生产判断必须是
`NODE_REGISTRATION_NOT_EXISTS_0`;本阶段代码完成不得冒充生产登记服务、节点密钥证明、
域会话或在线运行已经存在。
## 注册表契约
注册表固定路径为 `routing/node-registration-endpoints.json`,由公共锚点的
`maps.node_registration_endpoints` 指向。每个端点声明:
- `endpointId``signerId``Ed25519 publicKeyPem`
- HTTPS `guanghulab.com/api/ai/v1/...` 端点;
- 允许的 `domainIds``local-terminal / cloud-resident` 节点类型;
- `ACTIVE / REVOKED` 状态。
同一域与节点类型不得存在两个活跃端点,避免客户端自行猜测权威。
## 登记声明契约
`gh-aios.node-registration-claim/v1` 只表达灯塔登记服务已经验证的账号—节点—公钥绑定。
声明最长有效 24 小时,必须周期刷新。它不代替后续登录挑战中的实时节点私钥持有证明,也不
代替域成员权限、短期会话能力、签名域清单和在线连接回执。
## 当前现实边界
- 当前公共锚点没有 `maps.node_registration_endpoints`
- 当前桌面没有可调用的登记声明端点;
- 当前没有生产节点登记签发公钥、登记声明或节点密钥挑战回执;
- 因而 Forgejo 账号验证以后仍停留在“需要节点登记”,这是正确的失败关闭结果。

View file

@ -8,6 +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):账号验证与节点登记分离后的签名声明、端点注册表、失败关闭和当前生产缺口验收基线。
- [`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 历史意图层,继续保存形成过程;不再作为当前默认入口。