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'
|
||||
|
|
|
|||
Loading…
Reference in a new issue