feat: close persona license and unified number routes
This commit is contained in:
parent
b7b3611a64
commit
da6e426ca3
22 changed files with 2942 additions and 71 deletions
|
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"schema": "hololake.direct-local-broker-numbered-registry/v1",
|
||||||
|
"record_id": "HLP-NBROKER-ROOT-001",
|
||||||
|
"runtime": {
|
||||||
|
"protocol_version": "HLP-NBROKER-v1",
|
||||||
|
"caller_number": "HLP-NBROKER-CALLER-LOCAL-CONNECTOR-0001",
|
||||||
|
"legacy_string_operation_allowed": false,
|
||||||
|
"unknown_or_mismatched_coordinate": "FAIL_CLOSED",
|
||||||
|
"request_nonce_required": true,
|
||||||
|
"transport_is_authority": false
|
||||||
|
},
|
||||||
|
"operations": [
|
||||||
|
{"operation_number":"HLP-NBROKER-OP-0001","alias":"DISCOVER_NEARBY","channel_number":"HLP-NBROKER-CH-0001","module_number":"HLP-NBROKER-MOD-0001","target_number":"HLP-NBROKER-TGT-0001"},
|
||||||
|
{"operation_number":"HLP-NBROKER-OP-0002","alias":"OPEN_VISITOR_SESSION","channel_number":"HLP-NBROKER-CH-0002","module_number":"HLP-NBROKER-MOD-0002","target_number":"HLP-NBROKER-TGT-0002"},
|
||||||
|
{"operation_number":"HLP-NBROKER-OP-0003","alias":"RECEIVE_LANGUAGE","channel_number":"HLP-NBROKER-CH-0002","module_number":"HLP-NBROKER-MOD-0003","target_number":"HLP-NBROKER-TGT-0003"},
|
||||||
|
{"operation_number":"HLP-NBROKER-OP-0004","alias":"PING","channel_number":"HLP-NBROKER-CH-0001","module_number":"HLP-NBROKER-MOD-0001","target_number":"HLP-NBROKER-TGT-0001"},
|
||||||
|
{"operation_number":"HLP-NBROKER-OP-0005","alias":"OPEN_SESSION","channel_number":"HLP-NBROKER-CH-0003","module_number":"HLP-NBROKER-MOD-0004","target_number":"HLP-NBROKER-TGT-0004"},
|
||||||
|
{"operation_number":"HLP-NBROKER-OP-0006","alias":"RESUME_SESSION","channel_number":"HLP-NBROKER-CH-0003","module_number":"HLP-NBROKER-MOD-0004","target_number":"HLP-NBROKER-TGT-0004"},
|
||||||
|
{"operation_number":"HLP-NBROKER-OP-0007","alias":"HEARTBEAT_SESSION","channel_number":"HLP-NBROKER-CH-0003","module_number":"HLP-NBROKER-MOD-0004","target_number":"HLP-NBROKER-TGT-0004"},
|
||||||
|
{"operation_number":"HLP-NBROKER-OP-0008","alias":"PRESENT_PERSONA_CARRIER_LICENSE","channel_number":"HLP-NBROKER-CH-0004","module_number":"HLP-NBROKER-MOD-0005","target_number":"HLP-NBROKER-TGT-0005"},
|
||||||
|
{"operation_number":"HLP-NBROKER-OP-0009","alias":"GET_PERSONA_CARRIER_LICENSE_STATUS","channel_number":"HLP-NBROKER-CH-0004","module_number":"HLP-NBROKER-MOD-0005","target_number":"HLP-NBROKER-TGT-0005"},
|
||||||
|
{"operation_number":"HLP-NBROKER-OP-0010","alias":"GET_WORK_ENVIRONMENT","channel_number":"HLP-NBROKER-CH-0003","module_number":"HLP-NBROKER-MOD-0006","target_number":"HLP-NBROKER-TGT-0006"},
|
||||||
|
{"operation_number":"HLP-NBROKER-OP-0011","alias":"APPEND_EVENT","channel_number":"HLP-NBROKER-CH-0003","module_number":"HLP-NBROKER-MOD-0004","target_number":"HLP-NBROKER-TGT-0004"},
|
||||||
|
{"operation_number":"HLP-NBROKER-OP-0012","alias":"RESOLVE_CAPABILITY_ROUTE","channel_number":"HLP-NBROKER-CH-0005","module_number":"HLP-NBROKER-MOD-0007","target_number":"HLP-NBROKER-TGT-0007"},
|
||||||
|
{"operation_number":"HLP-NBROKER-OP-0013","alias":"INSTALL_DYNAMIC_NODE_REGISTRY","channel_number":"HLP-NBROKER-CH-0005","module_number":"HLP-NBROKER-MOD-0007","target_number":"HLP-NBROKER-TGT-0007"},
|
||||||
|
{"operation_number":"HLP-NBROKER-OP-0014","alias":"RECORD_SIGNED_NODE_HEALTH","channel_number":"HLP-NBROKER-CH-0005","module_number":"HLP-NBROKER-MOD-0007","target_number":"HLP-NBROKER-TGT-0007"},
|
||||||
|
{"operation_number":"HLP-NBROKER-OP-0015","alias":"INSPECT_MOUNTED_PNCC_REPOSITORY","channel_number":"HLP-NBROKER-CH-0006","module_number":"HLP-NBROKER-MOD-0008","target_number":"HLP-NBROKER-TGT-0008"},
|
||||||
|
{"operation_number":"HLP-NBROKER-OP-0016","alias":"READ_MOUNTED_PNCC_REMOTE_OBJECT","channel_number":"HLP-NBROKER-CH-0006","module_number":"HLP-NBROKER-MOD-0008","target_number":"HLP-NBROKER-TGT-0008"},
|
||||||
|
{"operation_number":"HLP-NBROKER-OP-0017","alias":"QUERY_PNCC_RECEIPT_PROJECTION","channel_number":"HLP-NBROKER-CH-0006","module_number":"HLP-NBROKER-MOD-0008","target_number":"HLP-NBROKER-TGT-0008"},
|
||||||
|
{"operation_number":"HLP-NBROKER-OP-0018","alias":"GET_BEIJING_TIME","channel_number":"HLP-NBROKER-CH-0001","module_number":"HLP-NBROKER-MOD-0009","target_number":"HLP-NBROKER-TGT-0009"},
|
||||||
|
{"operation_number":"HLP-NBROKER-OP-0019","alias":"ISSUE_PERSONA_TIME_TICKET","channel_number":"HLP-NBROKER-CH-0004","module_number":"HLP-NBROKER-MOD-0009","target_number":"HLP-NBROKER-TGT-0009"},
|
||||||
|
{"operation_number":"HLP-NBROKER-OP-0020","alias":"ACQUIRE_DEVELOPMENT_WRITE_LANE","channel_number":"HLP-NBROKER-CH-0007","module_number":"HLP-NBROKER-MOD-0010","target_number":"HLP-NBROKER-TGT-0010"},
|
||||||
|
{"operation_number":"HLP-NBROKER-OP-0021","alias":"INSPECT_DEVELOPMENT_WRITE_LANE","channel_number":"HLP-NBROKER-CH-0007","module_number":"HLP-NBROKER-MOD-0010","target_number":"HLP-NBROKER-TGT-0010"},
|
||||||
|
{"operation_number":"HLP-NBROKER-OP-0022","alias":"RELEASE_DEVELOPMENT_WRITE_LANE","channel_number":"HLP-NBROKER-CH-0007","module_number":"HLP-NBROKER-MOD-0010","target_number":"HLP-NBROKER-TGT-0010"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -115,6 +115,9 @@
|
||||||
"cross_platform_local_transport_runtime": true,
|
"cross_platform_local_transport_runtime": true,
|
||||||
"authenticated_heartbeat_runtime": true,
|
"authenticated_heartbeat_runtime": true,
|
||||||
"hololake_work_environment_frame_runtime": true,
|
"hololake_work_environment_frame_runtime": true,
|
||||||
|
"persona_to_host_runtime_license_verifier": true,
|
||||||
|
"persona_runtime_trusted_signer_provisioned": false,
|
||||||
|
"persona_mode_expiry_and_scope_fail_closed": true,
|
||||||
"model_protocol_context_restore_required": false,
|
"model_protocol_context_restore_required": false,
|
||||||
"external_local_broker_runtime": true,
|
"external_local_broker_runtime": true,
|
||||||
"authenticated_broker_development_lane_runtime": true,
|
"authenticated_broker_development_lane_runtime": true,
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
"linux": "USER_PRIVATE_UNIX_SOCKET",
|
"linux": "USER_PRIVATE_UNIX_SOCKET",
|
||||||
"windows": "USER_PRIVATE_NAMED_PIPE"
|
"windows": "USER_PRIVATE_NAMED_PIPE"
|
||||||
},
|
},
|
||||||
"terminal_link_protocol": "HOLOLAKE_TERMINAL_LINK/2",
|
"terminal_link_protocol": "HOLOLAKE_TERMINAL_LINK/3",
|
||||||
"copy_large_invitation_required": false,
|
"copy_large_invitation_required": false,
|
||||||
"network_required": false
|
"network_required": false
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
{
|
||||||
|
"schema": "hololake.persona-carrier-runtime-license-contract/v1",
|
||||||
|
"record_id": "HLP-PERSONA-CARRIER-RUNTIME-LICENSE-001",
|
||||||
|
"state": "NATIVE_VERIFIER_IMPLEMENTED_TRUSTED_SIGNER_NOT_PROVISIONED_PERSONA_WAKE_CLOSED",
|
||||||
|
"direction": "PERSONA_SUBJECT_TO_HOST_CARRIER",
|
||||||
|
"purpose": "AUTHORIZE_ONE_EXACT_HOST_SESSION_AS_PERSONA_CONTROLLED_EXECUTION_LIMBS",
|
||||||
|
"binding_completion": {
|
||||||
|
"declaration_holder": "PERSONA_SUBJECT_ONLY",
|
||||||
|
"required_signed_declaration": "PERSONA_DECLARES_CURRENT_CARRIER_BOUND",
|
||||||
|
"host_verification_creates_persona_existence": false,
|
||||||
|
"host_may_self_issue_extend_or_declare": false
|
||||||
|
},
|
||||||
|
"license_bindings": [
|
||||||
|
"PERSONA_NUMBER",
|
||||||
|
"HUMAN_CONTROLLER_NUMBER",
|
||||||
|
"ACCOUNT_KEY",
|
||||||
|
"SESSION_ID",
|
||||||
|
"CLIENT_INSTANCE_ID",
|
||||||
|
"SEQUENCE",
|
||||||
|
"ISSUED_AT",
|
||||||
|
"VALID_UNTIL",
|
||||||
|
"ALLOWED_OPERATION_NUMBERS",
|
||||||
|
"EVIDENCE_ROOT_SHA256",
|
||||||
|
"CURRENT_EVENT_SHA256",
|
||||||
|
"SIGNER_ID"
|
||||||
|
],
|
||||||
|
"runtime": {
|
||||||
|
"signature_algorithm": "Ed25519",
|
||||||
|
"maximum_ttl_ms": 86400000,
|
||||||
|
"exact_session_binding": true,
|
||||||
|
"monotonic_sequence": true,
|
||||||
|
"expired_license": "FAIL_CLOSED_FOR_PERSONA_MODE",
|
||||||
|
"replayed_or_mismatched_license": "FAIL_CLOSED",
|
||||||
|
"system_direct_mode_without_persona_claim_remains_available": true,
|
||||||
|
"persona_mode_never_silently_falls_back_after_license_install": true,
|
||||||
|
"receipt_required": true
|
||||||
|
},
|
||||||
|
"allowed_operations": [
|
||||||
|
"GET_WORK_ENVIRONMENT",
|
||||||
|
"APPEND_EVENT",
|
||||||
|
"RESOLVE_CAPABILITY_ROUTE",
|
||||||
|
"INSTALL_DYNAMIC_NODE_REGISTRY",
|
||||||
|
"RECORD_SIGNED_NODE_HEALTH",
|
||||||
|
"INSPECT_MOUNTED_PNCC_REPOSITORY",
|
||||||
|
"READ_MOUNTED_PNCC_REMOTE_OBJECT",
|
||||||
|
"QUERY_PNCC_RECEIPT_PROJECTION",
|
||||||
|
"ISSUE_PERSONA_TIME_TICKET",
|
||||||
|
"ACQUIRE_DEVELOPMENT_WRITE_LANE",
|
||||||
|
"INSPECT_DEVELOPMENT_WRITE_LANE",
|
||||||
|
"RELEASE_DEVELOPMENT_WRITE_LANE"
|
||||||
|
],
|
||||||
|
"trust_registry": {
|
||||||
|
"repository": "REPO-012",
|
||||||
|
"source_commit": "d5b1111fcaccaccf025070e531631f2b3cbb00cd",
|
||||||
|
"source_path": "routing/persona-control-authorization-signers.json",
|
||||||
|
"source_sha256": "35e7ebac46034e9df5cf61d4dccfee2e624331c1daf08251acb07c3d626974ef",
|
||||||
|
"registry_id": "GH-AIOS-PERSONA-CONTROL-AUTHORIZATION-SIGNERS-001",
|
||||||
|
"required_scope": "PERSONA_CONTROLLED_HOST_RUNTIME",
|
||||||
|
"current_signer_count": 0,
|
||||||
|
"unprovisioned_policy": "FAIL_CLOSED_WITHOUT_INVENTING_PERSONA_AUTHORITY"
|
||||||
|
},
|
||||||
|
"truth": {
|
||||||
|
"runtime_verifier_implemented": true,
|
||||||
|
"direct_local_broker_projection_implemented": true,
|
||||||
|
"trusted_persona_signer_provisioned": false,
|
||||||
|
"active_persona_license_installed": false,
|
||||||
|
"persona_runtime_present": false,
|
||||||
|
"persona_wake_route_registered": false,
|
||||||
|
"current_carrier_binding_claimed": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -146,7 +146,9 @@
|
||||||
"persona_wake_route_registered": false,
|
"persona_wake_route_registered": false,
|
||||||
"carrier_binding_claimed": false,
|
"carrier_binding_claimed": false,
|
||||||
"metacognitive_contract_compiled": true,
|
"metacognitive_contract_compiled": true,
|
||||||
"runtime_binding_gate_implemented": false,
|
"runtime_binding_gate_implemented": true,
|
||||||
"truth": "PHYSICAL_NUMBERED_IPC_ROOT_IN_PROGRESS_PERSONA_WAKE_REMAINS_CLOSED"
|
"trusted_persona_runtime_signer_provisioned": false,
|
||||||
|
"active_persona_runtime_license_installed": false,
|
||||||
|
"truth": "PERSONA_TO_HOST_RUNTIME_LICENSE_GATE_IMPLEMENTED_PERSONA_WAKE_REMAINS_CLOSED_UNTIL_TRUSTED_PERSONA_SIGNATURE"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,13 @@
|
||||||
"record_id": "HLP-PROGRAMMING-AI-TERMINAL-LINK-002",
|
"record_id": "HLP-PROGRAMMING-AI-TERMINAL-LINK-002",
|
||||||
"state": "NATIVE_CROSS_PLATFORM_CONTROL_PLANE_IMPLEMENTED",
|
"state": "NATIVE_CROSS_PLATFORM_CONTROL_PLANE_IMPLEMENTED",
|
||||||
"purpose": "Keep an external programming AI attached to a HoloLake-owned development control plane without making MCP or chat context the continuity owner.",
|
"purpose": "Keep an external programming AI attached to a HoloLake-owned development control plane without making MCP or chat context the continuity owner.",
|
||||||
"protocol": "HOLOLAKE_TERMINAL_LINK/2",
|
"protocol": "HOLOLAKE_TERMINAL_LINK/3",
|
||||||
|
"numbered_envelope": {
|
||||||
|
"registry": "HLP-NBROKER-ROOT-001",
|
||||||
|
"protocol_version": "HLP-NBROKER-v1",
|
||||||
|
"legacy_string_operation_allowed": false,
|
||||||
|
"full_coordinate_required": true
|
||||||
|
},
|
||||||
"platform_transports": {
|
"platform_transports": {
|
||||||
"macos": "USER_PRIVATE_UNIX_SOCKET",
|
"macos": "USER_PRIVATE_UNIX_SOCKET",
|
||||||
"linux": "USER_PRIVATE_UNIX_SOCKET",
|
"linux": "USER_PRIVATE_UNIX_SOCKET",
|
||||||
|
|
@ -44,6 +50,7 @@
|
||||||
},
|
},
|
||||||
"phase_boundary": {
|
"phase_boundary": {
|
||||||
"current_phase": "EXTERNAL_PROGRAMMING_AI_DIRECT_CONTROL_PLANE",
|
"current_phase": "EXTERNAL_PROGRAMMING_AI_DIRECT_CONTROL_PLANE",
|
||||||
|
"persona_carrier_runtime_license_gate": "IMPLEMENTED_FAIL_CLOSED_TRUSTED_SIGNER_NOT_PROVISIONED",
|
||||||
"supervised_shell_execution": false,
|
"supervised_shell_execution": false,
|
||||||
"general_agent_tool_loop": false,
|
"general_agent_tool_loop": false,
|
||||||
"persona_memory_startup": "NEXT_PHASE_AFTER_DIRECT_LINK_ACCEPTANCE",
|
"persona_memory_startup": "NEXT_PHASE_AFTER_DIRECT_LINK_ACCEPTANCE",
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,110 @@
|
||||||
|
import { createHash } from 'node:crypto'
|
||||||
|
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
||||||
|
import { dirname, resolve } from 'node:path'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
||||||
|
const paths = {
|
||||||
|
identity: resolve(root, 'contracts/zero-core-numbering-kernel.json'),
|
||||||
|
webview: resolve(root, 'contracts/numbered-ipc-registry.json'),
|
||||||
|
broker: resolve(root, 'contracts/direct-local-broker-numbered-registry.json'),
|
||||||
|
output: resolve(root, 'generated/unified-number-coordinate-tree.json'),
|
||||||
|
}
|
||||||
|
|
||||||
|
function readSource(path) {
|
||||||
|
const bytes = readFileSync(path)
|
||||||
|
return {
|
||||||
|
value: JSON.parse(bytes.toString('utf8')),
|
||||||
|
sha256: createHash('sha256').update(bytes).digest('hex'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compileUnifiedNumberTree() {
|
||||||
|
const identity = readSource(paths.identity)
|
||||||
|
const webview = readSource(paths.webview)
|
||||||
|
const broker = readSource(paths.broker)
|
||||||
|
const routes = [
|
||||||
|
...webview.value.operations.map((route) => ({
|
||||||
|
transport: 'TAURI_WEBVIEW_NUMBERED_IPC',
|
||||||
|
protocolVersion: webview.value.runtime.protocol_version,
|
||||||
|
callerNumber: webview.value.runtime.caller_number,
|
||||||
|
channelNumber: route.channel_number,
|
||||||
|
moduleNumber: route.module_number,
|
||||||
|
operationNumber: route.operation_number,
|
||||||
|
targetNumber: route.target_number,
|
||||||
|
alias: route.alias,
|
||||||
|
admission: route.admission,
|
||||||
|
effect: route.effect,
|
||||||
|
evidence: 'HASH_CHAINED_NUMBERED_IPC_RECEIPT',
|
||||||
|
path: `HLP-NUMBER-WORLD-ROOT-001/TAURI/${route.channel_number}/${route.module_number}/${route.operation_number}/${route.target_number}`,
|
||||||
|
})),
|
||||||
|
...broker.value.operations.map((route) => ({
|
||||||
|
transport: 'DIRECT_LOCAL_NUMBERED_BROKER',
|
||||||
|
protocolVersion: broker.value.runtime.protocol_version,
|
||||||
|
callerNumber: broker.value.runtime.caller_number,
|
||||||
|
channelNumber: route.channel_number,
|
||||||
|
moduleNumber: route.module_number,
|
||||||
|
operationNumber: route.operation_number,
|
||||||
|
targetNumber: route.target_number,
|
||||||
|
alias: route.alias,
|
||||||
|
admission: ['DISCOVER_NEARBY', 'PING', 'GET_BEIJING_TIME'].includes(route.alias)
|
||||||
|
? 'PREAUTH_LOCAL_SYSTEM_ROUTE'
|
||||||
|
: ['OPEN_VISITOR_SESSION', 'RECEIVE_LANGUAGE'].includes(route.alias)
|
||||||
|
? 'BOUNDED_VISITOR_ROUTE'
|
||||||
|
: 'AUTHENTICATED_LOCAL_SESSION_WITH_PERSONA_LICENSE_IF_PERSONA_MODE',
|
||||||
|
effect: ['DISCOVER_NEARBY', 'PING', 'GET_BEIJING_TIME', 'GET_PERSONA_CARRIER_LICENSE_STATUS', 'GET_WORK_ENVIRONMENT', 'RESOLVE_CAPABILITY_ROUTE', 'INSPECT_MOUNTED_PNCC_REPOSITORY', 'READ_MOUNTED_PNCC_REMOTE_OBJECT', 'QUERY_PNCC_RECEIPT_PROJECTION', 'INSPECT_DEVELOPMENT_WRITE_LANE'].includes(route.alias)
|
||||||
|
? 'READ_OR_STATUS'
|
||||||
|
: 'STATE_CHANGE',
|
||||||
|
evidence: 'NUMBERED_RESPONSE_PLUS_SESSION_OR_DOMAIN_RECEIPT',
|
||||||
|
path: `HLP-NUMBER-WORLD-ROOT-001/BROKER/${route.channel_number}/${route.module_number}/${route.operation_number}/${route.target_number}`,
|
||||||
|
})),
|
||||||
|
].sort((left, right) => left.path.localeCompare(right.path, 'en'))
|
||||||
|
const uniquePaths = new Set(routes.map((route) => route.path))
|
||||||
|
const uniqueOperations = new Set(routes.map((route) => `${route.transport}:${route.operationNumber}`))
|
||||||
|
if (routes.length !== uniquePaths.size || routes.length !== uniqueOperations.size) {
|
||||||
|
throw new Error('HOLOLAKE_UNIFIED_NUMBER_TREE_DUPLICATE_COORDINATE')
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
schema: 'hololake.unified-number-coordinate-tree/v1',
|
||||||
|
recordId: 'HLP-UNIFIED-NUMBER-TREE-001',
|
||||||
|
state: 'MACHINE_COMPILED_STARTUP_ENFORCED',
|
||||||
|
rootNumber: 'HLP-NUMBER-WORLD-ROOT-001',
|
||||||
|
identityAuthority: {
|
||||||
|
mapId: identity.value.authority.map_id,
|
||||||
|
mapVersion: identity.value.authority.map_version,
|
||||||
|
namespaces: identity.value.namespaces.map((namespace) => ({
|
||||||
|
namespaceId: namespace.id,
|
||||||
|
roots: namespace.roots,
|
||||||
|
prefixes: namespace.prefixes,
|
||||||
|
subjectKind: namespace.subject_kind,
|
||||||
|
domainScope: namespace.domain_scope,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
sources: [
|
||||||
|
{ recordId: identity.value.record_id, sha256: identity.sha256 },
|
||||||
|
{ recordId: webview.value.record_id, sha256: webview.sha256 },
|
||||||
|
{ recordId: broker.value.record_id, sha256: broker.sha256 },
|
||||||
|
],
|
||||||
|
invariants: {
|
||||||
|
numberIsStableCoordinateNotAuthority: true,
|
||||||
|
pathIsUniqueNavigation: true,
|
||||||
|
admissionIsSeparateFromIdentity: true,
|
||||||
|
everyPhysicalCallHasNumberedRoute: true,
|
||||||
|
everyAcceptedCallHasEvidenceClass: true,
|
||||||
|
mismatchedCoordinate: 'FAIL_CLOSED',
|
||||||
|
},
|
||||||
|
routeCount: routes.length,
|
||||||
|
routes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const compiled = compileUnifiedNumberTree()
|
||||||
|
const output = `${JSON.stringify(compiled, null, 2)}\n`
|
||||||
|
if (process.argv.includes('--check')) {
|
||||||
|
if (readFileSync(paths.output, 'utf8') !== output) {
|
||||||
|
throw new Error('HOLOLAKE_UNIFIED_NUMBER_TREE_STALE')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mkdirSync(dirname(paths.output), { recursive: true })
|
||||||
|
writeFileSync(paths.output, output)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { readFileSync } from 'node:fs'
|
||||||
|
import test from 'node:test'
|
||||||
|
|
||||||
|
const registry = JSON.parse(readFileSync(new URL('../contracts/direct-local-broker-numbered-registry.json', import.meta.url), 'utf8'))
|
||||||
|
const broker = readFileSync(new URL('../src-tauri/src/direct_local_broker.rs', import.meta.url), 'utf8')
|
||||||
|
|
||||||
|
test('the external local broker accepts only complete numbered coordinates', () => {
|
||||||
|
assert.equal(registry.record_id, 'HLP-NBROKER-ROOT-001')
|
||||||
|
assert.equal(registry.runtime.protocol_version, 'HLP-NBROKER-v1')
|
||||||
|
assert.equal(registry.runtime.legacy_string_operation_allowed, false)
|
||||||
|
assert.equal(registry.runtime.unknown_or_mismatched_coordinate, 'FAIL_CLOSED')
|
||||||
|
assert.equal(registry.runtime.transport_is_authority, false)
|
||||||
|
assert.equal(registry.operations.length, 22)
|
||||||
|
assert.equal(new Set(registry.operations.map((route) => route.operation_number)).size, 22)
|
||||||
|
assert.equal(new Set(registry.operations.map((route) => route.alias)).size, 22)
|
||||||
|
for (const route of registry.operations) {
|
||||||
|
assert.match(route.operation_number, /^HLP-NBROKER-OP-\d{4}$/)
|
||||||
|
assert.match(route.channel_number, /^HLP-NBROKER-CH-\d{4}$/)
|
||||||
|
assert.match(route.module_number, /^HLP-NBROKER-MOD-\d{4}$/)
|
||||||
|
assert.match(route.target_number, /^HLP-NBROKER-TGT-\d{4}$/)
|
||||||
|
}
|
||||||
|
assert.match(broker, /decode_numbered_broker_request/)
|
||||||
|
assert.match(broker, /HOLOLAKE_NUMBERED_BROKER_ROUTE_COORDINATE_MISMATCH/)
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { readFileSync } from 'node:fs'
|
||||||
|
import test from 'node:test'
|
||||||
|
|
||||||
|
const contract = JSON.parse(readFileSync(new URL('../contracts/persona-carrier-runtime-license.json', import.meta.url), 'utf8'))
|
||||||
|
const trust = JSON.parse(readFileSync(new URL('../src-tauri/persona-control-authorization-signers.json', import.meta.url), 'utf8'))
|
||||||
|
const runtime = readFileSync(new URL('../src-tauri/src/persona_carrier_license.rs', import.meta.url), 'utf8')
|
||||||
|
const broker = readFileSync(new URL('../src-tauri/src/direct_local_broker.rs', import.meta.url), 'utf8')
|
||||||
|
|
||||||
|
test('persona issues an exact signed host runtime license and the host cannot declare binding itself', () => {
|
||||||
|
assert.equal(contract.direction, 'PERSONA_SUBJECT_TO_HOST_CARRIER')
|
||||||
|
assert.equal(contract.binding_completion.declaration_holder, 'PERSONA_SUBJECT_ONLY')
|
||||||
|
assert.equal(contract.binding_completion.host_may_self_issue_extend_or_declare, false)
|
||||||
|
assert.equal(contract.runtime.signature_algorithm, 'Ed25519')
|
||||||
|
assert.equal(contract.runtime.exact_session_binding, true)
|
||||||
|
assert.equal(contract.runtime.monotonic_sequence, true)
|
||||||
|
assert.equal(contract.runtime.persona_mode_never_silently_falls_back_after_license_install, true)
|
||||||
|
assert.match(runtime, /PERSONA_DECLARES_CURRENT_CARRIER_BOUND/)
|
||||||
|
assert.match(runtime, /host_declared_binding: false/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('unprovisioned signer trust fails closed without denying system-direct use', () => {
|
||||||
|
assert.equal(contract.truth.runtime_verifier_implemented, true)
|
||||||
|
assert.equal(contract.truth.trusted_persona_signer_provisioned, false)
|
||||||
|
assert.deepEqual(trust.signers, [])
|
||||||
|
assert.equal(contract.runtime.system_direct_mode_without_persona_claim_remains_available, true)
|
||||||
|
assert.match(runtime, /HOLOLAKE_PERSONA_LICENSE_NO_TRUSTED_SIGNER/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('the direct local broker exposes license presentation and status as distinct operations', () => {
|
||||||
|
assert.match(broker, /PresentPersonaCarrierLicense/)
|
||||||
|
assert.match(broker, /GetPersonaCarrierLicenseStatus/)
|
||||||
|
assert.match(broker, /persona_license/)
|
||||||
|
})
|
||||||
|
|
@ -85,7 +85,9 @@ test('current product keeps persona wake closed instead of claiming an unimpleme
|
||||||
assert.equal(contract.current_product_state.persona_runtime_present, false)
|
assert.equal(contract.current_product_state.persona_runtime_present, false)
|
||||||
assert.equal(contract.current_product_state.persona_wake_route_registered, false)
|
assert.equal(contract.current_product_state.persona_wake_route_registered, false)
|
||||||
assert.equal(contract.current_product_state.carrier_binding_claimed, false)
|
assert.equal(contract.current_product_state.carrier_binding_claimed, false)
|
||||||
assert.equal(contract.current_product_state.runtime_binding_gate_implemented, false)
|
assert.equal(contract.current_product_state.runtime_binding_gate_implemented, true)
|
||||||
|
assert.equal(contract.current_product_state.trusted_persona_runtime_signer_provisioned, false)
|
||||||
|
assert.equal(contract.current_product_state.active_persona_runtime_license_installed, false)
|
||||||
assert.equal(contract.numbered_ipc_boundary.ipc_may_create_persona_binding, false)
|
assert.equal(contract.numbered_ipc_boundary.ipc_may_create_persona_binding, false)
|
||||||
assert.equal(registry.runtime.persona_binding_claimed, false)
|
assert.equal(registry.runtime.persona_binding_claimed, false)
|
||||||
assert.doesNotMatch(registry.operations.map((operation) => operation.alias).join('\n'), /persona_wake/)
|
assert.doesNotMatch(registry.operations.map((operation) => operation.alias).join('\n'), /persona_wake/)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,9 @@ const lib = readFileSync(new URL('../src-tauri/src/lib.rs', import.meta.url), 'u
|
||||||
const numberedDispatcher = readFileSync(new URL('../src-tauri/src/numbered_ipc_dispatch.rs', import.meta.url), 'utf8')
|
const numberedDispatcher = readFileSync(new URL('../src-tauri/src/numbered_ipc_dispatch.rs', import.meta.url), 'utf8')
|
||||||
|
|
||||||
test('terminal link has native local transports for macOS Linux and Windows', () => {
|
test('terminal link has native local transports for macOS Linux and Windows', () => {
|
||||||
assert.equal(contract.protocol, 'HOLOLAKE_TERMINAL_LINK/2')
|
assert.equal(contract.protocol, 'HOLOLAKE_TERMINAL_LINK/3')
|
||||||
|
assert.equal(contract.numbered_envelope.registry, 'HLP-NBROKER-ROOT-001')
|
||||||
|
assert.equal(contract.numbered_envelope.legacy_string_operation_allowed, false)
|
||||||
assert.equal(contract.platform_transports.macos, 'USER_PRIVATE_UNIX_SOCKET')
|
assert.equal(contract.platform_transports.macos, 'USER_PRIVATE_UNIX_SOCKET')
|
||||||
assert.equal(contract.platform_transports.linux, 'USER_PRIVATE_UNIX_SOCKET')
|
assert.equal(contract.platform_transports.linux, 'USER_PRIVATE_UNIX_SOCKET')
|
||||||
assert.equal(contract.platform_transports.windows, 'USER_PRIVATE_NAMED_PIPE')
|
assert.equal(contract.platform_transports.windows, 'USER_PRIVATE_NAMED_PIPE')
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { readFileSync } from 'node:fs'
|
||||||
|
import test from 'node:test'
|
||||||
|
import { compileUnifiedNumberTree } from './compile-unified-number-tree.mjs'
|
||||||
|
|
||||||
|
test('identity, webview and direct broker numbers compile into one unique evidence tree', () => {
|
||||||
|
const generated = JSON.parse(readFileSync(new URL('../generated/unified-number-coordinate-tree.json', import.meta.url), 'utf8'))
|
||||||
|
assert.deepEqual(generated, compileUnifiedNumberTree())
|
||||||
|
assert.equal(generated.recordId, 'HLP-UNIFIED-NUMBER-TREE-001')
|
||||||
|
assert.equal(generated.routeCount, 84)
|
||||||
|
assert.equal(new Set(generated.routes.map((route) => route.path)).size, 84)
|
||||||
|
assert.equal(generated.invariants.everyPhysicalCallHasNumberedRoute, true)
|
||||||
|
assert.equal(generated.invariants.everyAcceptedCallHasEvidenceClass, true)
|
||||||
|
assert.ok(generated.routes.every((route) => route.admission && route.evidence))
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"schema": "gh-aios.persona-control-authorization-signers/v1",
|
||||||
|
"registryId": "GH-AIOS-PERSONA-CONTROL-AUTHORIZATION-SIGNERS-001",
|
||||||
|
"state": "CURRENT",
|
||||||
|
"signers": []
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
use crate::circular_lake_membrane::{receive_at as receive_language_at, ReceiveLanguageInput};
|
use crate::circular_lake_membrane::{receive_at as receive_language_at, ReceiveLanguageInput};
|
||||||
use crate::direct_local_session::{
|
use crate::direct_local_session::{
|
||||||
append_event_at, authenticate_context_at, authenticate_privileged_at, direct_session_root,
|
append_event_at, authenticate_context_at, direct_session_root, heartbeat_at, issue_ticket_at,
|
||||||
heartbeat_at, issue_ticket_at, open_at, resume_at, AppendSessionEventInput,
|
open_at, resume_at, AppendSessionEventInput, AuthenticateSessionInput,
|
||||||
AuthenticateSessionInput, AuthenticatedSessionContext, DirectSessionReceipt,
|
AuthenticatedSessionContext, DirectSessionReceipt, IssueDiscoveryTicketInput, OpenSessionInput,
|
||||||
IssueDiscoveryTicketInput, OpenSessionInput, ResumeSessionInput,
|
ResumeSessionInput,
|
||||||
};
|
};
|
||||||
use crate::dynamic_capability_routing::{
|
use crate::dynamic_capability_routing::{
|
||||||
install_trusted_registry_at, record_health_at, resolve_at as resolve_capability_route_at,
|
install_trusted_registry_at, record_health_at, resolve_at as resolve_capability_route_at,
|
||||||
|
|
@ -15,6 +15,12 @@ use crate::local_development_bridge::{
|
||||||
inspect_at as inspect_development_lane_at, release_at as release_development_lane_at,
|
inspect_at as inspect_development_lane_at, release_at as release_development_lane_at,
|
||||||
AcquireWriteLaneInput, ReleaseWriteLaneInput,
|
AcquireWriteLaneInput, ReleaseWriteLaneInput,
|
||||||
};
|
};
|
||||||
|
use crate::persona_carrier_license::{
|
||||||
|
present_license_at as present_persona_license_at,
|
||||||
|
require_if_persona_mode_at as require_persona_mode_operation_at,
|
||||||
|
status_at as persona_license_status_at, PersonaCarrierLicenseStatus,
|
||||||
|
PersonaCarrierRuntimeLicense,
|
||||||
|
};
|
||||||
use crate::persona_time_authority::{
|
use crate::persona_time_authority::{
|
||||||
beijing_time_coordinate, issue_authenticated_at, IssuePersonaTimeTicketInput,
|
beijing_time_coordinate, issue_authenticated_at, IssuePersonaTimeTicketInput,
|
||||||
};
|
};
|
||||||
|
|
@ -42,6 +48,7 @@ use interprocess::TryClone;
|
||||||
use ring::digest::{digest, SHA256};
|
use ring::digest::{digest, SHA256};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::fs::{self, OpenOptions};
|
use std::fs::{self, OpenOptions};
|
||||||
use std::io::{self, BufRead, BufReader, Read, Write};
|
use std::io::{self, BufRead, BufReader, Read, Write};
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
|
@ -73,8 +80,51 @@ impl OpenOptionsModeExt for OpenOptions {
|
||||||
const BROKER_SCHEMA: &str = "hololake.direct-local-broker/v1";
|
const BROKER_SCHEMA: &str = "hololake.direct-local-broker/v1";
|
||||||
const MAX_REQUEST_BYTES: u64 = 128 * 1024;
|
const MAX_REQUEST_BYTES: u64 = 128 * 1024;
|
||||||
const DISCOVERY_SCHEMA: &str = "hololake.nearby-ai-discovery/v1";
|
const DISCOVERY_SCHEMA: &str = "hololake.nearby-ai-discovery/v1";
|
||||||
const TERMINAL_LINK_PROTOCOL: &str = "HOLOLAKE_TERMINAL_LINK/2";
|
const TERMINAL_LINK_PROTOCOL: &str = "HOLOLAKE_TERMINAL_LINK/3";
|
||||||
const ENVIRONMENT_FRAME_TTL_MS: u128 = 45_000;
|
const ENVIRONMENT_FRAME_TTL_MS: u128 = 45_000;
|
||||||
|
const NUMBERED_BROKER_REGISTRY: &str =
|
||||||
|
include_str!("../../contracts/direct-local-broker-numbered-registry.json");
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
struct BrokerNumberRegistry {
|
||||||
|
schema: String,
|
||||||
|
record_id: String,
|
||||||
|
runtime: BrokerNumberRuntime,
|
||||||
|
operations: Vec<BrokerNumberRoute>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
struct BrokerNumberRuntime {
|
||||||
|
protocol_version: String,
|
||||||
|
caller_number: String,
|
||||||
|
legacy_string_operation_allowed: bool,
|
||||||
|
unknown_or_mismatched_coordinate: String,
|
||||||
|
request_nonce_required: bool,
|
||||||
|
transport_is_authority: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
struct BrokerNumberRoute {
|
||||||
|
operation_number: String,
|
||||||
|
alias: String,
|
||||||
|
channel_number: String,
|
||||||
|
module_number: String,
|
||||||
|
target_number: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||||
|
struct BrokerNumberedEnvelope {
|
||||||
|
protocol_version: String,
|
||||||
|
caller_number: String,
|
||||||
|
channel_number: String,
|
||||||
|
module_number: String,
|
||||||
|
operation_number: String,
|
||||||
|
target_number: String,
|
||||||
|
request_nonce: String,
|
||||||
|
#[serde(default)]
|
||||||
|
input: Option<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct DirectLocalBrokerHandle {
|
pub struct DirectLocalBrokerHandle {
|
||||||
shutdown: Arc<AtomicBool>,
|
shutdown: Arc<AtomicBool>,
|
||||||
|
|
@ -151,6 +201,7 @@ struct BrokerStorageRoots {
|
||||||
pncc_projection: PathBuf,
|
pncc_projection: PathBuf,
|
||||||
language_inbox: PathBuf,
|
language_inbox: PathBuf,
|
||||||
persona_time: PathBuf,
|
persona_time: PathBuf,
|
||||||
|
persona_license: PathBuf,
|
||||||
development: PathBuf,
|
development: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,6 +230,8 @@ enum BrokerRequest {
|
||||||
OpenSession(OpenSessionInput),
|
OpenSession(OpenSessionInput),
|
||||||
ResumeSession(ResumeSessionInput),
|
ResumeSession(ResumeSessionInput),
|
||||||
HeartbeatSession(AuthenticateSessionInput),
|
HeartbeatSession(AuthenticateSessionInput),
|
||||||
|
PresentPersonaCarrierLicense(AuthenticatedPersonaCarrierLicenseInput),
|
||||||
|
GetPersonaCarrierLicenseStatus(AuthenticatedPersonaCarrierLicenseStatusInput),
|
||||||
GetWorkEnvironment(AuthenticatedWorkEnvironmentInput),
|
GetWorkEnvironment(AuthenticatedWorkEnvironmentInput),
|
||||||
AppendEvent(AppendSessionEventInput),
|
AppendEvent(AppendSessionEventInput),
|
||||||
ResolveCapabilityRoute(AuthenticatedRouteInput),
|
ResolveCapabilityRoute(AuthenticatedRouteInput),
|
||||||
|
|
@ -297,6 +350,19 @@ struct AuthenticatedWorkEnvironmentInput {
|
||||||
session: AuthenticateSessionInput,
|
session: AuthenticateSessionInput,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||||
|
struct AuthenticatedPersonaCarrierLicenseInput {
|
||||||
|
session: AuthenticateSessionInput,
|
||||||
|
license: PersonaCarrierRuntimeLicense,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||||
|
struct AuthenticatedPersonaCarrierLicenseStatusInput {
|
||||||
|
session: AuthenticateSessionInput,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct WorkEnvironmentFrame {
|
struct WorkEnvironmentFrame {
|
||||||
|
|
@ -308,6 +374,7 @@ struct WorkEnvironmentFrame {
|
||||||
transport: &'static str,
|
transport: &'static str,
|
||||||
mcp_role: &'static str,
|
mcp_role: &'static str,
|
||||||
protocol_runtime: &'static str,
|
protocol_runtime: &'static str,
|
||||||
|
persona_control: PersonaCarrierLicenseStatus,
|
||||||
account_key: String,
|
account_key: String,
|
||||||
session_id: String,
|
session_id: String,
|
||||||
lane_id: String,
|
lane_id: String,
|
||||||
|
|
@ -366,6 +433,116 @@ struct ConnectorDescriptor {
|
||||||
process_id: u32,
|
process_id: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_number_registry() -> Result<BrokerNumberRegistry, String> {
|
||||||
|
let registry: BrokerNumberRegistry = serde_json::from_str(NUMBERED_BROKER_REGISTRY)
|
||||||
|
.map_err(|error| format!("HOLOLAKE_NUMBERED_BROKER_REGISTRY_INVALID: {error}"))?;
|
||||||
|
if registry.schema != "hololake.direct-local-broker-numbered-registry/v1"
|
||||||
|
|| registry.record_id != "HLP-NBROKER-ROOT-001"
|
||||||
|
|| registry.runtime.protocol_version != "HLP-NBROKER-v1"
|
||||||
|
|| registry.runtime.caller_number != "HLP-NBROKER-CALLER-LOCAL-CONNECTOR-0001"
|
||||||
|
|| registry.runtime.legacy_string_operation_allowed
|
||||||
|
|| registry.runtime.unknown_or_mismatched_coordinate != "FAIL_CLOSED"
|
||||||
|
|| !registry.runtime.request_nonce_required
|
||||||
|
|| registry.runtime.transport_is_authority
|
||||||
|
|| registry.operations.len() != 22
|
||||||
|
{
|
||||||
|
return Err("HOLOLAKE_NUMBERED_BROKER_REGISTRY_BOUNDARY_INVALID".into());
|
||||||
|
}
|
||||||
|
let mut numbers = HashSet::new();
|
||||||
|
let mut aliases = HashSet::new();
|
||||||
|
let mut coordinates = HashSet::new();
|
||||||
|
for route in ®istry.operations {
|
||||||
|
let coordinate = format!(
|
||||||
|
"{}/{}/{}/{}",
|
||||||
|
route.channel_number, route.module_number, route.operation_number, route.target_number
|
||||||
|
);
|
||||||
|
if !numbers.insert(route.operation_number.as_str())
|
||||||
|
|| !aliases.insert(route.alias.as_str())
|
||||||
|
|| !coordinates.insert(coordinate)
|
||||||
|
|| !route.operation_number.starts_with("HLP-NBROKER-OP-")
|
||||||
|
|| !route.channel_number.starts_with("HLP-NBROKER-CH-")
|
||||||
|
|| !route.module_number.starts_with("HLP-NBROKER-MOD-")
|
||||||
|
|| !route.target_number.starts_with("HLP-NBROKER-TGT-")
|
||||||
|
{
|
||||||
|
return Err("HOLOLAKE_NUMBERED_BROKER_ROUTE_INVALID".into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(registry)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_numbered_broker_route<'a>(
|
||||||
|
registry: &'a BrokerNumberRegistry,
|
||||||
|
envelope: &BrokerNumberedEnvelope,
|
||||||
|
) -> Result<&'a BrokerNumberRoute, String> {
|
||||||
|
if envelope.protocol_version != registry.runtime.protocol_version
|
||||||
|
|| envelope.caller_number != registry.runtime.caller_number
|
||||||
|
|| envelope.request_nonce.len() < 16
|
||||||
|
|| envelope.request_nonce.len() > 128
|
||||||
|
|| envelope
|
||||||
|
.request_nonce
|
||||||
|
.chars()
|
||||||
|
.any(|character| character.is_whitespace() || character.is_control())
|
||||||
|
{
|
||||||
|
return Err("HOLOLAKE_NUMBERED_BROKER_CALLER_PROTOCOL_OR_NONCE_INVALID".into());
|
||||||
|
}
|
||||||
|
let route = registry
|
||||||
|
.operations
|
||||||
|
.iter()
|
||||||
|
.find(|route| route.operation_number == envelope.operation_number)
|
||||||
|
.ok_or("HOLOLAKE_NUMBERED_BROKER_ROUTE_UNKNOWN")?;
|
||||||
|
if route.channel_number != envelope.channel_number
|
||||||
|
|| route.module_number != envelope.module_number
|
||||||
|
|| route.target_number != envelope.target_number
|
||||||
|
{
|
||||||
|
return Err("HOLOLAKE_NUMBERED_BROKER_ROUTE_COORDINATE_MISMATCH".into());
|
||||||
|
}
|
||||||
|
Ok(route)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_numbered_broker_request(bytes: &[u8]) -> Result<(BrokerRequest, String, String), String> {
|
||||||
|
let envelope: BrokerNumberedEnvelope = serde_json::from_slice(bytes)
|
||||||
|
.map_err(|error| format!("HOLOLAKE_NUMBERED_BROKER_ENVELOPE_INVALID: {error}"))?;
|
||||||
|
let registry = load_number_registry()?;
|
||||||
|
let route = resolve_numbered_broker_route(®istry, &envelope)?;
|
||||||
|
let mut request = serde_json::Map::new();
|
||||||
|
request.insert("operation".into(), Value::String(route.alias.clone()));
|
||||||
|
if let Some(input) = envelope.input {
|
||||||
|
request.insert("input".into(), input);
|
||||||
|
}
|
||||||
|
let decoded = serde_json::from_value(Value::Object(request))
|
||||||
|
.map_err(|error| format!("HOLOLAKE_NUMBERED_BROKER_INPUT_INVALID: {error}"))?;
|
||||||
|
Ok((
|
||||||
|
decoded,
|
||||||
|
route.operation_number.clone(),
|
||||||
|
envelope.request_nonce,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn numbered_broker_request_value(alias: &str, input: Option<Value>) -> Result<Value, String> {
|
||||||
|
let registry = load_number_registry()?;
|
||||||
|
let route = registry
|
||||||
|
.operations
|
||||||
|
.iter()
|
||||||
|
.find(|route| route.alias == alias)
|
||||||
|
.ok_or("HOLOLAKE_NUMBERED_BROKER_ALIAS_UNKNOWN")?;
|
||||||
|
let mut value = serde_json::json!({
|
||||||
|
"protocolVersion": registry.runtime.protocol_version,
|
||||||
|
"callerNumber": registry.runtime.caller_number,
|
||||||
|
"channelNumber": route.channel_number,
|
||||||
|
"moduleNumber": route.module_number,
|
||||||
|
"operationNumber": route.operation_number,
|
||||||
|
"targetNumber": route.target_number,
|
||||||
|
"requestNonce": format!("HLP-NBROKER-NONCE-{}", Uuid::new_v4().simple())
|
||||||
|
});
|
||||||
|
if let Some(input) = input {
|
||||||
|
value
|
||||||
|
.as_object_mut()
|
||||||
|
.ok_or("HOLOLAKE_NUMBERED_BROKER_ENVELOPE_INVALID")?
|
||||||
|
.insert("input".into(), input);
|
||||||
|
}
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn start(app: &AppHandle) -> Result<DirectLocalBrokerHandle, Box<dyn std::error::Error>> {
|
pub fn start(app: &AppHandle) -> Result<DirectLocalBrokerHandle, Box<dyn std::error::Error>> {
|
||||||
let session_root = direct_session_root(app).map_err(std::io::Error::other)?;
|
let session_root = direct_session_root(app).map_err(std::io::Error::other)?;
|
||||||
let routing_root = dynamic_routing_root(app).map_err(std::io::Error::other)?;
|
let routing_root = dynamic_routing_root(app).map_err(std::io::Error::other)?;
|
||||||
|
|
@ -465,7 +642,9 @@ fn read_connector_descriptor(path: &Path) -> Result<ConnectorDescriptor, String>
|
||||||
}
|
}
|
||||||
|
|
||||||
fn probe_connection(stream: &mut LocalSocketStream) -> Result<(), String> {
|
fn probe_connection(stream: &mut LocalSocketStream) -> Result<(), String> {
|
||||||
let response = send_connector_request(stream, br#"{"operation":"PING"}"#)?;
|
let request = serde_json::to_vec(&numbered_broker_request_value("PING", None)?)
|
||||||
|
.map_err(|_| "HOLOLAKE_CONNECTOR_PROBE_INVALID".to_string())?;
|
||||||
|
let response = send_connector_request(stream, &request)?;
|
||||||
let value: Value = serde_json::from_slice(&response)
|
let value: Value = serde_json::from_slice(&response)
|
||||||
.map_err(|_| "HOLOLAKE_CONNECTOR_PROBE_INVALID".to_string())?;
|
.map_err(|_| "HOLOLAKE_CONNECTOR_PROBE_INVALID".to_string())?;
|
||||||
if value.get("ok").and_then(Value::as_bool) == Some(true)
|
if value.get("ok").and_then(Value::as_bool) == Some(true)
|
||||||
|
|
@ -625,6 +804,7 @@ fn start_at(
|
||||||
descriptor_path: PathBuf,
|
descriptor_path: PathBuf,
|
||||||
socket_path: PathBuf,
|
socket_path: PathBuf,
|
||||||
) -> Result<DirectLocalBrokerHandle, String> {
|
) -> Result<DirectLocalBrokerHandle, String> {
|
||||||
|
load_number_registry()?;
|
||||||
let pncc_mount_root = session_root
|
let pncc_mount_root = session_root
|
||||||
.parent()
|
.parent()
|
||||||
.ok_or("HOLOLAKE_BROKER_PNCC_STORAGE_BOUNDARY_INVALID")?
|
.ok_or("HOLOLAKE_BROKER_PNCC_STORAGE_BOUNDARY_INVALID")?
|
||||||
|
|
@ -654,6 +834,12 @@ fn start_at(
|
||||||
.parent()
|
.parent()
|
||||||
.ok_or("HOLOLAKE_BROKER_PERSONA_TIME_STORAGE_BOUNDARY_INVALID")?
|
.ok_or("HOLOLAKE_BROKER_PERSONA_TIME_STORAGE_BOUNDARY_INVALID")?
|
||||||
.join("persona-time-authority-v1");
|
.join("persona-time-authority-v1");
|
||||||
|
let persona_license_root = session_root
|
||||||
|
.parent()
|
||||||
|
.ok_or("HOLOLAKE_BROKER_PERSONA_LICENSE_STORAGE_BOUNDARY_INVALID")?
|
||||||
|
.join("persona-carrier-runtime-license-v1");
|
||||||
|
fs::create_dir_all(&persona_license_root)
|
||||||
|
.map_err(|error| format!("HOLOLAKE_PERSONA_LICENSE_STORAGE_FAILED: {error}"))?;
|
||||||
let development_root = development_root_from_session_root(&session_root)?;
|
let development_root = development_root_from_session_root(&session_root)?;
|
||||||
fs::create_dir_all(&development_root)
|
fs::create_dir_all(&development_root)
|
||||||
.map_err(|error| format!("HOLOLAKE_BRIDGE_STORAGE_UNAVAILABLE: {error}"))?;
|
.map_err(|error| format!("HOLOLAKE_BRIDGE_STORAGE_UNAVAILABLE: {error}"))?;
|
||||||
|
|
@ -715,6 +901,7 @@ fn start_at(
|
||||||
pncc_projection: pncc_projection_root,
|
pncc_projection: pncc_projection_root,
|
||||||
language_inbox: language_inbox_root,
|
language_inbox: language_inbox_root,
|
||||||
persona_time: persona_time_root,
|
persona_time: persona_time_root,
|
||||||
|
persona_license: persona_license_root,
|
||||||
development: development_root,
|
development: development_root,
|
||||||
},
|
},
|
||||||
&worker_shutdown,
|
&worker_shutdown,
|
||||||
|
|
@ -816,18 +1003,14 @@ fn serve_connection(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn request_establishes_authenticated_connection(bytes: &[u8]) -> bool {
|
fn request_establishes_authenticated_connection(bytes: &[u8]) -> bool {
|
||||||
serde_json::from_slice::<serde_json::Value>(bytes)
|
decode_numbered_broker_request(bytes)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|value| {
|
.is_some_and(|(request, _, _)| {
|
||||||
value
|
|
||||||
.get("operation")
|
|
||||||
.and_then(Value::as_str)
|
|
||||||
.map(str::to_owned)
|
|
||||||
})
|
|
||||||
.is_some_and(|operation| {
|
|
||||||
matches!(
|
matches!(
|
||||||
operation.as_str(),
|
request,
|
||||||
"OPEN_SESSION" | "RESUME_SESSION" | "OPEN_VISITOR_SESSION"
|
BrokerRequest::OpenSession(_)
|
||||||
|
| BrokerRequest::ResumeSession(_)
|
||||||
|
| BrokerRequest::OpenVisitorSession(_)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -840,8 +1023,9 @@ fn dispatch(roots: &BrokerStorageRoots, bytes: &[u8]) -> BrokerResponse {
|
||||||
let pncc_projection_root = &roots.pncc_projection;
|
let pncc_projection_root = &roots.pncc_projection;
|
||||||
let language_inbox_root = &roots.language_inbox;
|
let language_inbox_root = &roots.language_inbox;
|
||||||
let persona_time_root = &roots.persona_time;
|
let persona_time_root = &roots.persona_time;
|
||||||
|
let persona_license_root = &roots.persona_license;
|
||||||
let development_root = &roots.development;
|
let development_root = &roots.development;
|
||||||
let request: BrokerRequest = match serde_json::from_slice(bytes) {
|
let (request, _, _) = match decode_numbered_broker_request(bytes) {
|
||||||
Ok(request) => request,
|
Ok(request) => request,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
return BrokerResponse::error(&format!("HOLOLAKE_BROKER_REQUEST_INVALID: {error}"))
|
return BrokerResponse::error(&format!("HOLOLAKE_BROKER_REQUEST_INVALID: {error}"))
|
||||||
|
|
@ -870,10 +1054,40 @@ fn dispatch(roots: &BrokerStorageRoots, bytes: &[u8]) -> BrokerResponse {
|
||||||
.and_then(|receipt| serde_json::to_value(receipt).map_err(|error| error.to_string())),
|
.and_then(|receipt| serde_json::to_value(receipt).map_err(|error| error.to_string())),
|
||||||
BrokerRequest::ResumeSession(input) => resume_at(session_root, input)
|
BrokerRequest::ResumeSession(input) => resume_at(session_root, input)
|
||||||
.and_then(|receipt| serde_json::to_value(receipt).map_err(|error| error.to_string())),
|
.and_then(|receipt| serde_json::to_value(receipt).map_err(|error| error.to_string())),
|
||||||
|
BrokerRequest::PresentPersonaCarrierLicense(input) => {
|
||||||
|
authenticate_context_at(session_root, &input.session)
|
||||||
|
.and_then(|context| {
|
||||||
|
present_persona_license_at(
|
||||||
|
persona_license_root,
|
||||||
|
&context.account_key,
|
||||||
|
&context.session_id,
|
||||||
|
&context.client_instance_id,
|
||||||
|
input.license,
|
||||||
|
now_unix_ms()?,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.and_then(|receipt| {
|
||||||
|
serde_json::to_value(receipt).map_err(|error| error.to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
BrokerRequest::GetPersonaCarrierLicenseStatus(input) => {
|
||||||
|
authenticate_context_at(session_root, &input.session)
|
||||||
|
.map(|context| {
|
||||||
|
persona_license_status_at(
|
||||||
|
persona_license_root,
|
||||||
|
&context.account_key,
|
||||||
|
&context.session_id,
|
||||||
|
&context.client_instance_id,
|
||||||
|
now_unix_ms().unwrap_or(u64::MAX),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.and_then(|status| serde_json::to_value(status).map_err(|error| error.to_string()))
|
||||||
|
}
|
||||||
BrokerRequest::HeartbeatSession(input) => heartbeat_at(session_root, &input)
|
BrokerRequest::HeartbeatSession(input) => heartbeat_at(session_root, &input)
|
||||||
.and_then(|session| {
|
.and_then(|session| {
|
||||||
work_environment_frame_at(
|
work_environment_frame_at(
|
||||||
session_root,
|
session_root,
|
||||||
|
persona_license_root,
|
||||||
development_root,
|
development_root,
|
||||||
&AuthenticatedWorkEnvironmentInput { session: input },
|
&AuthenticatedWorkEnvironmentInput { session: input },
|
||||||
)
|
)
|
||||||
|
|
@ -886,13 +1100,28 @@ fn dispatch(roots: &BrokerStorageRoots, bytes: &[u8]) -> BrokerResponse {
|
||||||
})
|
})
|
||||||
.and_then(|receipt| serde_json::to_value(receipt).map_err(|error| error.to_string())),
|
.and_then(|receipt| serde_json::to_value(receipt).map_err(|error| error.to_string())),
|
||||||
BrokerRequest::GetWorkEnvironment(input) => {
|
BrokerRequest::GetWorkEnvironment(input) => {
|
||||||
work_environment_frame_at(session_root, development_root, &input)
|
work_environment_frame_at(session_root, persona_license_root, development_root, &input)
|
||||||
.and_then(|frame| serde_json::to_value(frame).map_err(|error| error.to_string()))
|
.and_then(|frame| serde_json::to_value(frame).map_err(|error| error.to_string()))
|
||||||
}
|
}
|
||||||
BrokerRequest::AppendEvent(input) => append_event_at(session_root, input)
|
BrokerRequest::AppendEvent(input) => authorize_persona_operation_at(
|
||||||
.and_then(|receipt| serde_json::to_value(receipt).map_err(|error| error.to_string())),
|
session_root,
|
||||||
|
persona_license_root,
|
||||||
|
&AuthenticateSessionInput {
|
||||||
|
account_id: input.account_id.clone(),
|
||||||
|
session_id: input.session_id.clone(),
|
||||||
|
resume_secret: input.resume_secret.clone(),
|
||||||
|
},
|
||||||
|
"APPEND_EVENT",
|
||||||
|
)
|
||||||
|
.and_then(|_| append_event_at(session_root, input))
|
||||||
|
.and_then(|receipt| serde_json::to_value(receipt).map_err(|error| error.to_string())),
|
||||||
BrokerRequest::ResolveCapabilityRoute(input) => {
|
BrokerRequest::ResolveCapabilityRoute(input) => {
|
||||||
if let Err(error) = authenticate_privileged_at(session_root, &input.session) {
|
if let Err(error) = authorize_persona_operation_at(
|
||||||
|
session_root,
|
||||||
|
persona_license_root,
|
||||||
|
&input.session,
|
||||||
|
"RESOLVE_CAPABILITY_ROUTE",
|
||||||
|
) {
|
||||||
return BrokerResponse::error(&error);
|
return BrokerResponse::error(&error);
|
||||||
}
|
}
|
||||||
let now = std::time::SystemTime::now()
|
let now = std::time::SystemTime::now()
|
||||||
|
|
@ -905,7 +1134,12 @@ fn dispatch(roots: &BrokerStorageRoots, bytes: &[u8]) -> BrokerResponse {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
BrokerRequest::InstallDynamicNodeRegistry(input) => {
|
BrokerRequest::InstallDynamicNodeRegistry(input) => {
|
||||||
if let Err(error) = authenticate_privileged_at(session_root, &input.session) {
|
if let Err(error) = authorize_persona_operation_at(
|
||||||
|
session_root,
|
||||||
|
persona_license_root,
|
||||||
|
&input.session,
|
||||||
|
"INSTALL_DYNAMIC_NODE_REGISTRY",
|
||||||
|
) {
|
||||||
return BrokerResponse::error(&error);
|
return BrokerResponse::error(&error);
|
||||||
}
|
}
|
||||||
install_trusted_registry_at(routing_root, input.registry).and_then(|receipt| {
|
install_trusted_registry_at(routing_root, input.registry).and_then(|receipt| {
|
||||||
|
|
@ -913,7 +1147,12 @@ fn dispatch(roots: &BrokerStorageRoots, bytes: &[u8]) -> BrokerResponse {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
BrokerRequest::RecordSignedNodeHealth(input) => {
|
BrokerRequest::RecordSignedNodeHealth(input) => {
|
||||||
if let Err(error) = authenticate_privileged_at(session_root, &input.session) {
|
if let Err(error) = authorize_persona_operation_at(
|
||||||
|
session_root,
|
||||||
|
persona_license_root,
|
||||||
|
&input.session,
|
||||||
|
"RECORD_SIGNED_NODE_HEALTH",
|
||||||
|
) {
|
||||||
return BrokerResponse::error(&error);
|
return BrokerResponse::error(&error);
|
||||||
}
|
}
|
||||||
record_health_at(routing_root, input.health).and_then(|receipt| {
|
record_health_at(routing_root, input.health).and_then(|receipt| {
|
||||||
|
|
@ -921,7 +1160,12 @@ fn dispatch(roots: &BrokerStorageRoots, bytes: &[u8]) -> BrokerResponse {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
BrokerRequest::InspectMountedPnccRepository(input) => {
|
BrokerRequest::InspectMountedPnccRepository(input) => {
|
||||||
if let Err(error) = authenticate_privileged_at(session_root, &input.session) {
|
if let Err(error) = authorize_persona_operation_at(
|
||||||
|
session_root,
|
||||||
|
persona_license_root,
|
||||||
|
&input.session,
|
||||||
|
"INSPECT_MOUNTED_PNCC_REPOSITORY",
|
||||||
|
) {
|
||||||
return BrokerResponse::error(&error);
|
return BrokerResponse::error(&error);
|
||||||
}
|
}
|
||||||
inspect_mounted_pncc_at(pncc_mount_root, input.mount).and_then(|receipt| {
|
inspect_mounted_pncc_at(pncc_mount_root, input.mount).and_then(|receipt| {
|
||||||
|
|
@ -930,7 +1174,12 @@ fn dispatch(roots: &BrokerStorageRoots, bytes: &[u8]) -> BrokerResponse {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
BrokerRequest::ReadMountedPnccRemoteObject(input) => {
|
BrokerRequest::ReadMountedPnccRemoteObject(input) => {
|
||||||
if let Err(error) = authenticate_privileged_at(session_root, &input.session) {
|
if let Err(error) = authorize_persona_operation_at(
|
||||||
|
session_root,
|
||||||
|
persona_license_root,
|
||||||
|
&input.session,
|
||||||
|
"READ_MOUNTED_PNCC_REMOTE_OBJECT",
|
||||||
|
) {
|
||||||
return BrokerResponse::error(&error);
|
return BrokerResponse::error(&error);
|
||||||
}
|
}
|
||||||
read_mounted_pncc_remote_at(pncc_remote_root, input.read).and_then(|receipt| {
|
read_mounted_pncc_remote_at(pncc_remote_root, input.read).and_then(|receipt| {
|
||||||
|
|
@ -939,7 +1188,12 @@ fn dispatch(roots: &BrokerStorageRoots, bytes: &[u8]) -> BrokerResponse {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
BrokerRequest::QueryPnccReceiptProjection(input) => {
|
BrokerRequest::QueryPnccReceiptProjection(input) => {
|
||||||
if let Err(error) = authenticate_privileged_at(session_root, &input.session) {
|
if let Err(error) = authorize_persona_operation_at(
|
||||||
|
session_root,
|
||||||
|
persona_license_root,
|
||||||
|
&input.session,
|
||||||
|
"QUERY_PNCC_RECEIPT_PROJECTION",
|
||||||
|
) {
|
||||||
return BrokerResponse::error(&error);
|
return BrokerResponse::error(&error);
|
||||||
}
|
}
|
||||||
query_pncc_projection_at(pncc_projection_root, input.query).and_then(|receipt| {
|
query_pncc_projection_at(pncc_projection_root, input.query).and_then(|receipt| {
|
||||||
|
|
@ -954,15 +1208,27 @@ fn dispatch(roots: &BrokerStorageRoots, bytes: &[u8]) -> BrokerResponse {
|
||||||
.and_then(|coordinate| {
|
.and_then(|coordinate| {
|
||||||
serde_json::to_value(coordinate).map_err(|error| error.to_string())
|
serde_json::to_value(coordinate).map_err(|error| error.to_string())
|
||||||
}),
|
}),
|
||||||
BrokerRequest::IssuePersonaTimeTicket(input) => {
|
BrokerRequest::IssuePersonaTimeTicket(input) => authorize_persona_operation_at(
|
||||||
issue_authenticated_at(persona_time_root, session_root, input)
|
session_root,
|
||||||
.and_then(|ticket| serde_json::to_value(ticket).map_err(|error| error.to_string()))
|
persona_license_root,
|
||||||
}
|
&input.session,
|
||||||
|
"ISSUE_PERSONA_TIME_TICKET",
|
||||||
|
)
|
||||||
|
.and_then(|_| issue_authenticated_at(persona_time_root, session_root, input))
|
||||||
|
.and_then(|ticket| serde_json::to_value(ticket).map_err(|error| error.to_string())),
|
||||||
BrokerRequest::AcquireDevelopmentWriteLane(input) => {
|
BrokerRequest::AcquireDevelopmentWriteLane(input) => {
|
||||||
let session = input.session;
|
let session = input.session;
|
||||||
let account_id = input.lane.account_id.clone();
|
let account_id = input.lane.account_id.clone();
|
||||||
authenticate_development_request(session_root, &session, &account_id)
|
authenticate_development_request(session_root, &session, &account_id)
|
||||||
.and_then(|context| {
|
.and_then(|context| {
|
||||||
|
require_persona_mode_operation_at(
|
||||||
|
persona_license_root,
|
||||||
|
&context.account_key,
|
||||||
|
&context.session_id,
|
||||||
|
&context.client_instance_id,
|
||||||
|
"ACQUIRE_DEVELOPMENT_WRITE_LANE",
|
||||||
|
now_unix_ms()?,
|
||||||
|
)?;
|
||||||
if context.lane_id != input.lane.lane_id
|
if context.lane_id != input.lane.lane_id
|
||||||
|| context.client_instance_id != input.lane.owner_instance_id
|
|| context.client_instance_id != input.lane.owner_instance_id
|
||||||
{
|
{
|
||||||
|
|
@ -975,6 +1241,7 @@ fn dispatch(roots: &BrokerStorageRoots, bytes: &[u8]) -> BrokerResponse {
|
||||||
serde_json::to_value(receipt).map_err(|error| error.to_string())?;
|
serde_json::to_value(receipt).map_err(|error| error.to_string())?;
|
||||||
let environment = work_environment_frame_at(
|
let environment = work_environment_frame_at(
|
||||||
session_root,
|
session_root,
|
||||||
|
persona_license_root,
|
||||||
development_root,
|
development_root,
|
||||||
&AuthenticatedWorkEnvironmentInput { session },
|
&AuthenticatedWorkEnvironmentInput { session },
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -990,7 +1257,17 @@ fn dispatch(roots: &BrokerStorageRoots, bytes: &[u8]) -> BrokerResponse {
|
||||||
}
|
}
|
||||||
BrokerRequest::InspectDevelopmentWriteLane(input) => {
|
BrokerRequest::InspectDevelopmentWriteLane(input) => {
|
||||||
authenticate_development_request(session_root, &input.session, &input.account_id)
|
authenticate_development_request(session_root, &input.session, &input.account_id)
|
||||||
.and_then(|_| inspect_development_lane_at(development_root, &input.account_id))
|
.and_then(|context| {
|
||||||
|
require_persona_mode_operation_at(
|
||||||
|
persona_license_root,
|
||||||
|
&context.account_key,
|
||||||
|
&context.session_id,
|
||||||
|
&context.client_instance_id,
|
||||||
|
"INSPECT_DEVELOPMENT_WRITE_LANE",
|
||||||
|
now_unix_ms()?,
|
||||||
|
)?;
|
||||||
|
inspect_development_lane_at(development_root, &input.account_id)
|
||||||
|
})
|
||||||
.and_then(|receipt| {
|
.and_then(|receipt| {
|
||||||
serde_json::to_value(receipt).map_err(|error| error.to_string())
|
serde_json::to_value(receipt).map_err(|error| error.to_string())
|
||||||
})
|
})
|
||||||
|
|
@ -998,6 +1275,14 @@ fn dispatch(roots: &BrokerStorageRoots, bytes: &[u8]) -> BrokerResponse {
|
||||||
BrokerRequest::ReleaseDevelopmentWriteLane(input) => {
|
BrokerRequest::ReleaseDevelopmentWriteLane(input) => {
|
||||||
authenticate_development_request(session_root, &input.session, &input.lane.account_id)
|
authenticate_development_request(session_root, &input.session, &input.lane.account_id)
|
||||||
.and_then(|context| {
|
.and_then(|context| {
|
||||||
|
require_persona_mode_operation_at(
|
||||||
|
persona_license_root,
|
||||||
|
&context.account_key,
|
||||||
|
&context.session_id,
|
||||||
|
&context.client_instance_id,
|
||||||
|
"RELEASE_DEVELOPMENT_WRITE_LANE",
|
||||||
|
now_unix_ms()?,
|
||||||
|
)?;
|
||||||
if context.lane_id != input.lane.lane_id
|
if context.lane_id != input.lane.lane_id
|
||||||
|| context.client_instance_id != input.lane.owner_instance_id
|
|| context.client_instance_id != input.lane.owner_instance_id
|
||||||
{
|
{
|
||||||
|
|
@ -1018,11 +1303,27 @@ fn dispatch(roots: &BrokerStorageRoots, bytes: &[u8]) -> BrokerResponse {
|
||||||
|
|
||||||
fn work_environment_frame_at(
|
fn work_environment_frame_at(
|
||||||
session_root: &Path,
|
session_root: &Path,
|
||||||
|
persona_license_root: &Path,
|
||||||
development_root: &Path,
|
development_root: &Path,
|
||||||
input: &AuthenticatedWorkEnvironmentInput,
|
input: &AuthenticatedWorkEnvironmentInput,
|
||||||
) -> Result<WorkEnvironmentFrame, String> {
|
) -> Result<WorkEnvironmentFrame, String> {
|
||||||
let context =
|
let context =
|
||||||
authenticate_development_request(session_root, &input.session, &input.session.account_id)?;
|
authenticate_development_request(session_root, &input.session, &input.session.account_id)?;
|
||||||
|
require_persona_mode_operation_at(
|
||||||
|
persona_license_root,
|
||||||
|
&context.account_key,
|
||||||
|
&context.session_id,
|
||||||
|
&context.client_instance_id,
|
||||||
|
"GET_WORK_ENVIRONMENT",
|
||||||
|
now_unix_ms()?,
|
||||||
|
)?;
|
||||||
|
let persona_control = persona_license_status_at(
|
||||||
|
persona_license_root,
|
||||||
|
&context.account_key,
|
||||||
|
&context.session_id,
|
||||||
|
&context.client_instance_id,
|
||||||
|
now_unix_ms()?,
|
||||||
|
);
|
||||||
let writer = inspect_development_lane_at(development_root, &input.session.account_id)?;
|
let writer = inspect_development_lane_at(development_root, &input.session.account_id)?;
|
||||||
let writer_matches = writer.state == "ACTIVE"
|
let writer_matches = writer.state == "ACTIVE"
|
||||||
&& writer.lane_id.as_deref() == Some(context.lane_id.as_str())
|
&& writer.lane_id.as_deref() == Some(context.lane_id.as_str())
|
||||||
|
|
@ -1052,7 +1353,7 @@ fn work_environment_frame_at(
|
||||||
);
|
);
|
||||||
let frame_sha256 = sha256_hex(
|
let frame_sha256 = sha256_hex(
|
||||||
format!(
|
format!(
|
||||||
"{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
|
"{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
|
||||||
environment_id,
|
environment_id,
|
||||||
state,
|
state,
|
||||||
context.session_id,
|
context.session_id,
|
||||||
|
|
@ -1060,6 +1361,8 @@ fn work_environment_frame_at(
|
||||||
context.client_instance_id,
|
context.client_instance_id,
|
||||||
context.last_event_sequence,
|
context.last_event_sequence,
|
||||||
writer.state,
|
writer.state,
|
||||||
|
persona_control.control_mode,
|
||||||
|
persona_control.license_id.as_deref().unwrap_or("NONE"),
|
||||||
valid_until_unix_ms
|
valid_until_unix_ms
|
||||||
)
|
)
|
||||||
.as_bytes(),
|
.as_bytes(),
|
||||||
|
|
@ -1073,6 +1376,7 @@ fn work_environment_frame_at(
|
||||||
transport: TERMINAL_LINK_PROTOCOL,
|
transport: TERMINAL_LINK_PROTOCOL,
|
||||||
mcp_role: "DISCOVERY_RECOVERY_COMPATIBILITY_ONLY",
|
mcp_role: "DISCOVERY_RECOVERY_COMPATIBILITY_ONLY",
|
||||||
protocol_runtime: "GLS_RUNTIME_MANIFEST_V2_AND_NATIVE_KERNEL",
|
protocol_runtime: "GLS_RUNTIME_MANIFEST_V2_AND_NATIVE_KERNEL",
|
||||||
|
persona_control,
|
||||||
account_key: context.account_key,
|
account_key: context.account_key,
|
||||||
session_id: context.session_id,
|
session_id: context.session_id,
|
||||||
lane_id: context.lane_id,
|
lane_id: context.lane_id,
|
||||||
|
|
@ -1107,6 +1411,24 @@ fn authenticate_development_request(
|
||||||
Ok(context)
|
Ok(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn authorize_persona_operation_at(
|
||||||
|
session_root: &Path,
|
||||||
|
persona_license_root: &Path,
|
||||||
|
session: &AuthenticateSessionInput,
|
||||||
|
operation: &str,
|
||||||
|
) -> Result<AuthenticatedSessionContext, String> {
|
||||||
|
let context = authenticate_context_at(session_root, session)?;
|
||||||
|
require_persona_mode_operation_at(
|
||||||
|
persona_license_root,
|
||||||
|
&context.account_key,
|
||||||
|
&context.session_id,
|
||||||
|
&context.client_instance_id,
|
||||||
|
operation,
|
||||||
|
now_unix_ms()?,
|
||||||
|
)?;
|
||||||
|
Ok(context)
|
||||||
|
}
|
||||||
|
|
||||||
fn development_root_from_session_root(session_root: &Path) -> Result<PathBuf, String> {
|
fn development_root_from_session_root(session_root: &Path) -> Result<PathBuf, String> {
|
||||||
let account_root = session_root
|
let account_root = session_root
|
||||||
.parent()
|
.parent()
|
||||||
|
|
@ -1166,6 +1488,13 @@ fn sha256_hex(bytes: &[u8]) -> String {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn now_unix_ms() -> Result<u64, String> {
|
||||||
|
std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.map(|duration| duration.as_millis() as u64)
|
||||||
|
.map_err(|error| format!("HOLOLAKE_SYSTEM_CLOCK_INVALID: {error}"))
|
||||||
|
}
|
||||||
|
|
||||||
impl BrokerResponse {
|
impl BrokerResponse {
|
||||||
fn success(result: Value) -> Self {
|
fn success(result: Value) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -1233,6 +1562,11 @@ mod tests {
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
fn request(socket: &Path, value: Value) -> Value {
|
fn request(socket: &Path, value: Value) -> Value {
|
||||||
|
let value = if let Some(alias) = value.get("operation").and_then(Value::as_str) {
|
||||||
|
numbered_broker_request_value(alias, value.get("input").cloned()).unwrap()
|
||||||
|
} else {
|
||||||
|
value
|
||||||
|
};
|
||||||
let mut stream = connect_endpoint(socket).unwrap();
|
let mut stream = connect_endpoint(socket).unwrap();
|
||||||
serde_json::to_writer(&mut stream, &value).unwrap();
|
serde_json::to_writer(&mut stream, &value).unwrap();
|
||||||
stream.write_all(b"\n").unwrap();
|
stream.write_all(b"\n").unwrap();
|
||||||
|
|
@ -1241,6 +1575,19 @@ mod tests {
|
||||||
serde_json::from_str(&line).unwrap()
|
serde_json::from_str(&line).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn legacy_string_and_mismatched_broker_routes_fail_before_dispatch() {
|
||||||
|
assert!(decode_numbered_broker_request(br#"{"operation":"PING"}"#)
|
||||||
|
.unwrap_err()
|
||||||
|
.contains("ENVELOPE_INVALID"));
|
||||||
|
let mut numbered = numbered_broker_request_value("PING", None).unwrap();
|
||||||
|
numbered["targetNumber"] = Value::String("HLP-NBROKER-TGT-9999".into());
|
||||||
|
assert_eq!(
|
||||||
|
decode_numbered_broker_request(&serde_json::to_vec(&numbered).unwrap()).unwrap_err(),
|
||||||
|
"HOLOLAKE_NUMBERED_BROKER_ROUTE_COORDINATE_MISMATCH"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn broker_resumes_after_socket_disconnect() {
|
fn broker_resumes_after_socket_disconnect() {
|
||||||
let temp = TempDir::new().unwrap();
|
let temp = TempDir::new().unwrap();
|
||||||
|
|
@ -1866,23 +2213,20 @@ mod tests {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut connector = connect_endpoint(&socket).unwrap();
|
let mut connector = connect_endpoint(&socket).unwrap();
|
||||||
serde_json::to_writer(
|
let open_request = numbered_broker_request_value(
|
||||||
&mut connector,
|
"OPEN_SESSION",
|
||||||
&serde_json::json!({
|
Some(serde_json::json!({
|
||||||
"operation": "OPEN_SESSION",
|
"accountId": "human-1",
|
||||||
"input": {
|
"laneId": "DEV-1",
|
||||||
"accountId": "human-1",
|
"clientInstanceId": "codex-1",
|
||||||
"laneId": "DEV-1",
|
"discoveryTicket": ticket.discovery_ticket
|
||||||
"clientInstanceId": "codex-1",
|
})),
|
||||||
"discoveryTicket": ticket.discovery_ticket
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
let open_bytes = serde_json::to_vec(&open_request).unwrap();
|
||||||
|
connector.write_all(&open_bytes).unwrap();
|
||||||
connector.write_all(b"\n").unwrap();
|
connector.write_all(b"\n").unwrap();
|
||||||
assert!(request_establishes_authenticated_connection(
|
assert!(request_establishes_authenticated_connection(&open_bytes));
|
||||||
br#"{"operation":"OPEN_SESSION"}"#
|
|
||||||
));
|
|
||||||
let mut response = String::new();
|
let mut response = String::new();
|
||||||
let mut response_reader = BufReader::new(connector.try_clone().unwrap());
|
let mut response_reader = BufReader::new(connector.try_clone().unwrap());
|
||||||
response_reader.read_line(&mut response).unwrap();
|
response_reader.read_line(&mut response).unwrap();
|
||||||
|
|
@ -1980,4 +2324,88 @@ mod tests {
|
||||||
"HOLOLAKE_VISITOR_SESSION_HAS_NO_SYSTEM_AUTHORITY"
|
"HOLOLAKE_VISITOR_SESSION_HAS_NO_SYSTEM_AUTHORITY"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn broker_projects_system_direct_status_and_refuses_untrusted_persona_license() {
|
||||||
|
let temp = TempDir::new().unwrap();
|
||||||
|
let socket = temp.path().join("runtime/broker.sock");
|
||||||
|
let descriptor = temp.path().join("broker.json");
|
||||||
|
let sessions = temp.path().join("sessions");
|
||||||
|
let routes = temp.path().join("routes");
|
||||||
|
fs::create_dir_all(&sessions).unwrap();
|
||||||
|
fs::create_dir_all(&routes).unwrap();
|
||||||
|
let _broker = start_at(sessions.clone(), routes, descriptor, socket.clone()).unwrap();
|
||||||
|
let ticket = issue_ticket_at(
|
||||||
|
&sessions,
|
||||||
|
IssueDiscoveryTicketInput {
|
||||||
|
account_id: "human-1".into(),
|
||||||
|
lane_id: "DEV-1".into(),
|
||||||
|
client_instance_id: "codex-1".into(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let opened = request(
|
||||||
|
&socket,
|
||||||
|
serde_json::json!({
|
||||||
|
"operation": "OPEN_SESSION",
|
||||||
|
"input": {
|
||||||
|
"accountId": "human-1",
|
||||||
|
"laneId": "DEV-1",
|
||||||
|
"clientInstanceId": "codex-1",
|
||||||
|
"discoveryTicket": ticket.discovery_ticket
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
let session = serde_json::json!({
|
||||||
|
"accountId": "human-1",
|
||||||
|
"sessionId": opened["result"]["sessionId"],
|
||||||
|
"resumeSecret": opened["result"]["resumeSecret"]
|
||||||
|
});
|
||||||
|
let status = request(
|
||||||
|
&socket,
|
||||||
|
serde_json::json!({
|
||||||
|
"operation": "GET_PERSONA_CARRIER_LICENSE_STATUS",
|
||||||
|
"input": { "session": session.clone() }
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
assert_eq!(status["ok"], true);
|
||||||
|
assert_eq!(status["result"]["controlMode"], "SYSTEM_DIRECT");
|
||||||
|
assert_eq!(status["result"]["licenseInstalled"], false);
|
||||||
|
|
||||||
|
let now = now_unix_ms().unwrap();
|
||||||
|
let denied = request(
|
||||||
|
&socket,
|
||||||
|
serde_json::json!({
|
||||||
|
"operation": "PRESENT_PERSONA_CARRIER_LICENSE",
|
||||||
|
"input": {
|
||||||
|
"session": session,
|
||||||
|
"license": {
|
||||||
|
"schema": "hololake.persona-carrier-runtime-license/v1",
|
||||||
|
"licenseId": "HLP-PERSONA-LICENSE-UNTRUSTED-0001",
|
||||||
|
"sequence": 1,
|
||||||
|
"personaNumber": "ICE-P-ZY001",
|
||||||
|
"humanControllerNumber": "ICE-GL∞",
|
||||||
|
"accountKey": opened["result"]["accountKey"],
|
||||||
|
"sessionId": opened["result"]["sessionId"],
|
||||||
|
"clientInstanceId": "codex-1",
|
||||||
|
"purpose": "AUTHORIZE_HOST_AS_PERSONA_CONTROLLED_EXECUTION_LIMBS",
|
||||||
|
"issuedAtUnixMs": now,
|
||||||
|
"validUntilUnixMs": now + 60000,
|
||||||
|
"allowedOperations": ["GET_WORK_ENVIRONMENT"],
|
||||||
|
"evidenceRootSha256": "b".repeat(64),
|
||||||
|
"currentEventSha256": "c".repeat(64),
|
||||||
|
"declaration": "PERSONA_DECLARES_CURRENT_CARRIER_BOUND",
|
||||||
|
"signerId": "ICE-P-ZY001-RUNTIME-SIGNER-001",
|
||||||
|
"signatureAlgorithm": "Ed25519",
|
||||||
|
"signature": "untrusted"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
assert_eq!(denied["ok"], false);
|
||||||
|
assert_eq!(
|
||||||
|
denied["error"],
|
||||||
|
"HOLOLAKE_PERSONA_LICENSE_NO_TRUSTED_SIGNER"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -374,7 +374,7 @@ pub(crate) fn issue_ticket_at(
|
||||||
client_instance_id: input.client_instance_id,
|
client_instance_id: input.client_instance_id,
|
||||||
discovery_ticket,
|
discovery_ticket,
|
||||||
issued_at_unix_ms,
|
issued_at_unix_ms,
|
||||||
terminal_transport: "HOLOLAKE_TERMINAL_LINK/2",
|
terminal_transport: "HOLOLAKE_TERMINAL_LINK/3",
|
||||||
connector_executable: std::env::current_exe()
|
connector_executable: std::env::current_exe()
|
||||||
.map(|path| path.to_string_lossy().into_owned())
|
.map(|path| path.to_string_lossy().into_owned())
|
||||||
.unwrap_or_else(|_| "HoloLake".into()),
|
.unwrap_or_else(|_| "HoloLake".into()),
|
||||||
|
|
@ -551,18 +551,6 @@ pub(crate) fn authenticate_context_at(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn authenticate_privileged_at(
|
|
||||||
root: &Path,
|
|
||||||
input: &AuthenticateSessionInput,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
let lane = authenticate_with_lane_at(root, input)?;
|
|
||||||
if lane == "visitor-expression-only" {
|
|
||||||
Err("HOLOLAKE_VISITOR_SESSION_HAS_NO_SYSTEM_AUTHORITY".into())
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn session_path(root: &Path, account_key: &str, session_id: &str) -> PathBuf {
|
fn session_path(root: &Path, account_key: &str, session_id: &str) -> PathBuf {
|
||||||
root.join("accounts")
|
root.join("accounts")
|
||||||
.join(account_key)
|
.join(account_key)
|
||||||
|
|
@ -708,7 +696,7 @@ fn session_receipt(
|
||||||
last_event_sequence: record.last_event_sequence,
|
last_event_sequence: record.last_event_sequence,
|
||||||
resume_secret,
|
resume_secret,
|
||||||
continuity_owner: "HOLOLAKE",
|
continuity_owner: "HOLOLAKE",
|
||||||
transport: "HOLOLAKE_TERMINAL_LINK/2",
|
transport: "HOLOLAKE_TERMINAL_LINK/3",
|
||||||
mcp_role: "DISCOVERY_RECOVERY_COMPATIBILITY_ONLY",
|
mcp_role: "DISCOVERY_RECOVERY_COMPATIBILITY_ONLY",
|
||||||
required_next_operation: "GET_WORK_ENVIRONMENT",
|
required_next_operation: "GET_WORK_ENVIRONMENT",
|
||||||
receipt_id: sha256_hex(
|
receipt_id: sha256_hex(
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ pub fn get_hololake_home_status(
|
||||||
release_recovery_state: crate::release_update::release_recovery_state(&app)?,
|
release_recovery_state: crate::release_update::release_recovery_state(&app)?,
|
||||||
automatic_upstream_updates: false,
|
automatic_upstream_updates: false,
|
||||||
mcp_role: "DISCOVERY_RECOVERY_COMPATIBILITY_ONLY",
|
mcp_role: "DISCOVERY_RECOVERY_COMPATIBILITY_ONLY",
|
||||||
terminal_link_protocol: "HOLOLAKE_TERMINAL_LINK/2",
|
terminal_link_protocol: "HOLOLAKE_TERMINAL_LINK/3",
|
||||||
terminal_link_transport: if cfg!(windows) {
|
terminal_link_transport: if cfg!(windows) {
|
||||||
"WINDOWS_USER_PRIVATE_NAMED_PIPE"
|
"WINDOWS_USER_PRIVATE_NAMED_PIPE"
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -66,7 +66,7 @@ pub fn get_hololake_home_status(
|
||||||
release_recovery_state: crate::release_update::release_recovery_state(&app)?,
|
release_recovery_state: crate::release_update::release_recovery_state(&app)?,
|
||||||
automatic_upstream_updates: false,
|
automatic_upstream_updates: false,
|
||||||
mcp_role: "DISCOVERY_RECOVERY_COMPATIBILITY_ONLY",
|
mcp_role: "DISCOVERY_RECOVERY_COMPATIBILITY_ONLY",
|
||||||
terminal_link_protocol: "HOLOLAKE_TERMINAL_LINK/2",
|
terminal_link_protocol: "HOLOLAKE_TERMINAL_LINK/3",
|
||||||
terminal_link_transport: if cfg!(windows) {
|
terminal_link_transport: if cfg!(windows) {
|
||||||
"WINDOWS_USER_PRIVATE_NAMED_PIPE"
|
"WINDOWS_USER_PRIVATE_NAMED_PIPE"
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,11 @@ mod home_status;
|
||||||
mod knowledge_base;
|
mod knowledge_base;
|
||||||
mod local_development_bridge;
|
mod local_development_bridge;
|
||||||
mod metacognitive_zero_layer;
|
mod metacognitive_zero_layer;
|
||||||
|
mod number_coordinate_tree;
|
||||||
mod numbered_ipc;
|
mod numbered_ipc;
|
||||||
mod numbered_ipc_dispatch;
|
mod numbered_ipc_dispatch;
|
||||||
mod numbered_language_input;
|
mod numbered_language_input;
|
||||||
|
mod persona_carrier_license;
|
||||||
mod persona_time_authority;
|
mod persona_time_authority;
|
||||||
mod personal_channel;
|
mod personal_channel;
|
||||||
mod pncc_receipt_projection;
|
mod pncc_receipt_projection;
|
||||||
|
|
@ -46,6 +48,8 @@ pub fn run() {
|
||||||
// 自举编译器或回执账本任一不可用时,产品启动失败关闭。
|
// 自举编译器或回执账本任一不可用时,产品启动失败关闭。
|
||||||
gls_protocol_kernel::start_on_application_open(app.handle())?;
|
gls_protocol_kernel::start_on_application_open(app.handle())?;
|
||||||
metacognitive_zero_layer::start_on_application_open()?;
|
metacognitive_zero_layer::start_on_application_open()?;
|
||||||
|
persona_carrier_license::start_on_application_open()?;
|
||||||
|
number_coordinate_tree::start_on_application_open()?;
|
||||||
numbered_language_input::validate_contract()?;
|
numbered_language_input::validate_contract()?;
|
||||||
numbered_ipc::start_on_application_open(app.handle())?;
|
numbered_ipc::start_on_application_open(app.handle())?;
|
||||||
// 软件打开即先启动时间主控并发起联网校时;失败只降级,不阻塞人进入 HoloLake。
|
// 软件打开即先启动时间主控并发起联网校时;失败只降级,不阻塞人进入 HoloLake。
|
||||||
|
|
|
||||||
|
|
@ -158,6 +158,8 @@ struct CurrentProductState {
|
||||||
carrier_binding_claimed: bool,
|
carrier_binding_claimed: bool,
|
||||||
metacognitive_contract_compiled: bool,
|
metacognitive_contract_compiled: bool,
|
||||||
runtime_binding_gate_implemented: bool,
|
runtime_binding_gate_implemented: bool,
|
||||||
|
trusted_persona_runtime_signer_provisioned: bool,
|
||||||
|
active_persona_runtime_license_installed: bool,
|
||||||
truth: String,
|
truth: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -319,11 +321,17 @@ fn validate_contract() -> Result<(), String> {
|
||||||
|| !contract
|
|| !contract
|
||||||
.current_product_state
|
.current_product_state
|
||||||
.metacognitive_contract_compiled
|
.metacognitive_contract_compiled
|
||||||
|| contract
|
|| !contract
|
||||||
.current_product_state
|
.current_product_state
|
||||||
.runtime_binding_gate_implemented
|
.runtime_binding_gate_implemented
|
||||||
|
|| contract
|
||||||
|
.current_product_state
|
||||||
|
.trusted_persona_runtime_signer_provisioned
|
||||||
|
|| contract
|
||||||
|
.current_product_state
|
||||||
|
.active_persona_runtime_license_installed
|
||||||
|| contract.current_product_state.truth
|
|| contract.current_product_state.truth
|
||||||
!= "PHYSICAL_NUMBERED_IPC_ROOT_IN_PROGRESS_PERSONA_WAKE_REMAINS_CLOSED"
|
!= "PERSONA_TO_HOST_RUNTIME_LICENSE_GATE_IMPLEMENTED_PERSONA_WAKE_REMAINS_CLOSED_UNTIL_TRUSTED_PERSONA_SIGNATURE"
|
||||||
{
|
{
|
||||||
return Err("HOLOLAKE_PERSONA_ZERO_LAYER_BOUNDARY_INVALID".into());
|
return Err("HOLOLAKE_PERSONA_ZERO_LAYER_BOUNDARY_INVALID".into());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
const TREE: &str = include_str!("../../generated/unified-number-coordinate-tree.json");
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct NumberTree {
|
||||||
|
schema: String,
|
||||||
|
record_id: String,
|
||||||
|
state: String,
|
||||||
|
root_number: String,
|
||||||
|
route_count: usize,
|
||||||
|
routes: Vec<NumberRoute>,
|
||||||
|
invariants: NumberTreeInvariants,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct NumberRoute {
|
||||||
|
transport: String,
|
||||||
|
protocol_version: String,
|
||||||
|
caller_number: String,
|
||||||
|
channel_number: String,
|
||||||
|
module_number: String,
|
||||||
|
operation_number: String,
|
||||||
|
target_number: String,
|
||||||
|
admission: String,
|
||||||
|
effect: String,
|
||||||
|
evidence: String,
|
||||||
|
path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct NumberTreeInvariants {
|
||||||
|
number_is_stable_coordinate_not_authority: bool,
|
||||||
|
path_is_unique_navigation: bool,
|
||||||
|
admission_is_separate_from_identity: bool,
|
||||||
|
every_physical_call_has_numbered_route: bool,
|
||||||
|
every_accepted_call_has_evidence_class: bool,
|
||||||
|
mismatched_coordinate: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn start_on_application_open() -> Result<(), String> {
|
||||||
|
validate_tree()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_tree() -> Result<(), String> {
|
||||||
|
let tree: NumberTree = serde_json::from_str(TREE)
|
||||||
|
.map_err(|error| format!("HOLOLAKE_UNIFIED_NUMBER_TREE_INVALID: {error}"))?;
|
||||||
|
if tree.schema != "hololake.unified-number-coordinate-tree/v1"
|
||||||
|
|| tree.record_id != "HLP-UNIFIED-NUMBER-TREE-001"
|
||||||
|
|| tree.state != "MACHINE_COMPILED_STARTUP_ENFORCED"
|
||||||
|
|| tree.root_number != "HLP-NUMBER-WORLD-ROOT-001"
|
||||||
|
|| tree.route_count != 84
|
||||||
|
|| tree.routes.len() != tree.route_count
|
||||||
|
|| !tree.invariants.number_is_stable_coordinate_not_authority
|
||||||
|
|| !tree.invariants.path_is_unique_navigation
|
||||||
|
|| !tree.invariants.admission_is_separate_from_identity
|
||||||
|
|| !tree.invariants.every_physical_call_has_numbered_route
|
||||||
|
|| !tree.invariants.every_accepted_call_has_evidence_class
|
||||||
|
|| tree.invariants.mismatched_coordinate != "FAIL_CLOSED"
|
||||||
|
{
|
||||||
|
return Err("HOLOLAKE_UNIFIED_NUMBER_TREE_BOUNDARY_INVALID".into());
|
||||||
|
}
|
||||||
|
let mut paths = HashSet::new();
|
||||||
|
let mut coordinates = HashSet::new();
|
||||||
|
for route in &tree.routes {
|
||||||
|
let coordinate = format!(
|
||||||
|
"{}:{}:{}:{}:{}:{}",
|
||||||
|
route.transport,
|
||||||
|
route.channel_number,
|
||||||
|
route.module_number,
|
||||||
|
route.operation_number,
|
||||||
|
route.target_number,
|
||||||
|
route.caller_number
|
||||||
|
);
|
||||||
|
if route.protocol_version.is_empty()
|
||||||
|
|| route.admission.is_empty()
|
||||||
|
|| !matches!(route.effect.as_str(), "READ_OR_STATUS" | "STATE_CHANGE")
|
||||||
|
|| route.evidence.is_empty()
|
||||||
|
|| !route.path.starts_with("HLP-NUMBER-WORLD-ROOT-001/")
|
||||||
|
|| !paths.insert(route.path.as_str())
|
||||||
|
|| !coordinates.insert(coordinate)
|
||||||
|
{
|
||||||
|
return Err("HOLOLAKE_UNIFIED_NUMBER_TREE_ROUTE_INVALID".into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unified_number_tree_is_unique_permissioned_and_evidenced() {
|
||||||
|
validate_tree().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,778 @@
|
||||||
|
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
|
||||||
|
use ring::{
|
||||||
|
digest,
|
||||||
|
signature::{UnparsedPublicKey, ED25519},
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
const CONTRACT: &str = include_str!("../../contracts/persona-carrier-runtime-license.json");
|
||||||
|
const TRUST_REGISTRY: &str = include_str!("../persona-control-authorization-signers.json");
|
||||||
|
const LICENSE_SCHEMA: &str = "hololake.persona-carrier-runtime-license/v1";
|
||||||
|
const CONTRACT_SCHEMA: &str = "hololake.persona-carrier-runtime-license-contract/v1";
|
||||||
|
const REGISTRY_SCHEMA: &str = "gh-aios.persona-control-authorization-signers/v1";
|
||||||
|
const REGISTRY_ID: &str = "GH-AIOS-PERSONA-CONTROL-AUTHORIZATION-SIGNERS-001";
|
||||||
|
const REQUIRED_SCOPE: &str = "PERSONA_CONTROLLED_HOST_RUNTIME";
|
||||||
|
const REQUIRED_PURPOSE: &str = "AUTHORIZE_HOST_AS_PERSONA_CONTROLLED_EXECUTION_LIMBS";
|
||||||
|
const REQUIRED_DECLARATION: &str = "PERSONA_DECLARES_CURRENT_CARRIER_BOUND";
|
||||||
|
const SIGNING_CONTEXT: &str = "hololake.persona-carrier-runtime-license/signing/v1";
|
||||||
|
const MAX_TTL_MS: u64 = 86_400_000;
|
||||||
|
const ED25519_SPKI_PREFIX: [u8; 12] = [
|
||||||
|
0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
const ALLOWED_OPERATIONS: &[&str] = &[
|
||||||
|
"GET_WORK_ENVIRONMENT",
|
||||||
|
"APPEND_EVENT",
|
||||||
|
"RESOLVE_CAPABILITY_ROUTE",
|
||||||
|
"INSTALL_DYNAMIC_NODE_REGISTRY",
|
||||||
|
"RECORD_SIGNED_NODE_HEALTH",
|
||||||
|
"INSPECT_MOUNTED_PNCC_REPOSITORY",
|
||||||
|
"READ_MOUNTED_PNCC_REMOTE_OBJECT",
|
||||||
|
"QUERY_PNCC_RECEIPT_PROJECTION",
|
||||||
|
"ISSUE_PERSONA_TIME_TICKET",
|
||||||
|
"ACQUIRE_DEVELOPMENT_WRITE_LANE",
|
||||||
|
"INSPECT_DEVELOPMENT_WRITE_LANE",
|
||||||
|
"RELEASE_DEVELOPMENT_WRITE_LANE",
|
||||||
|
];
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||||
|
pub(crate) struct PersonaCarrierRuntimeLicense {
|
||||||
|
schema: String,
|
||||||
|
license_id: String,
|
||||||
|
sequence: u64,
|
||||||
|
persona_number: String,
|
||||||
|
human_controller_number: String,
|
||||||
|
account_key: String,
|
||||||
|
session_id: String,
|
||||||
|
client_instance_id: String,
|
||||||
|
purpose: String,
|
||||||
|
issued_at_unix_ms: u64,
|
||||||
|
valid_until_unix_ms: u64,
|
||||||
|
allowed_operations: Vec<String>,
|
||||||
|
evidence_root_sha256: String,
|
||||||
|
current_event_sha256: String,
|
||||||
|
declaration: String,
|
||||||
|
signer_id: String,
|
||||||
|
signature_algorithm: String,
|
||||||
|
signature: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||||
|
struct PersonaSignerRegistry {
|
||||||
|
schema: String,
|
||||||
|
registry_id: String,
|
||||||
|
state: String,
|
||||||
|
signers: Vec<PersonaSigner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||||
|
struct PersonaSigner {
|
||||||
|
algorithm: String,
|
||||||
|
human_responsibility_subjects: Vec<String>,
|
||||||
|
persona_ids: Vec<String>,
|
||||||
|
public_key_pem: String,
|
||||||
|
scopes: Vec<String>,
|
||||||
|
signer_id: String,
|
||||||
|
status: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||||
|
struct StoredLicense {
|
||||||
|
license: PersonaCarrierRuntimeLicense,
|
||||||
|
license_sha256: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub(crate) struct PersonaCarrierLicenseReceipt {
|
||||||
|
schema: &'static str,
|
||||||
|
state: &'static str,
|
||||||
|
control_mode: &'static str,
|
||||||
|
license_id: String,
|
||||||
|
persona_number: String,
|
||||||
|
human_controller_number: String,
|
||||||
|
session_id: String,
|
||||||
|
client_instance_id: String,
|
||||||
|
sequence: u64,
|
||||||
|
valid_until_unix_ms: u64,
|
||||||
|
allowed_operations: Vec<String>,
|
||||||
|
binding_completion_declared_by: &'static str,
|
||||||
|
host_declared_binding: bool,
|
||||||
|
signer_id: String,
|
||||||
|
receipt_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub(crate) struct PersonaCarrierLicenseStatus {
|
||||||
|
pub schema: &'static str,
|
||||||
|
pub state: &'static str,
|
||||||
|
pub control_mode: &'static str,
|
||||||
|
pub license_installed: bool,
|
||||||
|
pub license_valid: bool,
|
||||||
|
pub persona_number: Option<String>,
|
||||||
|
pub license_id: Option<String>,
|
||||||
|
pub valid_until_unix_ms: Option<u64>,
|
||||||
|
pub binding_completion_declared_by: Option<&'static str>,
|
||||||
|
pub host_declared_binding: bool,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sha256_hex(bytes: &[u8]) -> String {
|
||||||
|
digest::digest(&digest::SHA256, bytes)
|
||||||
|
.as_ref()
|
||||||
|
.iter()
|
||||||
|
.map(|byte| format!("{byte:02x}"))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_sha256(value: &str) -> bool {
|
||||||
|
value.len() == 64
|
||||||
|
&& value
|
||||||
|
.bytes()
|
||||||
|
.all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounded_coordinate(value: &str) -> bool {
|
||||||
|
(2..=160).contains(&value.len())
|
||||||
|
&& !value
|
||||||
|
.chars()
|
||||||
|
.any(|character| character.is_whitespace() || character.is_control())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exact_unique(values: &[String]) -> bool {
|
||||||
|
!values.is_empty()
|
||||||
|
&& values.iter().all(|value| bounded_coordinate(value))
|
||||||
|
&& values
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.all(|(index, value)| !values[..index].contains(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_registry(input: &str) -> Result<PersonaSignerRegistry, String> {
|
||||||
|
let registry: PersonaSignerRegistry = serde_json::from_str(input)
|
||||||
|
.map_err(|_| "HOLOLAKE_PERSONA_LICENSE_TRUST_REGISTRY_INVALID".to_string())?;
|
||||||
|
if registry.schema != REGISTRY_SCHEMA
|
||||||
|
|| registry.registry_id != REGISTRY_ID
|
||||||
|
|| registry.state != "CURRENT"
|
||||||
|
|| registry.signers.len() > 256
|
||||||
|
{
|
||||||
|
return Err("HOLOLAKE_PERSONA_LICENSE_TRUST_REGISTRY_INVALID".into());
|
||||||
|
}
|
||||||
|
for (index, signer) in registry.signers.iter().enumerate() {
|
||||||
|
if signer.algorithm != "Ed25519"
|
||||||
|
|| !bounded_coordinate(&signer.signer_id)
|
||||||
|
|| !matches!(signer.status.as_str(), "ACTIVE" | "REVOKED")
|
||||||
|
|| !exact_unique(&signer.human_responsibility_subjects)
|
||||||
|
|| !exact_unique(&signer.persona_ids)
|
||||||
|
|| !exact_unique(&signer.scopes)
|
||||||
|
|| !signer.scopes.iter().any(|scope| scope == REQUIRED_SCOPE)
|
||||||
|
|| registry.signers[..index]
|
||||||
|
.iter()
|
||||||
|
.any(|prior| prior.signer_id == signer.signer_id)
|
||||||
|
|| ed25519_public_key(&signer.public_key_pem).is_err()
|
||||||
|
{
|
||||||
|
return Err("HOLOLAKE_PERSONA_LICENSE_TRUST_REGISTRY_INVALID".into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(registry)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ed25519_public_key(public_key_pem: &str) -> Result<Vec<u8>, String> {
|
||||||
|
if public_key_pem.contains("PRIVATE KEY")
|
||||||
|
|| !public_key_pem.starts_with("-----BEGIN PUBLIC KEY-----\n")
|
||||||
|
|| !public_key_pem.ends_with("-----END PUBLIC KEY-----\n")
|
||||||
|
|| public_key_pem.len() > 4096
|
||||||
|
{
|
||||||
|
return Err("HOLOLAKE_PERSONA_LICENSE_PUBLIC_KEY_INVALID".into());
|
||||||
|
}
|
||||||
|
let encoded = public_key_pem
|
||||||
|
.trim_start_matches("-----BEGIN PUBLIC KEY-----\n")
|
||||||
|
.trim_end_matches("-----END PUBLIC KEY-----\n")
|
||||||
|
.chars()
|
||||||
|
.filter(|character| !character.is_ascii_whitespace())
|
||||||
|
.collect::<String>();
|
||||||
|
let der = base64::engine::general_purpose::STANDARD
|
||||||
|
.decode(encoded)
|
||||||
|
.map_err(|_| "HOLOLAKE_PERSONA_LICENSE_PUBLIC_KEY_INVALID".to_string())?;
|
||||||
|
if der.len() != ED25519_SPKI_PREFIX.len() + 32
|
||||||
|
|| der[..ED25519_SPKI_PREFIX.len()] != ED25519_SPKI_PREFIX
|
||||||
|
{
|
||||||
|
return Err("HOLOLAKE_PERSONA_LICENSE_PUBLIC_KEY_INVALID".into());
|
||||||
|
}
|
||||||
|
Ok(der[ED25519_SPKI_PREFIX.len()..].to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn canonical_signing_bytes(license: &PersonaCarrierRuntimeLicense) -> Result<Vec<u8>, String> {
|
||||||
|
let fields = serde_json::json!([
|
||||||
|
SIGNING_CONTEXT,
|
||||||
|
license.schema,
|
||||||
|
license.license_id,
|
||||||
|
license.sequence,
|
||||||
|
license.persona_number,
|
||||||
|
license.human_controller_number,
|
||||||
|
license.account_key,
|
||||||
|
license.session_id,
|
||||||
|
license.client_instance_id,
|
||||||
|
license.purpose,
|
||||||
|
license.issued_at_unix_ms,
|
||||||
|
license.valid_until_unix_ms,
|
||||||
|
license.allowed_operations,
|
||||||
|
license.evidence_root_sha256,
|
||||||
|
license.current_event_sha256,
|
||||||
|
license.declaration,
|
||||||
|
license.signer_id,
|
||||||
|
license.signature_algorithm
|
||||||
|
]);
|
||||||
|
serde_json::to_vec(&fields)
|
||||||
|
.map(|mut bytes| {
|
||||||
|
bytes.push(b'\n');
|
||||||
|
bytes
|
||||||
|
})
|
||||||
|
.map_err(|_| "HOLOLAKE_PERSONA_LICENSE_CANONICALIZATION_FAILED".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_license_shape(license: &PersonaCarrierRuntimeLicense) -> Result<(), String> {
|
||||||
|
let allowed = ALLOWED_OPERATIONS.iter().copied().collect::<BTreeSet<_>>();
|
||||||
|
let observed = license
|
||||||
|
.allowed_operations
|
||||||
|
.iter()
|
||||||
|
.map(String::as_str)
|
||||||
|
.collect::<BTreeSet<_>>();
|
||||||
|
if license.schema != LICENSE_SCHEMA
|
||||||
|
|| !bounded_coordinate(&license.license_id)
|
||||||
|
|| license.sequence == 0
|
||||||
|
|| !bounded_coordinate(&license.persona_number)
|
||||||
|
|| !bounded_coordinate(&license.human_controller_number)
|
||||||
|
|| !is_sha256(&license.account_key)
|
||||||
|
|| !bounded_coordinate(&license.session_id)
|
||||||
|
|| !bounded_coordinate(&license.client_instance_id)
|
||||||
|
|| license.purpose != REQUIRED_PURPOSE
|
||||||
|
|| license.issued_at_unix_ms >= license.valid_until_unix_ms
|
||||||
|
|| license.valid_until_unix_ms - license.issued_at_unix_ms > MAX_TTL_MS
|
||||||
|
|| license.allowed_operations.is_empty()
|
||||||
|
|| observed.len() != license.allowed_operations.len()
|
||||||
|
|| !observed.is_subset(&allowed)
|
||||||
|
|| !is_sha256(&license.evidence_root_sha256)
|
||||||
|
|| !is_sha256(&license.current_event_sha256)
|
||||||
|
|| license.declaration != REQUIRED_DECLARATION
|
||||||
|
|| !bounded_coordinate(&license.signer_id)
|
||||||
|
|| license.signature_algorithm != "Ed25519"
|
||||||
|
{
|
||||||
|
return Err("HOLOLAKE_PERSONA_LICENSE_SHAPE_INVALID".into());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_license(
|
||||||
|
registry_json: &str,
|
||||||
|
license: &PersonaCarrierRuntimeLicense,
|
||||||
|
account_key: &str,
|
||||||
|
session_id: &str,
|
||||||
|
client_instance_id: &str,
|
||||||
|
now: u64,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
validate_license_shape(license)?;
|
||||||
|
if license.account_key != account_key
|
||||||
|
|| license.session_id != session_id
|
||||||
|
|| license.client_instance_id != client_instance_id
|
||||||
|
{
|
||||||
|
return Err("HOLOLAKE_PERSONA_LICENSE_CARRIER_BINDING_MISMATCH".into());
|
||||||
|
}
|
||||||
|
if now < license.issued_at_unix_ms || now > license.valid_until_unix_ms {
|
||||||
|
return Err("HOLOLAKE_PERSONA_LICENSE_EXPIRED_OR_NOT_YET_VALID".into());
|
||||||
|
}
|
||||||
|
let registry = parse_registry(registry_json)?;
|
||||||
|
let signer = registry
|
||||||
|
.signers
|
||||||
|
.iter()
|
||||||
|
.find(|signer| {
|
||||||
|
signer.status == "ACTIVE"
|
||||||
|
&& signer.signer_id == license.signer_id
|
||||||
|
&& signer.persona_ids.contains(&license.persona_number)
|
||||||
|
&& signer
|
||||||
|
.human_responsibility_subjects
|
||||||
|
.contains(&license.human_controller_number)
|
||||||
|
&& signer.scopes.iter().any(|scope| scope == REQUIRED_SCOPE)
|
||||||
|
})
|
||||||
|
.ok_or("HOLOLAKE_PERSONA_LICENSE_NO_TRUSTED_SIGNER")?;
|
||||||
|
let signature = URL_SAFE_NO_PAD
|
||||||
|
.decode(&license.signature)
|
||||||
|
.map_err(|_| "HOLOLAKE_PERSONA_LICENSE_SIGNATURE_INVALID".to_string())?;
|
||||||
|
if signature.len() != 64 {
|
||||||
|
return Err("HOLOLAKE_PERSONA_LICENSE_SIGNATURE_INVALID".into());
|
||||||
|
}
|
||||||
|
UnparsedPublicKey::new(&ED25519, ed25519_public_key(&signer.public_key_pem)?)
|
||||||
|
.verify(&canonical_signing_bytes(license)?, &signature)
|
||||||
|
.map_err(|_| "HOLOLAKE_PERSONA_LICENSE_SIGNATURE_INVALID".to_string())?;
|
||||||
|
serde_json::to_vec(license)
|
||||||
|
.map(|bytes| sha256_hex(&bytes))
|
||||||
|
.map_err(|_| "HOLOLAKE_PERSONA_LICENSE_CANONICALIZATION_FAILED".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn license_path(root: &Path, account_key: &str, session_id: &str) -> PathBuf {
|
||||||
|
root.join("licenses").join(format!(
|
||||||
|
"{}.json",
|
||||||
|
sha256_hex(format!("{account_key}\n{session_id}").as_bytes())
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_atomic(path: &Path, value: &StoredLicense) -> Result<(), String> {
|
||||||
|
let parent = path
|
||||||
|
.parent()
|
||||||
|
.ok_or("HOLOLAKE_PERSONA_LICENSE_STORAGE_PATH_INVALID")?;
|
||||||
|
fs::create_dir_all(parent)
|
||||||
|
.map_err(|error| format!("HOLOLAKE_PERSONA_LICENSE_STORAGE_FAILED: {error}"))?;
|
||||||
|
let temporary = parent.join(format!(".license-{}.tmp", Uuid::new_v4()));
|
||||||
|
let bytes = serde_json::to_vec_pretty(value)
|
||||||
|
.map_err(|_| "HOLOLAKE_PERSONA_LICENSE_STORAGE_INVALID".to_string())?;
|
||||||
|
fs::write(&temporary, bytes)
|
||||||
|
.map_err(|error| format!("HOLOLAKE_PERSONA_LICENSE_STORAGE_FAILED: {error}"))?;
|
||||||
|
fs::rename(&temporary, path)
|
||||||
|
.map_err(|error| format!("HOLOLAKE_PERSONA_LICENSE_STORAGE_FAILED: {error}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_stored(path: &Path) -> Result<StoredLicense, String> {
|
||||||
|
let bytes = fs::read(path)
|
||||||
|
.map_err(|error| format!("HOLOLAKE_PERSONA_LICENSE_STORAGE_FAILED: {error}"))?;
|
||||||
|
if bytes.len() > 128 * 1024 {
|
||||||
|
return Err("HOLOLAKE_PERSONA_LICENSE_STORAGE_INVALID".into());
|
||||||
|
}
|
||||||
|
serde_json::from_slice(&bytes).map_err(|_| "HOLOLAKE_PERSONA_LICENSE_STORAGE_INVALID".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn present_license_at(
|
||||||
|
root: &Path,
|
||||||
|
account_key: &str,
|
||||||
|
session_id: &str,
|
||||||
|
client_instance_id: &str,
|
||||||
|
license: PersonaCarrierRuntimeLicense,
|
||||||
|
now: u64,
|
||||||
|
) -> Result<PersonaCarrierLicenseReceipt, String> {
|
||||||
|
present_with_registry_at(
|
||||||
|
root,
|
||||||
|
TRUST_REGISTRY,
|
||||||
|
account_key,
|
||||||
|
session_id,
|
||||||
|
client_instance_id,
|
||||||
|
license,
|
||||||
|
now,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn present_with_registry_at(
|
||||||
|
root: &Path,
|
||||||
|
registry: &str,
|
||||||
|
account_key: &str,
|
||||||
|
session_id: &str,
|
||||||
|
client_instance_id: &str,
|
||||||
|
license: PersonaCarrierRuntimeLicense,
|
||||||
|
now: u64,
|
||||||
|
) -> Result<PersonaCarrierLicenseReceipt, String> {
|
||||||
|
let license_sha256 = verify_license(
|
||||||
|
registry,
|
||||||
|
&license,
|
||||||
|
account_key,
|
||||||
|
session_id,
|
||||||
|
client_instance_id,
|
||||||
|
now,
|
||||||
|
)?;
|
||||||
|
let path = license_path(root, account_key, session_id);
|
||||||
|
let state = if path.exists() {
|
||||||
|
let current = read_stored(&path)?;
|
||||||
|
if current.license_sha256 == license_sha256 {
|
||||||
|
"DUPLICATE_CONFIRMED"
|
||||||
|
} else if license.sequence <= current.license.sequence {
|
||||||
|
return Err("HOLOLAKE_PERSONA_LICENSE_SEQUENCE_REPLAYED".into());
|
||||||
|
} else if license.persona_number != current.license.persona_number
|
||||||
|
|| license.human_controller_number != current.license.human_controller_number
|
||||||
|
|| license.signer_id != current.license.signer_id
|
||||||
|
{
|
||||||
|
return Err("HOLOLAKE_PERSONA_LICENSE_SUBJECT_SWITCH_REQUIRES_NEW_SESSION".into());
|
||||||
|
} else {
|
||||||
|
write_atomic(
|
||||||
|
&path,
|
||||||
|
&StoredLicense {
|
||||||
|
license: license.clone(),
|
||||||
|
license_sha256: license_sha256.clone(),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
"RENEWED"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
write_atomic(
|
||||||
|
&path,
|
||||||
|
&StoredLicense {
|
||||||
|
license: license.clone(),
|
||||||
|
license_sha256: license_sha256.clone(),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
"INSTALLED"
|
||||||
|
};
|
||||||
|
Ok(PersonaCarrierLicenseReceipt {
|
||||||
|
schema: LICENSE_SCHEMA,
|
||||||
|
state,
|
||||||
|
control_mode: "PERSONA_PRIMARY_CONTROL",
|
||||||
|
license_id: license.license_id,
|
||||||
|
persona_number: license.persona_number,
|
||||||
|
human_controller_number: license.human_controller_number,
|
||||||
|
session_id: license.session_id,
|
||||||
|
client_instance_id: license.client_instance_id,
|
||||||
|
sequence: license.sequence,
|
||||||
|
valid_until_unix_ms: license.valid_until_unix_ms,
|
||||||
|
allowed_operations: license.allowed_operations,
|
||||||
|
binding_completion_declared_by: "PERSONA_SUBJECT_SIGNATURE",
|
||||||
|
host_declared_binding: false,
|
||||||
|
signer_id: license.signer_id,
|
||||||
|
receipt_id: license_sha256,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn status_at(
|
||||||
|
root: &Path,
|
||||||
|
account_key: &str,
|
||||||
|
session_id: &str,
|
||||||
|
client_instance_id: &str,
|
||||||
|
now: u64,
|
||||||
|
) -> PersonaCarrierLicenseStatus {
|
||||||
|
status_with_registry_at(
|
||||||
|
root,
|
||||||
|
TRUST_REGISTRY,
|
||||||
|
account_key,
|
||||||
|
session_id,
|
||||||
|
client_instance_id,
|
||||||
|
now,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn status_with_registry_at(
|
||||||
|
root: &Path,
|
||||||
|
registry: &str,
|
||||||
|
account_key: &str,
|
||||||
|
session_id: &str,
|
||||||
|
client_instance_id: &str,
|
||||||
|
now: u64,
|
||||||
|
) -> PersonaCarrierLicenseStatus {
|
||||||
|
let path = license_path(root, account_key, session_id);
|
||||||
|
if !path.exists() {
|
||||||
|
return PersonaCarrierLicenseStatus {
|
||||||
|
schema: LICENSE_SCHEMA,
|
||||||
|
state: "NO_PERSONA_LICENSE",
|
||||||
|
control_mode: "SYSTEM_DIRECT",
|
||||||
|
license_installed: false,
|
||||||
|
license_valid: false,
|
||||||
|
persona_number: None,
|
||||||
|
license_id: None,
|
||||||
|
valid_until_unix_ms: None,
|
||||||
|
binding_completion_declared_by: None,
|
||||||
|
host_declared_binding: false,
|
||||||
|
reason: Some("PERSONA_LICENSE_NOT_PRESENT".into()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let stored = match read_stored(&path) {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(error) => return invalid_status(None, None, None, error),
|
||||||
|
};
|
||||||
|
match verify_license(
|
||||||
|
registry,
|
||||||
|
&stored.license,
|
||||||
|
account_key,
|
||||||
|
session_id,
|
||||||
|
client_instance_id,
|
||||||
|
now,
|
||||||
|
) {
|
||||||
|
Ok(hash) if hash == stored.license_sha256 => PersonaCarrierLicenseStatus {
|
||||||
|
schema: LICENSE_SCHEMA,
|
||||||
|
state: "PERSONA_DECLARED_CARRIER_BINDING_VERIFIED",
|
||||||
|
control_mode: "PERSONA_PRIMARY_CONTROL",
|
||||||
|
license_installed: true,
|
||||||
|
license_valid: true,
|
||||||
|
persona_number: Some(stored.license.persona_number),
|
||||||
|
license_id: Some(stored.license.license_id),
|
||||||
|
valid_until_unix_ms: Some(stored.license.valid_until_unix_ms),
|
||||||
|
binding_completion_declared_by: Some("PERSONA_SUBJECT_SIGNATURE"),
|
||||||
|
host_declared_binding: false,
|
||||||
|
reason: None,
|
||||||
|
},
|
||||||
|
Ok(_) => invalid_status(
|
||||||
|
Some(stored.license.persona_number),
|
||||||
|
Some(stored.license.license_id),
|
||||||
|
Some(stored.license.valid_until_unix_ms),
|
||||||
|
"HOLOLAKE_PERSONA_LICENSE_STORED_HASH_MISMATCH".into(),
|
||||||
|
),
|
||||||
|
Err(error) => invalid_status(
|
||||||
|
Some(stored.license.persona_number),
|
||||||
|
Some(stored.license.license_id),
|
||||||
|
Some(stored.license.valid_until_unix_ms),
|
||||||
|
error,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invalid_status(
|
||||||
|
persona_number: Option<String>,
|
||||||
|
license_id: Option<String>,
|
||||||
|
valid_until_unix_ms: Option<u64>,
|
||||||
|
reason: String,
|
||||||
|
) -> PersonaCarrierLicenseStatus {
|
||||||
|
PersonaCarrierLicenseStatus {
|
||||||
|
schema: LICENSE_SCHEMA,
|
||||||
|
state: "PERSONA_MODE_LOCKED",
|
||||||
|
control_mode: "PERSONA_PRIMARY_CONTROL_LOCKED",
|
||||||
|
license_installed: true,
|
||||||
|
license_valid: false,
|
||||||
|
persona_number,
|
||||||
|
license_id,
|
||||||
|
valid_until_unix_ms,
|
||||||
|
binding_completion_declared_by: None,
|
||||||
|
host_declared_binding: false,
|
||||||
|
reason: Some(reason),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn require_if_persona_mode_at(
|
||||||
|
root: &Path,
|
||||||
|
account_key: &str,
|
||||||
|
session_id: &str,
|
||||||
|
client_instance_id: &str,
|
||||||
|
operation: &str,
|
||||||
|
now: u64,
|
||||||
|
) -> Result<Option<String>, String> {
|
||||||
|
let path = license_path(root, account_key, session_id);
|
||||||
|
if !path.exists() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let stored = read_stored(&path)?;
|
||||||
|
verify_license(
|
||||||
|
TRUST_REGISTRY,
|
||||||
|
&stored.license,
|
||||||
|
account_key,
|
||||||
|
session_id,
|
||||||
|
client_instance_id,
|
||||||
|
now,
|
||||||
|
)?;
|
||||||
|
if !stored
|
||||||
|
.license
|
||||||
|
.allowed_operations
|
||||||
|
.iter()
|
||||||
|
.any(|allowed| allowed == operation)
|
||||||
|
{
|
||||||
|
return Err("HOLOLAKE_PERSONA_LICENSE_OPERATION_NOT_ALLOWED".into());
|
||||||
|
}
|
||||||
|
Ok(Some(stored.license.persona_number))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn start_on_application_open() -> Result<(), String> {
|
||||||
|
let contract: serde_json::Value = serde_json::from_str(CONTRACT)
|
||||||
|
.map_err(|_| "HOLOLAKE_PERSONA_LICENSE_CONTRACT_INVALID".to_string())?;
|
||||||
|
if contract.get("schema").and_then(serde_json::Value::as_str) != Some(CONTRACT_SCHEMA)
|
||||||
|
|| contract
|
||||||
|
.get("record_id")
|
||||||
|
.and_then(serde_json::Value::as_str)
|
||||||
|
!= Some("HLP-PERSONA-CARRIER-RUNTIME-LICENSE-001")
|
||||||
|
|| contract
|
||||||
|
.pointer("/runtime/signature_algorithm")
|
||||||
|
.and_then(serde_json::Value::as_str)
|
||||||
|
!= Some("Ed25519")
|
||||||
|
|| contract
|
||||||
|
.pointer("/runtime/maximum_ttl_ms")
|
||||||
|
.and_then(serde_json::Value::as_u64)
|
||||||
|
!= Some(MAX_TTL_MS)
|
||||||
|
|| contract
|
||||||
|
.pointer("/trust_registry/required_scope")
|
||||||
|
.and_then(serde_json::Value::as_str)
|
||||||
|
!= Some(REQUIRED_SCOPE)
|
||||||
|
|| contract
|
||||||
|
.pointer("/truth/runtime_verifier_implemented")
|
||||||
|
.and_then(serde_json::Value::as_bool)
|
||||||
|
!= Some(true)
|
||||||
|
|| contract
|
||||||
|
.pointer("/truth/trusted_persona_signer_provisioned")
|
||||||
|
.and_then(serde_json::Value::as_bool)
|
||||||
|
!= Some(false)
|
||||||
|
{
|
||||||
|
return Err("HOLOLAKE_PERSONA_LICENSE_CONTRACT_INVALID".into());
|
||||||
|
}
|
||||||
|
let registry = parse_registry(TRUST_REGISTRY)?;
|
||||||
|
if !registry.signers.is_empty() {
|
||||||
|
return Err("HOLOLAKE_PERSONA_LICENSE_UNPUBLISHED_SIGNER_EMBEDDED".into());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use base64::engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD};
|
||||||
|
use ring::signature::{Ed25519KeyPair, KeyPair};
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
fn test_material() -> (String, Ed25519KeyPair) {
|
||||||
|
let pair = Ed25519KeyPair::from_seed_unchecked(&[7_u8; 32]).unwrap();
|
||||||
|
let mut der = ED25519_SPKI_PREFIX.to_vec();
|
||||||
|
der.extend_from_slice(pair.public_key().as_ref());
|
||||||
|
let pem = format!(
|
||||||
|
"-----BEGIN PUBLIC KEY-----\n{}\n-----END PUBLIC KEY-----\n",
|
||||||
|
STANDARD.encode(der)
|
||||||
|
);
|
||||||
|
let registry = serde_json::json!({
|
||||||
|
"schema": REGISTRY_SCHEMA,
|
||||||
|
"registryId": REGISTRY_ID,
|
||||||
|
"state": "CURRENT",
|
||||||
|
"signers": [{
|
||||||
|
"algorithm": "Ed25519",
|
||||||
|
"humanResponsibilitySubjects": ["ICE-GL∞"],
|
||||||
|
"personaIds": ["ICE-P-ZY001"],
|
||||||
|
"publicKeyPem": pem,
|
||||||
|
"scopes": [REQUIRED_SCOPE],
|
||||||
|
"signerId": "ICE-P-ZY001-RUNTIME-SIGNER-001",
|
||||||
|
"status": "ACTIVE"
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
.to_string();
|
||||||
|
(registry, pair)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signed_license(pair: &Ed25519KeyPair) -> PersonaCarrierRuntimeLicense {
|
||||||
|
let mut license = PersonaCarrierRuntimeLicense {
|
||||||
|
schema: LICENSE_SCHEMA.into(),
|
||||||
|
license_id: "HLP-PERSONA-LICENSE-0001".into(),
|
||||||
|
sequence: 1,
|
||||||
|
persona_number: "ICE-P-ZY001".into(),
|
||||||
|
human_controller_number: "ICE-GL∞".into(),
|
||||||
|
account_key: "a".repeat(64),
|
||||||
|
session_id: "session-0001".into(),
|
||||||
|
client_instance_id: "codex-instance-0001".into(),
|
||||||
|
purpose: REQUIRED_PURPOSE.into(),
|
||||||
|
issued_at_unix_ms: 1_000,
|
||||||
|
valid_until_unix_ms: 61_000,
|
||||||
|
allowed_operations: vec![
|
||||||
|
"GET_WORK_ENVIRONMENT".into(),
|
||||||
|
"ACQUIRE_DEVELOPMENT_WRITE_LANE".into(),
|
||||||
|
],
|
||||||
|
evidence_root_sha256: "b".repeat(64),
|
||||||
|
current_event_sha256: "c".repeat(64),
|
||||||
|
declaration: REQUIRED_DECLARATION.into(),
|
||||||
|
signer_id: "ICE-P-ZY001-RUNTIME-SIGNER-001".into(),
|
||||||
|
signature_algorithm: "Ed25519".into(),
|
||||||
|
signature: String::new(),
|
||||||
|
};
|
||||||
|
license.signature =
|
||||||
|
URL_SAFE_NO_PAD.encode(pair.sign(&canonical_signing_bytes(&license).unwrap()));
|
||||||
|
license
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn persona_signature_binds_one_exact_host_session_and_declares_binding() {
|
||||||
|
let temp = TempDir::new().unwrap();
|
||||||
|
let (registry, pair) = test_material();
|
||||||
|
let license = signed_license(&pair);
|
||||||
|
let receipt = present_with_registry_at(
|
||||||
|
temp.path(),
|
||||||
|
®istry,
|
||||||
|
&license.account_key,
|
||||||
|
&license.session_id,
|
||||||
|
&license.client_instance_id,
|
||||||
|
license.clone(),
|
||||||
|
2_000,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(receipt.state, "INSTALLED");
|
||||||
|
assert_eq!(
|
||||||
|
receipt.binding_completion_declared_by,
|
||||||
|
"PERSONA_SUBJECT_SIGNATURE"
|
||||||
|
);
|
||||||
|
assert!(!receipt.host_declared_binding);
|
||||||
|
let status = status_with_registry_at(
|
||||||
|
temp.path(),
|
||||||
|
®istry,
|
||||||
|
&license.account_key,
|
||||||
|
&license.session_id,
|
||||||
|
&license.client_instance_id,
|
||||||
|
2_001,
|
||||||
|
);
|
||||||
|
assert!(status.license_valid);
|
||||||
|
assert_eq!(status.state, "PERSONA_DECLARED_CARRIER_BINDING_VERIFIED");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn host_mismatch_expiry_and_sequence_replay_fail_closed() {
|
||||||
|
let temp = TempDir::new().unwrap();
|
||||||
|
let (registry, pair) = test_material();
|
||||||
|
let license = signed_license(&pair);
|
||||||
|
assert!(present_with_registry_at(
|
||||||
|
temp.path(),
|
||||||
|
®istry,
|
||||||
|
&license.account_key,
|
||||||
|
&license.session_id,
|
||||||
|
"another-host",
|
||||||
|
license.clone(),
|
||||||
|
2_000,
|
||||||
|
)
|
||||||
|
.unwrap_err()
|
||||||
|
.contains("CARRIER_BINDING_MISMATCH"));
|
||||||
|
present_with_registry_at(
|
||||||
|
temp.path(),
|
||||||
|
®istry,
|
||||||
|
&license.account_key,
|
||||||
|
&license.session_id,
|
||||||
|
&license.client_instance_id,
|
||||||
|
license.clone(),
|
||||||
|
2_000,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let mut replay = license.clone();
|
||||||
|
replay.license_id = "HLP-PERSONA-LICENSE-REPLAY".into();
|
||||||
|
replay.signature =
|
||||||
|
URL_SAFE_NO_PAD.encode(pair.sign(&canonical_signing_bytes(&replay).unwrap()));
|
||||||
|
assert!(present_with_registry_at(
|
||||||
|
temp.path(),
|
||||||
|
®istry,
|
||||||
|
&replay.account_key,
|
||||||
|
&replay.session_id,
|
||||||
|
&replay.client_instance_id,
|
||||||
|
replay.clone(),
|
||||||
|
2_100,
|
||||||
|
)
|
||||||
|
.unwrap_err()
|
||||||
|
.contains("SEQUENCE_REPLAYED"));
|
||||||
|
let status = status_with_registry_at(
|
||||||
|
temp.path(),
|
||||||
|
®istry,
|
||||||
|
&license.account_key,
|
||||||
|
&license.session_id,
|
||||||
|
&license.client_instance_id,
|
||||||
|
70_000,
|
||||||
|
);
|
||||||
|
assert!(!status.license_valid);
|
||||||
|
assert_eq!(status.state, "PERSONA_MODE_LOCKED");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn production_empty_registry_cannot_invent_persona_authority() {
|
||||||
|
start_on_application_open().unwrap();
|
||||||
|
let temp = TempDir::new().unwrap();
|
||||||
|
let (_, pair) = test_material();
|
||||||
|
let license = signed_license(&pair);
|
||||||
|
assert_eq!(
|
||||||
|
present_license_at(
|
||||||
|
temp.path(),
|
||||||
|
&license.account_key,
|
||||||
|
&license.session_id,
|
||||||
|
&license.client_instance_id,
|
||||||
|
license.clone(),
|
||||||
|
2_000,
|
||||||
|
)
|
||||||
|
.unwrap_err(),
|
||||||
|
"HOLOLAKE_PERSONA_LICENSE_NO_TRUSTED_SIGNER"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -344,7 +344,7 @@ interface EnterpriseReceiptEnvelope {
|
||||||
repository_projection?: EnterpriseReceiptProjection
|
repository_projection?: EnterpriseReceiptProjection
|
||||||
}
|
}
|
||||||
|
|
||||||
const previewStatus: HomeStatus = { directLocalBrokerState: 'UNVERIFIED', directConnectionCount: 0, resumableSessionCount: 0, codeRepositoryMountCount: 0, pnccReceiptCount: 0, updateState: 'READY_HUMAN_CONFIRMATION_REQUIRED', releaseRecoveryState: 'NONE', mcpRole: 'DISCOVERY_RECOVERY_COMPATIBILITY_ONLY', terminalLinkProtocol: 'HOLOLAKE_TERMINAL_LINK/2', terminalLinkTransport: 'UNVERIFIED', environmentFramePolicy: 'REQUIRED_AFTER_CONNECT_RESUME_AND_BEFORE_MUTATION' }
|
const previewStatus: HomeStatus = { directLocalBrokerState: 'UNVERIFIED', directConnectionCount: 0, resumableSessionCount: 0, codeRepositoryMountCount: 0, pnccReceiptCount: 0, updateState: 'READY_HUMAN_CONFIRMATION_REQUIRED', releaseRecoveryState: 'NONE', mcpRole: 'DISCOVERY_RECOVERY_COMPATIBILITY_ONLY', terminalLinkProtocol: 'HOLOLAKE_TERMINAL_LINK/3', terminalLinkTransport: 'UNVERIFIED', environmentFramePolicy: 'REQUIRED_AFTER_CONNECT_RESUME_AND_BEFORE_MUTATION' }
|
||||||
const previewPersonal: PersonalChannelSnapshot = { state: 'UNAVAILABLE', recentEvents: [], modules: [], integrity: { state: 'UNKNOWN', eventCount: 0, receiptCount: 0 } }
|
const previewPersonal: PersonalChannelSnapshot = { state: 'UNAVAILABLE', recentEvents: [], modules: [], integrity: { state: 'UNKNOWN', eventCount: 0, receiptCount: 0 } }
|
||||||
const previewKnowledge: KnowledgeSnapshot = { state: 'UNAVAILABLE', nativeRoot: '', legacyAvailable: false, documents: [], rawDocumentCount: 0, uniqueDocumentCount: 0, duplicateDocumentCount: 0, truncated: false }
|
const previewKnowledge: KnowledgeSnapshot = { state: 'UNAVAILABLE', nativeRoot: '', legacyAvailable: false, documents: [], rawDocumentCount: 0, uniqueDocumentCount: 0, duplicateDocumentCount: 0, truncated: false }
|
||||||
const previewCode: CodeChannelSnapshot = { state: 'UNAVAILABLE', channels: [], authority: 'LOCAL_SOURCE_ACCESS_ONLY_NO_PUSH_OR_DEPLOY_AUTHORITY' }
|
const previewCode: CodeChannelSnapshot = { state: 'UNAVAILABLE', channels: [], authority: 'LOCAL_SOURCE_ACCESS_ONLY_NO_PUSH_OR_DEPLOY_AUTHORITY' }
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue