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

@ -2,6 +2,7 @@ import {
evaluateDomainAccess,
verifyDomainAccessHandoffFromSnapshot,
type DomainAccessEvidence,
type DomainNodeType,
type DomainAccessStatus,
} from './domain-access.js';
import {
@ -14,10 +15,11 @@ const IDENTIFIER_PATTERN = /^[A-Z0-9][A-Z0-9._:-]{1,159}$/;
export interface DomainIdentityProjection {
accountVerified: boolean;
nodeId: string;
nodeRegistrationVerified: boolean;
}
export interface DomainIdentitySource {
read(domainId: string): Promise<DomainIdentityProjection>;
read(domainId: string, nodeType: DomainNodeType): Promise<DomainIdentityProjection>;
}
export interface DomainRuntimeHandoffCandidate {
@ -29,7 +31,7 @@ export interface DomainRuntimeHandoffCandidate {
}
export interface DomainRuntimeHandoffSource {
read(domainId: string, nodeId: string): Promise<DomainRuntimeHandoffCandidate | null>;
read(domainId: string, nodeId: string, nodeType: DomainNodeType): Promise<DomainRuntimeHandoffCandidate | null>;
}
export interface DomainAccessProjection {
@ -37,6 +39,7 @@ export interface DomainAccessProjection {
domainId: string;
localWorkspaceAllowed: true;
nodeId: string;
nodeType: DomainNodeType;
runtimeReady: boolean;
stage: DomainAccessStatus['stage'];
trustSource: Readonly<TrustedSignerSnapshotReceipt>;
@ -44,7 +47,7 @@ export interface DomainAccessProjection {
function safeIdentity(input: unknown): DomainIdentityProjection {
if (typeof input !== 'object' || input === null || Array.isArray(input)) {
return { accountVerified: false, nodeId: '' };
return { accountVerified: false, nodeId: '', nodeRegistrationVerified: false };
}
const candidate = input as Record<string, unknown>;
const nodeId = typeof candidate.nodeId === 'string' && IDENTIFIER_PATTERN.test(candidate.nodeId)
@ -53,6 +56,7 @@ function safeIdentity(input: unknown): DomainIdentityProjection {
return {
accountVerified: candidate.accountVerified === true && Boolean(nodeId),
nodeId,
nodeRegistrationVerified: candidate.nodeRegistrationVerified === true && Boolean(nodeId),
};
}
@ -63,22 +67,25 @@ export class DomainAccessOrchestrator {
private readonly handoffs: DomainRuntimeHandoffSource,
) {}
async domainAccess(domainId: string, now = Date.now()): Promise<Readonly<DomainAccessProjection>> {
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([
this.signerSnapshots.refresh(),
this.identities.read(domainId).then(safeIdentity, () => safeIdentity(null)),
this.identities.read(domainId, nodeType).then(safeIdentity, () => safeIdentity(null)),
]);
const evidence: DomainAccessEvidence = {
accountVerified: identity.accountVerified,
domainId,
nodeId: identity.nodeId,
nodeRegistrationVerified: identity.nodeRegistrationVerified,
nodeType,
};
if (identity.accountVerified) {
if (identity.accountVerified && identity.nodeRegistrationVerified) {
let candidate: DomainRuntimeHandoffCandidate | null = null;
try {
candidate = await this.handoffs.read(domainId, identity.nodeId);
candidate = await this.handoffs.read(domainId, identity.nodeId, nodeType);
} catch {
candidate = null;
}
@ -88,6 +95,7 @@ export class DomainAccessOrchestrator {
candidate.handoff,
domainId,
identity.nodeId,
nodeType,
candidate.signerLookup,
snapshot,
now,