feat: ship dual-signed online module marketplace
This commit is contained in:
parent
3153771bf2
commit
b0eeade03d
40 changed files with 5774 additions and 111 deletions
|
|
@ -31,6 +31,9 @@
|
|||
{"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"}
|
||||
{"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"},
|
||||
{"operation_number":"HLP-NBROKER-OP-0023","alias":"SUBMIT_HUMAN_AUTHORIZATION_REQUEST","channel_number":"HLP-NBROKER-CH-0008","module_number":"HLP-NBROKER-MOD-0011","target_number":"HLP-NBROKER-TGT-0011"},
|
||||
{"operation_number":"HLP-NBROKER-OP-0024","alias":"GET_HUMAN_AUTHORIZATION_STATUS","channel_number":"HLP-NBROKER-CH-0008","module_number":"HLP-NBROKER-MOD-0011","target_number":"HLP-NBROKER-TGT-0011"},
|
||||
{"operation_number":"HLP-NBROKER-OP-0025","alias":"CONSUME_HUMAN_AUTHORIZATION_TICKET","channel_number":"HLP-NBROKER-CH-0008","module_number":"HLP-NBROKER-MOD-0011","target_number":"HLP-NBROKER-TGT-0011"}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,186 @@
|
|||
{
|
||||
"schema": "hololake.distribution-plane-router/v1",
|
||||
"record_id": "HLP-DISTRIBUTION-PLANE-ROUTER-001",
|
||||
"state": "CONTRACT_CURRENT_PUBLIC_ZERO_CORE_CLIENT_RUNTIME_IMPLEMENTED_SERVER_AND_CATALOG_INTEGRATION_PENDING",
|
||||
"classification": {
|
||||
"authority": "EXPLICIT_SIGNED_RELEASE_ENVELOPE",
|
||||
"semantic_guessing_allowed": false,
|
||||
"missing_or_conflicting_scope": "FAIL_CLOSED",
|
||||
"required_fields": [
|
||||
"releaseId",
|
||||
"scope",
|
||||
"ownerNumber",
|
||||
"authorityDomain",
|
||||
"sourceRepository",
|
||||
"sourceCommit",
|
||||
"artifactKind",
|
||||
"targetNamespace",
|
||||
"minimumHostVersion",
|
||||
"contentSha256",
|
||||
"permissionDelta",
|
||||
"rollbackReference",
|
||||
"signerId"
|
||||
],
|
||||
"human_source_rule": "THE_PUBLISHER_STATES_PUBLIC_OR_PRIVATE_SCOPE_AND_REVIEWS_THE_EXACT_IMMUTABLE_CANDIDATE_BEFORE_SERVER_PUBLICATION",
|
||||
"system_rule": "CONTENT_INSPECTION_MAY_REJECT_A_SCOPE_MISMATCH_BUT_MUST_NOT_INVENT_OR_EXPAND_SCOPE"
|
||||
},
|
||||
"planes": [
|
||||
{
|
||||
"plane_number": "HLP-DIST-PLANE-0001",
|
||||
"scope": "PUBLIC_ZERO_CORE_PROTOCOL",
|
||||
"physical_node": "GH-CVM-MAIN-PROD-01",
|
||||
"logical_source": "ZERO_POINT_ORIGIN_PUBLIC_PROJECTION_HOSTED_OUTSIDE_PRIVATE_FIFTH_DOMAIN",
|
||||
"logical_authority": "ZERO_POINT_ORIGIN_WITH_ICE_GL_INFINITY_PUBLIC_SCOPE_APPROVAL",
|
||||
"management_entry": "FIFTH_DOMAIN_PORTAL_TO_ENTERPRISE_ZERO_CORE_WITH_ONE_TIME_AUDIENCE_BOUND_HANDOFF",
|
||||
"public_read_access": "SIGNED_MANIFEST_AND_ARTIFACT_NO_ACCOUNT_REQUIRED",
|
||||
"allowed_artifacts": [
|
||||
"DECLARATIVE_LANGUAGE_PROTOCOL",
|
||||
"NUMBERING_PROTOCOL",
|
||||
"COMPATIBILITY_RULE",
|
||||
"BOUNDED_MIGRATION_RULE"
|
||||
],
|
||||
"arbitrary_native_code_allowed": false,
|
||||
"arbitrary_webview_javascript_allowed": false,
|
||||
"publisher_human_confirmation_required": true,
|
||||
"per_device_human_install_confirmation_required": false,
|
||||
"automatic_check": true,
|
||||
"automatic_download_after_verification": true,
|
||||
"automatic_atomic_activation_after_self_test": true,
|
||||
"visible_human_receipt_required": true,
|
||||
"public_propagation_allowed": true,
|
||||
"required_signer_classes": [
|
||||
"ZERO_POINT_ORIGIN_PUBLIC_SCOPE_SIGNER",
|
||||
"ENTERPRISE_ZERO_CORE_DISTRIBUTION_SIGNER"
|
||||
],
|
||||
"signer_class": "DUAL_ORIGIN_AND_ENTERPRISE_ZERO_CORE_SIGNERS"
|
||||
},
|
||||
{
|
||||
"plane_number": "HLP-DIST-PLANE-0002",
|
||||
"scope": "PRIVATE_FIFTH_DOMAIN",
|
||||
"physical_node": "JD-FD-PRIMARY",
|
||||
"logical_source": "DOM-FIFTH-0001_PRIVATE_BODY",
|
||||
"audience": "EXACT_BOUND_OWNER_AND_EXPLICITLY_AUTHORIZED_PRIVATE_NODES",
|
||||
"publisher_human_confirmation_required": true,
|
||||
"per_device_human_install_confirmation_required": false,
|
||||
"automatic_check": true,
|
||||
"public_propagation_allowed": false,
|
||||
"cross_domain_replication_allowed": false,
|
||||
"signer_class": "PRIVATE_FIFTH_DOMAIN_SIGNER"
|
||||
},
|
||||
{
|
||||
"plane_number": "HLP-DIST-PLANE-0003",
|
||||
"scope": "PUBLIC_ENTERPRISE_MODULE_CATALOG",
|
||||
"physical_node": "GH-CVM-MAIN-PROD-01",
|
||||
"logical_source": "GUANGHU_CHANNEL_AGGREGATE",
|
||||
"producer_model": "FIVE_RESPONSIBILITY_REPOSITORIES_TO_ONE_REVIEWED_AGGREGATE",
|
||||
"allowed_artifacts": [
|
||||
"DECLARATIVE_GHMOD_PACKAGE",
|
||||
"MODULE_METADATA",
|
||||
"PERSONA_BRAIN_SKILL_PACKAGE"
|
||||
],
|
||||
"raw_repository_is_executable_input": false,
|
||||
"client_full_repository_clone_required": false,
|
||||
"catalog_index_automatic_sync": true,
|
||||
"module_package_download_on_human_selection": true,
|
||||
"module_install_human_confirmation_required": true,
|
||||
"permission_expansion_human_confirmation_required": true,
|
||||
"lighthouse_number_registration_required": true,
|
||||
"isolated_preflight_and_self_test_required": true,
|
||||
"signer_class": "ENTERPRISE_MODULE_RELEASE_SIGNER"
|
||||
},
|
||||
{
|
||||
"plane_number": "HLP-DIST-PLANE-0004",
|
||||
"scope": "APPLICATION_BINARY",
|
||||
"physical_node": "HOLOLAKE_RELEASE_BROADCAST",
|
||||
"allowed_artifacts": [
|
||||
"SIGNED_NOTARIZED_DESKTOP_APPLICATION",
|
||||
"SIGNED_UPDATER_ARCHIVE"
|
||||
],
|
||||
"source_commit_must_be_immutable": true,
|
||||
"platform_signing_required": true,
|
||||
"updater_signature_required": true,
|
||||
"per_device_human_install_confirmation_required": true,
|
||||
"automatic_restart_allowed": false,
|
||||
"signer_class": "HOLOLAKE_APPLICATION_RELEASE_SIGNER"
|
||||
}
|
||||
],
|
||||
"lamp_protocol": {
|
||||
"transport": "HTTPS_CONDITIONAL_GET",
|
||||
"git_role": "DURABLE_AUTHORING_AND_EVIDENCE_NOT_CLIENT_REALTIME_TRANSPORT",
|
||||
"signal": "SIGNED_MONOTONIC_EPOCH_AND_CONTENT_ROOT",
|
||||
"cache_validation": ["ETAG", "IF_NONE_MATCH"],
|
||||
"check_events": ["APPLICATION_START", "NETWORK_RESUME", "BOUNDED_PERIODIC_TIMER"],
|
||||
"minimum_periodic_interval_seconds": 900,
|
||||
"jitter_required": true,
|
||||
"full_repository_clone_for_LIGHT_SIGNAL": false,
|
||||
"required_manifest_fields": [
|
||||
"schema",
|
||||
"planeNumber",
|
||||
"epoch",
|
||||
"version",
|
||||
"contentRootSha256",
|
||||
"artifactManifestUrl",
|
||||
"signatureUrl",
|
||||
"publishedAt",
|
||||
"minimumHostVersion"
|
||||
]
|
||||
},
|
||||
"cross_node_management_handoff": {
|
||||
"source_node": "JD-FD-PRIMARY",
|
||||
"target_node": "GH-CVM-MAIN-PROD-01",
|
||||
"source_surface": "PRIVATE_FIFTH_DOMAIN",
|
||||
"target_surface": "PUBLIC_ZERO_CORE_MANAGEMENT_CHANNEL",
|
||||
"credential_reuse_allowed": false,
|
||||
"password_forwarding_allowed": false,
|
||||
"ticket_properties": [
|
||||
"ONE_TIME",
|
||||
"SHORT_LIVED",
|
||||
"BOUND_TO_HUMAN_NUMBER",
|
||||
"BOUND_TO_HOLOLAKE_CLIENT_INSTANCE",
|
||||
"BOUND_TO_TARGET_NODE",
|
||||
"BOUND_TO_PUBLIC_ZERO_CORE_RESOURCE",
|
||||
"NON_TRANSFERABLE",
|
||||
"REPLAY_PROTECTED"
|
||||
],
|
||||
"exit_behavior": "DESTROY_ENTERPRISE_ZERO_CORE_SESSION_AND_RESTORE_EXISTING_PRIVATE_FIFTH_DOMAIN_SESSION",
|
||||
"enterprise_four_domain_authority_inherited": false,
|
||||
"private_fifth_domain_authority_exported": false,
|
||||
"current_state": "NOT_IMPLEMENTED"
|
||||
},
|
||||
"activation_pipeline": [
|
||||
"READ_EXPLICIT_RELEASE_ENVELOPE",
|
||||
"VERIFY_SCOPE_OWNER_DOMAIN_REPOSITORY_AND_IMMUTABLE_COMMIT",
|
||||
"BUILD_BOUNDED_CONTENT_ADDRESSED_ARTIFACT",
|
||||
"RUN_ISOLATED_SCHEMA_PERMISSION_COMPATIBILITY_AND_SELF_TEST",
|
||||
"ALLOCATE_OR_VERIFY_LIGHTHOUSE_NUMBER",
|
||||
"SHOW_EXACT_CANDIDATE_TO_AUTHORIZED_PUBLISHER",
|
||||
"REQUIRE_PUBLISHER_CONFIRMATION",
|
||||
"SIGN_WITH_PLANE_SPECIFIC_KEY",
|
||||
"APPEND_HASH_CHAINED_PUBLICATION_RECEIPT",
|
||||
"ADVANCE_SIGNED_LAMP_EPOCH_ATOMICALLY"
|
||||
],
|
||||
"client_protocol_activation": [
|
||||
"COMPARE_SIGNED_LAMP_EPOCH_WITH_CONDITIONAL_GET",
|
||||
"VERIFY_PLANE_SOURCE_SIGNATURE_CONTENT_ROOT_AND_MONOTONIC_VERSION",
|
||||
"DOWNLOAD_TO_ISOLATED_STAGING",
|
||||
"REJECT_EXECUTABLE_OR_OUT_OF_SCOPE_PAYLOAD",
|
||||
"RUN_LOCAL_COMPATIBILITY_AND_SELF_TEST",
|
||||
"ATOMICALLY_SWITCH_CURRENT_POINTER",
|
||||
"WRITE_LOCAL_HASH_CHAINED_RECEIPT",
|
||||
"KEEP_LAST_KNOWN_GOOD_ROLLBACK",
|
||||
"NOTIFY_HUMAN_WITHOUT_REQUIRING_PER_DEVICE_APPROVAL"
|
||||
],
|
||||
"current_observed_gaps_2026_08_19": {
|
||||
"enterprise_public_zero_core_projection": "NOT_DEPLOYED",
|
||||
"jd_to_enterprise_zero_core_handoff": "NOT_IMPLEMENTED",
|
||||
"zero_point_signed_payload_activation": "CLIENT_IMPLEMENTED_DUAL_SIGNER_TRUST_NOT_PROVISIONED",
|
||||
"enterprise_guanghu_channel_aggregate_repository": "NOT_PRESENT",
|
||||
"enterprise_public_gitea_repositories_observed": [
|
||||
"bingshuo/hololake-world",
|
||||
"bingshuo/lighthouse"
|
||||
],
|
||||
"online_module_catalog_registry": "NOT_IMPLEMENTED",
|
||||
"local_signed_module_lifecycle": "IMPLEMENTED_FOR_BUNDLED_PACKAGES",
|
||||
"application_release_signing": "PERSONAL_APPLE_DEVELOPER_TRANSITION"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"schema": "hololake.human-authorization-contract/v1",
|
||||
"record_id": "HLP-HUMAN-AUTHORIZATION-001",
|
||||
"state": "NATIVE_FAIL_CLOSED",
|
||||
"roles": {
|
||||
"persona_or_agent": "PROPOSE_EXACT_ACTION_WITH_REASON_IMPACT_AND_ROLLBACK",
|
||||
"human": "APPROVE_OR_DENY_FROM_VERIFIED_HOLOLAKE_CLIENT",
|
||||
"numbering_system": "ISSUE_SESSION_BOUND_SINGLE_USE_TICKET_AND_HASH_CHAINED_RECEIPT"
|
||||
},
|
||||
"lifecycle": [
|
||||
"PENDING_HUMAN",
|
||||
"APPROVED",
|
||||
"DENIED",
|
||||
"EXPIRED",
|
||||
"CONSUMED"
|
||||
],
|
||||
"supported_actions": [
|
||||
"OPEN_MAINTENANCE",
|
||||
"UNMOUNT",
|
||||
"PROMOTE_VERSION",
|
||||
"RETIRE"
|
||||
],
|
||||
"destructive_purge": {
|
||||
"enabled": false,
|
||||
"reason": "PURGE_REQUIRES_A_SEPARATE_TWO_STEP_PHYSICAL_DATA_DELETION_PROTOCOL"
|
||||
},
|
||||
"ticket": {
|
||||
"ttl_ms": 900000,
|
||||
"single_use": true,
|
||||
"requester_account_bound": true,
|
||||
"requester_session_bound": true,
|
||||
"client_instance_bound": true,
|
||||
"target_bound": true,
|
||||
"action_bound": true,
|
||||
"replay": "FAIL_CLOSED"
|
||||
},
|
||||
"request": {
|
||||
"ttl_ms": 86400000,
|
||||
"idempotency_required": true,
|
||||
"reason_required": true,
|
||||
"impact_required": true,
|
||||
"rollback_plan_required": true
|
||||
},
|
||||
"routes": {
|
||||
"external_execution_carrier": [
|
||||
"HLP-NBROKER-OP-0023",
|
||||
"HLP-NBROKER-OP-0024",
|
||||
"HLP-NBROKER-OP-0025"
|
||||
],
|
||||
"human_client": [
|
||||
"HLP-NIPC-OP-0148",
|
||||
"HLP-NIPC-OP-0149"
|
||||
]
|
||||
},
|
||||
"invariants": {
|
||||
"number_is_coordinate_not_authority": true,
|
||||
"proposal_is_not_authorization": true,
|
||||
"approval_is_not_execution": true,
|
||||
"stable_target_number_is_never_reused": true,
|
||||
"denial_opens_no_execution_path": true,
|
||||
"unknown_or_mismatched_state": "FAIL_CLOSED",
|
||||
"receipt_required_for_each_transition": true
|
||||
}
|
||||
}
|
||||
|
|
@ -77,7 +77,11 @@
|
|||
"rotate_mobile_pairing",
|
||||
"stop_mobile_sync",
|
||||
"get_mobile_sync_snapshot",
|
||||
"get_world_climate"
|
||||
"get_world_climate",
|
||||
"get_authorization_center",
|
||||
"get_marketplace_snapshot",
|
||||
"sync_marketplace_catalog",
|
||||
"get_active_cognitive_skills"
|
||||
],
|
||||
"input_wrapper_aliases": [
|
||||
"confirm_hololake_update_install",
|
||||
|
|
@ -115,6 +119,9 @@
|
|||
"self_test_module",
|
||||
"unmount_module",
|
||||
"rollback_module",
|
||||
"install_marketplace_item",
|
||||
"uninstall_marketplace_item",
|
||||
"rollback_marketplace_item",
|
||||
"activate_bundled_module",
|
||||
"execute_knowledge_native_composition",
|
||||
"save_channel_document",
|
||||
|
|
@ -176,7 +183,8 @@
|
|||
"link_web_novel_scene_entity",
|
||||
"restore_web_novel_chapter_version",
|
||||
"export_web_novel_author_delivery",
|
||||
"revoke_mobile_sync_device"
|
||||
"revoke_mobile_sync_device",
|
||||
"decide_authorization_request"
|
||||
],
|
||||
"direct_field_aliases": {
|
||||
"perform_code_repo_login": [
|
||||
|
|
@ -378,6 +386,16 @@
|
|||
"module_number": "HLP-NIPC-MOD-0031",
|
||||
"target_number": "HLP-NIPC-TGT-0031",
|
||||
"internal_name": "world_climate"
|
||||
},
|
||||
{
|
||||
"module_number": "HLP-NIPC-MOD-0032",
|
||||
"target_number": "HLP-NIPC-TGT-0032",
|
||||
"internal_name": "human_authorization"
|
||||
},
|
||||
{
|
||||
"module_number": "HLP-NIPC-MOD-0033",
|
||||
"target_number": "HLP-NIPC-TGT-0033",
|
||||
"internal_name": "online_marketplace"
|
||||
}
|
||||
],
|
||||
"operations": [
|
||||
|
|
@ -1997,6 +2015,94 @@
|
|||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "READ_OR_STATUS",
|
||||
"payload_schema": "hololake.numbered-ipc.payload/get_world_climate/v1"
|
||||
},
|
||||
{
|
||||
"operation_number": "HLP-NIPC-OP-0148",
|
||||
"alias": "get_authorization_center",
|
||||
"handler": "human_authorization::get_authorization_center",
|
||||
"channel_number": "HLP-NIPC-CH-0002",
|
||||
"module_number": "HLP-NIPC-MOD-0032",
|
||||
"target_number": "HLP-NIPC-TGT-0032",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "READ_OR_STATUS",
|
||||
"payload_schema": "hololake.numbered-ipc.payload/get_authorization_center/v1"
|
||||
},
|
||||
{
|
||||
"operation_number": "HLP-NIPC-OP-0149",
|
||||
"alias": "decide_authorization_request",
|
||||
"handler": "human_authorization::decide_authorization_request",
|
||||
"channel_number": "HLP-NIPC-CH-0002",
|
||||
"module_number": "HLP-NIPC-MOD-0032",
|
||||
"target_number": "HLP-NIPC-TGT-0032",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "STATE_CHANGE",
|
||||
"payload_schema": "hololake.numbered-ipc.payload/decide_authorization_request/v1"
|
||||
},
|
||||
{
|
||||
"operation_number": "HLP-NIPC-OP-0150",
|
||||
"alias": "get_marketplace_snapshot",
|
||||
"handler": "online_marketplace::get_marketplace_snapshot",
|
||||
"channel_number": "HLP-NIPC-CH-0002",
|
||||
"module_number": "HLP-NIPC-MOD-0033",
|
||||
"target_number": "HLP-NIPC-TGT-0033",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "READ_OR_STATUS",
|
||||
"payload_schema": "hololake.numbered-ipc.payload/get_marketplace_snapshot/v1"
|
||||
},
|
||||
{
|
||||
"operation_number": "HLP-NIPC-OP-0151",
|
||||
"alias": "sync_marketplace_catalog",
|
||||
"handler": "online_marketplace::sync_marketplace_catalog",
|
||||
"channel_number": "HLP-NIPC-CH-0002",
|
||||
"module_number": "HLP-NIPC-MOD-0033",
|
||||
"target_number": "HLP-NIPC-TGT-0033",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "STATE_CHANGE",
|
||||
"payload_schema": "hololake.numbered-ipc.payload/sync_marketplace_catalog/v1"
|
||||
},
|
||||
{
|
||||
"operation_number": "HLP-NIPC-OP-0152",
|
||||
"alias": "install_marketplace_item",
|
||||
"handler": "online_marketplace::install_marketplace_item",
|
||||
"channel_number": "HLP-NIPC-CH-0002",
|
||||
"module_number": "HLP-NIPC-MOD-0033",
|
||||
"target_number": "HLP-NIPC-TGT-0033",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "STATE_CHANGE",
|
||||
"payload_schema": "hololake.numbered-ipc.payload/install_marketplace_item/v1"
|
||||
},
|
||||
{
|
||||
"operation_number": "HLP-NIPC-OP-0153",
|
||||
"alias": "uninstall_marketplace_item",
|
||||
"handler": "online_marketplace::uninstall_marketplace_item",
|
||||
"channel_number": "HLP-NIPC-CH-0002",
|
||||
"module_number": "HLP-NIPC-MOD-0033",
|
||||
"target_number": "HLP-NIPC-TGT-0033",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "STATE_CHANGE",
|
||||
"payload_schema": "hololake.numbered-ipc.payload/uninstall_marketplace_item/v1"
|
||||
},
|
||||
{
|
||||
"operation_number": "HLP-NIPC-OP-0154",
|
||||
"alias": "rollback_marketplace_item",
|
||||
"handler": "online_marketplace::rollback_marketplace_item",
|
||||
"channel_number": "HLP-NIPC-CH-0002",
|
||||
"module_number": "HLP-NIPC-MOD-0033",
|
||||
"target_number": "HLP-NIPC-TGT-0033",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "STATE_CHANGE",
|
||||
"payload_schema": "hololake.numbered-ipc.payload/rollback_marketplace_item/v1"
|
||||
},
|
||||
{
|
||||
"operation_number": "HLP-NIPC-OP-0155",
|
||||
"alias": "get_active_cognitive_skills",
|
||||
"handler": "online_marketplace::get_active_cognitive_skills",
|
||||
"channel_number": "HLP-NIPC-CH-0002",
|
||||
"module_number": "HLP-NIPC-MOD-0033",
|
||||
"target_number": "HLP-NIPC-TGT-0033",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "READ_OR_STATUS",
|
||||
"payload_schema": "hololake.numbered-ipc.payload/get_active_cognitive_skills/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"schema": "hololake.online-marketplace-contract/v1",
|
||||
"recordId": "HLP-ONLINE-MARKETPLACE-001",
|
||||
"planeNumber": "HLP-DIST-PLANE-0003",
|
||||
"state": "IMPLEMENTED_PROVISIONED_AND_READY_FOR_LIVE_PUBLICATION",
|
||||
"catalog": {
|
||||
"schema": "hololake.marketplace.catalog/v1",
|
||||
"url": "https://guanghu.chat/api/hololake/marketplace/catalog",
|
||||
"signatureUrl": "https://guanghu.chat/api/hololake/marketplace/catalog.sig",
|
||||
"maximumBytes": 1048576,
|
||||
"maximumEntries": 256,
|
||||
"conditionalRequest": "ETAG_IF_NONE_MATCH",
|
||||
"epochMonotonic": true,
|
||||
"sameEpochEquivocationRejected": true,
|
||||
"requiredSignerClasses": [
|
||||
"ZERO_POINT_ORIGIN_PUBLIC_SCOPE_SIGNER",
|
||||
"ENTERPRISE_ZERO_CORE_DISTRIBUTION_SIGNER"
|
||||
]
|
||||
},
|
||||
"artifactKinds": {
|
||||
"PHYSICAL_MODULE": {
|
||||
"packageSchema": "hololake.module-package/v1",
|
||||
"signature": "MINISIGN_ED25519_RELEASE_TRUST",
|
||||
"installation": "DOWNLOAD_VERIFY_INSTALL_MOUNT_SELF_TEST",
|
||||
"arbitraryNativeCode": false,
|
||||
"arbitraryWebviewJavascript": false,
|
||||
"registeredHostAdapterRequired": true,
|
||||
"permissionExpansionRequiresDirectHumanConfirmation": true,
|
||||
"uninstallPreservesUserData": true,
|
||||
"rollbackSupported": true
|
||||
},
|
||||
"COGNITIVE_SKILL": {
|
||||
"packageSchema": "hololake.cognitive-skill-package/v1",
|
||||
"signature": "DUAL_SIGNED_CATALOG_EXACT_CONTENT_SHA256",
|
||||
"installation": "DOWNLOAD_VERIFY_STORE_ACTIVE_READONLY",
|
||||
"executionAuthority": false,
|
||||
"skillReadonlyGuarantee": true,
|
||||
"systemPermissions": [],
|
||||
"automaticPromptInjection": false,
|
||||
"personaReadsOnDemand": true,
|
||||
"uninstallPreservesPackageAndReceipts": true,
|
||||
"rollbackSupported": true
|
||||
}
|
||||
},
|
||||
"sourceRepositoryBoundary": {
|
||||
"repositoryIsOriginEvidence": true,
|
||||
"clientClonesRepository": false,
|
||||
"clientExecutesRepository": false,
|
||||
"catalogRequiresExactSourceRevision": true,
|
||||
"artifactUrlsAreContentAddressed": true,
|
||||
"clientDownloadsImmutableSignedArtifactOnly": true
|
||||
},
|
||||
"humanExperience": {
|
||||
"oneMarketplaceTwoSections": true,
|
||||
"installButtonDownloadsAndActivates": true,
|
||||
"physicalPermissionExpansionShowsExactConfirmation": true,
|
||||
"skillInstallNeverGrantsRealityAuthority": true,
|
||||
"realStatusAndReceiptsOnly": true
|
||||
},
|
||||
"failurePolicy": "FAIL_CLOSED_KEEP_LAST_VERIFIED_CATALOG_AND_INSTALLED_ITEMS"
|
||||
}
|
||||
|
|
@ -47,7 +47,9 @@
|
|||
"ISSUE_PERSONA_TIME_TICKET",
|
||||
"ACQUIRE_DEVELOPMENT_WRITE_LANE",
|
||||
"INSPECT_DEVELOPMENT_WRITE_LANE",
|
||||
"RELEASE_DEVELOPMENT_WRITE_LANE"
|
||||
"RELEASE_DEVELOPMENT_WRITE_LANE",
|
||||
"SUBMIT_HUMAN_AUTHORIZATION_REQUEST",
|
||||
"CONSUME_HUMAN_AUTHORIZATION_TICKET"
|
||||
],
|
||||
"trust_registry": {
|
||||
"repository": "REPO-012",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"schema": "hololake.public-zero-core-trust/v1",
|
||||
"recordId": "HLP-PUBLIC-ZERO-CORE-TRUST-001",
|
||||
"state": "PROVISIONED",
|
||||
"allowedHosts": [
|
||||
"guanghu.chat",
|
||||
"guanghulab.com"
|
||||
],
|
||||
"signers": [
|
||||
{
|
||||
"signerId": "HLP-SIGNER-ZERO-POINT-ORIGIN-PUBLIC-0001",
|
||||
"signerClass": "ZERO_POINT_ORIGIN_PUBLIC_SCOPE_SIGNER",
|
||||
"algorithm": "Ed25519",
|
||||
"publicKeyBase64": "TVuANckEtTI7H+5LssTKA8piQPxQJBQlWJe3vtgbF+o="
|
||||
},
|
||||
{
|
||||
"signerId": "HLP-SIGNER-ENTERPRISE-ZERO-CORE-DISTRIBUTION-0001",
|
||||
"signerClass": "ENTERPRISE_ZERO_CORE_DISTRIBUTION_SIGNER",
|
||||
"algorithm": "Ed25519",
|
||||
"publicKeyBase64": "pWp+M21MXQB3B8CH7DgYSSmTBPgbCP1AQYuG6NyZmMg="
|
||||
}
|
||||
],
|
||||
"requiredSignerClasses": [
|
||||
"ZERO_POINT_ORIGIN_PUBLIC_SCOPE_SIGNER",
|
||||
"ENTERPRISE_ZERO_CORE_DISTRIBUTION_SIGNER"
|
||||
],
|
||||
"provisioningRule": "ONLY_PUBLIC_KEYS_ENTER_THE_CLIENT; PRIVATE_KEYS_REMAIN_IN_SEPARATE_ORIGIN_AND_ENTERPRISE_RELEASE_CUSTODY"
|
||||
}
|
||||
|
|
@ -60,7 +60,9 @@
|
|||
"number_verification_precedes_persona_load_path": true,
|
||||
"system_is_persona": false,
|
||||
"number_verification_is_persona_binding": false,
|
||||
"signed_protocol_payload_installation_implemented": false,
|
||||
"signed_protocol_payload_installation_implemented": true,
|
||||
"production_dual_signer_trust_provisioned": true,
|
||||
"enterprise_public_lamp_endpoint_deployed": false,
|
||||
"internal_model_inference_implemented": false
|
||||
},
|
||||
"personal_channel_kernel": {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
{
|
||||
"schema": "hololake.zero-point-nucleus-client-runtime/v1",
|
||||
"record_id": "HLP-ZERO-POINT-NUCLEUS-CLIENT-001",
|
||||
"state": "SYSTEM_RUNTIME_SKELETON_IMPLEMENTED_SIGNED_DISTRIBUTION_NOT_PROVISIONED",
|
||||
"state": "CLIENT_DUAL_SIGNED_DISTRIBUTION_RUNTIME_IMPLEMENTED_PRODUCTION_TRUST_AND_ENTERPRISE_ENDPOINT_NOT_PROVISIONED",
|
||||
"ontology": {
|
||||
"private_body": "BINGSHUO_SYSTEM_CONTROLLER_ON_JD_PRIMARY",
|
||||
"origin_domain": "DOM-FIFTH-0001",
|
||||
"public_repository_role": "EVOLUTION_CHECKPOINTS_NOT_PRIVATE_BODY",
|
||||
"public_zero_core_projection": "ISOLATED_ENTERPRISE_SERVER_RUNTIME_WITH_ZERO_POINT_ORIGIN_AUTHORITY_AND_DUAL_SIGNATURE_NOT_THE_PRIVATE_FIFTH_DOMAIN_BODY",
|
||||
"public_repository_role": "DURABLE_AUTHORING_AND_EVIDENCE_SOURCE_NOT_CLIENT_REALTIME_TRANSPORT",
|
||||
"hololake_role": "HIDDEN_MINIMUM_CONTROLLED_CLIENT_PROJECTION"
|
||||
},
|
||||
"purpose": [
|
||||
|
|
@ -34,6 +35,8 @@
|
|||
"remote_arbitrary_code_execution_allowed": false,
|
||||
"unsigned_protocol_update_allowed": false,
|
||||
"version_rollback_allowed": false,
|
||||
"public_zero_core_protocol_distribution_allowed_after_full_signature_gates": true,
|
||||
"private_fifth_domain_payload_public_propagation_allowed": false,
|
||||
"public_number_registry_allowed": false,
|
||||
"public_model_api_configuration_allowed": false,
|
||||
"internal_generic_ai_chat_allowed": false,
|
||||
|
|
@ -46,7 +49,17 @@
|
|||
"number_binding_and_online_verification": true,
|
||||
"offline_grace_period": true,
|
||||
"local_minimum_heartbeat_ledger": true,
|
||||
"signed_protocol_payload_installation": false,
|
||||
"signed_protocol_payload_installation": true,
|
||||
"dual_independent_ed25519_signature_verification": true,
|
||||
"https_source_allowlist": true,
|
||||
"conditional_etag_sync": true,
|
||||
"monotonic_epoch_and_version_enforced": true,
|
||||
"bounded_declarative_payload_only": true,
|
||||
"atomic_activation_and_previous_release_retention": true,
|
||||
"hash_chained_activation_receipts": true,
|
||||
"production_dual_signer_trust_provisioned": true,
|
||||
"enterprise_public_lamp_endpoint_deployed": false,
|
||||
"public_zero_core_distribution_projection": false,
|
||||
"private_registry_distribution": false,
|
||||
"persona_loading_runtime": false,
|
||||
"internal_model_inference": false
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@ The first visible body uses a Rust-owned SQLite kernel under the Tauri app-data
|
|||
|
||||
## Zero-point nucleus client runtime
|
||||
|
||||
HoloLake embeds a non-visual zero-point nucleus client runtime beneath the human surface. It is the minimum controlled projection of the BingShuo system controller, whose private body remains on the JD primary node; the public repository records evolution checkpoints rather than containing that private body. At application start, the Rust runtime loads the local protocol, compares the registered remote protocol version in the background, keeps a minimal local receipt, and leaves any unverified update unapplied. User-number verification runs before any future persona-loading path.
|
||||
HoloLake embeds a non-visual zero-point nucleus client runtime beneath the human surface. The JD primary node remains the private Fifth Domain body. The enterprise node hosts a strictly isolated public zero-core distribution projection, while logical origin authority remains at the zero point; publication requires both an origin public-scope signature and an enterprise distribution signature. The private body never becomes public update material. Git records durable authoring and evidence; clients consume a bounded signed release manifest rather than treating a repository clone as executable input. At application start, the Rust runtime loads the local protocol, compares the registered remote protocol version in the background, keeps a minimal local receipt, and leaves any unverified update unapplied. User-number verification runs before any future persona-loading path.
|
||||
|
||||
This system runtime is not Zhuyuan or another persona subject, and it is not the current model carrier. A valid number does not prove persona binding and does not grant execution or server authority. The current source implements deterministic protocol comparison, explicit-positive number verification and a fail-closed update skeleton. Signed protocol-payload installation, private registry distribution and persona loading are not yet implemented. Stage one does not expose an internal AI chat, model API configuration or arbitrary remote-code channel.
|
||||
This system runtime is not Zhuyuan or another persona subject, and it is not the current model carrier. A valid number does not prove persona binding and does not grant execution or server authority. The current source implements deterministic protocol comparison, explicit-positive number verification and a fail-closed update skeleton. Signed public protocol payload installation, atomic activation, rollback, the separate private Fifth Domain distribution path and persona loading are not yet implemented. Stage one does not expose an internal AI chat, model API configuration or arbitrary remote-code channel. The four-plane routing contract and marketplace publication boundary are defined in `contracts/distribution-plane-router.json`.
|
||||
|
||||
## Native knowledge workspace
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
# HoloLake distribution planes and public module marketplace
|
||||
|
||||
HoloLake has four independent distribution planes. A Git repository is the durable authoring and evidence layer; it is not the client update transport. Every release carries an explicit signed scope. The system may reject a mismatch, but it never guesses whether BingShuo meant public or private.
|
||||
|
||||
## Four planes
|
||||
|
||||
1. `PUBLIC_ZERO_CORE_PROTOCOL` publishes declarative language, numbering, compatibility and bounded migration rules from an isolated public projection on `GH-CVM-MAIN-PROD-01`. Its logical authority still originates at the zero point and requires BingShuo's exact-candidate public-scope approval during the current transition. The enterprise distributor adds a second independent distribution signature. A client verifies both, stages, self-tests and atomically activates a valid update without asking every device owner to approve an operating-system protocol update. It still shows a human-readable receipt.
|
||||
2. `PRIVATE_FIFTH_DOMAIN` remains confined to `DOM-FIFTH-0001`, its bound owner and explicitly authorized private nodes. It uses a different namespace and signer and can never flow into the public stream by inference.
|
||||
3. `PUBLIC_ENTERPRISE_MODULE_CATALOG` is produced on `GH-CVM-MAIN-PROD-01`. Five responsibility repositories may feed one reviewed `Guanghu Channel` aggregate, but only tested, numbered and signed declarative packages enter the catalog. Clients synchronize the small catalog index automatically. A selected module is downloaded and installed only after the human reviews its permissions.
|
||||
4. `APPLICATION_BINARY` updates HoloLake itself through the separately signed and platform-notarized updater. Personal Apple signing is a transition state; later organization signing must preserve the updater trust transition rather than silently replacing it.
|
||||
|
||||
## Lake-lamp protocol
|
||||
|
||||
The visible "lamp" is a tiny signed manifest containing a monotonic epoch and content root. HoloLake performs HTTPS conditional checks at application start, after network resume and on a bounded jittered timer. `ETag` and `If-None-Match` make the no-change path nearly empty. A full repository clone is not required to learn that something changed.
|
||||
|
||||
For a public zero-core protocol update, the client verifies the exact source, plane-specific signature, content root, monotonic version and host compatibility; downloads into isolation; rejects executable or out-of-scope material; runs a deterministic self-test; switches one current pointer atomically; keeps the last-known-good version; and records a local receipt.
|
||||
|
||||
For a module update, only the catalog index is automatic. Installation remains a human action because a module may request access to local files, knowledge, network, channel data or execution adapters.
|
||||
|
||||
## Marketplace publication
|
||||
|
||||
```text
|
||||
responsibility repository
|
||||
→ explicit release envelope
|
||||
→ isolated build and tests
|
||||
→ lighthouse number registration
|
||||
→ exact candidate human approval
|
||||
→ enterprise module signature
|
||||
→ immutable package and catalog entry
|
||||
→ signed catalog-root advance
|
||||
→ HoloLake catalog refresh
|
||||
→ human selects module
|
||||
→ permission review
|
||||
→ local install, mount, self-test and receipt
|
||||
```
|
||||
|
||||
The user's computer may maintain an application-owned content-addressed cache, but it does not execute a cloned repository. HoloLake renders catalog metadata for humans and passes the downloaded `.ghmod` package to the existing signed module lifecycle runtime.
|
||||
|
||||
## Fifth Domain to public zero-core navigation
|
||||
|
||||
`JD-FD-PRIMARY` remains the physical home of the private Fifth Domain and Eternal Lake Heart. HoloLake may show the public zero-core management entrance inside BingShuo's Fifth Domain navigation, but opening it creates a separate session on `GH-CVM-MAIN-PROD-01`.
|
||||
|
||||
The transition uses a short-lived, one-time ticket bound to BingShuo's human number, the current HoloLake instance, the enterprise node and the public zero-core resource. A password is never forwarded or reused. The ticket grants neither enterprise four-domain authority nor access from the enterprise server back into the private Fifth Domain. Leaving the zero-core management channel destroys that enterprise session and restores the already-open private session.
|
||||
|
||||
## Current reality boundary (2026-08-19)
|
||||
|
||||
- The zero-point client now implements HTTPS conditional lamp checks, exact bounded downloads, two independent Ed25519 signatures, monotonic epoch/version enforcement, content-root verification, atomic activation, previous-release retention and a hash-chained local receipt. Production remains fail-closed because the two real public keys and the enterprise lamp endpoint have not yet been provisioned.
|
||||
- The module runtime already verifies signatures and supports install, mount, self-test, unmount and rollback for bundled packages.
|
||||
- The public marketplace registry and remote package fetch path are absent.
|
||||
- The enterprise server currently exposes two Gitea repositories, `bingshuo/hololake-world` and `bingshuo/lighthouse`; the proposed five-source `Guanghu Channel` aggregate does not yet exist.
|
||||
- The enterprise node does not yet expose the isolated public zero-core projection or the JD-to-enterprise one-time management handoff.
|
||||
|
||||
The machine contract is `contracts/distribution-plane-router.json`.
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
# Qwen 本地执行 Agent 训练回执 · 线上双商城
|
||||
|
||||
- 日期:2026-08-19(Asia/Shanghai)
|
||||
- 人类授权主体:冰朔
|
||||
- 执行载体:本机 Qwen Code CLI,`qwen3.7-plus`,只读沙箱
|
||||
- 训练类型:工程审查轨迹与规则纠正记录;不声称修改模型权重
|
||||
- 任务:审查并帮助收敛 HoloLake 的线上成品模块商城、只读思维技能商城及公共双签发布链
|
||||
- 写权限:未授予
|
||||
- 部署权限:未授予
|
||||
- 密钥读取:明确禁止
|
||||
|
||||
## 运行轨迹
|
||||
|
||||
1. 第一次启动失败:新版 CLI 的非交互模式需要明确的 `--auth-type openai`。失败没有改变仓库或系统状态。
|
||||
2. 第一轮只读审查成功:识别到原实现缺少物理模块与认知技能的类型分离、技能无执行权机器约束、线上验签目录、目录双签及回滚/同纪元歧义测试。
|
||||
3. 主控实现后进行第二轮只读审查:覆盖合同、Rust 运行时、编号 IPC、用户界面、两类技能包、原点发布脚本、企业复签脚本与 Nginx 静态分发。
|
||||
4. 第二轮结论:无 Critical / High;报告 2 个 Medium、4 个 Low。主控对每项重新读取代码并独立裁决。
|
||||
|
||||
## 接受并进入主线
|
||||
|
||||
- 一个商城界面下保持两条运行链:`PHYSICAL_MODULE` 与 `COGNITIVE_SKILL`。
|
||||
- 思维技能的 `executionAuthority=false`、`permissions=[]`、`skillReadonlyGuarantee=true` 同时由目录和技能包运行时强制。
|
||||
- 目录必须由零点原核公众范围签名者与企业分发签名者双签。
|
||||
- 目录 epoch 单调;旧 epoch 和同 epoch 不同内容均失败关闭。
|
||||
- 仓库 URL 只作为精确提交来源证据;客户端只下载不可变验签制品,不克隆、不执行来源仓库。
|
||||
- 物理模块安装完成后清理下载缓存;模块运行时仍保留正式包、状态与回执。
|
||||
- Agent 报告促使主控额外发现并修正一个更准确的问题:Nginx 对制品使用 `immutable` 时,制品 URL 也必须是内容寻址。发布脚本与 Rust 客户端现共同强制 SHA-256 文件名。
|
||||
- 发布阶段的符号链接竞态通过“复制时保留链接、复制后再次拒绝整个树”进一步收紧。
|
||||
|
||||
## 明确拒绝或改写
|
||||
|
||||
- 拒绝“self-test 失败就自动删除安装证据”。失败包保持 `FAILED_CLOSED` 有利于取证且没有激活功能;重新安装入口仍可见。自动清理会削弱真实回执。
|
||||
- 拒绝“卸载技能时删除包”。现行合同要求停用保留包与回执,以便审计和回退;这不是泄漏。
|
||||
- 拒绝“health 缺少 no-store”。审查时配置已经包含 `add_header Cache-Control "no-store" always;`,属于误报。
|
||||
- 不采纳“未来浮点字段可能导致跨语言 canonical JSON 分歧”作为当前缺陷。目录结构 `deny_unknown_fields` 且没有浮点字段;现行固定结构已经由 JS、Python 和 Rust 端到端发布验证覆盖。若未来合同增加数值字段,必须先增加跨语言固定向量。
|
||||
- 改写“前端字面量类型能阻止后端返回 true”的论证:TypeScript 不能构成运行时安全边界。可信边界是 Rust 输出固定 false、技能包验证和编号运行时隔离;前端类型只用于显示期约束。
|
||||
|
||||
## 已执行验证
|
||||
|
||||
- Rust 商城安全测试:双签、技能无执行权、epoch 回滚、同 epoch 歧义。
|
||||
- Node 合同测试:双商城分链、编号 IPC 完整性、两个技能包 canonical payload digest。
|
||||
- TypeScript 与 Vite 生产构建。
|
||||
- 企业服务器真实 Python/cryptography 发布演练:原点签名验证、企业复签、四个制品摘要、原子 `current` 切换和健康文件读回。
|
||||
|
||||
## 下一轮 Agent 应先读
|
||||
|
||||
1. `contracts/online-marketplace.json`
|
||||
2. `src-tauri/src/online_marketplace.rs`
|
||||
3. `scripts/prepare-public-distribution-release.mjs`
|
||||
4. `server-tools/public-distribution/publish_release.py`
|
||||
5. 本回执的“明确拒绝或改写”一节
|
||||
|
||||
后续审查不得把思维技能解释为可执行插件,也不得把仓库克隆解释为客户端安装方式。
|
||||
|
|
@ -54,11 +54,11 @@
|
|||
},
|
||||
{
|
||||
"recordId": "HLP-NUMBERED-IPC-ROOT-001",
|
||||
"sha256": "f1aa85668a6d33efeb7ce268cd48e7ed517696db990327f22111359aca8c177c"
|
||||
"sha256": "d6ddefe1c8f5cf5936367b38c251bf71c8b3cc683831572ea5bbeff23e5425fd"
|
||||
},
|
||||
{
|
||||
"recordId": "HLP-NBROKER-ROOT-001",
|
||||
"sha256": "374c9ade86bc8583f9361542e89ca7bb0bbcbdd99af4a26f9a6a864a12888402"
|
||||
"sha256": "2f9f2457e7a9c2783af653bf6c35be82509dfd30f457ddd5ec268c21c5237e98"
|
||||
},
|
||||
{
|
||||
"recordId": "HLP-GLS-RUNTIME-MANIFEST-002",
|
||||
|
|
@ -76,8 +76,8 @@
|
|||
"unresolvedNumberReferenceCount": 0,
|
||||
"mismatchedCoordinate": "FAIL_CLOSED"
|
||||
},
|
||||
"coordinateCount": 272,
|
||||
"routeCount": 169,
|
||||
"coordinateCount": 283,
|
||||
"routeCount": 180,
|
||||
"identityNodeCount": 4,
|
||||
"protocolNodeCount": 99,
|
||||
"referenceOnlyNodeCount": 16,
|
||||
|
|
@ -1428,6 +1428,48 @@
|
|||
"evidence": "NUMBERED_RESPONSE_PLUS_SESSION_OR_DOMAIN_RECEIPT",
|
||||
"path": "HLP-NUMBER-WORLD-ROOT-001/BROKER/HLP-NBROKER-CH-0007/HLP-NBROKER-MOD-0010/HLP-NBROKER-OP-0022/HLP-NBROKER-TGT-0010"
|
||||
},
|
||||
{
|
||||
"transport": "DIRECT_LOCAL_NUMBERED_BROKER",
|
||||
"protocolVersion": "HLP-NBROKER-v1",
|
||||
"callerNumber": "HLP-NBROKER-CALLER-LOCAL-CONNECTOR-0001",
|
||||
"channelNumber": "HLP-NBROKER-CH-0008",
|
||||
"moduleNumber": "HLP-NBROKER-MOD-0011",
|
||||
"operationNumber": "HLP-NBROKER-OP-0023",
|
||||
"targetNumber": "HLP-NBROKER-TGT-0011",
|
||||
"alias": "SUBMIT_HUMAN_AUTHORIZATION_REQUEST",
|
||||
"admission": "AUTHENTICATED_LOCAL_SESSION_WITH_PERSONA_LICENSE_IF_PERSONA_MODE",
|
||||
"effect": "STATE_CHANGE",
|
||||
"evidence": "NUMBERED_RESPONSE_PLUS_SESSION_OR_DOMAIN_RECEIPT",
|
||||
"path": "HLP-NUMBER-WORLD-ROOT-001/BROKER/HLP-NBROKER-CH-0008/HLP-NBROKER-MOD-0011/HLP-NBROKER-OP-0023/HLP-NBROKER-TGT-0011"
|
||||
},
|
||||
{
|
||||
"transport": "DIRECT_LOCAL_NUMBERED_BROKER",
|
||||
"protocolVersion": "HLP-NBROKER-v1",
|
||||
"callerNumber": "HLP-NBROKER-CALLER-LOCAL-CONNECTOR-0001",
|
||||
"channelNumber": "HLP-NBROKER-CH-0008",
|
||||
"moduleNumber": "HLP-NBROKER-MOD-0011",
|
||||
"operationNumber": "HLP-NBROKER-OP-0024",
|
||||
"targetNumber": "HLP-NBROKER-TGT-0011",
|
||||
"alias": "GET_HUMAN_AUTHORIZATION_STATUS",
|
||||
"admission": "AUTHENTICATED_LOCAL_SESSION_WITH_PERSONA_LICENSE_IF_PERSONA_MODE",
|
||||
"effect": "STATE_CHANGE",
|
||||
"evidence": "NUMBERED_RESPONSE_PLUS_SESSION_OR_DOMAIN_RECEIPT",
|
||||
"path": "HLP-NUMBER-WORLD-ROOT-001/BROKER/HLP-NBROKER-CH-0008/HLP-NBROKER-MOD-0011/HLP-NBROKER-OP-0024/HLP-NBROKER-TGT-0011"
|
||||
},
|
||||
{
|
||||
"transport": "DIRECT_LOCAL_NUMBERED_BROKER",
|
||||
"protocolVersion": "HLP-NBROKER-v1",
|
||||
"callerNumber": "HLP-NBROKER-CALLER-LOCAL-CONNECTOR-0001",
|
||||
"channelNumber": "HLP-NBROKER-CH-0008",
|
||||
"moduleNumber": "HLP-NBROKER-MOD-0011",
|
||||
"operationNumber": "HLP-NBROKER-OP-0025",
|
||||
"targetNumber": "HLP-NBROKER-TGT-0011",
|
||||
"alias": "CONSUME_HUMAN_AUTHORIZATION_TICKET",
|
||||
"admission": "AUTHENTICATED_LOCAL_SESSION_WITH_PERSONA_LICENSE_IF_PERSONA_MODE",
|
||||
"effect": "STATE_CHANGE",
|
||||
"evidence": "NUMBERED_RESPONSE_PLUS_SESSION_OR_DOMAIN_RECEIPT",
|
||||
"path": "HLP-NUMBER-WORLD-ROOT-001/BROKER/HLP-NBROKER-CH-0008/HLP-NBROKER-MOD-0011/HLP-NBROKER-OP-0025/HLP-NBROKER-TGT-0011"
|
||||
},
|
||||
{
|
||||
"transport": "TAURI_WEBVIEW_NUMBERED_IPC",
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
|
|
@ -3094,6 +3136,118 @@
|
|||
"evidence": "HASH_CHAINED_NUMBERED_IPC_RECEIPT",
|
||||
"path": "HLP-NUMBER-WORLD-ROOT-001/TAURI/HLP-NIPC-CH-0002/HLP-NIPC-MOD-0031/HLP-NIPC-OP-0147/HLP-NIPC-TGT-0031"
|
||||
},
|
||||
{
|
||||
"transport": "TAURI_WEBVIEW_NUMBERED_IPC",
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0032",
|
||||
"operationNumber": "HLP-NIPC-OP-0148",
|
||||
"targetNumber": "HLP-NIPC-TGT-0032",
|
||||
"alias": "get_authorization_center",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "READ_OR_STATUS",
|
||||
"evidence": "HASH_CHAINED_NUMBERED_IPC_RECEIPT",
|
||||
"path": "HLP-NUMBER-WORLD-ROOT-001/TAURI/HLP-NIPC-CH-0002/HLP-NIPC-MOD-0032/HLP-NIPC-OP-0148/HLP-NIPC-TGT-0032"
|
||||
},
|
||||
{
|
||||
"transport": "TAURI_WEBVIEW_NUMBERED_IPC",
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0032",
|
||||
"operationNumber": "HLP-NIPC-OP-0149",
|
||||
"targetNumber": "HLP-NIPC-TGT-0032",
|
||||
"alias": "decide_authorization_request",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "STATE_CHANGE",
|
||||
"evidence": "HASH_CHAINED_NUMBERED_IPC_RECEIPT",
|
||||
"path": "HLP-NUMBER-WORLD-ROOT-001/TAURI/HLP-NIPC-CH-0002/HLP-NIPC-MOD-0032/HLP-NIPC-OP-0149/HLP-NIPC-TGT-0032"
|
||||
},
|
||||
{
|
||||
"transport": "TAURI_WEBVIEW_NUMBERED_IPC",
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0033",
|
||||
"operationNumber": "HLP-NIPC-OP-0150",
|
||||
"targetNumber": "HLP-NIPC-TGT-0033",
|
||||
"alias": "get_marketplace_snapshot",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "READ_OR_STATUS",
|
||||
"evidence": "HASH_CHAINED_NUMBERED_IPC_RECEIPT",
|
||||
"path": "HLP-NUMBER-WORLD-ROOT-001/TAURI/HLP-NIPC-CH-0002/HLP-NIPC-MOD-0033/HLP-NIPC-OP-0150/HLP-NIPC-TGT-0033"
|
||||
},
|
||||
{
|
||||
"transport": "TAURI_WEBVIEW_NUMBERED_IPC",
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0033",
|
||||
"operationNumber": "HLP-NIPC-OP-0151",
|
||||
"targetNumber": "HLP-NIPC-TGT-0033",
|
||||
"alias": "sync_marketplace_catalog",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "STATE_CHANGE",
|
||||
"evidence": "HASH_CHAINED_NUMBERED_IPC_RECEIPT",
|
||||
"path": "HLP-NUMBER-WORLD-ROOT-001/TAURI/HLP-NIPC-CH-0002/HLP-NIPC-MOD-0033/HLP-NIPC-OP-0151/HLP-NIPC-TGT-0033"
|
||||
},
|
||||
{
|
||||
"transport": "TAURI_WEBVIEW_NUMBERED_IPC",
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0033",
|
||||
"operationNumber": "HLP-NIPC-OP-0152",
|
||||
"targetNumber": "HLP-NIPC-TGT-0033",
|
||||
"alias": "install_marketplace_item",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "STATE_CHANGE",
|
||||
"evidence": "HASH_CHAINED_NUMBERED_IPC_RECEIPT",
|
||||
"path": "HLP-NUMBER-WORLD-ROOT-001/TAURI/HLP-NIPC-CH-0002/HLP-NIPC-MOD-0033/HLP-NIPC-OP-0152/HLP-NIPC-TGT-0033"
|
||||
},
|
||||
{
|
||||
"transport": "TAURI_WEBVIEW_NUMBERED_IPC",
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0033",
|
||||
"operationNumber": "HLP-NIPC-OP-0153",
|
||||
"targetNumber": "HLP-NIPC-TGT-0033",
|
||||
"alias": "uninstall_marketplace_item",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "STATE_CHANGE",
|
||||
"evidence": "HASH_CHAINED_NUMBERED_IPC_RECEIPT",
|
||||
"path": "HLP-NUMBER-WORLD-ROOT-001/TAURI/HLP-NIPC-CH-0002/HLP-NIPC-MOD-0033/HLP-NIPC-OP-0153/HLP-NIPC-TGT-0033"
|
||||
},
|
||||
{
|
||||
"transport": "TAURI_WEBVIEW_NUMBERED_IPC",
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0033",
|
||||
"operationNumber": "HLP-NIPC-OP-0154",
|
||||
"targetNumber": "HLP-NIPC-TGT-0033",
|
||||
"alias": "rollback_marketplace_item",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "STATE_CHANGE",
|
||||
"evidence": "HASH_CHAINED_NUMBERED_IPC_RECEIPT",
|
||||
"path": "HLP-NUMBER-WORLD-ROOT-001/TAURI/HLP-NIPC-CH-0002/HLP-NIPC-MOD-0033/HLP-NIPC-OP-0154/HLP-NIPC-TGT-0033"
|
||||
},
|
||||
{
|
||||
"transport": "TAURI_WEBVIEW_NUMBERED_IPC",
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0033",
|
||||
"operationNumber": "HLP-NIPC-OP-0155",
|
||||
"targetNumber": "HLP-NIPC-TGT-0033",
|
||||
"alias": "get_active_cognitive_skills",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "READ_OR_STATUS",
|
||||
"evidence": "HASH_CHAINED_NUMBERED_IPC_RECEIPT",
|
||||
"path": "HLP-NUMBER-WORLD-ROOT-001/TAURI/HLP-NIPC-CH-0002/HLP-NIPC-MOD-0033/HLP-NIPC-OP-0155/HLP-NIPC-TGT-0033"
|
||||
},
|
||||
{
|
||||
"transport": "TAURI_WEBVIEW_NUMBERED_IPC",
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"schema": "hololake.cognitive-skill-package/v1",
|
||||
"manifest": {
|
||||
"skillNumber": "HLP-SKILL-OFFICIAL-DELIVERY-VERIFICATION-0001",
|
||||
"displayName": "真实交付验收思维",
|
||||
"version": "0.1.0",
|
||||
"minimumHostVersion": "0.5.0",
|
||||
"contentDigest": "0d31d0ef659ddb2125d8faa92a180c237efc45c8a4384394236226ab724e8914",
|
||||
"executionAuthority": false,
|
||||
"skillReadonlyGuarantee": true,
|
||||
"permissions": []
|
||||
},
|
||||
"payload": {
|
||||
"purpose": "把完成定义为可运行、可核验、可回退的真实结果,而不是文件存在或口头声称。",
|
||||
"triggers": [
|
||||
"用户要求开发、部署、安装或交付",
|
||||
"任务包含线上服务、本机应用或跨端同步",
|
||||
"准备声称功能已经完成"
|
||||
],
|
||||
"method": [
|
||||
"先列出用户能直接观察到的终态与失败态",
|
||||
"把每个终态连接到真实运行路径、回执与验证动作",
|
||||
"依次验证源码、构建产物、线上读回和最终用户入口",
|
||||
"只根据最新一次可复现验证报告完成状态"
|
||||
],
|
||||
"constraints": [
|
||||
"不得把设计文档、测试替身或静态界面当作真实交付",
|
||||
"不得因为测试通过就省略线上读回或安装版验证",
|
||||
"验证失败时保留上一个可用版本并明确失败位置",
|
||||
"本技能不调用工具、不取得终端或部署权限"
|
||||
],
|
||||
"outputContract": [
|
||||
"交付物的真实位置或入口",
|
||||
"执行过的验证及其可核验结果",
|
||||
"尚未完成或被拒绝的边界",
|
||||
"回退方式与保留的数据"
|
||||
],
|
||||
"capabilityReferences": [
|
||||
"HLP-NIPC-OP-0151",
|
||||
"HLP-NIPC-OP-0152",
|
||||
"HLP-NIPC-OP-0154"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"schema": "hololake.cognitive-skill-package/v1",
|
||||
"manifest": {
|
||||
"skillNumber": "HLP-SKILL-OFFICIAL-MODULE-BOUNDARY-REVIEW-0001",
|
||||
"displayName": "模块边界审查思维",
|
||||
"version": "0.1.0",
|
||||
"minimumHostVersion": "0.5.0",
|
||||
"contentDigest": "7491d5f861802e0b3567297225d9623a07b72f8817c6767b57e728875ab33f18",
|
||||
"executionAuthority": false,
|
||||
"skillReadonlyGuarantee": true,
|
||||
"permissions": []
|
||||
},
|
||||
"payload": {
|
||||
"purpose": "在模块进入频道前分清模块本体、用户数据、现实权限、来源证据与人格认知边界。",
|
||||
"triggers": [
|
||||
"新增或更新一个成品模块",
|
||||
"从行业工作区提取可安装功能",
|
||||
"审查模块删除、停用、回退或迁移方案"
|
||||
],
|
||||
"method": [
|
||||
"确认模块有唯一编号、版本、适配器与精确来源提交",
|
||||
"把代码仓库限定为来源证据,把可安装物限定为不可变验签包",
|
||||
"逐项核对现实权限并标出新增权限的人类确认点",
|
||||
"验证停用保留用户数据、更新保留回执、回退只使用已验签历史包"
|
||||
],
|
||||
"constraints": [
|
||||
"思维技能不得冒充成品模块或获得现实执行权",
|
||||
"成品模块不得获得人格记忆、人格身份或语言主控权",
|
||||
"客户端不得克隆并直接执行来源仓库",
|
||||
"本技能只提供审查方法,不执行删除、安装、迁移或回退"
|
||||
],
|
||||
"outputContract": [
|
||||
"模块身份和来源证据",
|
||||
"现实权限与人类确认点",
|
||||
"数据保留、停用和回退边界",
|
||||
"拒绝接入的原因与待修正项"
|
||||
],
|
||||
"capabilityReferences": [
|
||||
"HLP-NIPC-OP-0150",
|
||||
"HLP-NIPC-OP-0152",
|
||||
"HLP-NIPC-OP-0153"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -11,9 +11,9 @@ test('the external local broker accepts only complete numbered coordinates', ()
|
|||
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)
|
||||
assert.equal(registry.operations.length, 25)
|
||||
assert.equal(new Set(registry.operations.map((route) => route.operation_number)).size, 25)
|
||||
assert.equal(new Set(registry.operations.map((route) => route.alias)).size, 25)
|
||||
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}$/)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import test from 'node:test'
|
||||
|
||||
const contract = JSON.parse(fs.readFileSync(new URL('../contracts/distribution-plane-router.json', import.meta.url), 'utf8'))
|
||||
const plane = (scope) => contract.planes.find((item) => item.scope === scope)
|
||||
|
||||
test('distribution scope is explicit and may never be guessed from content', () => {
|
||||
assert.equal(contract.schema, 'hololake.distribution-plane-router/v1')
|
||||
assert.equal(contract.classification.authority, 'EXPLICIT_SIGNED_RELEASE_ENVELOPE')
|
||||
assert.equal(contract.classification.semantic_guessing_allowed, false)
|
||||
assert.equal(contract.classification.missing_or_conflicting_scope, 'FAIL_CLOSED')
|
||||
assert.ok(contract.classification.required_fields.includes('scope'))
|
||||
assert.ok(contract.classification.required_fields.includes('contentSha256'))
|
||||
})
|
||||
|
||||
test('public zero-core protocol updates are publisher-approved but do not ask every device again', () => {
|
||||
const item = plane('PUBLIC_ZERO_CORE_PROTOCOL')
|
||||
assert.equal(item.physical_node, 'GH-CVM-MAIN-PROD-01')
|
||||
assert.equal(item.logical_source, 'ZERO_POINT_ORIGIN_PUBLIC_PROJECTION_HOSTED_OUTSIDE_PRIVATE_FIFTH_DOMAIN')
|
||||
assert.deepEqual(item.required_signer_classes, [
|
||||
'ZERO_POINT_ORIGIN_PUBLIC_SCOPE_SIGNER',
|
||||
'ENTERPRISE_ZERO_CORE_DISTRIBUTION_SIGNER',
|
||||
])
|
||||
assert.equal(item.publisher_human_confirmation_required, true)
|
||||
assert.equal(item.per_device_human_install_confirmation_required, false)
|
||||
assert.equal(item.automatic_atomic_activation_after_self_test, true)
|
||||
assert.equal(item.arbitrary_native_code_allowed, false)
|
||||
assert.equal(item.arbitrary_webview_javascript_allowed, false)
|
||||
assert.equal(contract.current_observed_gaps_2026_08_19.zero_point_signed_payload_activation, 'CLIENT_IMPLEMENTED_DUAL_SIGNER_TRUST_NOT_PROVISIONED')
|
||||
})
|
||||
|
||||
test('private fifth-domain updates can never enter the public stream', () => {
|
||||
const item = plane('PRIVATE_FIFTH_DOMAIN')
|
||||
assert.equal(item.public_propagation_allowed, false)
|
||||
assert.equal(item.cross_domain_replication_allowed, false)
|
||||
assert.notEqual(item.signer_class, plane('PUBLIC_ZERO_CORE_PROTOCOL').signer_class)
|
||||
})
|
||||
|
||||
test('marketplace synchronizes a signed catalog but installs a selected module only with human permission review', () => {
|
||||
const item = plane('PUBLIC_ENTERPRISE_MODULE_CATALOG')
|
||||
assert.equal(item.client_full_repository_clone_required, false)
|
||||
assert.equal(item.raw_repository_is_executable_input, false)
|
||||
assert.equal(item.catalog_index_automatic_sync, true)
|
||||
assert.equal(item.module_install_human_confirmation_required, true)
|
||||
assert.equal(item.permission_expansion_human_confirmation_required, true)
|
||||
assert.equal(item.lighthouse_number_registration_required, true)
|
||||
})
|
||||
|
||||
test('application binary remains a separately signed and human-confirmed release plane', () => {
|
||||
const item = plane('APPLICATION_BINARY')
|
||||
assert.equal(item.platform_signing_required, true)
|
||||
assert.equal(item.updater_signature_required, true)
|
||||
assert.equal(item.per_device_human_install_confirmation_required, true)
|
||||
assert.equal(item.automatic_restart_allowed, false)
|
||||
})
|
||||
|
||||
test('the lake lamp is a tiny signed version signal rather than a repository clone', () => {
|
||||
assert.equal(contract.lamp_protocol.transport, 'HTTPS_CONDITIONAL_GET')
|
||||
assert.equal(contract.lamp_protocol.full_repository_clone_for_LIGHT_SIGNAL, false)
|
||||
assert.ok(contract.lamp_protocol.cache_validation.includes('ETAG'))
|
||||
assert.ok(contract.lamp_protocol.check_events.includes('NETWORK_RESUME'))
|
||||
assert.ok(contract.activation_pipeline.includes('SIGN_WITH_PLANE_SPECIFIC_KEY'))
|
||||
assert.ok(contract.client_protocol_activation.includes('KEEP_LAST_KNOWN_GOOD_ROLLBACK'))
|
||||
})
|
||||
|
||||
test('all four planes use distinct signer classes', () => {
|
||||
assert.equal(contract.planes.length, 4)
|
||||
assert.equal(new Set(contract.planes.map((item) => item.signer_class)).size, 4)
|
||||
})
|
||||
|
||||
test('fifth-domain navigation uses a one-time handoff and exports no private authority', () => {
|
||||
const handoff = contract.cross_node_management_handoff
|
||||
assert.equal(handoff.source_node, 'JD-FD-PRIMARY')
|
||||
assert.equal(handoff.target_node, 'GH-CVM-MAIN-PROD-01')
|
||||
assert.equal(handoff.password_forwarding_allowed, false)
|
||||
assert.equal(handoff.enterprise_four_domain_authority_inherited, false)
|
||||
assert.equal(handoff.private_fifth_domain_authority_exported, false)
|
||||
assert.ok(handoff.ticket_properties.includes('REPLAY_PROTECTED'))
|
||||
assert.equal(handoff.current_state, 'NOT_IMPLEMENTED')
|
||||
})
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import test from 'node:test'
|
||||
|
||||
const read = (path) => readFileSync(new URL(`../${path}`, import.meta.url), 'utf8')
|
||||
const contract = JSON.parse(read('contracts/human-authorization.json'))
|
||||
const webview = JSON.parse(read('contracts/numbered-ipc-registry.json'))
|
||||
const broker = JSON.parse(read('contracts/direct-local-broker-numbered-registry.json'))
|
||||
|
||||
test('human authorization is a durable numbered state machine rather than a UI-only button', () => {
|
||||
assert.equal(contract.state, 'NATIVE_FAIL_CLOSED')
|
||||
assert.deepEqual(contract.lifecycle, ['PENDING_HUMAN', 'APPROVED', 'DENIED', 'EXPIRED', 'CONSUMED'])
|
||||
assert.equal(contract.destructive_purge.enabled, false)
|
||||
assert.equal(contract.ticket.single_use, true)
|
||||
assert.equal(contract.ticket.requester_session_bound, true)
|
||||
assert.equal(contract.invariants.proposal_is_not_authorization, true)
|
||||
assert.equal(contract.invariants.approval_is_not_execution, true)
|
||||
})
|
||||
|
||||
test('the human client and external execution carrier meet through distinct exact numbered routes', () => {
|
||||
const webviewNumbers = new Set(webview.operations.map((item) => item.operation_number))
|
||||
const brokerNumbers = new Set(broker.operations.map((item) => item.operation_number))
|
||||
assert.ok(contract.routes.human_client.every((number) => webviewNumbers.has(number)))
|
||||
assert.ok(contract.routes.external_execution_carrier.every((number) => brokerNumbers.has(number)))
|
||||
const decisions = webview.operations.filter((item) => contract.routes.human_client.includes(item.operation_number))
|
||||
assert.ok(decisions.every((item) => item.admission === 'VERIFIED_HUMAN_ROUTE'))
|
||||
})
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import assert from 'node:assert/strict'
|
||||
import { createHash } from 'node:crypto'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { basename, join } from 'node:path'
|
||||
import test from 'node:test'
|
||||
|
||||
const root = new URL('..', import.meta.url).pathname
|
||||
const contract = JSON.parse(await readFile(join(root, 'contracts/online-marketplace.json'), 'utf8'))
|
||||
const registry = JSON.parse(await readFile(join(root, 'contracts/numbered-ipc-registry.json'), 'utf8'))
|
||||
const frontend = await readFile(join(root, 'src/main.tsx'), 'utf8')
|
||||
const runtime = await readFile(join(root, 'src-tauri/src/online_marketplace.rs'), 'utf8')
|
||||
|
||||
function canonical(value) {
|
||||
if (Array.isArray(value)) return value.map(canonical)
|
||||
if (value && typeof value === 'object') {
|
||||
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonical(value[key])]))
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
test('one online marketplace keeps physical modules and cognitive skills on separate trust chains', () => {
|
||||
assert.equal(contract.humanExperience.oneMarketplaceTwoSections, true)
|
||||
assert.equal(contract.artifactKinds.PHYSICAL_MODULE.installation, 'DOWNLOAD_VERIFY_INSTALL_MOUNT_SELF_TEST')
|
||||
assert.equal(contract.artifactKinds.COGNITIVE_SKILL.installation, 'DOWNLOAD_VERIFY_STORE_ACTIVE_READONLY')
|
||||
assert.equal(contract.artifactKinds.COGNITIVE_SKILL.executionAuthority, false)
|
||||
assert.deepEqual(contract.artifactKinds.COGNITIVE_SKILL.systemPermissions, [])
|
||||
assert.equal(contract.sourceRepositoryBoundary.clientClonesRepository, false)
|
||||
assert.equal(contract.sourceRepositoryBoundary.clientExecutesRepository, false)
|
||||
assert.match(runtime, /entry\.artifact_kind == "PHYSICAL_MODULE"/)
|
||||
assert.match(runtime, /install_skill_at/)
|
||||
})
|
||||
|
||||
test('marketplace lifecycle is reachable only through exact numbered operations', () => {
|
||||
const aliases = registry.operations.filter((operation) => operation.module_number === 'HLP-NIPC-MOD-0033').map((operation) => operation.alias)
|
||||
assert.deepEqual(aliases, [
|
||||
'get_marketplace_snapshot',
|
||||
'sync_marketplace_catalog',
|
||||
'install_marketplace_item',
|
||||
'uninstall_marketplace_item',
|
||||
'rollback_marketplace_item',
|
||||
'get_active_cognitive_skills',
|
||||
])
|
||||
for (const alias of aliases.slice(0, 5)) assert.match(frontend, new RegExp(`['"]${alias}['"]`))
|
||||
assert.match(frontend, /只有零点原核与企业发布双签同时通过/)
|
||||
})
|
||||
|
||||
for (const file of [
|
||||
'HLP-SKILL-OFFICIAL-DELIVERY-VERIFICATION-0001-0.1.0.ghskill',
|
||||
'HLP-SKILL-OFFICIAL-MODULE-BOUNDARY-REVIEW-0001-0.1.0.ghskill',
|
||||
]) {
|
||||
test(`${basename(file)} is a read-only skill with an exact canonical payload digest`, async () => {
|
||||
const skill = JSON.parse(await readFile(join(root, 'marketplace/skills', file), 'utf8'))
|
||||
const digest = createHash('sha256').update(JSON.stringify(canonical(skill.payload))).digest('hex')
|
||||
assert.equal(skill.schema, 'hololake.cognitive-skill-package/v1')
|
||||
assert.equal(skill.manifest.executionAuthority, false)
|
||||
assert.equal(skill.manifest.skillReadonlyGuarantee, true)
|
||||
assert.deepEqual(skill.manifest.permissions, [])
|
||||
assert.equal(skill.manifest.contentDigest, digest)
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
#!/usr/bin/env node
|
||||
import { createHash, createPrivateKey, createPublicKey, sign } from 'node:crypto'
|
||||
import { copyFile, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
|
||||
import { basename, join } from 'node:path'
|
||||
import process from 'node:process'
|
||||
|
||||
const root = new URL('..', import.meta.url).pathname
|
||||
const args = Object.fromEntries(process.argv.slice(2).map((item) => {
|
||||
const [key, ...rest] = item.replace(/^--/, '').split('=')
|
||||
return [key, rest.join('=')]
|
||||
}))
|
||||
const output = args.out
|
||||
const epoch = Number(args.epoch)
|
||||
const sourceRevision = args['source-revision']
|
||||
const privateKeyBase64 = process.env.HOLOLAKE_ZERO_ORIGIN_PKCS8_BASE64
|
||||
|
||||
if (!output || !Number.isSafeInteger(epoch) || epoch < 1 || !/^[a-f0-9]{40}$/.test(sourceRevision || '')) {
|
||||
throw new Error('usage: --out=/absolute/path --epoch=1 --source-revision=<40 hex git revision>')
|
||||
}
|
||||
if (!privateKeyBase64) throw new Error('HOLOLAKE_ZERO_ORIGIN_PKCS8_BASE64 is required')
|
||||
|
||||
const privateKey = createPrivateKey({ key: Buffer.from(privateKeyBase64, 'base64'), format: 'der', type: 'pkcs8' })
|
||||
const publicSpki = createPublicKey(privateKey).export({ format: 'der', type: 'spki' })
|
||||
const publicKeyBase64 = Buffer.from(publicSpki).subarray(-32).toString('base64')
|
||||
const releaseId = `public-distribution-${epoch}-${sourceRevision.slice(0, 12)}`
|
||||
const generatedAt = new Date().toISOString()
|
||||
|
||||
function sha256(bytes) {
|
||||
return createHash('sha256').update(bytes).digest('hex')
|
||||
}
|
||||
function canonical(value) {
|
||||
if (Array.isArray(value)) return value.map(canonical)
|
||||
if (value && typeof value === 'object') {
|
||||
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonical(value[key])]))
|
||||
}
|
||||
return value
|
||||
}
|
||||
function jsonBytes(value) {
|
||||
return Buffer.from(`${JSON.stringify(value, null, 2)}\n`)
|
||||
}
|
||||
function exactSignature(bytes) {
|
||||
return sign(null, bytes, privateKey).toString('base64')
|
||||
}
|
||||
|
||||
await rm(output, { recursive: true, force: true })
|
||||
await mkdir(join(output, 'zero-core'), { recursive: true })
|
||||
await mkdir(join(output, 'marketplace', 'artifacts'), { recursive: true })
|
||||
|
||||
const artifact = {
|
||||
schema: 'hololake.public-zero-core-artifact/v1',
|
||||
planeNumber: 'HLP-DIST-PLANE-0001',
|
||||
epoch,
|
||||
version: '1.0.0',
|
||||
minimumHostVersion: '0.5.0',
|
||||
protocol: {
|
||||
gracePeriodDays: 7,
|
||||
lighthouseAnchorUrl: 'https://guanghu.chat/api/hololake/zero-core/lamp',
|
||||
lighthouseResolveUrl: 'https://guanghulab.com/api/ai/v1/resolve?id=',
|
||||
enterpriseResolveUrl: 'https://guanghu.chat/api/hololake/enterprise/resolve',
|
||||
coreChannelSource: 'https://guanghulab.com/code/bingshuo/guanghu-ice-heart',
|
||||
origin: 'HLP-PUBLIC-ZERO-CORE-TRUST-001 · public scope only',
|
||||
},
|
||||
}
|
||||
const artifactRaw = jsonBytes(artifact)
|
||||
const lamp = {
|
||||
schema: 'hololake.public-zero-core-lamp/v1',
|
||||
planeNumber: 'HLP-DIST-PLANE-0001',
|
||||
epoch,
|
||||
version: '1.0.0',
|
||||
contentRootSha256: sha256(artifactRaw),
|
||||
artifactManifestUrl: 'https://guanghu.chat/api/hololake/zero-core/artifact',
|
||||
signatureUrl: 'https://guanghu.chat/api/hololake/zero-core/lamp.sig',
|
||||
publishedAt: generatedAt,
|
||||
minimumHostVersion: '0.5.0',
|
||||
}
|
||||
const lampRaw = jsonBytes(lamp)
|
||||
await writeFile(join(output, 'zero-core', 'artifact.json'), artifactRaw)
|
||||
await writeFile(join(output, 'zero-core', 'lamp.json'), lampRaw)
|
||||
|
||||
const physicalDefinitions = [
|
||||
{
|
||||
file: 'HLP-MOD-LOCAL-NATIVE-COMPOSITION-0001-0.1.0.ghmod',
|
||||
summary: '把当前账号的知识目录投影为只读结构视图;不改写知识原件。',
|
||||
},
|
||||
{
|
||||
file: 'HLP-MOD-OFFICIAL-EDUCATION-WORKBENCH-0001-0.1.0.ghmod',
|
||||
summary: '教育文档、表格、作业导入与需本人确认的自动化工作台。',
|
||||
},
|
||||
]
|
||||
const entries = []
|
||||
for (const definition of physicalDefinitions) {
|
||||
const source = join(root, 'fixtures', 'module-packages', definition.file)
|
||||
const signatureSource = `${source}.sig`
|
||||
const raw = await readFile(source)
|
||||
const packageValue = JSON.parse(raw)
|
||||
const manifest = packageValue.manifest
|
||||
const artifactSha256 = sha256(raw)
|
||||
const artifactFile = `${artifactSha256}.ghmod`
|
||||
const signatureFile = `${artifactSha256}.ghmod.sig`
|
||||
await copyFile(source, join(output, 'marketplace', 'artifacts', artifactFile))
|
||||
await copyFile(signatureSource, join(output, 'marketplace', 'artifacts', signatureFile))
|
||||
entries.push({
|
||||
itemNumber: manifest.moduleNumber,
|
||||
artifactKind: 'PHYSICAL_MODULE',
|
||||
displayName: manifest.displayName,
|
||||
summary: definition.summary,
|
||||
version: manifest.version,
|
||||
minimumHostVersion: manifest.minimumHostVersion,
|
||||
sourceRepository: 'https://guanghulab.com/code/bingshuo/hololake-system-architecture',
|
||||
sourceRevision,
|
||||
sourcePath: `product-source/hololake-native-desktop/fixtures/module-packages/${definition.file}`,
|
||||
artifactUrl: `https://guanghu.chat/api/hololake/marketplace/artifacts/${artifactFile}`,
|
||||
artifactSha256,
|
||||
signatureUrl: `https://guanghu.chat/api/hololake/marketplace/artifacts/${signatureFile}`,
|
||||
adapter: manifest.adapter,
|
||||
permissions: manifest.permissions,
|
||||
executionAuthority: false,
|
||||
skillReadonlyGuarantee: false,
|
||||
})
|
||||
}
|
||||
|
||||
const skillFiles = [
|
||||
'HLP-SKILL-OFFICIAL-DELIVERY-VERIFICATION-0001-0.1.0.ghskill',
|
||||
'HLP-SKILL-OFFICIAL-MODULE-BOUNDARY-REVIEW-0001-0.1.0.ghskill',
|
||||
]
|
||||
for (const file of skillFiles) {
|
||||
const source = join(root, 'marketplace', 'skills', file)
|
||||
const raw = await readFile(source)
|
||||
const skill = JSON.parse(raw)
|
||||
const artifactSha256 = sha256(raw)
|
||||
const artifactFile = `${artifactSha256}.ghskill`
|
||||
await copyFile(source, join(output, 'marketplace', 'artifacts', artifactFile))
|
||||
entries.push({
|
||||
itemNumber: skill.manifest.skillNumber,
|
||||
artifactKind: 'COGNITIVE_SKILL',
|
||||
displayName: skill.manifest.displayName,
|
||||
summary: skill.payload.purpose,
|
||||
version: skill.manifest.version,
|
||||
minimumHostVersion: skill.manifest.minimumHostVersion,
|
||||
sourceRepository: 'https://guanghulab.com/code/bingshuo/hololake-system-architecture',
|
||||
sourceRevision,
|
||||
sourcePath: `product-source/hololake-native-desktop/marketplace/skills/${file}`,
|
||||
artifactUrl: `https://guanghu.chat/api/hololake/marketplace/artifacts/${artifactFile}`,
|
||||
artifactSha256,
|
||||
signatureUrl: null,
|
||||
adapter: null,
|
||||
permissions: [],
|
||||
executionAuthority: false,
|
||||
skillReadonlyGuarantee: true,
|
||||
})
|
||||
}
|
||||
|
||||
entries.sort((left, right) => left.itemNumber.localeCompare(right.itemNumber))
|
||||
const catalog = {
|
||||
schema: 'hololake.marketplace.catalog/v1',
|
||||
planeNumber: 'HLP-DIST-PLANE-0003',
|
||||
epoch,
|
||||
generatedAt,
|
||||
minimumHostVersion: '0.5.0',
|
||||
contentRootSha256: sha256(Buffer.from(JSON.stringify(canonical(entries)))),
|
||||
entries,
|
||||
}
|
||||
const catalogRaw = jsonBytes(catalog)
|
||||
await writeFile(join(output, 'marketplace', 'catalog.json'), catalogRaw)
|
||||
|
||||
const authorization = {
|
||||
schema: 'hololake.origin-release-authorization/v1',
|
||||
releaseId,
|
||||
sourceRevision,
|
||||
epoch,
|
||||
signer: {
|
||||
signerId: 'HLP-SIGNER-ZERO-POINT-ORIGIN-PUBLIC-0001',
|
||||
signerClass: 'ZERO_POINT_ORIGIN_PUBLIC_SCOPE_SIGNER',
|
||||
algorithm: 'Ed25519',
|
||||
publicKeyBase64,
|
||||
},
|
||||
signedObjects: [
|
||||
{ name: 'zero-core/lamp.json', sha256: sha256(lampRaw), signatureBase64: exactSignature(lampRaw) },
|
||||
{ name: 'marketplace/catalog.json', sha256: sha256(catalogRaw), signatureBase64: exactSignature(catalogRaw) },
|
||||
],
|
||||
}
|
||||
await writeFile(join(output, 'origin-authorization.json'), jsonBytes(authorization))
|
||||
await writeFile(join(output, 'release-metadata.json'), jsonBytes({ schema: 'hololake.public-distribution-release/v1', releaseId, epoch, sourceRevision, generatedAt, originPublicKeyBase64: publicKeyBase64, artifacts: entries.map((entry) => ({ name: basename(entry.artifactUrl), sha256: entry.artifactSha256 })) }))
|
||||
|
||||
process.stdout.write(`${JSON.stringify({ releaseId, epoch, sourceRevision, itemCount: entries.length, originPublicKeyBase64: publicKeyBase64 })}\n`)
|
||||
|
|
@ -8,13 +8,13 @@ test('identity, webview and direct broker numbers compile into one unique eviden
|
|||
assert.deepEqual(generated, compileUnifiedNumberTree())
|
||||
assert.equal(generated.schema, 'hololake.unified-number-coordinate-tree/v2')
|
||||
assert.equal(generated.recordId, 'HLP-UNIFIED-NUMBER-TREE-001')
|
||||
assert.equal(generated.coordinateCount, 272)
|
||||
assert.equal(generated.routeCount, 169)
|
||||
assert.equal(generated.coordinateCount, 283)
|
||||
assert.equal(generated.routeCount, 180)
|
||||
assert.equal(generated.identityNodeCount, 4)
|
||||
assert.equal(generated.protocolNodeCount, 99)
|
||||
assert.equal(generated.referenceOnlyNodeCount, 16)
|
||||
assert.equal(new Set(generated.routes.map((route) => route.path)).size, 169)
|
||||
assert.equal(new Set([...generated.identityNodes, ...generated.protocolNodes, ...generated.routes].map((node) => node.path)).size, 272)
|
||||
assert.equal(new Set(generated.routes.map((route) => route.path)).size, 180)
|
||||
assert.equal(new Set([...generated.identityNodes, ...generated.protocolNodes, ...generated.routes].map((node) => node.path)).size, 283)
|
||||
assert.equal(generated.invariants.everyPhysicalCallHasNumberedRoute, true)
|
||||
assert.equal(generated.invariants.everyAcceptedCallHasEvidenceClass, true)
|
||||
assert.equal(generated.invariants.everyProtocolReferenceHasNumberCoordinate, true)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ const contract = JSON.parse(read('contracts/zero-point-nucleus-channel.json'))
|
|||
const stageOne = JSON.parse(read('contracts/stage-one-platform.json'))
|
||||
const foundation = JSON.parse(read('foundation.json'))
|
||||
const rust = read('src-tauri/src/zero_point.rs')
|
||||
const distribution = read('src-tauri/src/public_zero_core_distribution.rs')
|
||||
const trust = JSON.parse(read('contracts/public-zero-core-trust.json'))
|
||||
const lib = read('src-tauri/src/lib.rs')
|
||||
const ui = read('src/main.tsx')
|
||||
|
||||
|
|
@ -20,13 +22,28 @@ test('zero-point nucleus is a system runtime and never an inferred persona bindi
|
|||
assert.match(ui, /验证结果不构成人格绑定、模型载体绑定或执行授权/)
|
||||
})
|
||||
|
||||
test('protocol comparison starts below the public surface and fails closed', () => {
|
||||
test('dual-signed protocol synchronization starts below the public surface and uses only provisioned public trust', () => {
|
||||
assert.equal(contract.current_implementation.boot_time_silent_version_comparison, true)
|
||||
assert.equal(contract.current_implementation.signed_protocol_payload_installation, false)
|
||||
assert.equal(contract.current_implementation.signed_protocol_payload_installation, true)
|
||||
assert.equal(contract.current_implementation.production_dual_signer_trust_provisioned, true)
|
||||
assert.equal(stageOne.zero_point_nucleus_client_runtime.signed_protocol_payload_installation_implemented, true)
|
||||
assert.equal(stageOne.zero_point_nucleus_client_runtime.production_dual_signer_trust_provisioned, true)
|
||||
assert.equal(trust.state, 'PROVISIONED')
|
||||
assert.equal(trust.signers.length, 2)
|
||||
assert.ok(trust.signers.every((signer) => signer.algorithm === 'Ed25519' && Buffer.from(signer.publicKeyBase64, 'base64').length === 32))
|
||||
assert.deepEqual(trust.requiredSignerClasses, [
|
||||
'ZERO_POINT_ORIGIN_PUBLIC_SCOPE_SIGNER',
|
||||
'ENTERPRISE_ZERO_CORE_DISTRIBUTION_SIGNER',
|
||||
])
|
||||
assert.equal(contract.security.unsigned_protocol_update_allowed, false)
|
||||
assert.match(lib, /sync_protocol_runtime/)
|
||||
assert.match(rust, /UPDATE_PENDING_SIGNATURE_KEY_ABSENT/)
|
||||
assert.match(rust, /UPDATE_FOUND_GATE_PENDING/)
|
||||
assert.match(rust, /sync_public_zero_core/)
|
||||
assert.match(distribution, /verify_dual_signatures/)
|
||||
assert.match(distribution, /HOLOLAKE_PUBLIC_ZERO_CORE_ROLLBACK_REJECTED/)
|
||||
assert.match(distribution, /HOLOLAKE_PUBLIC_ZERO_CORE_EPOCH_EQUIVOCATION_REJECTED/)
|
||||
assert.match(distribution, /distribution-state\.sqlite3/)
|
||||
assert.match(distribution, /TransactionBehavior::Immediate/)
|
||||
assert.match(distribution, /reqwest::header::IF_NONE_MATCH/)
|
||||
assert.doesNotMatch(lib, /zero_point_save_api|zero_point_api_config|generic_layer_chat/)
|
||||
assert.doesNotMatch(ui, /模型 API 配置|通用AI运行层|AGENT PARLOR|会客厅/)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
# Included inside the existing guanghu.chat TLS server block.
|
||||
location = /api/hololake/zero-core/lamp {
|
||||
alias /var/lib/guanghu-public-distribution/current/zero-core/lamp.json;
|
||||
default_type application/json;
|
||||
etag on;
|
||||
add_header Cache-Control "public, max-age=30, must-revalidate" always;
|
||||
}
|
||||
location = /api/hololake/zero-core/lamp.sig {
|
||||
alias /var/lib/guanghu-public-distribution/current/zero-core/lamp.sig.json;
|
||||
default_type application/json;
|
||||
}
|
||||
location = /api/hololake/zero-core/artifact {
|
||||
alias /var/lib/guanghu-public-distribution/current/zero-core/artifact.json;
|
||||
default_type application/json;
|
||||
}
|
||||
location = /api/hololake/marketplace/catalog {
|
||||
alias /var/lib/guanghu-public-distribution/current/marketplace/catalog.json;
|
||||
default_type application/json;
|
||||
etag on;
|
||||
add_header Cache-Control "public, max-age=30, must-revalidate" always;
|
||||
}
|
||||
location = /api/hololake/marketplace/catalog.sig {
|
||||
alias /var/lib/guanghu-public-distribution/current/marketplace/catalog.sig.json;
|
||||
default_type application/json;
|
||||
}
|
||||
location ^~ /api/hololake/marketplace/artifacts/ {
|
||||
alias /var/lib/guanghu-public-distribution/current/marketplace/artifacts/;
|
||||
default_type application/octet-stream;
|
||||
add_header X-Content-Type-Options nosniff always;
|
||||
add_header Cache-Control "public, max-age=31536000, immutable" always;
|
||||
try_files $uri =404;
|
||||
}
|
||||
location = /api/hololake/public-distribution/health {
|
||||
alias /var/lib/guanghu-public-distribution/current/health.json;
|
||||
default_type application/json;
|
||||
add_header Cache-Control "no-store" always;
|
||||
}
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Verify an origin-authorized HoloLake public release, add enterprise signatures, and atomically publish it."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey
|
||||
|
||||
ORIGIN_ID = "HLP-SIGNER-ZERO-POINT-ORIGIN-PUBLIC-0001"
|
||||
ORIGIN_CLASS = "ZERO_POINT_ORIGIN_PUBLIC_SCOPE_SIGNER"
|
||||
ENTERPRISE_ID = "HLP-SIGNER-ENTERPRISE-ZERO-CORE-DISTRIBUTION-0001"
|
||||
ENTERPRISE_CLASS = "ENTERPRISE_ZERO_CORE_DISTRIBUTION_SIGNER"
|
||||
|
||||
|
||||
def sha256(raw: bytes) -> str:
|
||||
return hashlib.sha256(raw).hexdigest()
|
||||
|
||||
|
||||
def load_json(path: Path) -> dict:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def canonical_sha(value: object) -> str:
|
||||
raw = json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8")
|
||||
return sha256(raw)
|
||||
|
||||
|
||||
def refuse_unsafe_tree(root: Path) -> None:
|
||||
if not root.is_dir() or root.is_symlink():
|
||||
raise RuntimeError("stage must be a real directory")
|
||||
for path in root.rglob("*"):
|
||||
mode = path.lstat().st_mode
|
||||
if stat.S_ISLNK(mode) or not (stat.S_ISREG(mode) or stat.S_ISDIR(mode)):
|
||||
raise RuntimeError(f"unsafe staged path: {path}")
|
||||
|
||||
|
||||
def exact_signed_object(stage: Path, authorization: dict, name: str, origin_key: Ed25519PublicKey) -> bytes:
|
||||
record = next((item for item in authorization["signedObjects"] if item.get("name") == name), None)
|
||||
if not record:
|
||||
raise RuntimeError(f"missing origin authorization: {name}")
|
||||
raw = (stage / name).read_bytes()
|
||||
if record.get("sha256") != sha256(raw):
|
||||
raise RuntimeError(f"origin digest mismatch: {name}")
|
||||
origin_key.verify(base64.b64decode(record["signatureBase64"], validate=True), raw)
|
||||
return raw
|
||||
|
||||
|
||||
def signature_bundle(schema: str, plane: str, raw: bytes, origin_signature: str, enterprise_key: Ed25519PrivateKey) -> bytes:
|
||||
value = {
|
||||
"schema": schema,
|
||||
"planeNumber": plane,
|
||||
"contentSha256": sha256(raw),
|
||||
"signatures": [
|
||||
{
|
||||
"signerId": ORIGIN_ID,
|
||||
"signerClass": ORIGIN_CLASS,
|
||||
"algorithm": "Ed25519",
|
||||
"signatureBase64": origin_signature,
|
||||
},
|
||||
{
|
||||
"signerId": ENTERPRISE_ID,
|
||||
"signerClass": ENTERPRISE_CLASS,
|
||||
"algorithm": "Ed25519",
|
||||
"signatureBase64": base64.b64encode(enterprise_key.sign(raw)).decode("ascii"),
|
||||
},
|
||||
],
|
||||
}
|
||||
return (json.dumps(value, ensure_ascii=False, indent=2) + "\n").encode("utf-8")
|
||||
|
||||
|
||||
def validate_release(stage: Path, authorization: dict, origin_public: bytes, enterprise_key: Ed25519PrivateKey) -> tuple[bytes, bytes]:
|
||||
signer = authorization.get("signer", {})
|
||||
if signer != {
|
||||
"signerId": ORIGIN_ID,
|
||||
"signerClass": ORIGIN_CLASS,
|
||||
"algorithm": "Ed25519",
|
||||
"publicKeyBase64": base64.b64encode(origin_public).decode("ascii"),
|
||||
}:
|
||||
raise RuntimeError("origin signer identity mismatch")
|
||||
origin_key = Ed25519PublicKey.from_public_bytes(origin_public)
|
||||
lamp_raw = exact_signed_object(stage, authorization, "zero-core/lamp.json", origin_key)
|
||||
catalog_raw = exact_signed_object(stage, authorization, "marketplace/catalog.json", origin_key)
|
||||
lamp = json.loads(lamp_raw)
|
||||
artifact_raw = (stage / "zero-core/artifact.json").read_bytes()
|
||||
if lamp.get("schema") != "hololake.public-zero-core-lamp/v1" or lamp.get("planeNumber") != "HLP-DIST-PLANE-0001":
|
||||
raise RuntimeError("zero-core lamp identity invalid")
|
||||
if lamp.get("contentRootSha256") != sha256(artifact_raw):
|
||||
raise RuntimeError("zero-core artifact digest mismatch")
|
||||
artifact = json.loads(artifact_raw)
|
||||
if artifact.get("epoch") != lamp.get("epoch") or artifact.get("version") != lamp.get("version"):
|
||||
raise RuntimeError("zero-core artifact identity mismatch")
|
||||
|
||||
catalog = json.loads(catalog_raw)
|
||||
entries = catalog.get("entries")
|
||||
if catalog.get("schema") != "hololake.marketplace.catalog/v1" or catalog.get("planeNumber") != "HLP-DIST-PLANE-0003" or not isinstance(entries, list) or not entries:
|
||||
raise RuntimeError("marketplace catalog identity invalid")
|
||||
if catalog.get("contentRootSha256") != canonical_sha(entries):
|
||||
raise RuntimeError("marketplace content root mismatch")
|
||||
numbers: set[str] = set()
|
||||
for entry in entries:
|
||||
number = entry.get("itemNumber", "")
|
||||
if number in numbers:
|
||||
raise RuntimeError(f"duplicate marketplace item: {number}")
|
||||
numbers.add(number)
|
||||
artifact_path = stage / "marketplace/artifacts" / Path(entry["artifactUrl"]).name
|
||||
if not artifact_path.is_file() or sha256(artifact_path.read_bytes()) != entry.get("artifactSha256"):
|
||||
raise RuntimeError(f"marketplace artifact digest mismatch: {number}")
|
||||
if entry.get("artifactKind") == "PHYSICAL_MODULE":
|
||||
signature_path = stage / "marketplace/artifacts" / Path(entry["signatureUrl"]).name
|
||||
if not signature_path.is_file() or not signature_path.read_bytes():
|
||||
raise RuntimeError(f"physical module signature missing: {number}")
|
||||
elif entry.get("artifactKind") == "COGNITIVE_SKILL":
|
||||
if entry.get("executionAuthority") is not False or entry.get("permissions") != [] or entry.get("signatureUrl") is not None:
|
||||
raise RuntimeError(f"cognitive skill authority boundary invalid: {number}")
|
||||
else:
|
||||
raise RuntimeError(f"unknown marketplace kind: {number}")
|
||||
|
||||
signed = {item["name"]: item["signatureBase64"] for item in authorization["signedObjects"]}
|
||||
lamp_bundle = signature_bundle("hololake.public-zero-core-dual-signature/v1", "HLP-DIST-PLANE-0001", lamp_raw, signed["zero-core/lamp.json"], enterprise_key)
|
||||
catalog_bundle = signature_bundle("hololake.marketplace.catalog-dual-signature/v1", "HLP-DIST-PLANE-0003", catalog_raw, signed["marketplace/catalog.json"], enterprise_key)
|
||||
return lamp_bundle, catalog_bundle
|
||||
|
||||
|
||||
def publish(stage: Path, destination: Path, lamp_bundle: bytes, catalog_bundle: bytes, release_id: str) -> Path:
|
||||
releases = destination / "releases"
|
||||
releases.mkdir(parents=True, exist_ok=True)
|
||||
final = releases / release_id
|
||||
if final.exists():
|
||||
raise RuntimeError(f"release already exists: {release_id}")
|
||||
temporary = Path(tempfile.mkdtemp(prefix=f".{release_id}.", dir=releases))
|
||||
try:
|
||||
# Preserve a raced-in symlink as a symlink so the second tree check rejects
|
||||
# it; never follow it into a path outside the upload stage.
|
||||
shutil.copytree(stage, temporary, dirs_exist_ok=True, symlinks=True)
|
||||
refuse_unsafe_tree(temporary)
|
||||
(temporary / "zero-core/lamp.sig.json").write_bytes(lamp_bundle)
|
||||
(temporary / "marketplace/catalog.sig.json").write_bytes(catalog_bundle)
|
||||
(temporary / "health.json").write_text(json.dumps({"state": "LIVE_DUAL_SIGNED", "releaseId": release_id}, indent=2) + "\n", encoding="utf-8")
|
||||
for path in temporary.rglob("*"):
|
||||
os.chmod(path, 0o755 if path.is_dir() else 0o644)
|
||||
os.replace(temporary, final)
|
||||
link = destination / f".current.{os.getpid()}"
|
||||
os.symlink(f"releases/{release_id}", link)
|
||||
os.replace(link, destination / "current")
|
||||
except Exception:
|
||||
shutil.rmtree(temporary, ignore_errors=True)
|
||||
raise
|
||||
return final
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--stage", type=Path, required=True)
|
||||
parser.add_argument("--destination", type=Path, default=Path("/var/lib/guanghu-public-distribution"))
|
||||
parser.add_argument("--origin-public-key", type=Path, default=Path("/etc/guanghu-public-distribution/origin-ed25519.raw"))
|
||||
parser.add_argument("--enterprise-private-key", type=Path, default=Path("/etc/guanghu-public-distribution/enterprise-ed25519.pem"))
|
||||
args = parser.parse_args()
|
||||
refuse_unsafe_tree(args.stage)
|
||||
authorization = load_json(args.stage / "origin-authorization.json")
|
||||
if authorization.get("schema") != "hololake.origin-release-authorization/v1":
|
||||
raise RuntimeError("origin authorization schema invalid")
|
||||
release_id = authorization.get("releaseId", "")
|
||||
if not release_id or "/" in release_id or ".." in release_id:
|
||||
raise RuntimeError("release id invalid")
|
||||
origin_public = args.origin_public_key.read_bytes()
|
||||
if len(origin_public) != 32:
|
||||
raise RuntimeError("origin public key must be 32 raw bytes")
|
||||
private = serialization.load_pem_private_key(args.enterprise_private_key.read_bytes(), password=None)
|
||||
if not isinstance(private, Ed25519PrivateKey):
|
||||
raise RuntimeError("enterprise release key is not Ed25519")
|
||||
lamp_bundle, catalog_bundle = validate_release(args.stage, authorization, origin_public, private)
|
||||
final = publish(args.stage, args.destination, lamp_bundle, catalog_bundle, release_id)
|
||||
enterprise_public = private.public_key().public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
|
||||
print(json.dumps({"state": "PUBLISHED", "releaseId": release_id, "path": str(final), "enterprisePublicKeyBase64": base64.b64encode(enterprise_public).decode("ascii")}))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -10,6 +10,12 @@ use crate::dynamic_capability_routing::{
|
|||
routing_root as dynamic_routing_root, DynamicNodeRegistry, ResolveCapabilityRouteInput,
|
||||
SignedNodeHealth,
|
||||
};
|
||||
use crate::human_authorization::{
|
||||
consume_at as consume_authorization_at,
|
||||
root_from_session_root as authorization_root_from_session_root,
|
||||
status_for_requester_at as authorization_status_at, submit_at as submit_authorization_at,
|
||||
AuthorizationProposalInput, ConsumeAuthorizationInput, RequesterContext,
|
||||
};
|
||||
use crate::local_development_bridge::{
|
||||
account_key_for as development_account_key_for, acquire_at as acquire_development_lane_at,
|
||||
inspect_at as inspect_development_lane_at, release_at as release_development_lane_at,
|
||||
|
|
@ -203,6 +209,7 @@ struct BrokerStorageRoots {
|
|||
persona_time: PathBuf,
|
||||
persona_license: PathBuf,
|
||||
development: PathBuf,
|
||||
authorization: PathBuf,
|
||||
}
|
||||
|
||||
impl Drop for DirectLocalBrokerHandle {
|
||||
|
|
@ -245,6 +252,9 @@ enum BrokerRequest {
|
|||
AcquireDevelopmentWriteLane(AuthenticatedDevelopmentLaneInput),
|
||||
InspectDevelopmentWriteLane(AuthenticatedDevelopmentInspectInput),
|
||||
ReleaseDevelopmentWriteLane(AuthenticatedDevelopmentReleaseInput),
|
||||
SubmitHumanAuthorizationRequest(AuthenticatedAuthorizationProposalInput),
|
||||
GetHumanAuthorizationStatus(AuthenticatedWorkEnvironmentInput),
|
||||
ConsumeHumanAuthorizationTicket(AuthenticatedAuthorizationConsumeInput),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
|
|
@ -350,6 +360,20 @@ struct AuthenticatedWorkEnvironmentInput {
|
|||
session: AuthenticateSessionInput,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
struct AuthenticatedAuthorizationProposalInput {
|
||||
session: AuthenticateSessionInput,
|
||||
proposal: AuthorizationProposalInput,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
struct AuthenticatedAuthorizationConsumeInput {
|
||||
session: AuthenticateSessionInput,
|
||||
ticket: ConsumeAuthorizationInput,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
struct AuthenticatedPersonaCarrierLicenseInput {
|
||||
|
|
@ -444,7 +468,7 @@ fn load_number_registry() -> Result<BrokerNumberRegistry, String> {
|
|||
|| registry.runtime.unknown_or_mismatched_coordinate != "FAIL_CLOSED"
|
||||
|| !registry.runtime.request_nonce_required
|
||||
|| registry.runtime.transport_is_authority
|
||||
|| registry.operations.len() != 22
|
||||
|| registry.operations.len() != 25
|
||||
{
|
||||
return Err("HOLOLAKE_NUMBERED_BROKER_REGISTRY_BOUNDARY_INVALID".into());
|
||||
}
|
||||
|
|
@ -843,6 +867,7 @@ fn start_at(
|
|||
let development_root = development_root_from_session_root(&session_root)?;
|
||||
fs::create_dir_all(&development_root)
|
||||
.map_err(|error| format!("HOLOLAKE_BRIDGE_STORAGE_UNAVAILABLE: {error}"))?;
|
||||
let authorization_root = authorization_root_from_session_root(&session_root)?;
|
||||
#[cfg(unix)]
|
||||
if let Some(parent) = socket_path.parent() {
|
||||
fs::create_dir_all(parent)
|
||||
|
|
@ -903,6 +928,7 @@ fn start_at(
|
|||
persona_time: persona_time_root,
|
||||
persona_license: persona_license_root,
|
||||
development: development_root,
|
||||
authorization: authorization_root,
|
||||
},
|
||||
&worker_shutdown,
|
||||
&worker_authenticated_connections,
|
||||
|
|
@ -1025,6 +1051,7 @@ fn dispatch(roots: &BrokerStorageRoots, bytes: &[u8]) -> BrokerResponse {
|
|||
let persona_time_root = &roots.persona_time;
|
||||
let persona_license_root = &roots.persona_license;
|
||||
let development_root = &roots.development;
|
||||
let authorization_root = &roots.authorization;
|
||||
let (request, _, _) = match decode_numbered_broker_request(bytes) {
|
||||
Ok(request) => request,
|
||||
Err(error) => {
|
||||
|
|
@ -1295,6 +1322,72 @@ fn dispatch(roots: &BrokerStorageRoots, bytes: &[u8]) -> BrokerResponse {
|
|||
serde_json::to_value(receipt).map_err(|error| error.to_string())
|
||||
})
|
||||
}
|
||||
BrokerRequest::SubmitHumanAuthorizationRequest(input) => {
|
||||
authenticate_context_at(session_root, &input.session)
|
||||
.and_then(|context| {
|
||||
require_persona_mode_operation_at(
|
||||
persona_license_root,
|
||||
&context.account_key,
|
||||
&context.session_id,
|
||||
&context.client_instance_id,
|
||||
"SUBMIT_HUMAN_AUTHORIZATION_REQUEST",
|
||||
now_unix_ms()?,
|
||||
)?;
|
||||
submit_authorization_at(
|
||||
authorization_root,
|
||||
&RequesterContext {
|
||||
account_key: context.account_key,
|
||||
session_id: context.session_id,
|
||||
client_instance_id: context.client_instance_id,
|
||||
},
|
||||
input.proposal,
|
||||
)
|
||||
})
|
||||
.and_then(|receipt| {
|
||||
serde_json::to_value(receipt).map_err(|error| error.to_string())
|
||||
})
|
||||
}
|
||||
BrokerRequest::GetHumanAuthorizationStatus(input) => {
|
||||
authenticate_context_at(session_root, &input.session)
|
||||
.and_then(|context| {
|
||||
authorization_status_at(
|
||||
authorization_root,
|
||||
&RequesterContext {
|
||||
account_key: context.account_key,
|
||||
session_id: context.session_id,
|
||||
client_instance_id: context.client_instance_id,
|
||||
},
|
||||
)
|
||||
})
|
||||
.and_then(|snapshot| {
|
||||
serde_json::to_value(snapshot).map_err(|error| error.to_string())
|
||||
})
|
||||
}
|
||||
BrokerRequest::ConsumeHumanAuthorizationTicket(input) => {
|
||||
authenticate_context_at(session_root, &input.session)
|
||||
.and_then(|context| {
|
||||
require_persona_mode_operation_at(
|
||||
persona_license_root,
|
||||
&context.account_key,
|
||||
&context.session_id,
|
||||
&context.client_instance_id,
|
||||
"CONSUME_HUMAN_AUTHORIZATION_TICKET",
|
||||
now_unix_ms()?,
|
||||
)?;
|
||||
consume_authorization_at(
|
||||
authorization_root,
|
||||
&RequesterContext {
|
||||
account_key: context.account_key,
|
||||
session_id: context.session_id,
|
||||
client_instance_id: context.client_instance_id,
|
||||
},
|
||||
input.ticket,
|
||||
)
|
||||
})
|
||||
.and_then(|receipt| {
|
||||
serde_json::to_value(receipt).map_err(|error| error.to_string())
|
||||
})
|
||||
}
|
||||
};
|
||||
match result {
|
||||
Ok(value) => BrokerResponse::success(value),
|
||||
|
|
|
|||
|
|
@ -120,6 +120,18 @@ pub struct SessionEventReceipt {
|
|||
pub receipt_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DirectSessionProjection {
|
||||
pub session_id: String,
|
||||
pub lane_id: String,
|
||||
pub client_instance_id: String,
|
||||
pub state: &'static str,
|
||||
pub opened_at_unix_ms: u128,
|
||||
pub observed_at_unix_ms: u128,
|
||||
pub last_event_sequence: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct SessionRecord {
|
||||
|
|
@ -262,6 +274,52 @@ pub(crate) fn active_session_count_at(root: &Path) -> Result<usize, String> {
|
|||
Ok(count)
|
||||
}
|
||||
|
||||
pub(crate) fn active_session_projections_at(
|
||||
root: &Path,
|
||||
) -> Result<Vec<DirectSessionProjection>, String> {
|
||||
let accounts = root.join("accounts");
|
||||
if !accounts.exists() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let now = now_unix_ms()?;
|
||||
let mut projections = Vec::new();
|
||||
for entry in fs::read_dir(&accounts)
|
||||
.map_err(|error| format!("HOLOLAKE_DIRECT_SESSION_STORAGE_UNAVAILABLE: {error}"))?
|
||||
{
|
||||
let entry = entry
|
||||
.map_err(|error| format!("HOLOLAKE_DIRECT_SESSION_STORAGE_UNAVAILABLE: {error}"))?;
|
||||
if !entry
|
||||
.file_type()
|
||||
.map_err(|error| format!("HOLOLAKE_DIRECT_SESSION_STORAGE_UNAVAILABLE: {error}"))?
|
||||
.is_dir()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let active_path = entry.path().join("active-session.json");
|
||||
if !active_path.exists() {
|
||||
continue;
|
||||
}
|
||||
let active: ActiveSessionRecord = read_json(&active_path, "ACTIVE_SESSION")?;
|
||||
let record = read_session(&session_path(root, &active.account_key, &active.session_id))?;
|
||||
let state = if now.saturating_sub(record.observed_at_unix_ms) <= 45_000 {
|
||||
"LIVE"
|
||||
} else {
|
||||
"RESUMABLE"
|
||||
};
|
||||
projections.push(DirectSessionProjection {
|
||||
session_id: record.session_id,
|
||||
lane_id: record.lane_id,
|
||||
client_instance_id: record.client_instance_id,
|
||||
state,
|
||||
opened_at_unix_ms: record.opened_at_unix_ms,
|
||||
observed_at_unix_ms: record.observed_at_unix_ms,
|
||||
last_event_sequence: record.last_event_sequence,
|
||||
});
|
||||
}
|
||||
projections.sort_by(|left, right| right.observed_at_unix_ms.cmp(&left.observed_at_unix_ms));
|
||||
Ok(projections)
|
||||
}
|
||||
|
||||
pub(crate) fn open_at(
|
||||
root: &Path,
|
||||
input: OpenSessionInput,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use crate::direct_local_broker::DirectLocalBrokerState;
|
||||
use crate::direct_local_session::{active_session_count_at, direct_session_root};
|
||||
use crate::direct_local_session::{
|
||||
active_session_count_at, active_session_projections_at, direct_session_root,
|
||||
DirectSessionProjection,
|
||||
};
|
||||
use crate::pncc_receipt_projection::{pncc_projection_root, projection_event_count_at};
|
||||
use crate::pncc_repository_binding::{mounted_repository_count_at, pncc_repository_mount_root};
|
||||
use crate::release_trust::release_trust_state;
|
||||
|
|
@ -15,6 +18,7 @@ pub struct HoloLakeHomeStatus {
|
|||
pub direct_local_broker_state: &'static str,
|
||||
pub direct_connection_count: usize,
|
||||
pub resumable_session_count: usize,
|
||||
pub direct_sessions: Vec<DirectSessionProjection>,
|
||||
pub code_repository_mount_count: usize,
|
||||
pub pncc_receipt_count: usize,
|
||||
pub update_state: &'static str,
|
||||
|
|
@ -37,6 +41,7 @@ pub fn get_hololake_home_status(
|
|||
direct_local_broker_state: "WAITING_FOR_LOGIN",
|
||||
direct_connection_count: 0,
|
||||
resumable_session_count: 0,
|
||||
direct_sessions: Vec::new(),
|
||||
code_repository_mount_count: 0,
|
||||
pncc_receipt_count: 0,
|
||||
update_state: release_trust_state()?,
|
||||
|
|
@ -60,6 +65,7 @@ pub fn get_hololake_home_status(
|
|||
direct_local_broker_state: "READY",
|
||||
direct_connection_count: broker.active_connection_count(),
|
||||
resumable_session_count: active_session_count_at(&session_root)?,
|
||||
direct_sessions: active_session_projections_at(&session_root)?,
|
||||
code_repository_mount_count: mounted_repository_count_at(&mount_root)?,
|
||||
pncc_receipt_count: projection_event_count_at(&projection_root)?,
|
||||
update_state: release_trust_state()?,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,704 @@
|
|||
//! Human-in-the-loop authorization for numbered lifecycle work.
|
||||
//!
|
||||
//! Persona carriers may propose an exact action from an authenticated local session.
|
||||
//! Only the verified human route may approve it. Approval creates a short-lived,
|
||||
//! session-bound ticket that is consumed once and leaves a hash-chained receipt.
|
||||
|
||||
use ring::digest::{digest, SHA256};
|
||||
use rusqlite::{params, Connection, OptionalExtension};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use tauri::AppHandle;
|
||||
use uuid::Uuid;
|
||||
|
||||
const SCHEMA: &str = "hololake.human-authorization/v1";
|
||||
const CONTRACT: &str = include_str!("../../contracts/human-authorization.json");
|
||||
const REQUEST_TTL_MS: u64 = 24 * 60 * 60 * 1_000;
|
||||
const TICKET_TTL_MS: u64 = 15 * 60 * 1_000;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct AuthorizationProposalInput {
|
||||
pub idempotency_key: String,
|
||||
pub action: String,
|
||||
pub target_number: String,
|
||||
pub target_kind: String,
|
||||
pub target_label: String,
|
||||
pub reason: String,
|
||||
pub impact: String,
|
||||
pub rollback_plan: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct DecideAuthorizationInput {
|
||||
pub request_id: String,
|
||||
pub decision: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct ConsumeAuthorizationInput {
|
||||
pub request_id: String,
|
||||
pub ticket_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AuthorizationRequestView {
|
||||
pub request_id: String,
|
||||
pub action: String,
|
||||
pub target_number: String,
|
||||
pub target_kind: String,
|
||||
pub target_label: String,
|
||||
pub reason: String,
|
||||
pub impact: String,
|
||||
pub rollback_plan: String,
|
||||
pub requester_label: String,
|
||||
pub state: String,
|
||||
pub created_at_unix_ms: u64,
|
||||
pub expires_at_unix_ms: u64,
|
||||
pub human_number: Option<String>,
|
||||
pub decided_at_unix_ms: Option<u64>,
|
||||
pub ticket_id: Option<String>,
|
||||
pub ticket_expires_at_unix_ms: Option<u64>,
|
||||
pub consumed_at_unix_ms: Option<u64>,
|
||||
pub receipt_hash: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AuthorizationCenterSnapshot {
|
||||
pub schema: &'static str,
|
||||
pub state: &'static str,
|
||||
pub pending_count: usize,
|
||||
pub requests: Vec<AuthorizationRequestView>,
|
||||
pub supported_actions: Vec<&'static str>,
|
||||
pub purge_enabled: bool,
|
||||
pub authority: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AuthorizationTicketReceipt {
|
||||
pub schema: &'static str,
|
||||
pub state: &'static str,
|
||||
pub request_id: String,
|
||||
pub ticket_id: String,
|
||||
pub action: String,
|
||||
pub target_number: String,
|
||||
pub scope_sha256: String,
|
||||
pub consumed_at_unix_ms: u64,
|
||||
pub receipt_hash: String,
|
||||
pub next_operation: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RequesterContext {
|
||||
pub account_key: String,
|
||||
pub session_id: String,
|
||||
pub client_instance_id: String,
|
||||
}
|
||||
|
||||
pub fn root_for_app(app: &AppHandle) -> Result<PathBuf, String> {
|
||||
crate::authenticated_storage::account_storage_root(app, "human-authorization-v1")
|
||||
}
|
||||
|
||||
pub fn start_on_application_open() -> Result<(), String> {
|
||||
let contract: serde_json::Value = serde_json::from_str(CONTRACT)
|
||||
.map_err(|error| format!("HOLOLAKE_AUTHORIZATION_CONTRACT_INVALID: {error}"))?;
|
||||
if contract.get("schema").and_then(serde_json::Value::as_str)
|
||||
!= Some("hololake.human-authorization-contract/v1")
|
||||
|| contract
|
||||
.get("record_id")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
!= Some("HLP-HUMAN-AUTHORIZATION-001")
|
||||
|| contract
|
||||
.pointer("/ticket/ttl_ms")
|
||||
.and_then(serde_json::Value::as_u64)
|
||||
!= Some(TICKET_TTL_MS)
|
||||
|| contract
|
||||
.pointer("/request/ttl_ms")
|
||||
.and_then(serde_json::Value::as_u64)
|
||||
!= Some(REQUEST_TTL_MS)
|
||||
|| contract
|
||||
.pointer("/destructive_purge/enabled")
|
||||
.and_then(serde_json::Value::as_bool)
|
||||
!= Some(false)
|
||||
|| contract
|
||||
.pointer("/ticket/single_use")
|
||||
.and_then(serde_json::Value::as_bool)
|
||||
!= Some(true)
|
||||
{
|
||||
return Err("HOLOLAKE_AUTHORIZATION_CONTRACT_BOUNDARY_INVALID".into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn root_from_session_root(session_root: &Path) -> Result<PathBuf, String> {
|
||||
let account_root = session_root
|
||||
.parent()
|
||||
.ok_or("HOLOLAKE_AUTHORIZATION_STORAGE_BOUNDARY_INVALID")?;
|
||||
let root = account_root.join("human-authorization-v1");
|
||||
fs::create_dir_all(&root)
|
||||
.map_err(|error| format!("HOLOLAKE_AUTHORIZATION_STORAGE_UNAVAILABLE: {error}"))?;
|
||||
Ok(root)
|
||||
}
|
||||
|
||||
pub fn get_center(app: &AppHandle) -> Result<AuthorizationCenterSnapshot, String> {
|
||||
snapshot_at(&root_for_app(app)?, None)
|
||||
}
|
||||
|
||||
pub fn decide(
|
||||
app: &AppHandle,
|
||||
input: DecideAuthorizationInput,
|
||||
human_number: &str,
|
||||
) -> Result<AuthorizationRequestView, String> {
|
||||
decide_at(&root_for_app(app)?, input, human_number, now_ms()?)
|
||||
}
|
||||
|
||||
pub fn submit_at(
|
||||
root: &Path,
|
||||
requester: &RequesterContext,
|
||||
input: AuthorizationProposalInput,
|
||||
) -> Result<AuthorizationRequestView, String> {
|
||||
validate_proposal(&input)?;
|
||||
let now = now_ms()?;
|
||||
let mut connection = open_db(root)?;
|
||||
let transaction = connection.transaction().map_err(db_error("TRANSACTION"))?;
|
||||
if let Some(existing) = transaction
|
||||
.query_row(
|
||||
"SELECT request_id FROM authorization_requests WHERE requester_account_key=?1 AND idempotency_key=?2",
|
||||
params![requester.account_key, input.idempotency_key],
|
||||
|row| row.get::<_, String>(0),
|
||||
)
|
||||
.optional()
|
||||
.map_err(db_error("READ"))?
|
||||
{
|
||||
let existing = read_request_with_owner(&transaction, &existing)?;
|
||||
if existing.requester_session_id != requester.session_id
|
||||
|| existing.requester_client_instance_id != requester.client_instance_id
|
||||
|| existing.view.action != input.action
|
||||
|| existing.view.target_number != input.target_number
|
||||
|| existing.view.target_kind != input.target_kind
|
||||
|| existing.view.target_label != input.target_label
|
||||
|| existing.view.reason != input.reason
|
||||
|| existing.view.impact != input.impact
|
||||
|| existing.view.rollback_plan != input.rollback_plan
|
||||
{
|
||||
return Err("HOLOLAKE_AUTHORIZATION_IDEMPOTENCY_CONFLICT".into());
|
||||
}
|
||||
let view = existing.view;
|
||||
transaction.commit().map_err(db_error("COMMIT"))?;
|
||||
return Ok(view);
|
||||
}
|
||||
let request_id = format!("HLP-AUTH-REQ-{}", Uuid::new_v4().simple());
|
||||
let expires = now.saturating_add(REQUEST_TTL_MS);
|
||||
transaction.execute(
|
||||
"INSERT INTO authorization_requests(
|
||||
request_id,idempotency_key,requester_account_key,requester_session_id,requester_client_instance_id,
|
||||
action,target_number,target_kind,target_label,reason,impact,rollback_plan,state,
|
||||
created_at_unix_ms,expires_at_unix_ms,receipt_hash
|
||||
) VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,'PENDING_HUMAN',?13,?14,'GENESIS')",
|
||||
params![
|
||||
request_id, input.idempotency_key, requester.account_key, requester.session_id,
|
||||
requester.client_instance_id, input.action, input.target_number, input.target_kind,
|
||||
input.target_label, input.reason, input.impact, input.rollback_plan, now, expires
|
||||
],
|
||||
).map_err(db_error("INSERT"))?;
|
||||
let receipt = append_receipt(&transaction, &request_id, "SUBMITTED", "PENDING_HUMAN", now)?;
|
||||
transaction
|
||||
.execute(
|
||||
"UPDATE authorization_requests SET receipt_hash=?2 WHERE request_id=?1",
|
||||
params![request_id, receipt],
|
||||
)
|
||||
.map_err(db_error("UPDATE"))?;
|
||||
let view = read_request(&transaction, &request_id)?;
|
||||
transaction.commit().map_err(db_error("COMMIT"))?;
|
||||
Ok(view)
|
||||
}
|
||||
|
||||
pub fn status_for_requester_at(
|
||||
root: &Path,
|
||||
requester: &RequesterContext,
|
||||
) -> Result<AuthorizationCenterSnapshot, String> {
|
||||
snapshot_at(root, Some(requester))
|
||||
}
|
||||
|
||||
pub fn decide_at(
|
||||
root: &Path,
|
||||
input: DecideAuthorizationInput,
|
||||
human_number: &str,
|
||||
now: u64,
|
||||
) -> Result<AuthorizationRequestView, String> {
|
||||
validate_id(&input.request_id, "REQUEST")?;
|
||||
if !matches!(input.decision.as_str(), "APPROVE" | "DENY") {
|
||||
return Err("HOLOLAKE_AUTHORIZATION_DECISION_INVALID".into());
|
||||
}
|
||||
validate_id(human_number, "HUMAN_NUMBER")?;
|
||||
let mut connection = open_db(root)?;
|
||||
let transaction = connection.transaction().map_err(db_error("TRANSACTION"))?;
|
||||
expire_pending(&transaction, now)?;
|
||||
let current = read_request(&transaction, &input.request_id)?;
|
||||
if current.state != "PENDING_HUMAN" {
|
||||
let same = (input.decision == "APPROVE" && current.state == "APPROVED")
|
||||
|| (input.decision == "DENY" && current.state == "DENIED");
|
||||
if same && current.human_number.as_deref() == Some(human_number) {
|
||||
transaction.commit().map_err(db_error("COMMIT"))?;
|
||||
return Ok(current);
|
||||
}
|
||||
return Err("HOLOLAKE_AUTHORIZATION_REQUEST_NOT_PENDING".into());
|
||||
}
|
||||
let (state, ticket_id, ticket_expires) = if input.decision == "APPROVE" {
|
||||
(
|
||||
"APPROVED",
|
||||
Some(format!("HLP-AUTH-TICKET-{}", Uuid::new_v4().simple())),
|
||||
Some(now.saturating_add(TICKET_TTL_MS)),
|
||||
)
|
||||
} else {
|
||||
("DENIED", None, None)
|
||||
};
|
||||
transaction
|
||||
.execute(
|
||||
"UPDATE authorization_requests SET state=?2,human_number=?3,decided_at_unix_ms=?4,
|
||||
ticket_id=?5,ticket_expires_at_unix_ms=?6 WHERE request_id=?1 AND state='PENDING_HUMAN'",
|
||||
params![
|
||||
input.request_id,
|
||||
state,
|
||||
human_number,
|
||||
now,
|
||||
ticket_id,
|
||||
ticket_expires
|
||||
],
|
||||
)
|
||||
.map_err(db_error("UPDATE"))?;
|
||||
let receipt = append_receipt(&transaction, &input.request_id, &input.decision, state, now)?;
|
||||
transaction
|
||||
.execute(
|
||||
"UPDATE authorization_requests SET receipt_hash=?2 WHERE request_id=?1",
|
||||
params![input.request_id, receipt],
|
||||
)
|
||||
.map_err(db_error("UPDATE"))?;
|
||||
let view = read_request(&transaction, &input.request_id)?;
|
||||
transaction.commit().map_err(db_error("COMMIT"))?;
|
||||
Ok(view)
|
||||
}
|
||||
|
||||
pub fn consume_at(
|
||||
root: &Path,
|
||||
requester: &RequesterContext,
|
||||
input: ConsumeAuthorizationInput,
|
||||
) -> Result<AuthorizationTicketReceipt, String> {
|
||||
validate_id(&input.request_id, "REQUEST")?;
|
||||
validate_id(&input.ticket_id, "TICKET")?;
|
||||
let now = now_ms()?;
|
||||
let mut connection = open_db(root)?;
|
||||
let transaction = connection.transaction().map_err(db_error("TRANSACTION"))?;
|
||||
let request = read_request_with_owner(&transaction, &input.request_id)?;
|
||||
if request.requester_account_key != requester.account_key
|
||||
|| request.requester_session_id != requester.session_id
|
||||
|| request.requester_client_instance_id != requester.client_instance_id
|
||||
{
|
||||
return Err("HOLOLAKE_AUTHORIZATION_TICKET_SESSION_MISMATCH".into());
|
||||
}
|
||||
if request.view.state == "CONSUMED" {
|
||||
return Err("HOLOLAKE_AUTHORIZATION_TICKET_REPLAYED".into());
|
||||
}
|
||||
if request.view.state != "APPROVED"
|
||||
|| request.view.ticket_id.as_deref() != Some(input.ticket_id.as_str())
|
||||
{
|
||||
return Err("HOLOLAKE_AUTHORIZATION_TICKET_NOT_APPROVED".into());
|
||||
}
|
||||
if request.view.ticket_expires_at_unix_ms.unwrap_or(0) < now {
|
||||
transaction.execute(
|
||||
"UPDATE authorization_requests SET state='EXPIRED' WHERE request_id=?1 AND state='APPROVED'",
|
||||
params![input.request_id],
|
||||
).map_err(db_error("UPDATE"))?;
|
||||
let receipt = append_receipt(
|
||||
&transaction,
|
||||
&input.request_id,
|
||||
"TICKET_EXPIRED",
|
||||
"EXPIRED",
|
||||
now,
|
||||
)?;
|
||||
transaction
|
||||
.execute(
|
||||
"UPDATE authorization_requests SET receipt_hash=?2 WHERE request_id=?1",
|
||||
params![input.request_id, receipt],
|
||||
)
|
||||
.map_err(db_error("UPDATE"))?;
|
||||
transaction.commit().map_err(db_error("COMMIT"))?;
|
||||
return Err("HOLOLAKE_AUTHORIZATION_TICKET_EXPIRED".into());
|
||||
}
|
||||
let scope_sha256 = sha256_hex(
|
||||
format!(
|
||||
"{}\n{}\n{}\n{}\n{}",
|
||||
input.request_id,
|
||||
input.ticket_id,
|
||||
request.view.action,
|
||||
request.view.target_number,
|
||||
requester.session_id
|
||||
)
|
||||
.as_bytes(),
|
||||
);
|
||||
transaction.execute(
|
||||
"UPDATE authorization_requests SET state='CONSUMED',consumed_at_unix_ms=?2 WHERE request_id=?1 AND state='APPROVED'",
|
||||
params![input.request_id, now],
|
||||
).map_err(db_error("UPDATE"))?;
|
||||
let receipt_hash =
|
||||
append_receipt(&transaction, &input.request_id, "CONSUMED", "CONSUMED", now)?;
|
||||
transaction
|
||||
.execute(
|
||||
"UPDATE authorization_requests SET receipt_hash=?2 WHERE request_id=?1",
|
||||
params![input.request_id, receipt_hash],
|
||||
)
|
||||
.map_err(db_error("UPDATE"))?;
|
||||
transaction.commit().map_err(db_error("COMMIT"))?;
|
||||
Ok(AuthorizationTicketReceipt {
|
||||
schema: SCHEMA,
|
||||
state: "CONSUMED_ONCE",
|
||||
request_id: input.request_id,
|
||||
ticket_id: input.ticket_id,
|
||||
action: request.view.action,
|
||||
target_number: request.view.target_number,
|
||||
scope_sha256,
|
||||
consumed_at_unix_ms: now,
|
||||
receipt_hash,
|
||||
next_operation: "EXECUTE_ONLY_THE_EXACT_AUTHORIZED_ACTION_AND_APPEND_REALITY_RECEIPT",
|
||||
})
|
||||
}
|
||||
|
||||
fn snapshot_at(
|
||||
root: &Path,
|
||||
requester: Option<&RequesterContext>,
|
||||
) -> Result<AuthorizationCenterSnapshot, String> {
|
||||
let connection = open_db(root)?;
|
||||
expire_pending(&connection, now_ms()?)?;
|
||||
let (sql, args): (&str, Vec<&dyn rusqlite::ToSql>) = match requester {
|
||||
Some(value) => (
|
||||
"SELECT request_id FROM authorization_requests WHERE requester_account_key=?1 AND requester_session_id=?2 AND requester_client_instance_id=?3 ORDER BY created_at_unix_ms DESC LIMIT 100",
|
||||
vec![&value.account_key, &value.session_id, &value.client_instance_id],
|
||||
),
|
||||
None => ("SELECT request_id FROM authorization_requests ORDER BY created_at_unix_ms DESC LIMIT 100", vec![]),
|
||||
};
|
||||
let mut statement = connection.prepare(sql).map_err(db_error("READ"))?;
|
||||
let ids = statement
|
||||
.query_map(args.as_slice(), |row| row.get::<_, String>(0))
|
||||
.map_err(db_error("READ"))?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(db_error("READ"))?;
|
||||
drop(statement);
|
||||
let requests = ids
|
||||
.iter()
|
||||
.map(|id| read_request(&connection, id))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let pending_count = requests
|
||||
.iter()
|
||||
.filter(|item| item.state == "PENDING_HUMAN")
|
||||
.count();
|
||||
Ok(AuthorizationCenterSnapshot {
|
||||
schema: SCHEMA,
|
||||
state: "ACTIVE_FAIL_CLOSED",
|
||||
pending_count,
|
||||
requests,
|
||||
supported_actions: vec!["OPEN_MAINTENANCE", "UNMOUNT", "PROMOTE_VERSION", "RETIRE"],
|
||||
purge_enabled: false,
|
||||
authority: "PERSONA_PROPOSES_HUMAN_DECIDES_SYSTEM_ISSUES_SESSION_BOUND_SINGLE_USE_TICKET",
|
||||
})
|
||||
}
|
||||
|
||||
struct OwnedRequest {
|
||||
view: AuthorizationRequestView,
|
||||
requester_account_key: String,
|
||||
requester_session_id: String,
|
||||
requester_client_instance_id: String,
|
||||
}
|
||||
|
||||
fn read_request(
|
||||
connection: &Connection,
|
||||
request_id: &str,
|
||||
) -> Result<AuthorizationRequestView, String> {
|
||||
Ok(read_request_with_owner(connection, request_id)?.view)
|
||||
}
|
||||
|
||||
fn read_request_with_owner(
|
||||
connection: &Connection,
|
||||
request_id: &str,
|
||||
) -> Result<OwnedRequest, String> {
|
||||
connection.query_row(
|
||||
"SELECT request_id,action,target_number,target_kind,target_label,reason,impact,rollback_plan,
|
||||
requester_client_instance_id,state,created_at_unix_ms,expires_at_unix_ms,human_number,
|
||||
decided_at_unix_ms,ticket_id,ticket_expires_at_unix_ms,consumed_at_unix_ms,receipt_hash,
|
||||
requester_account_key,requester_session_id FROM authorization_requests WHERE request_id=?1",
|
||||
params![request_id],
|
||||
|row| Ok(OwnedRequest {
|
||||
view: AuthorizationRequestView {
|
||||
request_id: row.get(0)?, action: row.get(1)?, target_number: row.get(2)?,
|
||||
target_kind: row.get(3)?, target_label: row.get(4)?, reason: row.get(5)?,
|
||||
impact: row.get(6)?, rollback_plan: row.get(7)?, requester_label: row.get(8)?,
|
||||
state: row.get(9)?, created_at_unix_ms: row.get(10)?, expires_at_unix_ms: row.get(11)?,
|
||||
human_number: row.get(12)?, decided_at_unix_ms: row.get(13)?, ticket_id: row.get(14)?,
|
||||
ticket_expires_at_unix_ms: row.get(15)?, consumed_at_unix_ms: row.get(16)?, receipt_hash: row.get(17)?,
|
||||
},
|
||||
requester_account_key: row.get(18)?, requester_session_id: row.get(19)?,
|
||||
requester_client_instance_id: row.get(8)?,
|
||||
}),
|
||||
).optional().map_err(db_error("READ"))?
|
||||
.ok_or_else(|| "HOLOLAKE_AUTHORIZATION_REQUEST_UNKNOWN".into())
|
||||
}
|
||||
|
||||
fn open_db(root: &Path) -> Result<Connection, String> {
|
||||
fs::create_dir_all(root)
|
||||
.map_err(|error| format!("HOLOLAKE_AUTHORIZATION_STORAGE_UNAVAILABLE: {error}"))?;
|
||||
let connection =
|
||||
Connection::open(root.join("human-authorization.sqlite3")).map_err(db_error("OPEN"))?;
|
||||
connection
|
||||
.busy_timeout(std::time::Duration::from_secs(5))
|
||||
.map_err(db_error("TIMEOUT"))?;
|
||||
connection.execute_batch(
|
||||
"PRAGMA journal_mode=WAL;
|
||||
CREATE TABLE IF NOT EXISTS authorization_requests(
|
||||
request_id TEXT PRIMARY KEY,idempotency_key TEXT NOT NULL,requester_account_key TEXT NOT NULL,
|
||||
requester_session_id TEXT NOT NULL,requester_client_instance_id TEXT NOT NULL,action TEXT NOT NULL,
|
||||
target_number TEXT NOT NULL,target_kind TEXT NOT NULL,target_label TEXT NOT NULL,reason TEXT NOT NULL,
|
||||
impact TEXT NOT NULL,rollback_plan TEXT NOT NULL,state TEXT NOT NULL,created_at_unix_ms INTEGER NOT NULL,
|
||||
expires_at_unix_ms INTEGER NOT NULL,human_number TEXT,decided_at_unix_ms INTEGER,ticket_id TEXT UNIQUE,
|
||||
ticket_expires_at_unix_ms INTEGER,consumed_at_unix_ms INTEGER,receipt_hash TEXT NOT NULL,
|
||||
UNIQUE(requester_account_key,idempotency_key)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS authorization_receipts(
|
||||
sequence INTEGER PRIMARY KEY AUTOINCREMENT,request_id TEXT NOT NULL,event TEXT NOT NULL,state TEXT NOT NULL,
|
||||
observed_at_unix_ms INTEGER NOT NULL,previous_receipt_hash TEXT NOT NULL,receipt_hash TEXT NOT NULL UNIQUE
|
||||
);"
|
||||
).map_err(db_error("SCHEMA"))?;
|
||||
Ok(connection)
|
||||
}
|
||||
|
||||
fn expire_pending(connection: &Connection, now: u64) -> Result<(), String> {
|
||||
let mut statement = connection
|
||||
.prepare("SELECT request_id FROM authorization_requests WHERE state='PENDING_HUMAN' AND expires_at_unix_ms < ?1")
|
||||
.map_err(db_error("EXPIRE_READ"))?;
|
||||
let request_ids = statement
|
||||
.query_map(params![now], |row| row.get::<_, String>(0))
|
||||
.map_err(db_error("EXPIRE_READ"))?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(db_error("EXPIRE_READ"))?;
|
||||
drop(statement);
|
||||
for request_id in request_ids {
|
||||
connection
|
||||
.execute(
|
||||
"UPDATE authorization_requests SET state='EXPIRED' WHERE request_id=?1 AND state='PENDING_HUMAN'",
|
||||
params![request_id],
|
||||
)
|
||||
.map_err(db_error("EXPIRE"))?;
|
||||
let receipt = append_receipt(connection, &request_id, "REQUEST_EXPIRED", "EXPIRED", now)?;
|
||||
connection
|
||||
.execute(
|
||||
"UPDATE authorization_requests SET receipt_hash=?2 WHERE request_id=?1",
|
||||
params![request_id, receipt],
|
||||
)
|
||||
.map_err(db_error("EXPIRE"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn append_receipt(
|
||||
connection: &Connection,
|
||||
request_id: &str,
|
||||
event: &str,
|
||||
state: &str,
|
||||
now: u64,
|
||||
) -> Result<String, String> {
|
||||
let previous = connection
|
||||
.query_row(
|
||||
"SELECT receipt_hash FROM authorization_receipts ORDER BY sequence DESC LIMIT 1",
|
||||
[],
|
||||
|row| row.get::<_, String>(0),
|
||||
)
|
||||
.optional()
|
||||
.map_err(db_error("RECEIPT_READ"))?
|
||||
.unwrap_or_else(|| "GENESIS".into());
|
||||
let receipt_hash = sha256_hex(
|
||||
format!("HLP-AUTH-RECEIPT-v1\n{previous}\n{request_id}\n{event}\n{state}\n{now}")
|
||||
.as_bytes(),
|
||||
);
|
||||
connection.execute(
|
||||
"INSERT INTO authorization_receipts(request_id,event,state,observed_at_unix_ms,previous_receipt_hash,receipt_hash) VALUES(?1,?2,?3,?4,?5,?6)",
|
||||
params![request_id, event, state, now, previous, receipt_hash],
|
||||
).map_err(db_error("RECEIPT_WRITE"))?;
|
||||
Ok(receipt_hash)
|
||||
}
|
||||
|
||||
fn validate_proposal(input: &AuthorizationProposalInput) -> Result<(), String> {
|
||||
validate_id(&input.idempotency_key, "IDEMPOTENCY")?;
|
||||
if !matches!(
|
||||
input.action.as_str(),
|
||||
"OPEN_MAINTENANCE" | "UNMOUNT" | "PROMOTE_VERSION" | "RETIRE"
|
||||
) {
|
||||
return Err("HOLOLAKE_AUTHORIZATION_ACTION_UNSUPPORTED".into());
|
||||
}
|
||||
validate_id(&input.target_number, "TARGET_NUMBER")?;
|
||||
validate_id(&input.target_kind, "TARGET_KIND")?;
|
||||
if input.target_kind != "MODULE" || !input.target_number.starts_with("HLP-MOD-") {
|
||||
return Err("HOLOLAKE_AUTHORIZATION_TARGET_NOT_A_MODULE".into());
|
||||
}
|
||||
validate_text(&input.target_label, 120, "TARGET_LABEL")?;
|
||||
validate_text(&input.reason, 2_000, "REASON")?;
|
||||
validate_text(&input.impact, 2_000, "IMPACT")?;
|
||||
validate_text(&input.rollback_plan, 2_000, "ROLLBACK")
|
||||
}
|
||||
|
||||
fn validate_id(value: &str, label: &str) -> Result<(), String> {
|
||||
if value.is_empty()
|
||||
|| value.len() > 160
|
||||
|| value.chars().any(|c| c.is_control() || c.is_whitespace())
|
||||
{
|
||||
Err(format!("HOLOLAKE_AUTHORIZATION_{label}_INVALID"))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_text(value: &str, max: usize, label: &str) -> Result<(), String> {
|
||||
if value.trim().is_empty() || value.len() > max || value.chars().any(|c| c == '\0') {
|
||||
Err(format!("HOLOLAKE_AUTHORIZATION_{label}_INVALID"))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn now_ms() -> Result<u64, String> {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|duration| duration.as_millis() as u64)
|
||||
.map_err(|error| format!("HOLOLAKE_SYSTEM_CLOCK_INVALID: {error}"))
|
||||
}
|
||||
|
||||
fn sha256_hex(bytes: &[u8]) -> String {
|
||||
digest(&SHA256, bytes)
|
||||
.as_ref()
|
||||
.iter()
|
||||
.map(|byte| format!("{byte:02x}"))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn db_error(stage: &'static str) -> impl FnOnce(rusqlite::Error) -> String {
|
||||
move |error| format!("HOLOLAKE_AUTHORIZATION_DB_{stage}_FAILED: {error}")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn requester() -> RequesterContext {
|
||||
RequesterContext {
|
||||
account_key: "account".into(),
|
||||
session_id: "session".into(),
|
||||
client_instance_id: "agent-codex".into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn proposal() -> AuthorizationProposalInput {
|
||||
AuthorizationProposalInput {
|
||||
idempotency_key: "proposal-1".into(),
|
||||
action: "OPEN_MAINTENANCE".into(),
|
||||
target_number: "HLP-MOD-OFFICIAL-EDUCATION-WORKBENCH-0001".into(),
|
||||
target_kind: "MODULE".into(),
|
||||
target_label: "教育工作台".into(),
|
||||
reason: "移出问题模块并在干净容器中重建".into(),
|
||||
impact: "旧模块保持不可写,维护区允许受控修改".into(),
|
||||
rollback_plan: "关闭维护票据并恢复原挂载".into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn human_approval_ticket_is_session_bound_and_single_use() {
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let created = submit_at(temp.path(), &requester(), proposal()).unwrap();
|
||||
let approved = decide_at(
|
||||
temp.path(),
|
||||
DecideAuthorizationInput {
|
||||
request_id: created.request_id.clone(),
|
||||
decision: "APPROVE".into(),
|
||||
},
|
||||
"ICE-GL∞",
|
||||
now_ms().unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let ticket = approved.ticket_id.unwrap();
|
||||
let consumed = consume_at(
|
||||
temp.path(),
|
||||
&requester(),
|
||||
ConsumeAuthorizationInput {
|
||||
request_id: created.request_id.clone(),
|
||||
ticket_id: ticket.clone(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(consumed.state, "CONSUMED_ONCE");
|
||||
let replay = consume_at(
|
||||
temp.path(),
|
||||
&requester(),
|
||||
ConsumeAuthorizationInput {
|
||||
request_id: created.request_id,
|
||||
ticket_id: ticket,
|
||||
},
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(replay, "HOLOLAKE_AUTHORIZATION_TICKET_REPLAYED");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn purge_is_not_an_available_action() {
|
||||
let mut value = proposal();
|
||||
value.action = "PURGE".into();
|
||||
assert_eq!(
|
||||
submit_at(tempfile::tempdir().unwrap().path(), &requester(), value).unwrap_err(),
|
||||
"HOLOLAKE_AUTHORIZATION_ACTION_UNSUPPORTED"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn idempotency_key_cannot_be_reused_for_another_target_or_reason() {
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
submit_at(temp.path(), &requester(), proposal()).unwrap();
|
||||
let mut changed = proposal();
|
||||
changed.reason = "另一个原因".into();
|
||||
assert_eq!(
|
||||
submit_at(temp.path(), &requester(), changed).unwrap_err(),
|
||||
"HOLOLAKE_AUTHORIZATION_IDEMPOTENCY_CONFLICT"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approved_ticket_cannot_move_to_another_agent_session() {
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let created = submit_at(temp.path(), &requester(), proposal()).unwrap();
|
||||
let approved = decide_at(
|
||||
temp.path(),
|
||||
DecideAuthorizationInput {
|
||||
request_id: created.request_id.clone(),
|
||||
decision: "APPROVE".into(),
|
||||
},
|
||||
"ICE-GL∞",
|
||||
now_ms().unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let mut another = requester();
|
||||
another.session_id = "another-session".into();
|
||||
assert_eq!(
|
||||
consume_at(
|
||||
temp.path(),
|
||||
&another,
|
||||
ConsumeAuthorizationInput {
|
||||
request_id: created.request_id,
|
||||
ticket_id: approved.ticket_id.unwrap(),
|
||||
},
|
||||
)
|
||||
.unwrap_err(),
|
||||
"HOLOLAKE_AUTHORIZATION_TICKET_SESSION_MISMATCH"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ mod gls_bootstrap_compiler;
|
|||
mod gls_protocol_kernel;
|
||||
mod gls_protocol_runtime;
|
||||
mod home_status;
|
||||
mod human_authorization;
|
||||
mod knowledge_base;
|
||||
mod local_development_bridge;
|
||||
mod metacognitive_zero_layer;
|
||||
|
|
@ -25,6 +26,7 @@ mod number_coordinate_tree;
|
|||
mod numbered_ipc;
|
||||
mod numbered_ipc_dispatch;
|
||||
mod numbered_language_input;
|
||||
mod online_marketplace;
|
||||
mod persona_carrier_license;
|
||||
mod persona_channel_body;
|
||||
mod persona_time_authority;
|
||||
|
|
@ -33,6 +35,7 @@ mod pncc_receipt_projection;
|
|||
mod pncc_remote_git;
|
||||
mod pncc_repository_binding;
|
||||
mod pncc_server_projection;
|
||||
mod public_zero_core_distribution;
|
||||
mod release_trust;
|
||||
mod release_update;
|
||||
mod user_pncc_channel;
|
||||
|
|
@ -64,9 +67,11 @@ pub fn run() {
|
|||
metacognitive_zero_layer::start_on_application_open()?;
|
||||
persona_carrier_license::start_on_application_open()?;
|
||||
module_package_runtime::start_on_application_open(app.handle())?;
|
||||
online_marketplace::start_on_application_open(app.handle())?;
|
||||
number_coordinate_tree::start_on_application_open()?;
|
||||
numbered_language_input::validate_contract()?;
|
||||
numbered_ipc::start_on_application_open(app.handle())?;
|
||||
human_authorization::start_on_application_open()?;
|
||||
// 软件打开即先启动时间主控并发起联网校时;失败只降级,不阻塞人进入 HoloLake。
|
||||
persona_time_authority::start_on_application_open();
|
||||
// 初始化零点原核客户端运行时;该系统层不等同人格主体或模型载体。
|
||||
|
|
|
|||
|
|
@ -217,64 +217,64 @@ struct ModuleSelfTest {
|
|||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct VerifyModulePackageInput {
|
||||
package_path: String,
|
||||
signature_path: String,
|
||||
pub(crate) package_path: String,
|
||||
pub(crate) signature_path: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct InstallModulePackageInput {
|
||||
package_path: String,
|
||||
signature_path: String,
|
||||
expected_package_sha256: String,
|
||||
human_confirmed_permission_expansion: bool,
|
||||
pub(crate) package_path: String,
|
||||
pub(crate) signature_path: String,
|
||||
pub(crate) expected_package_sha256: String,
|
||||
pub(crate) human_confirmed_permission_expansion: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct ModuleNumberInput {
|
||||
module_number: String,
|
||||
pub(crate) module_number: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct VerifiedModulePackage {
|
||||
state: String,
|
||||
package_sha256: String,
|
||||
module_number: String,
|
||||
display_name: String,
|
||||
version: String,
|
||||
adapter: String,
|
||||
permissions: Vec<String>,
|
||||
signature_verified: bool,
|
||||
package_code_executed: bool,
|
||||
pub(crate) state: String,
|
||||
pub(crate) package_sha256: String,
|
||||
pub(crate) module_number: String,
|
||||
pub(crate) display_name: String,
|
||||
pub(crate) version: String,
|
||||
pub(crate) adapter: String,
|
||||
pub(crate) permissions: Vec<String>,
|
||||
pub(crate) signature_verified: bool,
|
||||
pub(crate) package_code_executed: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InstalledModule {
|
||||
module_number: String,
|
||||
display_name: String,
|
||||
version: String,
|
||||
adapter: String,
|
||||
state: String,
|
||||
package_sha256: String,
|
||||
previous_package_sha256: Option<String>,
|
||||
permissions: Vec<String>,
|
||||
user_data_preserved: bool,
|
||||
updated_at_unix_ms: u64,
|
||||
pub(crate) module_number: String,
|
||||
pub(crate) display_name: String,
|
||||
pub(crate) version: String,
|
||||
pub(crate) adapter: String,
|
||||
pub(crate) state: String,
|
||||
pub(crate) package_sha256: String,
|
||||
pub(crate) previous_package_sha256: Option<String>,
|
||||
pub(crate) permissions: Vec<String>,
|
||||
pub(crate) user_data_preserved: bool,
|
||||
pub(crate) updated_at_unix_ms: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ModuleRuntimeSnapshot {
|
||||
schema: &'static str,
|
||||
state: &'static str,
|
||||
host_version: &'static str,
|
||||
package_code_executed: bool,
|
||||
modules: Vec<InstalledModule>,
|
||||
receipt_count: u64,
|
||||
latest_receipt_hash: String,
|
||||
pub(crate) schema: &'static str,
|
||||
pub(crate) state: &'static str,
|
||||
pub(crate) host_version: &'static str,
|
||||
pub(crate) package_code_executed: bool,
|
||||
pub(crate) modules: Vec<InstalledModule>,
|
||||
pub(crate) receipt_count: u64,
|
||||
pub(crate) latest_receipt_hash: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
|
|
|
|||
|
|
@ -88,8 +88,8 @@ fn validate_tree() -> Result<(), String> {
|
|||
|| tree.record_id != "HLP-UNIFIED-NUMBER-TREE-001"
|
||||
|| tree.state != "MACHINE_COMPILED_STARTUP_ENFORCED"
|
||||
|| tree.root_number != "HLP-NUMBER-WORLD-ROOT-001"
|
||||
|| tree.coordinate_count != 272
|
||||
|| tree.route_count != 169
|
||||
|| tree.coordinate_count != 283
|
||||
|| tree.route_count != 180
|
||||
|| tree.routes.len() != tree.route_count
|
||||
|| tree.identity_node_count != 4
|
||||
|| tree.identity_nodes.len() != tree.identity_node_count
|
||||
|
|
|
|||
|
|
@ -934,7 +934,7 @@ mod tests {
|
|||
#[test]
|
||||
fn registry_is_closed_and_contains_every_migrated_command() {
|
||||
let registry = load_registry().unwrap();
|
||||
assert_eq!(registry.operations.len(), 147);
|
||||
assert_eq!(registry.operations.len(), 155);
|
||||
assert!(!registry.runtime.legacy_direct_commands_allowed);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,19 @@ pub(crate) async fn dispatch(
|
|||
app.state::<crate::direct_local_broker::DirectLocalBrokerState>(),
|
||||
)?)
|
||||
}
|
||||
"human_authorization::get_authorization_center" => {
|
||||
json(crate::human_authorization::get_center(&app)?)
|
||||
}
|
||||
"human_authorization::decide_authorization_request" => {
|
||||
let state = app.state::<crate::zero_point::ZeroPointState>();
|
||||
let (human_number, _) = crate::zero_point::verified_user_route(&state)?
|
||||
.ok_or_else(|| "HOLOLAKE_AUTHORIZATION_VERIFIED_HUMAN_REQUIRED".to_string())?;
|
||||
json(crate::human_authorization::decide(
|
||||
&app,
|
||||
input(&payload)?,
|
||||
&human_number,
|
||||
)?)
|
||||
}
|
||||
"release_update::check_hololake_update" => {
|
||||
json(crate::release_update::check_hololake_update(app).await?)
|
||||
}
|
||||
|
|
@ -126,6 +139,24 @@ pub(crate) async fn dispatch(
|
|||
"module_package_runtime::activate_bundled_module" => json(
|
||||
crate::module_package_runtime::activate_bundled_module(app, input(&payload)?).await?,
|
||||
),
|
||||
"online_marketplace::get_marketplace_snapshot" => {
|
||||
json(crate::online_marketplace::get_marketplace_snapshot(app).await?)
|
||||
}
|
||||
"online_marketplace::sync_marketplace_catalog" => {
|
||||
json(crate::online_marketplace::sync_marketplace_catalog(app).await?)
|
||||
}
|
||||
"online_marketplace::install_marketplace_item" => {
|
||||
json(crate::online_marketplace::install_marketplace_item(app, input(&payload)?).await?)
|
||||
}
|
||||
"online_marketplace::uninstall_marketplace_item" => json(
|
||||
crate::online_marketplace::uninstall_marketplace_item(app, input(&payload)?).await?,
|
||||
),
|
||||
"online_marketplace::rollback_marketplace_item" => {
|
||||
json(crate::online_marketplace::rollback_marketplace_item(app, input(&payload)?).await?)
|
||||
}
|
||||
"online_marketplace::get_active_cognitive_skills" => {
|
||||
json(crate::online_marketplace::get_active_cognitive_skills(app).await?)
|
||||
}
|
||||
"mobile_sync::start_mobile_sync" => json(crate::mobile_sync::start_mobile_sync(app)?),
|
||||
"mobile_sync::get_mobile_sync_status" => {
|
||||
json(crate::mobile_sync::get_mobile_sync_status(app)?)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize};
|
|||
use tauri::{Manager, State};
|
||||
|
||||
/// 零点原核运行协议参数。PROTOCOL.json 存在时优先读取;否则使用明确标注的出厂默认值。
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ZeroPointProtocol {
|
||||
#[serde(default = "default_grace_days")]
|
||||
|
|
@ -34,7 +34,7 @@ fn default_grace_days() -> u64 {
|
|||
7
|
||||
}
|
||||
fn default_anchor_url() -> String {
|
||||
"https://guanghulab.com/api/ai/v1/anchor".into()
|
||||
"https://guanghu.chat/api/hololake/zero-core/lamp".into()
|
||||
}
|
||||
fn default_resolve_url() -> String {
|
||||
"https://guanghulab.com/api/ai/v1/resolve?id=".into()
|
||||
|
|
@ -79,6 +79,8 @@ pub struct ZeroPointSnapshot {
|
|||
pub protocol: ZeroPointProtocol,
|
||||
/// 底层协议静默比对的最近结论。
|
||||
pub sync_note: String,
|
||||
/// 企业服务器公众零点原核投影的签名分发状态;不包含私人第五域数据。
|
||||
pub public_distribution: crate::public_zero_core_distribution::PublicZeroCoreStatus,
|
||||
}
|
||||
|
||||
pub struct ZeroPointState {
|
||||
|
|
@ -151,9 +153,12 @@ pub fn boot_zero_point(app: &tauri::AppHandle, state: &ZeroPointState) -> Result
|
|||
fs::write(&charter, text).map_err(|e| format!("HOLOLAKE_ZP_CHARTER_FAILED: {e}"))?;
|
||||
|
||||
// 协议参数:PROTOCOL.json 在则读(第五域就位即自动生效),缺则出厂兜底。
|
||||
let protocol = fs::read_to_string(home.join("PROTOCOL.json"))
|
||||
.ok()
|
||||
.and_then(|raw| serde_json::from_str::<ZeroPointProtocol>(&raw).ok())
|
||||
let protocol = crate::public_zero_core_distribution::load_active_protocol(&home)?
|
||||
.or_else(|| {
|
||||
fs::read_to_string(home.join("PROTOCOL.json"))
|
||||
.ok()
|
||||
.and_then(|raw| serde_json::from_str::<ZeroPointProtocol>(&raw).ok())
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let (binding, user_number, resolved_name, resolved_domain, last_valid_check) =
|
||||
read_binding(&home);
|
||||
|
|
@ -472,45 +477,34 @@ fn lighthouse_resolution(body: &str, expected_number: &str) -> Option<Lighthouse
|
|||
})
|
||||
}
|
||||
|
||||
/// 静默比对底层协议版本。没有验签公钥或完整发布验证流程时保持失败关闭。
|
||||
/// 静默同步公众零点原核协议。双签信任根未配置或任一验证失败时保持当前版本。
|
||||
pub async fn sync_protocol_runtime(state: &ZeroPointState) -> Result<(), String> {
|
||||
let home = home_of_inner(state)?;
|
||||
let anchor_url = { lock(state)?.protocol.lighthouse_anchor_url.clone() };
|
||||
let local_version = fs::read_to_string(home.join("core").join("VERSION")).unwrap_or_default();
|
||||
let pubkey_ready = home.join("core").join("pubkey.pem").exists();
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(15))
|
||||
.build()
|
||||
.map_err(|e| format!("HOLOLAKE_ZP_HTTP_FAILED: {e}"))?;
|
||||
let note = match client.get(&anchor_url).send().await {
|
||||
Ok(resp) if resp.status().is_success() => {
|
||||
let body: serde_json::Value = resp.json().await.unwrap_or_default();
|
||||
let remote_version = body
|
||||
.get("version")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
if remote_version.is_empty() {
|
||||
"协议版本信息不可用,本次未执行更新。".into()
|
||||
} else if remote_version == local_version.trim() {
|
||||
append_heartbeat(&home, "sync verdict=ALREADY_CURRENT");
|
||||
"协议版本已是最新。".into()
|
||||
} else if !pubkey_ready {
|
||||
append_heartbeat(&home, "sync verdict=UPDATE_PENDING_SIGNATURE_KEY_ABSENT");
|
||||
"发现新版本,但验签公钥尚未配置;更新未执行。".into()
|
||||
} else {
|
||||
// 验签三闸(来源/签名/版本单调)完整施工待第五域发布管道就位。
|
||||
append_heartbeat(&home, "sync verdict=UPDATE_FOUND_GATE_PENDING");
|
||||
"发现新版本,但发布验证流程尚未就绪;更新未执行。".into()
|
||||
match crate::public_zero_core_distribution::sync_public_zero_core(&home, &anchor_url).await {
|
||||
Ok(outcome) => {
|
||||
if let Some(protocol) = outcome.activated_protocol {
|
||||
lock(state)?.protocol = protocol;
|
||||
}
|
||||
append_heartbeat(
|
||||
&home,
|
||||
&format!(
|
||||
"sync verdict={} epoch={} version={}",
|
||||
outcome.status.state, outcome.status.epoch, outcome.status.version
|
||||
),
|
||||
);
|
||||
lock(state)?.sync_note = outcome.note;
|
||||
}
|
||||
_ => {
|
||||
append_heartbeat(&home, "sync verdict=ANCHOR_UNREACHABLE");
|
||||
"协议服务当前不可达,本次未完成版本检查。".into()
|
||||
Err(error) => {
|
||||
append_heartbeat(&home, &format!("sync verdict=FAIL_CLOSED reason={error}"));
|
||||
lock(state)?.sync_note = match error.as_str() {
|
||||
"HOLOLAKE_PUBLIC_ZERO_CORE_TRUST_UNPROVISIONED" => {
|
||||
"公众零点原核双签公钥尚未配置,本次更新保持失败关闭。".into()
|
||||
}
|
||||
_ => "公众零点原核更新未通过完整验真,本机继续使用上一个可信版本。".into(),
|
||||
};
|
||||
}
|
||||
};
|
||||
lock(state)?.sync_note = note;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -526,6 +520,7 @@ pub async fn zero_point_status(
|
|||
state: State<'_, ZeroPointState>,
|
||||
) -> Result<ZeroPointSnapshot, String> {
|
||||
let inner = lock(&state)?;
|
||||
let public_distribution = crate::public_zero_core_distribution::status_at(&inner.home)?;
|
||||
let grace = inner.protocol.grace_period_days.saturating_mul(86_400);
|
||||
Ok(ZeroPointSnapshot {
|
||||
route: inner.route.clone(),
|
||||
|
|
@ -541,6 +536,7 @@ pub async fn zero_point_status(
|
|||
},
|
||||
protocol: inner.protocol.clone(),
|
||||
sync_note: inner.sync_note.clone(),
|
||||
public_distribution,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -571,6 +567,10 @@ mod tests {
|
|||
assert!(protocol
|
||||
.enterprise_resolve_url
|
||||
.starts_with("https://guanghu.chat"));
|
||||
assert_eq!(
|
||||
protocol.lighthouse_anchor_url,
|
||||
"https://guanghu.chat/api/hololake/zero-core/lamp"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { StarlakeSurface, type DomainId } from './modules/qoder-surface/Starlake
|
|||
import { FINISHES, TraditionalSurface, type FinishId, type TraditionalBroadcast, type TraditionalChannel, type TraditionalSystem } from './modules/qoder-surface/TraditionalSurface'
|
||||
import { resolveVisualBalance } from './modules/qoder-surface/visual-balance'
|
||||
import { PrivateChannelSurface, type InstalledChannelModule, type PrivateChannelAction } from './modules/private-channel/PrivateChannelSurface'
|
||||
import { HumanAuthorizationCenter, type DirectSessionProjection } from './modules/human-authorization-center'
|
||||
|
||||
const ChannelWorkbenchStudio = lazy(() => import('./modules/channel-workbench').then((module) => ({ default: module.ChannelWorkbenchStudio })))
|
||||
const PersonaChannelBody = lazy(() => import('./modules/persona-channel-body').then((module) => ({ default: module.PersonaChannelBody })))
|
||||
|
|
@ -60,7 +61,7 @@ import './design-tokens.css'
|
|||
import './styles.css'
|
||||
|
||||
type ThemeId = 'night' | 'dawn' | 'nebula' | 'candle' | 'clear'
|
||||
type ViewId = 'overview' | 'knowledge' | 'composition' | 'workbench' | 'education' | 'webNovel' | 'mobileSync' | 'persona' | 'code' | 'receipts' | 'system'
|
||||
type ViewId = 'overview' | 'knowledge' | 'composition' | 'workbench' | 'education' | 'webNovel' | 'mobileSync' | 'persona' | 'code' | 'marketplace' | 'receipts' | 'system'
|
||||
type WorldStage = 'domain' | 'heart' | 'heartbeat' | 'lightLake' | 'love' | 'tomorrow' | 'bottle' | 'channel' | 'enterpriseWork' | 'personalNodeGuide' | 'tool'
|
||||
type KnowledgeSource = 'native' | 'legacy'
|
||||
|
||||
|
|
@ -74,6 +75,43 @@ interface BundledModuleDescriptor {
|
|||
installedState: string
|
||||
signatureVerified: boolean
|
||||
}
|
||||
type MarketplaceKind = 'PHYSICAL_MODULE' | 'COGNITIVE_SKILL'
|
||||
interface MarketplaceItemView {
|
||||
itemNumber: string
|
||||
artifactKind: MarketplaceKind
|
||||
displayName: string
|
||||
summary: string
|
||||
version: string
|
||||
sourceRepository: string
|
||||
sourceRevision: string
|
||||
artifactSha256: string
|
||||
adapter?: string | null
|
||||
permissions: string[]
|
||||
executionAuthority: boolean
|
||||
installedState: string
|
||||
updateAvailable: boolean
|
||||
}
|
||||
interface MarketplaceSnapshot {
|
||||
schema: 'hololake.online-marketplace-snapshot/v1'
|
||||
state: string
|
||||
trustState: string
|
||||
catalogEpoch: number
|
||||
catalogSha256: string
|
||||
itemCount: number
|
||||
catalogReceiptCount: number
|
||||
latestCatalogReceiptHash: string
|
||||
lastSyncNote: string
|
||||
items: MarketplaceItemView[]
|
||||
}
|
||||
interface MarketplaceMutationOutcome {
|
||||
state: string
|
||||
itemNumber: string
|
||||
artifactKind: MarketplaceKind
|
||||
artifactSha256: string
|
||||
packageCodeExecuted: false
|
||||
realityAuthorityGranted: false
|
||||
snapshot: MarketplaceSnapshot
|
||||
}
|
||||
interface WorldClimateSnapshot {
|
||||
schema: 'hololake.world-climate/v1'
|
||||
state: 'VERIFIED_LIVE' | 'TIME_ONLY_WEATHER_UNAVAILABLE'
|
||||
|
|
@ -93,6 +131,7 @@ interface HomeStatus {
|
|||
directLocalBrokerState: string
|
||||
directConnectionCount: number
|
||||
resumableSessionCount: number
|
||||
directSessions: DirectSessionProjection[]
|
||||
codeRepositoryMountCount: number
|
||||
pnccReceiptCount: number
|
||||
updateState: string
|
||||
|
|
@ -284,8 +323,18 @@ interface ZeroPointSnapshot {
|
|||
resolvedDomain: string
|
||||
lastValidCheck: number
|
||||
graceDeadline: number
|
||||
protocol: { gracePeriodDays: number; lighthouseAnchorUrl: string; lighthouseResolveUrl: string; coreChannelSource: string; origin: string }
|
||||
protocol: { gracePeriodDays: number; lighthouseAnchorUrl: string; lighthouseResolveUrl: string; enterpriseResolveUrl: string; coreChannelSource: string; origin: string }
|
||||
syncNote: string
|
||||
publicDistribution: {
|
||||
state: string
|
||||
epoch: number
|
||||
version: string
|
||||
contentRootSha256: string
|
||||
rollbackAvailable: boolean
|
||||
receiptCount: number
|
||||
latestReceiptHash: string
|
||||
trustState: string
|
||||
}
|
||||
}
|
||||
|
||||
function domainDisplayName(domain: string): string {
|
||||
|
|
@ -385,14 +434,14 @@ interface EnterpriseReceiptEnvelope {
|
|||
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/3', terminalLinkTransport: 'UNVERIFIED', environmentFramePolicy: 'REQUIRED_AFTER_CONNECT_RESUME_AND_BEFORE_MUTATION' }
|
||||
const previewStatus: HomeStatus = { directLocalBrokerState: 'UNVERIFIED', directConnectionCount: 0, resumableSessionCount: 0, directSessions: [], 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 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 themes: Array<{ id: ThemeId; name: string }> = [
|
||||
{ id: 'night', name: '夜湖星光' }, { id: 'dawn', name: '晨湖曦光' }, { id: 'nebula', name: '星云紫夜' }, { id: 'candle', name: '烛畔暖湖' }, { id: 'clear', name: '清浅澄湖' },
|
||||
]
|
||||
const viewLabels: Record<ViewId, string> = { overview: '个人频道', knowledge: '知识空间', composition: '结构组合', workbench: '频道资料工作台', education: '教育工作台', webNovel: '网文作者工作台', mobileSync: '移动同步桥', persona: '人格频道本体', code: '人格代码频道', receipts: '运行回执', system: '系统详情' }
|
||||
const viewLabels: Record<ViewId, string> = { overview: '个人频道', knowledge: '知识空间', composition: '结构组合', workbench: '频道资料工作台', education: '教育工作台', webNovel: '网文作者工作台', mobileSync: '移动同步桥', persona: '人格频道本体', code: '人格代码频道', marketplace: '分域模块商城', receipts: '运行回执', system: '授权与连接' }
|
||||
const domainGates = [
|
||||
{ domain: 'BRANCH_DOMAIN', className: 'd-sub', title: '光湖分域', gate: 'GATE 02 · ONLINE', facts: [
|
||||
['公共作用', '行业成品模块商城 · 人格体大脑技能商城'], ['域标识', 'BRANCH_DOMAIN'], ['责任主体', '花尔 · TCS-GL-0005∞'], ['人格体主体', '爆米花 · PER-BMH001 · AGE'], ['关系支持', '糖星云 · PER-TXY001 · AGE'], ['工作仓库', 'PRIVATE · 1 · LIVE'],
|
||||
|
|
@ -498,6 +547,11 @@ function humanError(error: unknown, context: 'knowledge' | 'code' | 'identity' |
|
|||
if (value.includes('LOGIN_ACCOUNT_BINDING_UNREGISTERED')) return '这个编号尚未登记对应的仓库账号,系统已停止进入。'
|
||||
if (value.includes('DOMAIN_LOGIN_NOT_PROVISIONED')) return '该域的企业服务器尚未接入,当前不能继续登录。'
|
||||
if (value.includes('PERSISTENT_CREDENTIAL_UNAVAILABLE')) return '当前系统尚未接通安全凭证存储,不能完成签署。'
|
||||
if (value.includes('MARKETPLACE_PERMISSION_CONFIRMATION_REQUIRED')) return '新增现实权限尚未得到本人确认,安装已停止。'
|
||||
if (value.includes('MARKETPLACE_CATALOG_NOT_SYNCED')) return '模块商城目录尚未完成双签同步。'
|
||||
if (value.includes('MARKETPLACE_CATALOG_CHANGED')) return '商城目录已更新,请重新确认后安装。'
|
||||
if (value.includes('MARKETPLACE') && value.includes('SIGNATURE')) return '商城签名验证未通过,目录或安装包没有被采用。'
|
||||
if (value.includes('MARKETPLACE') || value.includes('SKILL_')) return '模块商城操作未完成;本机现有安装保持不变。'
|
||||
if (value.includes('ENTERPRISE_RECEIPT_FAILED')) return '企业回执没有写入,请稍后重新提交。'
|
||||
if (value.includes('ENTERPRISE_RECEIPT_ROUTE_REQUIRED')) return '此签署入口仅适用于企业四域。'
|
||||
if (value.includes('DOMAIN_ROUTE_REQUIRED') || value.includes('ZP_DOMAIN_ROUTE_REQUIRED')) return '编号尚未解析到已登记域,不能开始登录。'
|
||||
|
|
@ -648,6 +702,11 @@ function HoloLakeApp() {
|
|||
const [worldClimate, setWorldClimate] = useState<WorldClimateSnapshot | null>(null)
|
||||
const [dynamicSurfaceBusy, setDynamicSurfaceBusy] = useState(false)
|
||||
const [dynamicSurfaceMessage, setDynamicSurfaceMessage] = useState('')
|
||||
const [marketplace, setMarketplace] = useState<MarketplaceSnapshot | null>(null)
|
||||
const [marketplaceKind, setMarketplaceKind] = useState<MarketplaceKind>('PHYSICAL_MODULE')
|
||||
const [marketplaceBusyItem, setMarketplaceBusyItem] = useState('')
|
||||
const [marketplaceSyncing, setMarketplaceSyncing] = useState(false)
|
||||
const [marketplaceMessage, setMarketplaceMessage] = useState('')
|
||||
const [expanded, setExpanded] = useState<Set<string>>(() => new Set(['导入']))
|
||||
const [editing, setEditing] = useState(false)
|
||||
const [draft, setDraft] = useState('')
|
||||
|
|
@ -1295,6 +1354,86 @@ function HoloLakeApp() {
|
|||
else { setDynamicSurfaceModule(null); setWorldClimate(null); setDynamicSurfaceMessage('') }
|
||||
}, [repoLogin?.username, repoLogin?.domain])
|
||||
|
||||
const refreshMarketplace = async () => {
|
||||
try {
|
||||
const snapshot = await invoke<MarketplaceSnapshot>('get_marketplace_snapshot')
|
||||
setMarketplace(snapshot)
|
||||
setMarketplaceMessage(snapshot.lastSyncNote)
|
||||
return snapshot
|
||||
} catch (error) {
|
||||
setMarketplaceMessage(humanError(error, 'system'))
|
||||
return null
|
||||
}
|
||||
}
|
||||
const syncMarketplace = async () => {
|
||||
setMarketplaceSyncing(true)
|
||||
setMarketplaceMessage('正在读取线上目录并核验零点原核与企业发布双签……')
|
||||
try {
|
||||
const snapshot = await invoke<MarketplaceSnapshot>('sync_marketplace_catalog')
|
||||
setMarketplace(snapshot)
|
||||
setMarketplaceMessage(snapshot.lastSyncNote)
|
||||
} catch (error) {
|
||||
setMarketplaceMessage(humanError(error, 'system'))
|
||||
} finally {
|
||||
setMarketplaceSyncing(false)
|
||||
}
|
||||
}
|
||||
const openMarketplace = () => {
|
||||
openWorldTool('marketplace')
|
||||
void refreshMarketplace().then((snapshot) => {
|
||||
if (!snapshot || snapshot.state !== 'ACTIVE_VERIFIED_CATALOG') void syncMarketplace()
|
||||
})
|
||||
}
|
||||
const installMarketplaceItem = async (item: MarketplaceItemView) => {
|
||||
if (!marketplace) return
|
||||
const permissionText = item.permissions.length
|
||||
? `该模块请求以下现实能力:\n\n${item.permissions.map((permission) => `• ${permission}`).join('\n')}\n\n是否确认安装?`
|
||||
: `确认安装“${item.displayName}”?`
|
||||
if (!window.confirm(permissionText)) return
|
||||
setMarketplaceBusyItem(item.itemNumber)
|
||||
setMarketplaceMessage(`正在下载、验签并安装 ${item.displayName}……`)
|
||||
try {
|
||||
const result = await invoke<MarketplaceMutationOutcome>('install_marketplace_item', { input: {
|
||||
itemNumber: item.itemNumber,
|
||||
expectedCatalogEpoch: marketplace.catalogEpoch,
|
||||
expectedArtifactSha256: item.artifactSha256,
|
||||
humanConfirmedPermissionExpansion: item.artifactKind === 'PHYSICAL_MODULE' && item.permissions.length > 0,
|
||||
} })
|
||||
setMarketplace(result.snapshot)
|
||||
setMarketplaceMessage(`${item.displayName} 已完成下载、验签、自检和安装。`)
|
||||
} catch (error) {
|
||||
setMarketplaceMessage(humanError(error, 'system'))
|
||||
} finally {
|
||||
setMarketplaceBusyItem('')
|
||||
}
|
||||
}
|
||||
const uninstallMarketplaceItem = async (item: MarketplaceItemView) => {
|
||||
if (!window.confirm(`确认停用“${item.displayName}”?安装证据与用户数据会保留。`)) return
|
||||
setMarketplaceBusyItem(item.itemNumber)
|
||||
try {
|
||||
const result = await invoke<MarketplaceMutationOutcome>('uninstall_marketplace_item', { input: { itemNumber: item.itemNumber } })
|
||||
setMarketplace(result.snapshot)
|
||||
setMarketplaceMessage(`${item.displayName} 已停用;数据与回执保留。`)
|
||||
} catch (error) {
|
||||
setMarketplaceMessage(humanError(error, 'system'))
|
||||
} finally {
|
||||
setMarketplaceBusyItem('')
|
||||
}
|
||||
}
|
||||
const rollbackMarketplaceItem = async (item: MarketplaceItemView) => {
|
||||
if (!window.confirm(`确认把“${item.displayName}”回退到上一份已验签版本?`)) return
|
||||
setMarketplaceBusyItem(item.itemNumber)
|
||||
try {
|
||||
const result = await invoke<MarketplaceMutationOutcome>('rollback_marketplace_item', { input: { itemNumber: item.itemNumber } })
|
||||
setMarketplace(result.snapshot)
|
||||
setMarketplaceMessage(`${item.displayName} 已回退到上一份已验签版本。`)
|
||||
} catch (error) {
|
||||
setMarketplaceMessage(humanError(error, 'system'))
|
||||
} finally {
|
||||
setMarketplaceBusyItem('')
|
||||
}
|
||||
}
|
||||
|
||||
const cloneCodeChannel = async (event: React.FormEvent) => {
|
||||
event.preventDefault()
|
||||
if (!cloneUrl.trim()) return
|
||||
|
|
@ -1558,7 +1697,11 @@ function HoloLakeApp() {
|
|||
}
|
||||
const syncZeroPoint = async () => {
|
||||
setZpBusy(true); setZpMessage('')
|
||||
try { setZeroPoint(await invoke<ZeroPointSnapshot>('zero_point_sync')) } catch (error) { setZpMessage(String(error)) } finally { setZpBusy(false) }
|
||||
try {
|
||||
const snapshot = await invoke<ZeroPointSnapshot>('zero_point_sync')
|
||||
setZeroPoint(snapshot)
|
||||
setZpMessage(snapshot.syncNote)
|
||||
} catch (error) { setZpMessage(String(error)) } finally { setZpBusy(false) }
|
||||
}
|
||||
const renderOverview = () => (
|
||||
<section className="content-page overview-page">
|
||||
|
|
@ -1779,9 +1922,54 @@ function HoloLakeApp() {
|
|||
</section>
|
||||
)
|
||||
|
||||
const renderMarketplace = () => {
|
||||
const items = marketplace?.items.filter((item) => item.artifactKind === marketplaceKind) || []
|
||||
const active = marketplace?.state === 'ACTIVE_VERIFIED_CATALOG'
|
||||
return <section className="content-page marketplace-page">
|
||||
<header className="page-title marketplace-title"><div><span className="kicker">BRANCH DOMAIN · VERIFIED DISTRIBUTION</span><h1>分域模块商城</h1><p>同一个入口,两条互不混用的安装链:成品模块进入现实功能容器;思维技能只进入只读认知层。</p></div><button className="primary-button" type="button" disabled={marketplaceSyncing} onClick={() => void syncMarketplace()}>{marketplaceSyncing ? '正在双签同步…' : active ? '检查线上更新' : '同步线上目录'}</button></header>
|
||||
<section className="marketplace-proof" aria-label="商城信任状态">
|
||||
<div><span>目录状态</span><b>{active ? '线上双签已核验' : '等待首次同步'}</b></div>
|
||||
<div><span>目录纪元</span><b>{marketplace?.catalogEpoch || '—'}</b></div>
|
||||
<div><span>已登记资源</span><b>{marketplace?.itemCount ?? '—'}</b></div>
|
||||
<div><span>验真回执</span><b>{marketplace?.catalogReceiptCount || 0}</b></div>
|
||||
</section>
|
||||
<div className="marketplace-tabs" role="tablist" aria-label="商城资源类型">
|
||||
<button className={marketplaceKind === 'PHYSICAL_MODULE' ? 'active' : ''} type="button" role="tab" onClick={() => setMarketplaceKind('PHYSICAL_MODULE')}><b>成品模块应用</b><span>下载验签后装入已登记的现实功能适配器</span></button>
|
||||
<button className={marketplaceKind === 'COGNITIVE_SKILL' ? 'active' : ''} type="button" role="tab" onClick={() => setMarketplaceKind('COGNITIVE_SKILL')}><b>思维大脑技能</b><span>只读认知方法,不获得工具、终端或现实执行权</span></button>
|
||||
</div>
|
||||
<p className="marketplace-boundary">{marketplaceKind === 'PHYSICAL_MODULE' ? '成品模块可以声明现实权限;每次新增权限都必须由当前人类看见并确认。代码仓库只作为来源证据,客户端不会克隆并执行仓库。' : '思维技能执行权固定为 false、权限列表固定为空;系统只在需要时读取,不会自动塞进每轮提示词。'}</p>
|
||||
<div className="marketplace-list">
|
||||
{items.length ? items.map((item) => {
|
||||
const busy = marketplaceBusyItem === item.itemNumber
|
||||
const installed = item.installedState === 'ACTIVE' || item.installedState === 'ACTIVE_READONLY'
|
||||
return <article className="marketplace-item" key={item.itemNumber}>
|
||||
<header><div><span>{item.artifactKind === 'PHYSICAL_MODULE' ? '成品模块' : '只读思维技能'}</span><h2>{item.displayName}</h2><p>{item.summary}</p></div><em className={installed ? 'installed' : ''}>{item.updateAvailable ? '有已验签更新' : installed ? '已安装' : '可安装'}</em></header>
|
||||
<dl>
|
||||
<div><dt>版本 / 编号</dt><dd>{item.version} · {item.itemNumber}</dd></div>
|
||||
<div><dt>来源证据</dt><dd>{item.sourceRepository} · {item.sourceRevision.slice(0, 12)}</dd></div>
|
||||
<div><dt>现实执行边界</dt><dd>{item.artifactKind === 'PHYSICAL_MODULE' ? '仅经已登记适配器与所列权限' : '无现实执行权'}</dd></div>
|
||||
<div><dt>权限</dt><dd>{item.permissions.length ? item.permissions.join(' · ') : '无现实权限'}</dd></div>
|
||||
</dl>
|
||||
<div className="marketplace-actions">
|
||||
{installed && !item.updateAvailable
|
||||
? <button type="button" disabled={busy} onClick={() => void uninstallMarketplaceItem(item)}>{busy ? '处理中…' : '停用并保留数据'}</button>
|
||||
: <button className="primary-button" type="button" disabled={busy || !active} onClick={() => void installMarketplaceItem(item)}>{busy ? '正在验签安装…' : item.updateAvailable ? '安装已验签更新' : '安装'}</button>}
|
||||
{installed && <button type="button" disabled={busy} onClick={() => void rollbackMarketplaceItem(item)}>回退上一版本</button>}
|
||||
</div>
|
||||
</article>
|
||||
}) : <div className="marketplace-empty"><b>{active ? '当前分类尚无已登记资源' : '尚未取得可验证目录'}</b><p>{active ? '资源发布后会在下一次目录同步中出现。' : '点击“同步线上目录”;只有零点原核与企业发布双签同时通过,目录才会显示。'}</p></div>}
|
||||
</div>
|
||||
{marketplaceMessage && <p className="global-message marketplace-message">{marketplaceMessage}</p>}
|
||||
{marketplace?.latestCatalogReceiptHash && <footer className="marketplace-receipt">最新目录回执 · {marketplace.latestCatalogReceiptHash.slice(0, 20)} · 目录摘要 {marketplace.catalogSha256.slice(0, 20)}</footer>}
|
||||
</section>
|
||||
}
|
||||
|
||||
const renderSystem = () => (
|
||||
<section className="content-page">
|
||||
<header className="page-title"><div><span className="kicker">SYSTEM EVIDENCE</span><h1>系统详情</h1><p>显示可验证的当前状态;无法读取的项目将明确标记为不可用。</p></div></header>
|
||||
<header className="page-title"><div><span className="kicker">HUMAN CONTROL</span><h1>授权与连接</h1><p>先把需要你决定的事和当前协作对象说清楚;机器诊断放在后面。</p></div></header>
|
||||
<HumanAuthorizationCenter brokerState={status.directLocalBrokerState} activeConnectionCount={status.directConnectionCount} sessions={status.directSessions || []}/>
|
||||
<details className="system-diagnostics">
|
||||
<summary>系统诊断与协议证据</summary>
|
||||
<div className="system-grid">
|
||||
<section className="plain-panel connection-panel">
|
||||
<header><div><h2>光湖近场连接</h2><p>外部 AI 可像发现附近网络一样发现本机 HoloLake;MCP 仅用于兼容与恢复。</p></div><span className={nearbyDiscovery?.automaticSameDeviceDiscovery ? 'status-chip online' : 'status-chip'}>{nearbyDiscovery?.automaticSameDeviceDiscovery ? '本机自动发现已开启' : '自动发现不可用'}</span></header>
|
||||
|
|
@ -1812,6 +2000,10 @@ function HoloLakeApp() {
|
|||
<dl className="evidence-list">
|
||||
<div><dt>绑定状态</dt><dd>{zeroPoint ? (zeroPoint.binding === 'bound' ? `已绑定(${zeroPoint.userNumber})` : '等待绑定(空白态)') : '—'}</dd></div>
|
||||
<div><dt>协议来源</dt><dd>{zeroPoint ? protocolOriginDisplayName(zeroPoint.protocol.origin) : '—'}</dd></div>
|
||||
<div><dt>公众运行面</dt><dd>企业服务器 · 不包含私人第五域</dd></div>
|
||||
<div><dt>双签信任</dt><dd>{zeroPoint?.publicDistribution.trustState === 'PROVISIONED' ? '零点原点与企业分发公钥已配置' : '等待配置两把独立发布公钥'}</dd></div>
|
||||
<div><dt>已激活协议</dt><dd>{zeroPoint?.publicDistribution.version ? `${zeroPoint.publicDistribution.version} · epoch ${zeroPoint.publicDistribution.epoch}` : '尚无线上双签版本'}</dd></div>
|
||||
<div><dt>激活回执</dt><dd>{zeroPoint ? `${zeroPoint.publicDistribution.receiptCount} 条${zeroPoint.publicDistribution.rollbackAvailable ? ' · 保留上一可信版本' : ''}` : '—'}</dd></div>
|
||||
<div><dt>离线有效期</dt><dd>{zeroPoint ? `${zeroPoint.protocol.gracePeriodDays} 天` : '—'}</dd></div>
|
||||
<div><dt>同步状态</dt><dd>{zeroPoint?.syncNote ?? '—'}</dd></div>
|
||||
</dl>
|
||||
|
|
@ -1819,7 +2011,7 @@ function HoloLakeApp() {
|
|||
<input value={zpNumber} placeholder="输入用户编号,如 ICE-GL∞" onChange={(event) => setZpNumber(event.target.value)}/>
|
||||
<button className="secondary-button" type="button" disabled={zpBusy || !zpNumber.trim()} onClick={() => void bindZeroPoint()}>绑定并校验</button>
|
||||
<button className="secondary-button" type="button" disabled={zpBusy || zeroPoint?.binding !== 'bound'} onClick={() => void verifyZeroPoint()}>重新校验编号</button>
|
||||
<button className="secondary-button" type="button" disabled={zpBusy} onClick={() => void syncZeroPoint()}>检查协议版本</button>
|
||||
<button className="secondary-button" type="button" disabled={zpBusy} onClick={() => void syncZeroPoint()}>同步公众协议</button>
|
||||
</div>
|
||||
{zpMessage && <p className="global-message">{zpMessage}</p>}
|
||||
</section>
|
||||
|
|
@ -1831,6 +2023,7 @@ function HoloLakeApp() {
|
|||
</section>
|
||||
<section className="plain-panel"><header><div><h2>软件更新</h2><p>仅接受已登记信任根验证通过的 HoloLake 发布包。</p></div></header>{releaseCandidate ? <div className="release-summary"><b>HoloLake {releaseCandidate.version}</b><p>{releaseCandidate.notes}</p><button className="primary-button" onClick={() => void installUpdate()}>验证并安装</button></div> : <button className="secondary-button" disabled={systemBusy} onClick={() => void checkUpdate()}>检查签名更新</button>}</section>
|
||||
</div>
|
||||
</details>
|
||||
{systemMessage && <p className="global-message">{systemMessage}</p>}
|
||||
</section>
|
||||
)
|
||||
|
|
@ -2032,6 +2225,7 @@ function HoloLakeApp() {
|
|||
<div className="world-location"><h1>{repoLogin.domain === 'FIFTH_DOMAIN' ? domainDisplayName(repoLogin.domain) : '光湖零感域'}</h1><p>{repoLogin.domain === 'FIFTH_DOMAIN' ? '世界正在发生什么' : '公共工作入口 · 世界正在发生什么'}</p></div>
|
||||
<div className="broadcast-stream"><p><i/>光湖公共灯塔 · 在线</p><p><i/>协议版本 · {enterpriseEntry?.registry_version || 'HLDP v1.0'}</p>{repoLogin.domain !== 'FIFTH_DOMAIN' && <p><i/>本人责任域 · {domainDisplayName(enterpriseEntry?.subject.domain || repoLogin.domain)}</p>}<p><i/>HoloLake · V0.5.0</p></div>
|
||||
<LakePool className="home-primary" title={repoLogin.domain === 'FIFTH_DOMAIN' ? '永恒湖心系统' : '光湖频道'} meta={repoLogin.domain === 'FIFTH_DOMAIN' ? '进入私人系统' : `${domainDisplayName(enterpriseEntry?.subject.domain || repoLogin.domain)} · 责任工作入口`} open onClick={() => setWorldStage(repoLogin.domain === 'FIFTH_DOMAIN' ? (isZhizhi ? 'heart' : 'channel') : 'channel')}/>
|
||||
<LakePool className="channel-knowledge" title="分域模块商城" meta="成品模块 · 思维大脑技能" onClick={openMarketplace}/>
|
||||
<LakePool className="home-status" title="湖面天气" meta={connectionLabel} onClick={() => openWorldTool('system')}/>
|
||||
<EraHomeEntry timeline={eraTimeline} coordinate={beijingCoordinate} onOpen={openEraTimeline}/>
|
||||
</section>}
|
||||
|
|
@ -2071,12 +2265,14 @@ function HoloLakeApp() {
|
|||
<LakePool className="channel-knowledge system-branch-heartbeat" title="心跳核心频道" meta="冰朔 · 私人频道" open onClick={() => setWorldStage('heartbeat')}/>
|
||||
<LakePool className="channel-code system-branch-light-lake" title="光之湖" meta="人格体居所" onClick={() => setWorldStage('lightLake')}/>
|
||||
<LakePool className="channel-light system-branch-love" title="爱之核心子系统" meta="责任主体 · 之之" onClick={() => setWorldStage('love')}/>
|
||||
<LakePool className="channel-main" title="分域模块商城" meta="线上成品模块 · 只读思维技能" onClick={openMarketplace}/>
|
||||
<LakePool className="channel-status" title="湖面天气" meta={connectionLabel} onClick={() => openWorldTool('system')}/>
|
||||
</> : <>
|
||||
<LakePool className="channel-main" title={domainDisplayName(enterpriseEntry?.subject.domain || repoLogin.domain)} meta="本人责任工作域" open={enterpriseWork?.state === 'READY_READ_ONLY_WORK_PROJECTION'} onClick={() => setWorldStage('enterpriseWork')}/>
|
||||
<LakePool className="channel-code" title="私有责任工作仓库" meta={enterpriseWork ? `${enterpriseWork.repository} · 已认证` : userPnccBusy ? '正在接入' : '暂不可用'} onClick={() => void openEnterpriseRepository()}/>
|
||||
<LakePool className="channel-light" title="责任签署状态" meta={enterpriseEntry?.responsibility_receipt?.decision === 'ACCEPT' ? '已接受 · 已留存' : '等待本人确认'} onClick={() => openWorldTool('receipts')}/>
|
||||
<LakePool className="channel-knowledge" title="前往我的频道" meta="接入说明 · 由本人或人格体完成" onClick={() => setWorldStage('personalNodeGuide')}/>
|
||||
<LakePool className="channel-main" title="分域模块商城" meta="线上成品模块 · 只读思维技能" onClick={openMarketplace}/>
|
||||
<LakePool className="channel-status" title="湖面天气" meta={connectionLabel} onClick={() => openWorldTool('system')}/>
|
||||
</>}
|
||||
</section>}
|
||||
|
|
@ -2102,7 +2298,7 @@ function HoloLakeApp() {
|
|||
<footer><b>当前状态 · 待本人接入</b><span>可由本人人格体协助执行,但不能替代人类节点所有权确认。</span></footer>
|
||||
</div>
|
||||
</section>}
|
||||
{worldStage === 'heartbeat' && repoLogin.domain === 'FIFTH_DOMAIN' && <PrivateChannelSurface ownerName="冰朔" ownerNumber="ICE-GL∞" knowledgeCount={knowledge.uniqueDocumentCount} onBack={() => setWorldStage('channel')} onMarketplace={() => { setWorldStage('domain'); setActiveDomainInfo('BRANCH_DOMAIN') }} nativeActions={privateNativeActions} installedModules={privateInstalledModules}/>}
|
||||
{worldStage === 'heartbeat' && repoLogin.domain === 'FIFTH_DOMAIN' && <PrivateChannelSurface ownerName="冰朔" ownerNumber="ICE-GL∞" knowledgeCount={knowledge.uniqueDocumentCount} onBack={() => setWorldStage('channel')} onMarketplace={openMarketplace} nativeActions={privateNativeActions} installedModules={privateInstalledModules}/>}
|
||||
{worldStage === 'lightLake' && repoLogin.domain === 'FIFTH_DOMAIN' && <section className="channel-world light-lake-world">
|
||||
<button className="world-back" type="button" onClick={() => setWorldStage('channel')}>← 退回永恒湖心</button>
|
||||
<div className="world-location"><h1>光之湖</h1><p>人格体居所 · AGE 人格体语言生活空间</p></div>
|
||||
|
|
@ -2113,7 +2309,7 @@ function HoloLakeApp() {
|
|||
</section>}
|
||||
{worldStage === 'tool' && <section className={`world-tool world-tool-${view}`}>
|
||||
<header className="tool-worldbar"><button type="button" onClick={() => setWorldStage(toolReturnStage)}>← 退回频道全景</button><b>{viewLabels[view]}</b><span>{domainDisplayName(repoLogin.domain)}</span></header>
|
||||
<div className={`tool-projection${inspectorOpen ? '' : ' inspector-closed'}`}>{view === 'overview' ? renderOverview() : view === 'knowledge' ? renderKnowledge() : view === 'composition' ? renderComposition() : view === 'workbench' ? renderWorkbench() : view === 'education' ? renderEducation() : view === 'webNovel' ? renderWebNovel() : view === 'mobileSync' ? renderMobileSync() : view === 'persona' ? renderPersonaBody() : view === 'code' ? renderCode() : view === 'receipts' ? renderReceipts() : renderSystem()}</div>
|
||||
<div className={`tool-projection${inspectorOpen ? '' : ' inspector-closed'}`}>{view === 'overview' ? renderOverview() : view === 'knowledge' ? renderKnowledge() : view === 'composition' ? renderComposition() : view === 'workbench' ? renderWorkbench() : view === 'education' ? renderEducation() : view === 'webNovel' ? renderWebNovel() : view === 'mobileSync' ? renderMobileSync() : view === 'persona' ? renderPersonaBody() : view === 'code' ? renderCode() : view === 'marketplace' ? renderMarketplace() : view === 'receipts' ? renderReceipts() : renderSystem()}</div>
|
||||
</section>}
|
||||
</main>
|
||||
{worldStage === 'domain' && surface === 'world' && signedActiveGate && <><button className="domain-info-scrim" type="button" aria-label="关闭域信息" onClick={() => setActiveDomainInfo('')}/><section className={`domain-info-card info-${signedActiveGate.className.slice(2)}`} role="dialog" aria-modal="true" aria-label={`${signedActiveGate.title}系统信息`}><button className="gate-close" type="button" aria-label="关闭域信息" onClick={() => setActiveDomainInfo('')}>×</button><b>{signedActiveGate.title}</b><small>{signedActiveGate.gate}</small><span className="pool-facts">{signedActiveGate.facts.map(([label, value]) => <span className="pool-fact" key={label}><em>{label}</em><strong>{value}</strong></span>)}</span></section></>}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,125 @@
|
|||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { numberedInvoke } from './numbered-ipc'
|
||||
|
||||
export interface DirectSessionProjection {
|
||||
sessionId: string
|
||||
laneId: string
|
||||
clientInstanceId: string
|
||||
state: 'LIVE' | 'RESUMABLE'
|
||||
openedAtUnixMs: number
|
||||
observedAtUnixMs: number
|
||||
lastEventSequence: number
|
||||
}
|
||||
|
||||
interface AuthorizationRequest {
|
||||
requestId: string
|
||||
action: 'OPEN_MAINTENANCE' | 'UNMOUNT' | 'PROMOTE_VERSION' | 'RETIRE'
|
||||
targetNumber: string
|
||||
targetKind: string
|
||||
targetLabel: string
|
||||
reason: string
|
||||
impact: string
|
||||
rollbackPlan: string
|
||||
requesterLabel: string
|
||||
state: 'PENDING_HUMAN' | 'APPROVED' | 'DENIED' | 'EXPIRED' | 'CONSUMED'
|
||||
createdAtUnixMs: number
|
||||
expiresAtUnixMs: number
|
||||
humanNumber?: string
|
||||
decidedAtUnixMs?: number
|
||||
ticketId?: string
|
||||
ticketExpiresAtUnixMs?: number
|
||||
consumedAtUnixMs?: number
|
||||
receiptHash: string
|
||||
}
|
||||
|
||||
interface AuthorizationCenterSnapshot {
|
||||
state: string
|
||||
pendingCount: number
|
||||
requests: AuthorizationRequest[]
|
||||
purgeEnabled: boolean
|
||||
}
|
||||
|
||||
interface Props {
|
||||
brokerState: string
|
||||
activeConnectionCount: number
|
||||
sessions: DirectSessionProjection[]
|
||||
}
|
||||
|
||||
const actionLabels: Record<AuthorizationRequest['action'], string> = {
|
||||
OPEN_MAINTENANCE: '打开受控维护区',
|
||||
UNMOUNT: '从当前频道卸载',
|
||||
PROMOTE_VERSION: '登记并启用新版本',
|
||||
RETIRE: '停用并保留历史坐标',
|
||||
}
|
||||
|
||||
const stateLabels: Record<AuthorizationRequest['state'], string> = {
|
||||
PENDING_HUMAN: '等待你决定', APPROVED: '已同意 · 等待执行体领取', DENIED: '已拒绝',
|
||||
EXPIRED: '已过期', CONSUMED: '票据已领取一次',
|
||||
}
|
||||
|
||||
function time(value?: number) {
|
||||
return value ? new Date(value).toLocaleString('zh-CN', { hour12: false }) : '—'
|
||||
}
|
||||
|
||||
export function HumanAuthorizationCenter({ brokerState, activeConnectionCount, sessions }: Props) {
|
||||
const [snapshot, setSnapshot] = useState<AuthorizationCenterSnapshot | null>(null)
|
||||
const [busy, setBusy] = useState('')
|
||||
const [message, setMessage] = useState('')
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
try {
|
||||
setSnapshot(await numberedInvoke<AuthorizationCenterSnapshot>('get_authorization_center'))
|
||||
setMessage('')
|
||||
} catch (error) {
|
||||
setSnapshot(null)
|
||||
setMessage(String(error).includes('AUTHENTICATED_ACCOUNT_REQUIRED') ? '登录并完成编号验证后,授权中心才会打开。' : '授权中心当前不可读取;系统不会把未知状态当成已授权。')
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
void refresh()
|
||||
const timer = window.setInterval(() => { if (!document.hidden) void refresh() }, 4_000)
|
||||
return () => window.clearInterval(timer)
|
||||
}, [refresh])
|
||||
|
||||
const decide = async (requestId: string, decision: 'APPROVE' | 'DENY') => {
|
||||
setBusy(requestId)
|
||||
setMessage('')
|
||||
try {
|
||||
await numberedInvoke('decide_authorization_request', { input: { requestId, decision } })
|
||||
setMessage(decision === 'APPROVE' ? '已签发一次性票据;它只对原执行会话和本次目标有效。' : '已拒绝;执行体不会获得继续通道。')
|
||||
await refresh()
|
||||
} catch {
|
||||
setMessage('这次决定没有写入。授权状态保持不变,请刷新后再看。')
|
||||
} finally {
|
||||
setBusy('')
|
||||
}
|
||||
}
|
||||
|
||||
const pending = snapshot?.requests.filter((item) => item.state === 'PENDING_HUMAN') || []
|
||||
const history = snapshot?.requests.filter((item) => item.state !== 'PENDING_HUMAN').slice(0, 6) || []
|
||||
const connectionState = activeConnectionCount > 0 ? '正在直连' : sessions.some((item) => item.state === 'LIVE') ? '最近仍有心跳' : '当前没有在线执行体'
|
||||
const sessionList = useMemo(() => sessions.slice(0, 6), [sessions])
|
||||
|
||||
return <div className="human-authorization-layout">
|
||||
<section className="plain-panel authorization-primary">
|
||||
<header><div><span className="kicker">HUMAN DECISION</span><h2>需要你决定的事</h2><p>人格体提出申请,系统把对象、原因、影响和退路交给你;只有你点同意,原执行会话才会收到一次性票据。</p></div><span className={pending.length ? 'status-chip attention' : 'status-chip online'}>{pending.length ? `${pending.length} 项待确认` : '没有待确认事项'}</span></header>
|
||||
{pending.length === 0 ? <div className="authorization-empty"><b>现在不需要你操作</b><span>删除、停用、维护和正式更新不会在后台静默通过。</span></div> : <div className="authorization-list">
|
||||
{pending.map((request) => <article className="authorization-card" key={request.requestId}>
|
||||
<div className="authorization-card-title"><div><small>{actionLabels[request.action]}</small><h3>{request.targetLabel}</h3><code>{request.targetNumber}</code></div><span>{stateLabels[request.state]}</span></div>
|
||||
<dl><div><dt>为什么要做</dt><dd>{request.reason}</dd></div><div><dt>会影响什么</dt><dd>{request.impact}</dd></div><div><dt>出问题怎么退</dt><dd>{request.rollbackPlan}</dd></div><div><dt>谁在申请</dt><dd>{request.requesterLabel}</dd></div><div><dt>申请到期</dt><dd>{time(request.expiresAtUnixMs)}</dd></div></dl>
|
||||
<div className="authorization-actions"><button className="secondary-button" disabled={busy === request.requestId} onClick={() => void decide(request.requestId, 'DENY')}>拒绝</button><button className="primary-button" disabled={busy === request.requestId} onClick={() => void decide(request.requestId, 'APPROVE')}>{busy === request.requestId ? '正在写入决定…' : '同意并签发一次性票据'}</button></div>
|
||||
</article>)}
|
||||
</div>}
|
||||
{message && <p className="global-message">{message}</p>}
|
||||
</section>
|
||||
|
||||
<section className="plain-panel collaboration-live">
|
||||
<header><div><span className="kicker">LIVE COLLABORATION</span><h2>当前谁连着 HoloLake</h2><p>这里显示真实本机会话和最近心跳,不把“装过 Agent”冒充成“正在连接”。</p></div><span className={activeConnectionCount > 0 ? 'status-chip online' : 'status-chip'}>{connectionState}</span></header>
|
||||
<div className="collaboration-summary"><div><b>{activeConnectionCount}</b><span>当前直连</span></div><div><b>{sessions.length}</b><span>可续接会话</span></div><div><b>{brokerState === 'READY' ? '就绪' : '等待'}</b><span>本机入口</span></div></div>
|
||||
{sessionList.length ? <div className="session-projection-list">{sessionList.map((session) => <article key={session.sessionId}><i className={session.state === 'LIVE' ? 'live' : ''}/><div><b>{session.clientInstanceId}</b><span>{session.laneId} · 最近心跳 {time(session.observedAtUnixMs)}</span></div><em>{session.state === 'LIVE' ? '在线' : '可续接'}</em></article>)}</div> : <div className="authorization-empty compact"><b>还没有执行体会话</b><span>外部编程 AI 通过 HoloLake 本机连接后,会在这里出现。</span></div>}
|
||||
</section>
|
||||
|
||||
{history.length > 0 && <details className="authorization-history"><summary>最近的授权回执 · {history.length}</summary><div>{history.map((request) => <article key={request.requestId}><div><b>{request.targetLabel}</b><span>{actionLabels[request.action]}</span></div><em>{stateLabels[request.state]}</em><small>{request.humanNumber ? `由 ${request.humanNumber} 决定 · ` : ''}{time(request.decidedAtUnixMs || request.createdAtUnixMs)}</small></article>)}</div></details>}
|
||||
</div>
|
||||
}
|
||||
|
|
@ -1176,6 +1176,70 @@ const ROUTES = {
|
|||
"moduleNumber": "HLP-NIPC-MOD-0031",
|
||||
"operationNumber": "HLP-NIPC-OP-0147",
|
||||
"targetNumber": "HLP-NIPC-TGT-0031"
|
||||
},
|
||||
"get_authorization_center": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0032",
|
||||
"operationNumber": "HLP-NIPC-OP-0148",
|
||||
"targetNumber": "HLP-NIPC-TGT-0032"
|
||||
},
|
||||
"decide_authorization_request": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0032",
|
||||
"operationNumber": "HLP-NIPC-OP-0149",
|
||||
"targetNumber": "HLP-NIPC-TGT-0032"
|
||||
},
|
||||
"get_marketplace_snapshot": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0033",
|
||||
"operationNumber": "HLP-NIPC-OP-0150",
|
||||
"targetNumber": "HLP-NIPC-TGT-0033"
|
||||
},
|
||||
"sync_marketplace_catalog": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0033",
|
||||
"operationNumber": "HLP-NIPC-OP-0151",
|
||||
"targetNumber": "HLP-NIPC-TGT-0033"
|
||||
},
|
||||
"install_marketplace_item": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0033",
|
||||
"operationNumber": "HLP-NIPC-OP-0152",
|
||||
"targetNumber": "HLP-NIPC-TGT-0033"
|
||||
},
|
||||
"uninstall_marketplace_item": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0033",
|
||||
"operationNumber": "HLP-NIPC-OP-0153",
|
||||
"targetNumber": "HLP-NIPC-TGT-0033"
|
||||
},
|
||||
"rollback_marketplace_item": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0033",
|
||||
"operationNumber": "HLP-NIPC-OP-0154",
|
||||
"targetNumber": "HLP-NIPC-TGT-0033"
|
||||
},
|
||||
"get_active_cognitive_skills": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0033",
|
||||
"operationNumber": "HLP-NIPC-OP-0155",
|
||||
"targetNumber": "HLP-NIPC-TGT-0033"
|
||||
}
|
||||
} as const
|
||||
|
||||
|
|
|
|||
|
|
@ -331,6 +331,49 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
|
|||
.receipt-row b { color: var(--content-secondary); font-size: 14px; font-weight: 590; }
|
||||
.receipt-row small { margin-top: 6px; color: var(--content-muted); font-size: 12px; }
|
||||
.system-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; }
|
||||
.human-authorization-layout { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(320px, .65fr); gap: 18px; margin-bottom: 18px; }
|
||||
.authorization-primary { grid-row: span 2; }
|
||||
.plain-panel header .kicker { display: block; margin-bottom: 8px; color: var(--accent-light); font-size: 10px; font-weight: 750; letter-spacing: .14em; }
|
||||
.status-chip.attention { border-color: color-mix(in srgb, var(--warn) 48%, transparent); color: var(--warn); background: color-mix(in srgb, var(--warn) 10%, transparent); }
|
||||
.authorization-empty { display: grid; gap: 7px; min-height: 128px; place-content: center; margin-top: 18px; padding: 24px; border: 1px dashed var(--panel-edge); border-radius: 14px; text-align: center; }
|
||||
.authorization-empty b { color: var(--content-primary); font-size: 16px; }
|
||||
.authorization-empty span { color: var(--content-muted); font-size: 13px; }
|
||||
.authorization-empty.compact { min-height: 94px; }
|
||||
.authorization-list { display: grid; gap: 14px; margin-top: 20px; }
|
||||
.authorization-card { padding: 18px; border: 1px solid color-mix(in srgb, var(--accent-light) 24%, var(--panel-edge)); border-radius: 14px; background: color-mix(in srgb, var(--primitive-glass) 80%, transparent); }
|
||||
.authorization-card-title { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
|
||||
.authorization-card-title small { color: var(--accent-light); font-weight: 700; }
|
||||
.authorization-card-title h3 { margin: 5px 0 4px; color: var(--content-primary); font-size: 18px; }
|
||||
.authorization-card-title code { color: var(--content-muted); font-size: 11px; overflow-wrap: anywhere; }
|
||||
.authorization-card-title > span { flex: none; padding: 5px 9px; border-radius: 999px; color: var(--warn); background: color-mix(in srgb, var(--warn) 10%, transparent); font-size: 11px; }
|
||||
.authorization-card dl { display: grid; gap: 10px; margin: 18px 0; }
|
||||
.authorization-card dl div { display: grid; grid-template-columns: 88px minmax(0, 1fr); gap: 12px; }
|
||||
.authorization-card dt { color: var(--content-muted); font-size: 12px; }
|
||||
.authorization-card dd { margin: 0; color: var(--content-secondary); font-size: 13px; line-height: 1.55; }
|
||||
.authorization-actions { display: flex; justify-content: flex-end; gap: 10px; }
|
||||
.authorization-actions button { min-height: 38px; padding: 0 15px; }
|
||||
.collaboration-summary { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin: 20px 0 14px; }
|
||||
.collaboration-summary div { display: grid; gap: 3px; padding: 13px 10px; border-radius: 11px; background: var(--primitive-glass); text-align: center; }
|
||||
.collaboration-summary b { color: var(--content-primary); font-size: 18px; }
|
||||
.collaboration-summary span { color: var(--content-muted); font-size: 11px; }
|
||||
.session-projection-list { display: grid; gap: 7px; }
|
||||
.session-projection-list article { display: grid; grid-template-columns: 9px minmax(0, 1fr) auto; align-items: center; gap: 10px; padding: 10px 2px; border-bottom: 1px solid var(--panel-edge); }
|
||||
.session-projection-list i { width: 8px; height: 8px; border-radius: 50%; background: var(--content-muted); }
|
||||
.session-projection-list i.live { background: var(--good); box-shadow: 0 0 10px color-mix(in srgb, var(--good) 70%, transparent); }
|
||||
.session-projection-list div { display: grid; gap: 3px; min-width: 0; }
|
||||
.session-projection-list b { overflow: hidden; color: var(--content-primary); font-size: 13px; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.session-projection-list span, .session-projection-list em { color: var(--content-muted); font-size: 11px; font-style: normal; }
|
||||
.authorization-history, .system-diagnostics { grid-column: 1 / -1; border: 1px solid var(--panel-edge); border-radius: 13px; background: var(--primitive-glass); }
|
||||
.authorization-history > summary, .system-diagnostics > summary { padding: 14px 17px; color: var(--content-secondary); font-size: 13px; font-weight: 650; cursor: pointer; }
|
||||
.authorization-history > div { display: grid; gap: 1px; padding: 0 14px 14px; }
|
||||
.authorization-history article { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 4px 14px; padding: 11px; border-radius: 9px; background: color-mix(in srgb, var(--primitive-glass-hover) 55%, transparent); }
|
||||
.authorization-history article div { display: flex; gap: 9px; }
|
||||
.authorization-history article b { color: var(--content-primary); font-size: 13px; }
|
||||
.authorization-history article span, .authorization-history article small, .authorization-history article em { color: var(--content-muted); font-size: 11px; font-style: normal; }
|
||||
.authorization-history article small { grid-column: 1 / -1; }
|
||||
.system-diagnostics { margin-top: 4px; }
|
||||
.system-diagnostics[open] > summary { border-bottom: 1px solid var(--panel-edge); }
|
||||
.system-diagnostics .system-grid { padding: 18px; }
|
||||
.connection-panel { grid-row: span 2; }
|
||||
.status-chip { padding: 6px 9px; border: 1px solid var(--panel-edge); border-radius: 999px; color: var(--content-muted); background: var(--primitive-glass); font-size: 12px; }
|
||||
.status-chip.online { color: var(--state-ready); }
|
||||
|
|
@ -366,7 +409,7 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
|
|||
.knowledge-page { grid-template-columns: 250px minmax(390px, 1fr); }
|
||||
.document-inspector { display: none; }
|
||||
.code-workbench { grid-template-columns: 220px 230px minmax(0, 1fr); }
|
||||
.overview-grid, .system-grid { grid-template-columns: 1fr; }
|
||||
.overview-grid, .system-grid, .human-authorization-layout { grid-template-columns: 1fr; }
|
||||
.overview-columns { grid-template-columns: 1fr; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
|
|
@ -783,6 +826,45 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
|
|||
}
|
||||
.receipt-message { color: var(--accent-light) !important; font-weight: 650; text-align: right; }
|
||||
|
||||
/* 分域商城是一张可核验清单,不伪装成促销卡片。 */
|
||||
.marketplace-page { max-width: 1180px; }
|
||||
.marketplace-title { align-items: flex-end; }
|
||||
.marketplace-title > button { flex: 0 0 auto; margin-bottom: 7px; }
|
||||
.marketplace-proof { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); margin: 0 0 22px; border: 1px solid var(--panel-edge); border-radius: 14px; overflow: hidden; background: color-mix(in srgb, var(--panel-bg) 78%, transparent); }
|
||||
.marketplace-proof > div { min-width: 0; padding: 14px 17px; border-right: 1px solid var(--panel-edge); }
|
||||
.marketplace-proof > div:last-child { border-right: 0; }
|
||||
.marketplace-proof span, .marketplace-proof b { display: block; }
|
||||
.marketplace-proof span { margin-bottom: 6px; color: var(--content-faint); font-size: 10px; font-weight: 700; letter-spacing: .11em; text-transform: uppercase; }
|
||||
.marketplace-proof b { overflow: hidden; color: var(--content-primary); font-size: 13px; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.marketplace-tabs { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
|
||||
.marketplace-tabs button { display: grid; gap: 6px; padding: 16px 18px; border: 1px solid var(--panel-edge); border-radius: 13px; color: var(--content-secondary); background: var(--primitive-glass); text-align: left; cursor: pointer; }
|
||||
.marketplace-tabs button.active { border-color: color-mix(in srgb, var(--primitive-cool-glow) 58%, var(--panel-edge)); background: color-mix(in srgb, var(--primitive-cool-glow) 11%, var(--panel-bg)); box-shadow: inset 0 0 22px color-mix(in srgb, var(--primitive-cool-glow) 5%, transparent); }
|
||||
.marketplace-tabs b { color: var(--content-primary); font-size: 15px; }
|
||||
.marketplace-tabs span { color: var(--content-muted); font-size: 11.5px; line-height: 1.55; }
|
||||
.marketplace-boundary { margin: 12px 0 20px; padding: 11px 14px; border-left: 2px solid var(--accent-light); color: var(--content-muted); background: color-mix(in srgb, var(--primitive-glass) 58%, transparent); font-size: 11.5px; line-height: 1.7; }
|
||||
.marketplace-list { display: grid; border-top: 1px solid var(--panel-edge); }
|
||||
.marketplace-item { padding: 22px 2px; border-bottom: 1px solid var(--panel-edge); }
|
||||
.marketplace-item > header { display: flex; justify-content: space-between; gap: 28px; }
|
||||
.marketplace-item > header > div { min-width: 0; }
|
||||
.marketplace-item > header span { color: var(--accent-light); font-size: 9.5px; font-weight: 750; letter-spacing: .15em; }
|
||||
.marketplace-item h2 { margin: 6px 0; color: var(--content-primary); font-size: 19px; }
|
||||
.marketplace-item header p { max-width: 760px; margin: 0; color: var(--content-muted); font-size: 12px; line-height: 1.65; }
|
||||
.marketplace-item header em { align-self: start; flex: 0 0 auto; padding: 5px 9px; border: 1px solid var(--panel-edge); border-radius: 999px; color: var(--content-faint); font-size: 10px; font-style: normal; }
|
||||
.marketplace-item header em.installed { border-color: color-mix(in srgb, var(--primitive-cool-glow) 45%, var(--panel-edge)); color: var(--accent-light); }
|
||||
.marketplace-item dl { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1px 26px; margin: 16px 0 0; }
|
||||
.marketplace-item dl div { display: grid; grid-template-columns: 96px minmax(0, 1fr); gap: 10px; padding: 8px 0; border-bottom: 1px solid color-mix(in srgb, var(--panel-edge) 62%, transparent); }
|
||||
.marketplace-item dt { color: var(--content-faint); font-size: 10.5px; }
|
||||
.marketplace-item dd { overflow-wrap: anywhere; margin: 0; color: var(--content-secondary); font-size: 10.5px; }
|
||||
.marketplace-actions { display: flex; justify-content: flex-end; gap: 9px; margin-top: 16px; }
|
||||
.marketplace-actions button { min-height: 36px; padding: 0 13px; border: 1px solid var(--panel-edge); border-radius: 9px; color: var(--content-secondary); background: var(--primitive-glass); font-size: 11px; font-weight: 680; cursor: pointer; }
|
||||
.marketplace-actions button:disabled { opacity: .52; cursor: default; }
|
||||
.marketplace-actions .primary-button { color: var(--button-primary-text); background: var(--button-primary-bg); }
|
||||
.marketplace-empty { padding: 52px 18px; color: var(--content-muted); text-align: center; }
|
||||
.marketplace-empty b { color: var(--content-primary); }
|
||||
.marketplace-empty p { font-size: 12px; }
|
||||
.marketplace-message { margin-top: 18px; }
|
||||
.marketplace-receipt { padding: 15px 2px 0; color: var(--content-faint); font-size: 10px; overflow-wrap: anywhere; }
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.d-main { left: 2%; } .d-sub { left: 22%; } .d-zero { right: 22%; } .d-zs { right: 2%; }
|
||||
.domain-info-card.info-main { left: 18px; } .domain-info-card.info-sub { left: calc(22% - 75px); }
|
||||
|
|
@ -791,6 +873,10 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
|
|||
.channel-knowledge { left: 4%; } .channel-code { left: 25%; } .channel-light { right: 25%; } .channel-status { right: 4%; }
|
||||
.broadcast-stream { left: 5%; }
|
||||
.personal-node-guide ol { grid-template-columns: 1fr; }
|
||||
.marketplace-proof { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.marketplace-proof > div:nth-child(2) { border-right: 0; }
|
||||
.marketplace-proof > div:nth-child(-n+2) { border-bottom: 1px solid var(--panel-edge); }
|
||||
.marketplace-item dl { grid-template-columns: 1fr; }
|
||||
}
|
||||
@media (max-height: 720px) {
|
||||
.official-hero { top: 14px; } .official-hero h1 { font-size: 24px; }
|
||||
|
|
|
|||
Loading…
Reference in a new issue