feat(domain): project verified runtime policy to desktop
This commit is contained in:
parent
fbff657d88
commit
683e135f5e
12 changed files with 224 additions and 25 deletions
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>}
|
||||
|
||||
|
|
|
|||
|
|
@ -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'],
|
||||
|
|
|
|||
|
|
@ -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)}` },
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
? '第五域运行端点、签名清单、主题策略、会话能力与在线回执已经匹配。'
|
||||
: '域接入证据声称已就绪,但可信运行策略缺失或不匹配;第五域入口保持关闭。';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue