feat(domain): project verified runtime policy to desktop

This commit is contained in:
冰朔 2026-08-10 10:25:25 +08:00
commit 683e135f5e
12 changed files with 224 additions and 25 deletions

View file

@ -13,6 +13,7 @@ import { ModuleLibrarySheet } from './components/ModuleLibrarySheet';
import { cleanDisplayText } from './presentation';
import { WorldEntry } from './components/WorldEntry';
import { DomainConnectionSheet } from './components/DomainConnectionSheet';
import type { DomainAccessProjection } from './domain-connection';
import { createDomainEntryTarget, type DomainEntryTarget, type DomainNodeType } from './domain-entry-state';
import type { DomainRouteId } from './public-domain-directory';
@ -43,22 +44,6 @@ interface ServerProfile {
channelSubtitle?: string;
}
interface DomainAccessStatus {
blockers: string[];
domainId?: string;
nodeId?: string;
nodeType?: DomainNodeType;
runtimeReady: boolean;
stage: 'checking' | 'login-required' | 'identity-verified' | 'runtime-ready';
trustSource?: {
reason?: 'ANCHOR_INVALID' | 'REGISTRY_INVALID' | 'SOURCE_UNAVAILABLE';
registryVersion: string | null;
signerCount: number;
sourceCommit: string | null;
status: 'CURRENT' | 'DEGRADED_LAST_KNOWN_GOOD' | 'UNAVAILABLE';
};
}
function findFirstDocument(nodes: DocTreeNode[]): string | null {
for (const node of nodes) {
if (node.type === 'document') return node.path;
@ -103,7 +88,7 @@ export default function App() {
const [moduleMessage, setModuleMessage] = useState('');
const [lastChannelReceipt, setLastChannelReceipt] = useState('');
const [worldEntered, setWorldEntered] = useState(false);
const [domainAccess, setDomainAccess] = useState<DomainAccessStatus>({ blockers: [], runtimeReady: false, stage: 'checking' });
const [domainAccess, setDomainAccess] = useState<DomainAccessProjection>({ blockers: [], runtimeReady: false, stage: 'checking' });
const domainAccessRequest = useRef(0);
const storageMode = repositoryStatus?.remote ? 'server' : 'local';

View file

@ -1,5 +1,5 @@
import { Cloud, Laptop, ShieldCheck, X } from 'lucide-react';
import { projectDomainConnectionSteps, projectDomainTrustSource, type DomainAccessProjection } from '../domain-connection';
import { projectDomainConnectionSteps, projectDomainRuntimePolicyReceipt, projectDomainTrustSource, type DomainAccessProjection } from '../domain-connection';
import { canEnterSelectedDomainRuntime, projectDomainRuntimeBoundary, type DomainEntryTarget, type DomainNodeType } from '../domain-entry-state';
import { publicDomainDirectory, type DomainRouteId } from '../public-domain-directory';
@ -17,6 +17,7 @@ interface Props {
export function DomainConnectionSheet({ access, target, open, onClose, onSelectTarget, onSelectNodeType, onEnterRuntime, onOpenCodeChannel }: Props) {
if (!open) return null;
const steps = projectDomainConnectionSteps(access);
const policyReceipt = projectDomainRuntimePolicyReceipt(access);
const canEnter = canEnterSelectedDomainRuntime(target, access);
return (
@ -54,6 +55,13 @@ export function DomainConnectionSheet({ access, target, open, onClose, onSelectT
))}
</ol>
{policyReceipt.length > 0 && <section className="domain-policy-receipt" aria-labelledby="domain-policy-receipt-title">
<h3 id="domain-policy-receipt-title"></h3>
<dl>
{policyReceipt.map(row => <div key={row.label}><dt>{row.label}</dt><dd>{row.value}</dd></div>)}
</dl>
</section>}
<p className="domain-connection-boundary">{projectDomainRuntimeBoundary(target, access)} </p>
</>}

View file

@ -1,6 +1,16 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { projectDomainConnectionSteps, projectDomainTrustSource } from './domain-connection.js';
import { projectDomainConnectionSteps, projectDomainRuntimePolicyReceipt, projectDomainTrustSource } from './domain-connection.js';
const verifiedRuntimePolicy = {
allowedSessionScopes: ['domain:enter', 'knowledge:read'],
forbiddenDataScopes: ['private:relationship-core'],
manifestDigest: 'a'.repeat(64),
permissionPolicyRef: 'policy://origin-domain/default',
routeRef: 'domain-route://origin-domain/runtime',
themeOwner: 'fifth-domain' as const,
themePackageRef: 'theme://origin-domain/lake-reflects-stars',
};
test('code-channel account absence cannot be projected as a domain runtime login', () => {
const steps = projectDomainConnectionSteps({
@ -32,10 +42,29 @@ test('verified account and node remain identity-only without domain handoff evid
});
test('runtime-ready requires all four verified steps', () => {
const steps = projectDomainConnectionSteps({ blockers: [], runtimeReady: true, stage: 'runtime-ready' });
const steps = projectDomainConnectionSteps({ blockers: [], runtimePolicy: verifiedRuntimePolicy, runtimeReady: true, stage: 'runtime-ready' });
assert.ok(steps.every(step => step.state === 'verified'));
});
test('a runtime-ready access projects only a compact verified policy receipt', () => {
const rows = projectDomainRuntimePolicyReceipt({
blockers: [],
runtimePolicy: verifiedRuntimePolicy,
runtimeReady: true,
stage: 'runtime-ready',
});
assert.deepEqual(rows.map(row => row.label), ['主题包', '运行路线', '权限策略', '允许会话', '禁止数据', '清单摘要']);
assert.match(rows[0].value, /^theme:\/\/origin-domain\//);
assert.match(rows.at(-1)?.value ?? '', /^a{12}…a{8}$/);
assert.doesNotMatch(JSON.stringify(rows), /signature|signer|repository|publicKey/i);
assert.deepEqual(projectDomainRuntimePolicyReceipt({
blockers: ['matching_connection_receipt_missing'],
runtimePolicy: verifiedRuntimePolicy,
runtimeReady: false,
stage: 'identity-verified',
}), []);
});
test('a current empty signer registry is projected as a real manifest blocker', () => {
const access = {
blockers: ['verified_domain_manifest_missing'],

View file

@ -1,10 +1,21 @@
export type DomainAccessStage = 'checking' | 'login-required' | 'identity-verified' | 'runtime-ready';
export interface VerifiedDomainRuntimePolicyProjection {
allowedSessionScopes: readonly string[];
forbiddenDataScopes: readonly string[];
manifestDigest: string;
permissionPolicyRef: string;
routeRef: string;
themeOwner: 'domain-team' | 'fifth-domain';
themePackageRef: string;
}
export interface DomainAccessProjection {
blockers: string[];
domainId?: string;
nodeId?: string;
nodeType?: 'local-terminal' | 'cloud-resident';
runtimePolicy?: VerifiedDomainRuntimePolicyProjection;
runtimeReady: boolean;
stage: DomainAccessStage;
trustSource?: {
@ -23,6 +34,11 @@ export interface DomainConnectionStep {
detail: string;
}
export interface DomainRuntimePolicyReceiptRow {
label: string;
value: string;
}
const blockerToStep: Record<string, DomainConnectionStep['id']> = {
account_identity_missing: 'identity',
verified_node_registration_missing: 'identity',
@ -90,3 +106,16 @@ export function projectDomainTrustSource(access: DomainAccessProjection): string
const health = source.status === 'CURRENT' ? '当前' : '上次可信快照';
return `可信清单 ${health} · v${version} · ${commit} · 已登记签名人 ${source.signerCount}`;
}
export function projectDomainRuntimePolicyReceipt(access: DomainAccessProjection): DomainRuntimePolicyReceiptRow[] {
const policy = access.runtimeReady ? access.runtimePolicy : undefined;
if (!policy) return [];
return [
{ label: '主题包', value: policy.themePackageRef },
{ label: '运行路线', value: policy.routeRef },
{ label: '权限策略', value: policy.permissionPolicyRef },
{ label: '允许会话', value: policy.allowedSessionScopes.join(' · ') },
{ label: '禁止数据', value: policy.forbiddenDataScopes.join(' · ') },
{ label: '清单摘要', value: `${policy.manifestDigest.slice(0, 12)}${policy.manifestDigest.slice(-8)}` },
];
}

View file

@ -2,7 +2,24 @@ import assert from 'node:assert/strict';
import test from 'node:test';
import { canEnterSelectedDomainRuntime, createDomainEntryTarget, projectDomainRuntimeBoundary } from './domain-entry-state.js';
const ready = (domainId: string, nodeType: 'local-terminal' | 'cloud-resident' = 'local-terminal') => ({ blockers: [], domainId, nodeType, runtimeReady: true, stage: 'runtime-ready' as const });
const validRuntimePolicy = {
allowedSessionScopes: ['domain:enter', 'knowledge:read'],
forbiddenDataScopes: ['private:relationship-core'],
manifestDigest: 'a'.repeat(64),
permissionPolicyRef: 'policy://origin-domain/default',
routeRef: 'domain-route://origin-domain/runtime',
themeOwner: 'fifth-domain' as const,
themePackageRef: 'theme://origin-domain/lake-reflects-stars',
};
const ready = (domainId: string, nodeType: 'local-terminal' | 'cloud-resident' = 'local-terminal') => ({
blockers: [],
domainId,
nodeType,
runtimePolicy: validRuntimePolicy,
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);
@ -30,3 +47,20 @@ test('the Fifth Domain opens only with matching runtime-ready evidence', () => {
assert.match(projectDomainRuntimeBoundary(fifth, ready('DOM-FIFTH-0001', 'local-terminal')), /另一种节点类型/);
assert.match(projectDomainRuntimeBoundary(fifth, ready('DOM-FIFTH-0001', 'cloud-resident')), /已经匹配/);
});
test('the Fifth Domain stays closed without the exact verified runtime policy projection', () => {
const fifth = createDomainEntryTarget('fifth');
const base = ready('DOM-FIFTH-0001');
assert.equal(canEnterSelectedDomainRuntime(fifth, { ...base, runtimePolicy: undefined }), false);
assert.match(projectDomainRuntimeBoundary(fifth, { ...base, runtimePolicy: undefined }), /可信运行策略缺失或不匹配/);
for (const runtimePolicy of [
{ ...validRuntimePolicy, manifestDigest: 'not-a-digest' },
{ ...validRuntimePolicy, themeOwner: 'domain-team' as const },
{ ...validRuntimePolicy, themePackageRef: 'theme://enterprise/fifth-look' },
{ ...validRuntimePolicy, routeRef: 'domain-route://enterprise/fifth' },
{ ...validRuntimePolicy, allowedSessionScopes: ['knowledge:read'] },
]) {
assert.equal(canEnterSelectedDomainRuntime(fifth, { ...base, runtimePolicy }), false);
}
});

View file

@ -20,7 +20,14 @@ export function canEnterSelectedDomainRuntime(target: DomainEntryTarget | null,
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';
if (target.domain.routeId !== 'fifth') return false;
const policy = access.runtimePolicy;
return Boolean(policy
&& /^[a-f0-9]{64}$/.test(policy.manifestDigest)
&& policy.themeOwner === 'fifth-domain'
&& policy.themePackageRef.startsWith('theme://origin-domain/')
&& policy.routeRef.startsWith('domain-route://origin-domain/')
&& policy.allowedSessionScopes.includes('domain:enter'));
}
export function projectDomainRuntimeBoundary(target: DomainEntryTarget | null, access: DomainAccessProjection): string {
@ -34,7 +41,8 @@ export function projectDomainRuntimeBoundary(target: DomainEntryTarget | null, a
if (target.domain.routeId !== 'fifth') {
return `${target.domain.displayName}的独立运行端点与主题包尚未登记;当前只能查看公开门厅。`;
}
return access.runtimeReady
? '第五域运行端点、签名清单、会话能力与在线回执已经匹配。'
: '第五域入口保持关闭,直到四项真实接入证据全部匹配。';
if (!access.runtimeReady) return '第五域入口保持关闭,直到四项真实接入证据全部匹配。';
return canEnterSelectedDomainRuntime(target, access)
? '第五域运行端点、签名清单、主题策略、会话能力与在线回执已经匹配。'
: '域接入证据声称已就绪,但可信运行策略缺失或不匹配;第五域入口保持关闭。';
}

View file

@ -3240,6 +3240,27 @@ select:focus-visible {
.domain-connection-steps em { color: #d9ad5f; font-size: 9px; font-style: normal; }
.domain-connection-steps li[data-state="verified"] em { color: var(--lake-success); }
.domain-policy-receipt {
margin: 12px 20px 0;
padding: 13px 14px;
border: 1px solid rgba(81, 200, 120, .2);
border-radius: 10px;
background: rgba(8, 26, 29, .58);
}
.domain-policy-receipt h3 {
margin: 0 0 9px;
color: var(--lake-success);
font-size: 10px;
font-weight: 600;
letter-spacing: .08em;
}
.domain-policy-receipt dl { display: grid; gap: 6px; margin: 0; }
.domain-policy-receipt dl > div { display: grid; grid-template-columns: 72px minmax(0, 1fr); gap: 10px; }
.domain-policy-receipt dt { color: var(--lake-muted); font-size: 9px; }
.domain-policy-receipt dd { margin: 0; overflow-wrap: anywhere; color: var(--lake-text-soft); font-size: 9px; line-height: 1.45; }
.domain-connection-boundary {
margin-bottom: 18px;
padding: 10px 12px;