Bind domain access to registered node type

This commit is contained in:
冰朔 2026-08-10 06:28:47 +08:00
commit 8807b99d98
15 changed files with 168 additions and 63 deletions

View file

@ -47,6 +47,7 @@ interface DomainAccessStatus {
blockers: string[];
domainId?: string;
nodeId?: string;
nodeType?: DomainNodeType;
runtimeReady: boolean;
stage: 'checking' | 'login-required' | 'identity-verified' | 'runtime-ready';
trustSource?: {
@ -179,18 +180,18 @@ export default function App() {
}
}, []);
const refreshDomainAccess = useCallback(async (domainId = 'DOM-FIFTH-0001') => {
const refreshDomainAccess = useCallback(async (domainId = 'DOM-FIFTH-0001', nodeType: DomainNodeType = 'local-terminal') => {
const request = ++domainAccessRequest.current;
const server = (window as any).hololake?.server;
if (!server?.domainAccess) {
if (request === domainAccessRequest.current) setDomainAccess({ blockers: ['desktop_runtime_required'], domainId, runtimeReady: false, stage: 'login-required' });
if (request === domainAccessRequest.current) setDomainAccess({ blockers: ['desktop_runtime_required'], domainId, nodeType, runtimeReady: false, stage: 'login-required' });
return;
}
try {
const result = await server.domainAccess(domainId);
const result = await server.domainAccess(domainId, nodeType);
if (request === domainAccessRequest.current) setDomainAccess(result);
} catch {
if (request === domainAccessRequest.current) setDomainAccess({ blockers: ['domain_access_probe_failed'], domainId, runtimeReady: false, stage: 'login-required' });
if (request === domainAccessRequest.current) setDomainAccess({ blockers: ['domain_access_probe_failed'], domainId, nodeType, runtimeReady: false, stage: 'login-required' });
}
}, []);
@ -200,13 +201,17 @@ export default function App() {
setDomainConnectionOpen(true);
if (target) {
setDomainAccess({ blockers: [], domainId: target.domain.stableDomainId, runtimeReady: false, stage: 'checking' });
void refreshDomainAccess(target.domain.stableDomainId);
void refreshDomainAccess(target.domain.stableDomainId, target.nodeType);
}
}, [refreshDomainAccess]);
const selectDomainNodeType = useCallback((nodeType: DomainNodeType) => {
setDomainEntryTarget(current => current ? createDomainEntryTarget(current.domain.routeId, nodeType) : null);
}, []);
if (!domainEntryTarget) return;
const target = createDomainEntryTarget(domainEntryTarget.domain.routeId, nodeType);
setDomainEntryTarget(target);
setDomainAccess({ blockers: [], domainId: target.domain.stableDomainId, nodeType, runtimeReady: false, stage: 'checking' });
void refreshDomainAccess(target.domain.stableDomainId, nodeType);
}, [domainEntryTarget, refreshDomainAccess]);
const refreshChannel = useCallback(async () => {
try {

View file

@ -5,7 +5,8 @@ import { projectDomainConnectionSteps, projectDomainTrustSource } from './domain
test('code-channel account absence cannot be projected as a domain runtime login', () => {
const steps = projectDomainConnectionSteps({
blockers: [
'account_node_identity_missing',
'account_identity_missing',
'verified_node_registration_missing',
'verified_domain_manifest_missing',
'scoped_session_capability_missing',
'matching_connection_receipt_missing',

View file

@ -4,6 +4,7 @@ export interface DomainAccessProjection {
blockers: string[];
domainId?: string;
nodeId?: string;
nodeType?: 'local-terminal' | 'cloud-resident';
runtimeReady: boolean;
stage: DomainAccessStage;
trustSource?: {
@ -23,7 +24,8 @@ export interface DomainConnectionStep {
}
const blockerToStep: Record<string, DomainConnectionStep['id']> = {
account_node_identity_missing: 'identity',
account_identity_missing: 'identity',
verified_node_registration_missing: 'identity',
verified_domain_manifest_missing: 'manifest',
scoped_session_capability_missing: 'capability',
matching_connection_receipt_missing: 'receipt',

View file

@ -2,7 +2,7 @@ import assert from 'node:assert/strict';
import test from 'node:test';
import { canEnterSelectedDomainRuntime, createDomainEntryTarget, projectDomainRuntimeBoundary } from './domain-entry-state.js';
const ready = (domainId: string) => ({ blockers: [], domainId, runtimeReady: true, stage: 'runtime-ready' as const });
const ready = (domainId: string, nodeType: 'local-terminal' | 'cloud-resident' = 'local-terminal') => ({ blockers: [], domainId, nodeType, runtimeReady: true, stage: 'runtime-ready' as const });
test('a generic login request does not silently target the Fifth Domain', () => {
assert.equal(canEnterSelectedDomainRuntime(null, ready('DOM-FIFTH-0001')), false);
@ -11,8 +11,8 @@ test('a generic login request does not silently target the Fifth Domain', () =>
test('the selected stable domain id must match the returned access evidence', () => {
const fifth = createDomainEntryTarget('fifth', 'cloud-resident');
assert.equal(canEnterSelectedDomainRuntime(fifth, ready('DOMAIN-MAIN')), false);
assert.match(projectDomainRuntimeBoundary(fifth, ready('DOMAIN-MAIN')), /另一个域/);
assert.equal(canEnterSelectedDomainRuntime(fifth, ready('DOMAIN-MAIN', 'cloud-resident')), false);
assert.match(projectDomainRuntimeBoundary(fifth, ready('DOMAIN-MAIN', 'cloud-resident')), /另一个域/);
});
test('enterprise vestibules cannot reuse the Fifth Domain renderer', () => {
@ -25,6 +25,8 @@ test('enterprise vestibules cannot reuse the Fifth Domain renderer', () => {
test('the Fifth Domain opens only with matching runtime-ready evidence', () => {
const fifth = createDomainEntryTarget('fifth', 'cloud-resident');
assert.equal(canEnterSelectedDomainRuntime(fifth, ready('DOM-FIFTH-0001')), true);
assert.match(projectDomainRuntimeBoundary(fifth, ready('DOM-FIFTH-0001')), /已经匹配/);
assert.equal(canEnterSelectedDomainRuntime(fifth, ready('DOM-FIFTH-0001', 'cloud-resident')), true);
assert.equal(canEnterSelectedDomainRuntime(fifth, ready('DOM-FIFTH-0001', 'local-terminal')), false);
assert.match(projectDomainRuntimeBoundary(fifth, ready('DOM-FIFTH-0001', 'local-terminal')), /另一种节点类型/);
assert.match(projectDomainRuntimeBoundary(fifth, ready('DOM-FIFTH-0001', 'cloud-resident')), /已经匹配/);
});

View file

@ -17,6 +17,7 @@ export function createDomainEntryTarget(routeId: DomainRouteId, nodeType: Domain
export function canEnterSelectedDomainRuntime(target: DomainEntryTarget | null, access: DomainAccessProjection): boolean {
if (!target || !access.runtimeReady) return false;
if (access.domainId !== target.domain.stableDomainId) return false;
if (access.nodeType !== target.nodeType) return false;
// The present desktop bundle contains only the Fifth Domain renderer. The four
// enterprise domains must provide their own signed runtime package and endpoint.
return target.domain.routeId === 'fifth';
@ -27,6 +28,9 @@ export function projectDomainRuntimeBoundary(target: DomainEntryTarget | null, a
if (access.domainId && access.domainId !== target.domain.stableDomainId) {
return '回读证据属于另一个域;当前入口保持关闭。';
}
if (access.nodeType && access.nodeType !== target.nodeType) {
return '回读证据属于另一种节点类型;当前入口保持关闭并重新核验。';
}
if (target.domain.routeId !== 'fifth') {
return `${target.domain.displayName}的独立运行端点与主题包尚未登记;当前只能查看公开门厅。`;
}