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
|
|
@ -294,6 +294,11 @@ test('current registries, signed node claim and verified handoff project runtime
|
|||
assert.equal(status.stage, 'runtime-ready');
|
||||
assert.equal(status.runtimeReady, true);
|
||||
assert.deepEqual(status.blockers, []);
|
||||
assert.equal(status.runtimePolicy?.themeOwner, 'fifth-domain');
|
||||
assert.equal(status.runtimePolicy?.themePackageRef, 'theme://origin-domain/lake-reflects-stars');
|
||||
assert.equal(status.runtimePolicy?.routeRef, 'domain-route://origin-domain/runtime');
|
||||
assert.deepEqual(status.runtimePolicy?.allowedSessionScopes, ['domain:enter', 'knowledge:read']);
|
||||
assert.doesNotMatch(JSON.stringify(status.runtimePolicy), /signature|signer|repository|publicKey/i);
|
||||
});
|
||||
|
||||
test('source failures are reduced to safe status without raw details', async () => {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {
|
|||
type DomainAccessEvidence,
|
||||
type DomainNodeType,
|
||||
type DomainAccessStatus,
|
||||
type DomainRuntimePolicyProjection,
|
||||
} from './domain-access.js';
|
||||
import {
|
||||
TrustedSignerSnapshotLoader,
|
||||
|
|
@ -57,6 +58,7 @@ export interface DomainAccessProjection {
|
|||
nodeId: string;
|
||||
nodeType: DomainNodeType;
|
||||
nodeRegistrationSource: Readonly<NodeRegistrationSnapshotReceipt>;
|
||||
runtimePolicy?: DomainRuntimePolicyProjection;
|
||||
runtimeReady: boolean;
|
||||
stage: DomainAccessStatus['stage'];
|
||||
trustSource: Readonly<TrustedSignerSnapshotReceipt>;
|
||||
|
|
|
|||
|
|
@ -208,6 +208,27 @@ test('runtime access requires matching manifest, scoped capability and online re
|
|||
assert.equal(status.runtimeReady, true);
|
||||
assert.equal(status.stage, 'runtime-ready');
|
||||
assert.deepEqual(status.blockers, []);
|
||||
assert.deepEqual(status.runtimePolicy, {
|
||||
allowedSessionScopes: ['domain:enter', 'knowledge:read'],
|
||||
forbiddenDataScopes: ['private:relationship-core'],
|
||||
manifestDigest: DIGEST,
|
||||
permissionPolicyRef: 'policy://origin-domain/default',
|
||||
routeRef: 'domain-route://origin-domain/runtime',
|
||||
themeOwner: 'fifth-domain',
|
||||
themePackageRef: 'theme://origin-domain/lake-reflects-stars',
|
||||
});
|
||||
assert.equal(Object.isFrozen(status.runtimePolicy), true);
|
||||
assert.equal(Object.isFrozen(status.runtimePolicy?.allowedSessionScopes), true);
|
||||
assert.deepEqual(Object.keys(status.runtimePolicy ?? {}).sort(), [
|
||||
'allowedSessionScopes',
|
||||
'forbiddenDataScopes',
|
||||
'manifestDigest',
|
||||
'permissionPolicyRef',
|
||||
'routeRef',
|
||||
'themeOwner',
|
||||
'themePackageRef',
|
||||
]);
|
||||
assert.doesNotMatch(JSON.stringify(status.runtimePolicy), /signature|signer|repository|sourceCommit|publicKey/i);
|
||||
});
|
||||
|
||||
test('the runtime handoff entry resolves its signer only from a loaded snapshot', async () => {
|
||||
|
|
@ -302,6 +323,7 @@ test('mismatched or expired evidence fails closed', () => {
|
|||
sessionCapability: { capabilityId: 'CAP-001', domainId: 'DOM-FIFTH-0001', expiresAt: NOW, nodeId: 'LOCAL-001', nodeType: NODE_TYPE, scopes: ['domain:enter'] },
|
||||
}, NOW);
|
||||
assert.equal(status.runtimeReady, false);
|
||||
assert.equal(status.runtimePolicy, undefined);
|
||||
assert.ok(status.blockers.includes('scoped_session_capability_missing'));
|
||||
assert.ok(status.blockers.includes('matching_connection_receipt_missing'));
|
||||
});
|
||||
|
|
@ -446,5 +468,6 @@ test('copying verified-looking fields cannot bypass the in-process verifier boun
|
|||
sessionCapability: verifiedHandoff.sessionCapability,
|
||||
}, NOW);
|
||||
assert.equal(status.runtimeReady, false);
|
||||
assert.equal(status.runtimePolicy, undefined);
|
||||
assert.ok(status.blockers.includes('verified_domain_manifest_missing'));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -99,12 +99,23 @@ export interface DomainAccessEvidence {
|
|||
sessionCapability?: DomainSessionCapability;
|
||||
}
|
||||
|
||||
export interface DomainRuntimePolicyProjection {
|
||||
allowedSessionScopes: readonly string[];
|
||||
forbiddenDataScopes: readonly string[];
|
||||
manifestDigest: string;
|
||||
permissionPolicyRef: string;
|
||||
routeRef: string;
|
||||
themeOwner: DomainThemeOwner;
|
||||
themePackageRef: string;
|
||||
}
|
||||
|
||||
export interface DomainAccessStatus {
|
||||
blockers: string[];
|
||||
domainId: string;
|
||||
localWorkspaceAllowed: true;
|
||||
nodeId: string;
|
||||
nodeType: DomainNodeType;
|
||||
runtimePolicy?: DomainRuntimePolicyProjection;
|
||||
runtimeReady: boolean;
|
||||
stage: 'login-required' | 'identity-verified' | 'runtime-ready';
|
||||
}
|
||||
|
|
@ -347,12 +358,24 @@ export function evaluateDomainAccess(evidence: DomainAccessEvidence, now = Date.
|
|||
blockers.push('matching_connection_receipt_missing');
|
||||
}
|
||||
const runtimeReady = blockers.length === 0;
|
||||
const runtimePolicy = runtimeReady && manifest
|
||||
? Object.freeze({
|
||||
allowedSessionScopes: Object.freeze([...manifest.runtimePolicy.allowedSessionScopes]),
|
||||
forbiddenDataScopes: Object.freeze([...manifest.runtimePolicy.forbiddenDataScopes]),
|
||||
manifestDigest: manifest.digest,
|
||||
permissionPolicyRef: manifest.runtimePolicy.permissionPolicyRef,
|
||||
routeRef: manifest.runtimePolicy.routeRef,
|
||||
themeOwner: manifest.runtimePolicy.themeOwner,
|
||||
themePackageRef: manifest.runtimePolicy.themePackageRef,
|
||||
})
|
||||
: undefined;
|
||||
return {
|
||||
blockers,
|
||||
domainId: evidence.domainId,
|
||||
localWorkspaceAllowed: true,
|
||||
nodeId: evidence.nodeId,
|
||||
nodeType: evidence.nodeType,
|
||||
...(runtimePolicy ? { runtimePolicy } : {}),
|
||||
runtimeReady,
|
||||
stage: runtimeReady
|
||||
? 'runtime-ready'
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
# HoloLake 桌面端可信域策略投影验收
|
||||
|
||||
日期:2026-08-10
|
||||
阶段:DEV-20260809-007 / Stage 008
|
||||
状态:实现前验收基线
|
||||
|
||||
## 目标
|
||||
|
||||
把服务端已经完成密码学核验的域运行策略,以只读、最小化的方式投影到真实 HoloLake 桌面入口。桌面端在载入第五域运行体前,除了核对域编号和节点类型,还必须核对签名清单绑定的运行路线、主题归属、主题包和会话权限。
|
||||
|
||||
这不是新增一套前端 manifest,也不是让 UI 自己判断签名;唯一权威仍是桌面进程内已经通过可信签名人快照、Ed25519 签名、清单摘要、会话能力和在线回执共同验证的服务端结果。
|
||||
|
||||
## 验收标准
|
||||
|
||||
1. 只有本进程内完成密码学核验、且四项域接入证据全部匹配时,服务端才返回 `runtimePolicy` 投影。
|
||||
2. 投影仅包含:manifest 摘要、运行路线、主题归属、主题包、权限策略、允许的会话范围和禁止的数据范围。
|
||||
3. 投影不得包含签名、签名人公钥、仓库地址、源提交、连接凭据或任何私人数据。
|
||||
4. 非 `runtime-ready` 状态不得携带策略投影;复制一份外观相同的 manifest 也不能越过进程内验证边界。
|
||||
5. 第五域最终入口必须同时满足:稳定域编号和节点类型匹配、摘要格式有效、主题归属为 `fifth-domain`、主题包属于 `theme://origin-domain/`、运行路线属于 `domain-route://origin-domain/`,并允许 `domain:enter`。
|
||||
6. 缺少或不匹配可信策略投影时,即使上层错误声称 `runtimeReady`,第五域渲染器也保持关闭,并向用户说明是策略回读不完整或不匹配。
|
||||
7. 四个企业域继续只有公开门厅;在各自独立运行端点和主题包真正登记、打包以前,不得复用第五域渲染器。
|
||||
8. 域接入面板以紧凑、可读的可信回执展示真实策略,不制造假状态,不暴露验证内部秘密。
|
||||
9. 公共灯塔和五域公开门厅的匿名可见边界不变;本阶段不改变已选定的公共主题或第五域主题。
|
||||
10. 本阶段只证明代码、测试、构建、安装和运行中实际完成的层级;真实生产 manifest、注册端点、handoff 端点或生产会话不存在时继续明确记为 `NOT_EXISTS_0`。
|
||||
|
||||
## 失败关闭边界
|
||||
|
||||
- 签名清单未验证:不投影策略,不进入域。
|
||||
- 策略投影缺失或被篡改:不进入域。
|
||||
- 域、节点、路线、主题归属、主题命名空间或权限任一不匹配:不进入域。
|
||||
- 代码频道登录、仓库同步或本地知识工作区可用:均不得被解释为已经取得域运行权限。
|
||||
|
||||
Loading…
Reference in a new issue