feat: admit signed education workbench module

This commit is contained in:
冰朔 2026-08-19 03:17:00 +08:00
commit df5e8f8e3c
24 changed files with 4986 additions and 20 deletions

View file

@ -0,0 +1,265 @@
{
"schema": "hololake.education-workspace/v1",
"record_id": "HLP-EDUCATION-WORKSPACE-001",
"state": "ADMITTED_INSTALLED_RUNTIME_ACCEPTED",
"domain_entry": "BRANCH_DOMAIN",
"industry": "EDUCATION",
"channel_id": "GH-EDU-INIT-001",
"package": {
"package_key": "hololake.official.education-workbench",
"official_module_number": "HLP-MOD-OFFICIAL-EDUCATION-WORKBENCH-0001",
"registration_state": "OFFICIAL_NUMBER_ACCEPTED",
"current_mount": "ACTIVE_AFTER_SIGNED_INSTALL_MOUNT_AND_SELF_TEST",
"future_distribution": "HOLOLAKE_OFFICIAL_MODULE_MARKETPLACE"
},
"foundation_dependency": {
"module_id": "hololake.builtin.channel-workbench",
"state": "BUILT_IN_FOUNDATION",
"engines": ["LEXICAL_0_49", "FORTUNE_SHEET_1_0_4"],
"rule": "EDUCATION_MODULE_CONSUMES_FOUNDATION_WITHOUT_DUPLICATING_OFFICE_ENGINES"
},
"native_storage": {
"owner": "HOLOLAKE_NATIVE_RUST_CORE",
"namespace": "education-workspace-v1",
"engine": "SQLITE",
"authenticated_account_required": true,
"cross_account_projection_allowed": false,
"public_catalog_contains_private_data": false,
"restart_readback_required": true
},
"document_module": {
"module_id": "EDU-DOCUMENT",
"state": "DEVELOPMENT_FEATURE_MOUNTED",
"engine": "CHANNEL_WORKBENCH_LEXICAL_0_49_0_WITH_EDUCATION_TEMPLATES_AND_HOLOLAKE_MARKDOWN_STATE",
"capabilities": [
"LIST",
"CREATE",
"CREATE_COPY",
"READ",
"EDIT_TITLE_AND_BODY",
"EXPLICIT_READ_AND_EDIT_MODES",
"HUMAN_SETTINGS_MENU",
"BASIC_FORMATTING_TOOLBAR",
"RICH_TEXT_COMMAND_ENGINE",
"UNDO_AND_REDO_HISTORY",
"MARKDOWN_SHORTCUTS_AND_BIDIRECTIONAL_STATE_TRANSLATION",
"TITLE_DIRECTORY_FILTER",
"FORMAL_TEACHING_TEMPLATES",
"NATIVE_READING_CANVAS",
"EXPORT_MARKDOWN_AND_HTML",
"SAVE_WITH_EXPECTED_REVISION",
"RECOVERABLE_ARCHIVE"
]
},
"table_module": {
"module_id": "EDU-TABLE",
"state": "DEVELOPMENT_FEATURE_MOUNTED",
"engine": "CHANNEL_WORKBENCH_FORTUNE_SHEET_1_0_4_FOR_REAL_CELLS_AND_FORMULAS_PLUS_TANSTACK_8_21_3_FOR_EDUCATION_VIEWS",
"capabilities": [
"LIST",
"CREATE",
"CREATE_COPY",
"READ",
"EDIT_TITLE",
"HUMAN_SETTINGS_MENU",
"ADD_AND_REMOVE_COLUMNS",
"ADD_AND_REMOVE_ROWS",
"EDIT_CELLS",
"TITLE_DIRECTORY_FILTER",
"CELL_CONTENT_FILTER",
"COLUMN_SORT_ASCENDING_DESCENDING_OR_SOURCE_ORDER",
"HEADLESS_FILTER_SORT_GROUP_AND_AGGREGATE_ROW_MODELS",
"BOUNDED_PAGINATION_ROW_MODEL_FOR_LARGE_EDITABLE_TABLES",
"GRID_AND_EDITABLE_CARD_VIEWS",
"GROUPED_CLASSIFICATION_BOARD_FROM_SELECTED_FIELD",
"LIVE_DATA_OVERVIEW_WITH_SELECTED_GROUP_AND_MEASURE",
"HEADLESS_TABLE_DATA_DETACHED_OUTSIDE_TABLE_SURFACE",
"MULTI_ROW_MULTI_COLUMN_TABULAR_PASTE",
"EXTERNAL_SPREADSHEET_IMPORT_WITH_PREWRITE_PROFILE",
"XLSX_CSV_TSV_AND_NATIVE_EXPORT",
"SAVE_WITH_EXPECTED_REVISION",
"RECOVERABLE_ARCHIVE"
],
"limits": {
"maximum_columns": 30,
"maximum_rows": 1000,
"maximum_cell_bytes": 10000
},
"sensitive_field_visibility": {
"local_default": "HIDDEN_UNTIL_HUMAN_REVEALS",
"human_can_hide": true,
"human_can_show_again": true,
"external_model_transfer_authority": "SEPARATE_EXPLICIT_PER_FILE_CONSENT"
}
},
"content_translation_layer": {
"service_id": "EDU-CONTENT-TRANSLATOR",
"state": "DETERMINISTIC_RUNTIME_READY_MODEL_ASSIST_SLOT_RESERVED",
"profile_schema": "hololake.content-profile/v1",
"import_adapter": "EDU-TABLE-IMPORT-ADAPTER/v1",
"export_adapter": "EDU-TABLE-EXPORT-ADAPTER/v1",
"order": [
"FILE_CONTAINER_SNIFF",
"PAGE_AND_STRUCTURE_INVENTORY",
"BOUNDED_LOCAL_RECOGNITION",
"EXPLAINABLE_SEMANTIC_FOCUS_INFERENCE",
"UNASSIGNED_CHANNEL_STAGING",
"HUMAN_INDUSTRY_ROUTE_DECISION",
"NATIVE_TRANSLATION",
"ATOMIC_MODULE_REGISTRATION_AFTER_CONFIRMATION",
"HUMAN_RECEIPT"
],
"import_assignment": {
"default_scope": "UNASSIGNED",
"education_directory_before_human_confirmation": false,
"existing_legacy_imports_reclassified_on_migration": true,
"source_file_mutated": false
},
"supported_imports": ["XLSX", "XLS", "XLSM", "XLSB", "ODS", "CSV", "TSV", "HOLOLAKE_NATIVE"],
"supported_exports": ["XLSX", "CSV", "TSV", "HOLOLAKE_NATIVE"],
"local_import_without_model_api": "ENABLED_FOR_ALL_SUPPORTED_FORMATS",
"semantic_focus": {
"current_mode": "DETERMINISTIC_BASELINE_ONLY",
"signals": ["SOURCE_AND_PAGE_TITLE", "COLUMN_SEMANTICS", "NUMERIC_COVERAGE"],
"low_confidence_behavior": "ASK_HUMAN_TO_SELECT_PRIMARY_MEASURE",
"model_dynamic_judgment": "RESERVED_FOR_MODEL_API_AND_AGENT_STAGE"
},
"adaptive_rendering": {
"current_mode": "DETERMINISTIC_BASELINE_WITH_EXPLAINABLE_DEGRADATION",
"editable_cell_budget": 240,
"baseline_inputs": [
"ROW_COUNT",
"COLUMN_COUNT",
"FIELD_TYPES",
"NUMERIC_COVERAGE",
"GROUP_CARDINALITY",
"FOCUS_CONFIDENCE"
],
"degradation_order": [
"LARGE_EDITABLE_GRID_TO_DYNAMIC_PAGINATION",
"NO_RELIABLE_MEASURE_TO_COUNT_SUMMARY",
"AMBIGUOUS_FOCUS_TO_HUMAN_SELECTION",
"UNSUPPORTED_STRUCTURE_TO_HUMAN_ASSISTANCE_RECEIPT"
],
"local_baseline_always_available": true,
"model_adjustment": {
"api_slot": "HOLOLAKE_MODEL_RECOGNITION_API/v1",
"state": "RESERVED_FOR_MODEL_API_AND_AGENT_STAGE",
"proposal_fields": [
"PRIMARY_FOCUS",
"GROUP_FIELD",
"MEASURE_FIELD",
"VIEW_COMPOSITION",
"PAGE_DENSITY"
],
"proposal_must_pass_local_structure_validation": true,
"proposal_must_obey_local_resource_limits": true,
"human_confirmation_before_rule_activation": true,
"model_can_mutate_source_data": false,
"failure_behavior": "RETURN_TO_LOCAL_BASELINE_WITH_RECEIPT"
}
},
"unknown_input_behavior": "RETURN_HUMAN_ASSISTANCE_RECEIPT_WITHOUT_MODULE_WRITE_OR_EXTERNAL_MODEL_TRANSFER",
"model_assist": {
"api_slot": "HOLOLAKE_MODEL_RECOGNITION_API/v1",
"current_state": "NOT_CONFIGURED",
"provider": "USER_SELECTED",
"secret_storage": "OPERATING_SYSTEM_SECRET_STORE_REQUIRED",
"file_transfer_default": "DENY_UNTIL_EXPLICIT_PER_FILE_CONSENT",
"rule_update_flow": ["MODEL_CANDIDATE", "LOCAL_VALIDATION", "HUMAN_CONFIRMATION", "VERSIONED_INSTALL"],
"learning_scopes": ["PRIVATE_ONLY", "SHARE_ANONYMIZED_RULE"]
},
"source_file_mutation_allowed": false,
"silent_truncation_allowed": false
},
"data_cleanup_module": {
"module_id": "EDU-DATA",
"state": "DEVELOPMENT_FEATURE_MOUNTED",
"source": "CURRENT_AUTHENTICATED_ACCOUNTS_EDUCATION_TABLES",
"capabilities": [
"SELECT_ACCOUNT_TABLE",
"TRIM_CELL_BOUNDARY_WHITESPACE",
"REMOVE_FULLY_EMPTY_ROWS",
"REMOVE_EXACT_DUPLICATE_ROWS_KEEP_FIRST",
"PREVIEW_BEFORE_WRITE",
"FULL_TABLE_ANALYSIS_WITH_BOUNDED_CHANGE_PREVIEW",
"ACCESSIBILITY_SAFE_NON_TABLE_CHANGE_LIST",
"HUMAN_CONFIRMATION_BEFORE_SAVE",
"SAVE_WITH_EXPECTED_REVISION"
],
"preview_change_limit": 12,
"preview_cell_value_character_limit": 120,
"full_table_analysis_is_limited_by_preview": false,
"automatic_destructive_cleanup_allowed": false,
"cross_account_data_allowed": false
},
"automation_module": {
"module_id": "EDU-AUTOMATION",
"state": "DEVELOPMENT_FEATURE_MOUNTED",
"source": "CURRENT_AUTHENTICATED_ACCOUNTS_EDUCATION_TABLES",
"capabilities": [
"CREATE_AND_SAVE_RULE",
"EQUALS_CONTAINS_EMPTY_AND_NONEMPTY_CONDITIONS",
"SET_TARGET_CELL_VALUE",
"PREVIEW_MATCHED_ROWS_AND_CHANGED_CELLS",
"HUMAN_CONFIRMATION_BEFORE_EXECUTION",
"RULE_AND_TABLE_REVISION_LOCK",
"SIGNED_PREVIEW_TOKEN",
"ATOMIC_TABLE_WRITE_AND_EXECUTION_RECEIPT",
"RECOVERABLE_RULE_ARCHIVE"
],
"background_or_scheduled_execution_allowed": false,
"cross_account_data_allowed": false,
"model_execution_authority_granted": false
},
"composition_module": {
"module_id": "EDU-COMPOSITION",
"state": "DEVELOPMENT_FEATURE_MOUNTED",
"source": "CURRENT_AUTHENTICATED_ACCOUNTS_REAL_KNOWLEDGE_CATALOG",
"capabilities": [
"NATIVE_TYPED_OBJECT",
"REGISTERED_MODULE_EXECUTION_GRAPH",
"SELECT_DIMENSION_AND_MEASURE",
"DASHBOARD_PROJECTION",
"COMPARISON_PROJECTION",
"VERTICAL_BAR_PROJECTION",
"CLASSIFICATION_PROJECTION",
"TABLE_PROJECTION",
"ONE_EXECUTION_RESULT_MULTIPLE_HUMAN_VIEWS"
],
"read_only": true,
"hardcoded_sample_data_used": false,
"model_execution_authority_granted": false
},
"remaining_slots": [],
"authority": {
"browser_local_storage_allowed": false,
"webview_direct_file_access_allowed": false,
"model_execution_authority_granted": false,
"external_application_embedded": false
},
"current_acceptance": {
"state": "PASS",
"runtime_module_number": "HLP-MOD-OFFICIAL-EDUCATION-WORKBENCH-0001",
"numbered_ipc_module": "HLP-NIPC-MOD-0024",
"numbered_operations": "HLP-NIPC-OP-0085..0102",
"signed_package_sha256": "a9f6fb7dd9c4aaf910a5d20d0a1d72dea6ab921cf36dd50d46dd88cde3a09e03",
"signed_app_binary_sha256": "028592b137fe4f19a642e1301d6fb500e2a4fde50ddb616e229de8a48090803c",
"developer_id_team": "825A9L3G7Q",
"developer_id_cdhash": "4c88261b4b573d08edcbba8d67d8afe27ad82a3e",
"runtime_receipts": {
"install": "68f296a11be19c384cb67af9f529a03cb8389bd256c153c5905451fbc0011a24",
"mount": "ddb6a51d47f6a58a72a7fc748eff71687cbd04be5dde33dbce590a7abba6af5f",
"self_test_pass": "80d59b7c9c2f14005fd8a24748a25c2be0d55198fdcb621f528267ae6dccc12a"
},
"restart_readback": {
"module_state": "ACTIVE",
"document": "冰朔教育迁移验收 · revision 2",
"table": "冰朔教育迁移验收表 · 1 row · revision 4",
"automation": "验收状态自动化 · preview 1 row / 1 cell · APPLIED revision 3 to 4"
},
"existing_user_data_deleted_or_overwritten": false,
"apple_notarization_scope": "FINAL_RELEASE_CANDIDATE_ONLY_NOT_THIS_DEBUG_ACCEPTANCE_BUNDLE"
},
"open_source_donor_assessment": "contracts/education-open-source-donor-assessment.json"
}

View file

@ -1,7 +1,7 @@
{ {
"schema": "hololake.module-donor-admission-registry/v1", "schema": "hololake.module-donor-admission-registry/v1",
"record_id": "HLP-MODULE-DONOR-ADMISSION-001", "record_id": "HLP-MODULE-DONOR-ADMISSION-001",
"state": "THREE_CANDIDATES_ADMITTED_REMAINING_DONORS_QUARANTINED", "state": "FOUR_CANDIDATES_ADMITTED_REMAINING_DONORS_QUARANTINED",
"root_rule": { "root_rule": {
"official_base": "HOLOLAKE_0.5.0_NUMBERED_IPC_ROOT", "official_base": "HOLOLAKE_0.5.0_NUMBERED_IPC_ROOT",
"repair_old_application_in_place": false, "repair_old_application_in_place": false,
@ -114,15 +114,17 @@
"admission_order": 4, "admission_order": 4,
"candidate_number": "HLP-DONOR-CAND-0004", "candidate_number": "HLP-DONOR-CAND-0004",
"name": "education_workbench", "name": "education_workbench",
"state": "QUARANTINED_PENDING_ADMISSION", "state": "ADMITTED_INSTALLED_RUNTIME_ACCEPTED",
"paths": [ "paths": [
"contracts/education-workspace.json", "contracts/education-workspace.json",
"src/modules/education-adaptive-rendering.ts", "src/modules/education-workspace",
"src/modules/education-data.ts",
"src/modules/education-document-engine.tsx",
"src-tauri/src/education_translation.rs", "src-tauri/src/education_translation.rs",
"src-tauri/src/education_workspace.rs" "src-tauri/src/education_workspace.rs"
] ],
"runtime_module_number": "HLP-MOD-OFFICIAL-EDUCATION-WORKBENCH-0001",
"numbered_ipc_module": "HLP-NIPC-MOD-0024",
"acceptance_evidence": "contracts/education-workspace.json#current_acceptance",
"boundary": "CURRENT_ACCOUNT_LOCAL; IMPORTS_DEFAULT_UNASSIGNED; CLEANUP_AND_AUTOMATION_REQUIRE_EXPLICIT_HUMAN_CONFIRMATION; MODEL_FILE_TRANSFER_DEFAULT_DENY"
}, },
{ {
"admission_order": 5, "admission_order": 5,

View file

@ -66,7 +66,10 @@
"get_native_composition_module_registry", "get_native_composition_module_registry",
"get_channel_workbench_snapshot", "get_channel_workbench_snapshot",
"get_persona_channel_body", "get_persona_channel_body",
"get_channel_growth_snapshot" "get_channel_growth_snapshot",
"get_education_workspace_snapshot",
"import_education_tables_from_dialog",
"get_education_recognition_capability"
], ],
"input_wrapper_aliases": [ "input_wrapper_aliases": [
"confirm_hololake_update_install", "confirm_hololake_update_install",
@ -113,7 +116,22 @@
"accept_persona_language_contract", "accept_persona_language_contract",
"append_persona_language", "append_persona_language",
"record_channel_growth_event", "record_channel_growth_event",
"update_channel_growth_sharing" "update_channel_growth_sharing",
"create_education_document",
"read_education_document",
"save_education_document",
"archive_education_document",
"create_education_table",
"read_education_table",
"save_education_table",
"archive_education_table",
"assign_imported_table_to_education",
"export_education_table_to_dialog",
"create_education_automation_rule",
"save_education_automation_rule",
"archive_education_automation_rule",
"preview_education_automation_rule",
"execute_education_automation_rule"
], ],
"direct_field_aliases": { "direct_field_aliases": {
"perform_code_repo_login": [ "perform_code_repo_login": [
@ -275,6 +293,11 @@
"module_number": "HLP-NIPC-MOD-0023", "module_number": "HLP-NIPC-MOD-0023",
"target_number": "HLP-NIPC-TGT-0023", "target_number": "HLP-NIPC-TGT-0023",
"internal_name": "persona_channel_body" "internal_name": "persona_channel_body"
},
{
"module_number": "HLP-NIPC-MOD-0024",
"target_number": "HLP-NIPC-TGT-0024",
"internal_name": "education_workspace"
} }
], ],
"operations": [ "operations": [
@ -1201,6 +1224,204 @@
"admission": "VERIFIED_HUMAN_ROUTE", "admission": "VERIFIED_HUMAN_ROUTE",
"effect": "STATE_CHANGE", "effect": "STATE_CHANGE",
"payload_schema": "hololake.numbered-ipc.payload/update_channel_growth_sharing/v1" "payload_schema": "hololake.numbered-ipc.payload/update_channel_growth_sharing/v1"
},
{
"operation_number": "HLP-NIPC-OP-0085",
"alias": "get_education_workspace_snapshot",
"handler": "education_workspace::get_education_workspace_snapshot",
"channel_number": "HLP-NIPC-CH-0002",
"module_number": "HLP-NIPC-MOD-0024",
"target_number": "HLP-NIPC-TGT-0024",
"admission": "VERIFIED_HUMAN_ROUTE",
"effect": "READ_OR_STATUS",
"payload_schema": "hololake.numbered-ipc.payload/get_education_workspace_snapshot/v1"
},
{
"operation_number": "HLP-NIPC-OP-0086",
"alias": "create_education_document",
"handler": "education_workspace::create_education_document",
"channel_number": "HLP-NIPC-CH-0002",
"module_number": "HLP-NIPC-MOD-0024",
"target_number": "HLP-NIPC-TGT-0024",
"admission": "VERIFIED_HUMAN_ROUTE",
"effect": "STATE_CHANGE",
"payload_schema": "hololake.numbered-ipc.payload/create_education_document/v1"
},
{
"operation_number": "HLP-NIPC-OP-0087",
"alias": "read_education_document",
"handler": "education_workspace::read_education_document",
"channel_number": "HLP-NIPC-CH-0002",
"module_number": "HLP-NIPC-MOD-0024",
"target_number": "HLP-NIPC-TGT-0024",
"admission": "VERIFIED_HUMAN_ROUTE",
"effect": "READ_OR_STATUS",
"payload_schema": "hololake.numbered-ipc.payload/read_education_document/v1"
},
{
"operation_number": "HLP-NIPC-OP-0088",
"alias": "save_education_document",
"handler": "education_workspace::save_education_document",
"channel_number": "HLP-NIPC-CH-0002",
"module_number": "HLP-NIPC-MOD-0024",
"target_number": "HLP-NIPC-TGT-0024",
"admission": "VERIFIED_HUMAN_ROUTE",
"effect": "STATE_CHANGE",
"payload_schema": "hololake.numbered-ipc.payload/save_education_document/v1"
},
{
"operation_number": "HLP-NIPC-OP-0089",
"alias": "archive_education_document",
"handler": "education_workspace::archive_education_document",
"channel_number": "HLP-NIPC-CH-0002",
"module_number": "HLP-NIPC-MOD-0024",
"target_number": "HLP-NIPC-TGT-0024",
"admission": "VERIFIED_HUMAN_ROUTE",
"effect": "STATE_CHANGE",
"payload_schema": "hololake.numbered-ipc.payload/archive_education_document/v1"
},
{
"operation_number": "HLP-NIPC-OP-0090",
"alias": "create_education_table",
"handler": "education_workspace::create_education_table",
"channel_number": "HLP-NIPC-CH-0002",
"module_number": "HLP-NIPC-MOD-0024",
"target_number": "HLP-NIPC-TGT-0024",
"admission": "VERIFIED_HUMAN_ROUTE",
"effect": "STATE_CHANGE",
"payload_schema": "hololake.numbered-ipc.payload/create_education_table/v1"
},
{
"operation_number": "HLP-NIPC-OP-0091",
"alias": "read_education_table",
"handler": "education_workspace::read_education_table",
"channel_number": "HLP-NIPC-CH-0002",
"module_number": "HLP-NIPC-MOD-0024",
"target_number": "HLP-NIPC-TGT-0024",
"admission": "VERIFIED_HUMAN_ROUTE",
"effect": "READ_OR_STATUS",
"payload_schema": "hololake.numbered-ipc.payload/read_education_table/v1"
},
{
"operation_number": "HLP-NIPC-OP-0092",
"alias": "save_education_table",
"handler": "education_workspace::save_education_table",
"channel_number": "HLP-NIPC-CH-0002",
"module_number": "HLP-NIPC-MOD-0024",
"target_number": "HLP-NIPC-TGT-0024",
"admission": "VERIFIED_HUMAN_ROUTE",
"effect": "STATE_CHANGE",
"payload_schema": "hololake.numbered-ipc.payload/save_education_table/v1"
},
{
"operation_number": "HLP-NIPC-OP-0093",
"alias": "archive_education_table",
"handler": "education_workspace::archive_education_table",
"channel_number": "HLP-NIPC-CH-0002",
"module_number": "HLP-NIPC-MOD-0024",
"target_number": "HLP-NIPC-TGT-0024",
"admission": "VERIFIED_HUMAN_ROUTE",
"effect": "STATE_CHANGE",
"payload_schema": "hololake.numbered-ipc.payload/archive_education_table/v1"
},
{
"operation_number": "HLP-NIPC-OP-0094",
"alias": "assign_imported_table_to_education",
"handler": "education_workspace::assign_imported_table_to_education",
"channel_number": "HLP-NIPC-CH-0002",
"module_number": "HLP-NIPC-MOD-0024",
"target_number": "HLP-NIPC-TGT-0024",
"admission": "VERIFIED_HUMAN_ROUTE",
"effect": "STATE_CHANGE",
"payload_schema": "hololake.numbered-ipc.payload/assign_imported_table_to_education/v1"
},
{
"operation_number": "HLP-NIPC-OP-0095",
"alias": "import_education_tables_from_dialog",
"handler": "education_translation::import_education_tables_from_dialog",
"channel_number": "HLP-NIPC-CH-0002",
"module_number": "HLP-NIPC-MOD-0024",
"target_number": "HLP-NIPC-TGT-0024",
"admission": "VERIFIED_HUMAN_ROUTE",
"effect": "STATE_CHANGE",
"payload_schema": "hololake.numbered-ipc.payload/import_education_tables_from_dialog/v1"
},
{
"operation_number": "HLP-NIPC-OP-0096",
"alias": "export_education_table_to_dialog",
"handler": "education_translation::export_education_table_to_dialog",
"channel_number": "HLP-NIPC-CH-0002",
"module_number": "HLP-NIPC-MOD-0024",
"target_number": "HLP-NIPC-TGT-0024",
"admission": "VERIFIED_HUMAN_ROUTE",
"effect": "STATE_CHANGE",
"payload_schema": "hololake.numbered-ipc.payload/export_education_table_to_dialog/v1"
},
{
"operation_number": "HLP-NIPC-OP-0097",
"alias": "get_education_recognition_capability",
"handler": "education_translation::get_education_recognition_capability",
"channel_number": "HLP-NIPC-CH-0002",
"module_number": "HLP-NIPC-MOD-0024",
"target_number": "HLP-NIPC-TGT-0024",
"admission": "VERIFIED_HUMAN_ROUTE",
"effect": "READ_OR_STATUS",
"payload_schema": "hololake.numbered-ipc.payload/get_education_recognition_capability/v1"
},
{
"operation_number": "HLP-NIPC-OP-0098",
"alias": "create_education_automation_rule",
"handler": "education_workspace::create_education_automation_rule",
"channel_number": "HLP-NIPC-CH-0002",
"module_number": "HLP-NIPC-MOD-0024",
"target_number": "HLP-NIPC-TGT-0024",
"admission": "VERIFIED_HUMAN_ROUTE",
"effect": "STATE_CHANGE",
"payload_schema": "hololake.numbered-ipc.payload/create_education_automation_rule/v1"
},
{
"operation_number": "HLP-NIPC-OP-0099",
"alias": "save_education_automation_rule",
"handler": "education_workspace::save_education_automation_rule",
"channel_number": "HLP-NIPC-CH-0002",
"module_number": "HLP-NIPC-MOD-0024",
"target_number": "HLP-NIPC-TGT-0024",
"admission": "VERIFIED_HUMAN_ROUTE",
"effect": "STATE_CHANGE",
"payload_schema": "hololake.numbered-ipc.payload/save_education_automation_rule/v1"
},
{
"operation_number": "HLP-NIPC-OP-0100",
"alias": "archive_education_automation_rule",
"handler": "education_workspace::archive_education_automation_rule",
"channel_number": "HLP-NIPC-CH-0002",
"module_number": "HLP-NIPC-MOD-0024",
"target_number": "HLP-NIPC-TGT-0024",
"admission": "VERIFIED_HUMAN_ROUTE",
"effect": "STATE_CHANGE",
"payload_schema": "hololake.numbered-ipc.payload/archive_education_automation_rule/v1"
},
{
"operation_number": "HLP-NIPC-OP-0101",
"alias": "preview_education_automation_rule",
"handler": "education_workspace::preview_education_automation_rule",
"channel_number": "HLP-NIPC-CH-0002",
"module_number": "HLP-NIPC-MOD-0024",
"target_number": "HLP-NIPC-TGT-0024",
"admission": "VERIFIED_HUMAN_ROUTE",
"effect": "READ_OR_STATUS",
"payload_schema": "hololake.numbered-ipc.payload/preview_education_automation_rule/v1"
},
{
"operation_number": "HLP-NIPC-OP-0102",
"alias": "execute_education_automation_rule",
"handler": "education_workspace::execute_education_automation_rule",
"channel_number": "HLP-NIPC-CH-0002",
"module_number": "HLP-NIPC-MOD-0024",
"target_number": "HLP-NIPC-TGT-0024",
"admission": "VERIFIED_HUMAN_ROUTE",
"effect": "STATE_CHANGE",
"payload_schema": "hololake.numbered-ipc.payload/execute_education_automation_rule/v1"
} }
] ]
} }

View file

@ -0,0 +1,33 @@
{
"schema": "hololake.module-package/v1",
"manifest": {
"moduleNumber": "HLP-MOD-OFFICIAL-EDUCATION-WORKBENCH-0001",
"registrationClass": "OFFICIAL_LIGHTHOUSE",
"displayName": "教育行业工作台",
"version": "0.1.0",
"minimumHostVersion": "0.5.0",
"adapter": "education-workbench-v1",
"contentDigest": "fd80c9957865a58cf62679528f110ae355b1385ad1dd988e31217ea3da66e65b",
"permissions": ["EDUCATION_WORKSPACE_READ", "EDUCATION_DOCUMENT_WRITE", "EDUCATION_TABLE_WRITE", "EDUCATION_IMPORT_READ_FILE", "EDUCATION_EXPORT_WRITE_FILE", "EDUCATION_IMPORT_ASSIGNMENT_WRITE", "EDUCATION_AUTOMATION_WRITE", "EDUCATION_AUTOMATION_EXECUTE"],
"userDataSchema": "hololake.module-data/education-workbench/v1",
"selfTest": {
"kind": "DECLARATIVE_SCHEMA_V1",
"expectedContentDigest": "fd80c9957865a58cf62679528f110ae355b1385ad1dd988e31217ea3da66e65b"
}
},
"payload": {
"entry": "education-workspace",
"adapterConfig": {
"dataBoundary": "CURRENT_AUTHENTICATED_ACCOUNT_LOCAL",
"foundationModule": "HLP-MOD-LOCAL-CHANNEL-WORKBENCH-0001",
"documentEngine": "LEXICAL_0_49",
"spreadsheetEngine": "FORTUNE_SHEET_1_0_4",
"importDefaultScope": "UNASSIGNED",
"automationExecution": "PREVIEW_TOKEN_AND_HUMAN_CONFIRMATION",
"modelFileTransferDefault": "DENY",
"maximumColumns": 30,
"maximumRows": 1000,
"persistent": true
}
}
}

View file

@ -0,0 +1 @@
dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVReHJHQ2hTVDYvRTFEa084d1l6MnB2OFVvVy9abW90S3RFMEVOdmIvWW1hbkllWmw4NHBJaDVNTGxpZHV4M0tVNUNUOExmdHAwSGw4V0NPQ1RPUjlRU0h0Tm50clFlekFJPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzg3MDc5ODQzCWZpbGU6SExQLU1PRC1PRkZJQ0lBTC1FRFVDQVRJT04tV09SS0JFTkNILTAwMDEtMC4xLjAuZ2htb2QKVXZzVklwQk5KR3Q3bkRZaXBYV1ArOGx6b3BKSWVyUkpvK1dyTXdpWnBhMXNqVnJKcU5vZ1BTdzlFWjlXRTJ1RlFhMXAwMjhXcFBEQnQxRzV4SFVuQkE9PQo=

View file

@ -54,7 +54,7 @@
}, },
{ {
"recordId": "HLP-NUMBERED-IPC-ROOT-001", "recordId": "HLP-NUMBERED-IPC-ROOT-001",
"sha256": "bd8446d66e03e220948f46c4ea9d0fbe65c517cdac500d548f2b643fb24649d6" "sha256": "be7f2a746a78284d4c763d9da62a381bf0c9430c56a923398e1948d2a0ae1fc0"
}, },
{ {
"recordId": "HLP-NBROKER-ROOT-001", "recordId": "HLP-NBROKER-ROOT-001",
@ -69,7 +69,7 @@
"everyAcceptedCallHasEvidenceClass": true, "everyAcceptedCallHasEvidenceClass": true,
"mismatchedCoordinate": "FAIL_CLOSED" "mismatchedCoordinate": "FAIL_CLOSED"
}, },
"routeCount": 106, "routeCount": 124,
"routes": [ "routes": [
{ {
"transport": "DIRECT_LOCAL_NUMBERED_BROKER", "transport": "DIRECT_LOCAL_NUMBERED_BROKER",
@ -1163,6 +1163,258 @@
"evidence": "HASH_CHAINED_NUMBERED_IPC_RECEIPT", "evidence": "HASH_CHAINED_NUMBERED_IPC_RECEIPT",
"path": "HLP-NUMBER-WORLD-ROOT-001/TAURI/HLP-NIPC-CH-0002/HLP-NIPC-MOD-0023/HLP-NIPC-OP-0084/HLP-NIPC-TGT-0023" "path": "HLP-NUMBER-WORLD-ROOT-001/TAURI/HLP-NIPC-CH-0002/HLP-NIPC-MOD-0023/HLP-NIPC-OP-0084/HLP-NIPC-TGT-0023"
}, },
{
"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-0024",
"operationNumber": "HLP-NIPC-OP-0085",
"targetNumber": "HLP-NIPC-TGT-0024",
"alias": "get_education_workspace_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-0024/HLP-NIPC-OP-0085/HLP-NIPC-TGT-0024"
},
{
"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-0024",
"operationNumber": "HLP-NIPC-OP-0086",
"targetNumber": "HLP-NIPC-TGT-0024",
"alias": "create_education_document",
"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-0024/HLP-NIPC-OP-0086/HLP-NIPC-TGT-0024"
},
{
"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-0024",
"operationNumber": "HLP-NIPC-OP-0087",
"targetNumber": "HLP-NIPC-TGT-0024",
"alias": "read_education_document",
"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-0024/HLP-NIPC-OP-0087/HLP-NIPC-TGT-0024"
},
{
"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-0024",
"operationNumber": "HLP-NIPC-OP-0088",
"targetNumber": "HLP-NIPC-TGT-0024",
"alias": "save_education_document",
"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-0024/HLP-NIPC-OP-0088/HLP-NIPC-TGT-0024"
},
{
"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-0024",
"operationNumber": "HLP-NIPC-OP-0089",
"targetNumber": "HLP-NIPC-TGT-0024",
"alias": "archive_education_document",
"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-0024/HLP-NIPC-OP-0089/HLP-NIPC-TGT-0024"
},
{
"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-0024",
"operationNumber": "HLP-NIPC-OP-0090",
"targetNumber": "HLP-NIPC-TGT-0024",
"alias": "create_education_table",
"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-0024/HLP-NIPC-OP-0090/HLP-NIPC-TGT-0024"
},
{
"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-0024",
"operationNumber": "HLP-NIPC-OP-0091",
"targetNumber": "HLP-NIPC-TGT-0024",
"alias": "read_education_table",
"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-0024/HLP-NIPC-OP-0091/HLP-NIPC-TGT-0024"
},
{
"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-0024",
"operationNumber": "HLP-NIPC-OP-0092",
"targetNumber": "HLP-NIPC-TGT-0024",
"alias": "save_education_table",
"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-0024/HLP-NIPC-OP-0092/HLP-NIPC-TGT-0024"
},
{
"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-0024",
"operationNumber": "HLP-NIPC-OP-0093",
"targetNumber": "HLP-NIPC-TGT-0024",
"alias": "archive_education_table",
"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-0024/HLP-NIPC-OP-0093/HLP-NIPC-TGT-0024"
},
{
"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-0024",
"operationNumber": "HLP-NIPC-OP-0094",
"targetNumber": "HLP-NIPC-TGT-0024",
"alias": "assign_imported_table_to_education",
"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-0024/HLP-NIPC-OP-0094/HLP-NIPC-TGT-0024"
},
{
"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-0024",
"operationNumber": "HLP-NIPC-OP-0095",
"targetNumber": "HLP-NIPC-TGT-0024",
"alias": "import_education_tables_from_dialog",
"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-0024/HLP-NIPC-OP-0095/HLP-NIPC-TGT-0024"
},
{
"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-0024",
"operationNumber": "HLP-NIPC-OP-0096",
"targetNumber": "HLP-NIPC-TGT-0024",
"alias": "export_education_table_to_dialog",
"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-0024/HLP-NIPC-OP-0096/HLP-NIPC-TGT-0024"
},
{
"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-0024",
"operationNumber": "HLP-NIPC-OP-0097",
"targetNumber": "HLP-NIPC-TGT-0024",
"alias": "get_education_recognition_capability",
"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-0024/HLP-NIPC-OP-0097/HLP-NIPC-TGT-0024"
},
{
"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-0024",
"operationNumber": "HLP-NIPC-OP-0098",
"targetNumber": "HLP-NIPC-TGT-0024",
"alias": "create_education_automation_rule",
"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-0024/HLP-NIPC-OP-0098/HLP-NIPC-TGT-0024"
},
{
"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-0024",
"operationNumber": "HLP-NIPC-OP-0099",
"targetNumber": "HLP-NIPC-TGT-0024",
"alias": "save_education_automation_rule",
"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-0024/HLP-NIPC-OP-0099/HLP-NIPC-TGT-0024"
},
{
"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-0024",
"operationNumber": "HLP-NIPC-OP-0100",
"targetNumber": "HLP-NIPC-TGT-0024",
"alias": "archive_education_automation_rule",
"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-0024/HLP-NIPC-OP-0100/HLP-NIPC-TGT-0024"
},
{
"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-0024",
"operationNumber": "HLP-NIPC-OP-0101",
"targetNumber": "HLP-NIPC-TGT-0024",
"alias": "preview_education_automation_rule",
"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-0024/HLP-NIPC-OP-0101/HLP-NIPC-TGT-0024"
},
{
"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-0024",
"operationNumber": "HLP-NIPC-OP-0102",
"targetNumber": "HLP-NIPC-TGT-0024",
"alias": "execute_education_automation_rule",
"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-0024/HLP-NIPC-OP-0102/HLP-NIPC-TGT-0024"
},
{ {
"transport": "TAURI_WEBVIEW_NUMBERED_IPC", "transport": "TAURI_WEBVIEW_NUMBERED_IPC",
"protocolVersion": "HLP-NIPC-v1", "protocolVersion": "HLP-NIPC-v1",

View file

@ -0,0 +1,53 @@
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/education-workspace.json'))
const registry = JSON.parse(read('contracts/numbered-ipc-registry.json'))
const modulePackage = JSON.parse(read('fixtures/module-packages/HLP-MOD-OFFICIAL-EDUCATION-WORKBENCH-0001-0.1.0.ghmod'))
const workspaceRust = read('src-tauri/src/education_workspace.rs')
const translationRust = read('src-tauri/src/education_translation.rs')
const frontend = [
read('src/modules/education-workspace/index.tsx'),
read('src/modules/education-workspace/education-data.ts'),
read('src/modules/education-workspace/education-adaptive-rendering.ts'),
].join('\n')
test('education is an official signed declarative adapter over the shared office foundation', () => {
assert.equal(contract.package.official_module_number, 'HLP-MOD-OFFICIAL-EDUCATION-WORKBENCH-0001')
assert.equal(contract.foundation_dependency.module_id, 'hololake.builtin.channel-workbench')
assert.equal(modulePackage.manifest.registrationClass, 'OFFICIAL_LIGHTHOUSE')
assert.equal(modulePackage.manifest.adapter, 'education-workbench-v1')
assert.equal(modulePackage.payload.adapterConfig.foundationModule, 'HLP-MOD-LOCAL-CHANNEL-WORKBENCH-0001')
assert.equal(modulePackage.payload.adapterConfig.importDefaultScope, 'UNASSIGNED')
assert.equal(modulePackage.payload.adapterConfig.modelFileTransferDefault, 'DENY')
assert.equal(modulePackage.manifest.permissions.length, 8)
})
test('all education effects cross the exact numbered adapter routes', () => {
const routes = registry.operations.filter((route) => route.module_number === 'HLP-NIPC-MOD-0024')
assert.deepEqual(routes.map((route) => route.operation_number), Array.from({ length: 18 }, (_, index) => `HLP-NIPC-OP-${String(index + 85).padStart(4, '0')}`))
assert.ok(routes.every((route) => route.target_number === 'HLP-NIPC-TGT-0024'))
assert.ok(routes.every((route) => route.admission === 'VERIFIED_HUMAN_ROUTE'))
assert.doesNotMatch(frontend, /from ['"]@tauri-apps\/api\/core['"]/)
assert.doesNotMatch(workspaceRust, /#\[tauri::command\]/)
assert.doesNotMatch(translationRust, /#\[tauri::command\]/)
assert.match(workspaceRust, /require_active_module_adapter/)
})
test('legacy silent corruption and automatic destructive behavior are rejected', () => {
assert.doesNotMatch(workspaceRust, /serde_json::from_str[^\n]+unwrap_or_default/)
assert.match(workspaceRust, /deny_unknown_fields/)
assert.match(frontend, /生成只读预览/)
assert.match(frontend, /我确认,写入清理结果/)
assert.match(frontend, /生成只读预览令牌/)
assert.match(frontend, /我确认,按此令牌执行/)
assert.match(frontend, /确认归入教育频道/)
})
test('education UI is independently lazy-loaded and reuses, rather than duplicates, office engines', () => {
assert.match(read('src/main.tsx'), /lazy\(\(\) => import\('\.\/modules\/education-workspace'\)/)
assert.match(read('src/modules/education-workspace/education-document-engine.tsx'), /channel-workbench\/document-engine/)
assert.match(read('src/modules/education-workspace/index.tsx'), /channel-workbench\/spreadsheet-engine/)
})

View file

@ -7,8 +7,8 @@ test('identity, webview and direct broker numbers compile into one unique eviden
const generated = JSON.parse(readFileSync(new URL('../generated/unified-number-coordinate-tree.json', import.meta.url), 'utf8')) const generated = JSON.parse(readFileSync(new URL('../generated/unified-number-coordinate-tree.json', import.meta.url), 'utf8'))
assert.deepEqual(generated, compileUnifiedNumberTree()) assert.deepEqual(generated, compileUnifiedNumberTree())
assert.equal(generated.recordId, 'HLP-UNIFIED-NUMBER-TREE-001') assert.equal(generated.recordId, 'HLP-UNIFIED-NUMBER-TREE-001')
assert.equal(generated.routeCount, 106) assert.equal(generated.routeCount, 124)
assert.equal(new Set(generated.routes.map((route) => route.path)).size, 106) assert.equal(new Set(generated.routes.map((route) => route.path)).size, 124)
assert.equal(generated.invariants.everyPhysicalCallHasNumberedRoute, true) assert.equal(generated.invariants.everyPhysicalCallHasNumberedRoute, true)
assert.equal(generated.invariants.everyAcceptedCallHasEvidenceClass, true) assert.equal(generated.invariants.everyAcceptedCallHasEvidenceClass, true)
assert.ok(generated.routes.every((route) => route.admission && route.evidence)) assert.ok(generated.routes.every((route) => route.admission && route.evidence))

View file

@ -268,6 +268,22 @@ dependencies = [
"system-deps", "system-deps",
] ]
[[package]]
name = "calamine"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138646b9af2c5d7f1804ea4bf93afc597737d2bd4f7341d67c48b03316976eb1"
dependencies = [
"byteorder",
"chrono",
"codepage",
"encoding_rs",
"log",
"quick-xml 0.31.0",
"serde",
"zip 2.4.2",
]
[[package]] [[package]]
name = "camino" name = "camino"
version = "1.2.5" version = "1.2.5"
@ -393,6 +409,15 @@ dependencies = [
"cc", "cc",
] ]
[[package]]
name = "codepage"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48f68d061bc2828ae826206326e61251aca94c1e4a5305cf52d9138639c918b4"
dependencies = [
"encoding_rs",
]
[[package]] [[package]]
name = "combine" name = "combine"
version = "4.6.7" version = "4.6.7"
@ -570,6 +595,27 @@ dependencies = [
"syn 2.0.115", "syn 2.0.115",
] ]
[[package]]
name = "csv"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe"
dependencies = [
"csv-core",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "csv-core"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "ctor" name = "ctor"
version = "0.8.0" version = "0.8.0"
@ -890,6 +936,15 @@ version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
[[package]]
name = "encoding_rs"
version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.2" version = "1.0.2"
@ -1495,7 +1550,10 @@ name = "hololake-native-desktop"
version = "0.5.0" version = "0.5.0"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"calamine",
"csv",
"dirs", "dirs",
"encoding_rs",
"fs2", "fs2",
"futures-util", "futures-util",
"interprocess", "interprocess",
@ -1503,6 +1561,7 @@ dependencies = [
"reqwest", "reqwest",
"ring", "ring",
"rusqlite", "rusqlite",
"rust_xlsxwriter",
"serde", "serde",
"serde_json", "serde_json",
"tauri", "tauri",
@ -2834,7 +2893,7 @@ checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"indexmap 2.14.0", "indexmap 2.14.0",
"quick-xml", "quick-xml 0.41.0",
"serde", "serde",
"time", "time",
] ]
@ -2981,6 +3040,16 @@ dependencies = [
"psl-types", "psl-types",
] ]
[[package]]
name = "quick-xml"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
dependencies = [
"encoding_rs",
"memchr",
]
[[package]] [[package]]
name = "quick-xml" name = "quick-xml"
version = "0.41.0" version = "0.41.0"
@ -3351,6 +3420,17 @@ dependencies = [
"smallvec", "smallvec",
] ]
[[package]]
name = "rust_xlsxwriter"
version = "0.64.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5f47f5318c1e512e57c07781559367577b1eb9618325cf1574cd30d38b112c5"
dependencies = [
"lazy_static",
"regex",
"zip 0.6.6",
]
[[package]] [[package]]
name = "rustc-hash" name = "rustc-hash"
version = "2.1.3" version = "2.1.3"
@ -4311,7 +4391,7 @@ dependencies = [
"tokio", "tokio",
"url", "url",
"windows-sys 0.60.2", "windows-sys 0.60.2",
"zip", "zip 4.6.1",
] ]
[[package]] [[package]]
@ -5847,6 +5927,35 @@ dependencies = [
"syn 2.0.115", "syn 2.0.115",
] ]
[[package]]
name = "zip"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
dependencies = [
"byteorder",
"crc32fast",
"crossbeam-utils",
"flate2",
]
[[package]]
name = "zip"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50"
dependencies = [
"arbitrary",
"crc32fast",
"crossbeam-utils",
"displaydoc",
"flate2",
"indexmap 2.14.0",
"memchr",
"thiserror 2.0.20",
"zopfli",
]
[[package]] [[package]]
name = "zip" name = "zip"
version = "4.6.1" version = "4.6.1"
@ -5864,3 +5973,15 @@ name = "zmij"
version = "1.0.23" version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
[[package]]
name = "zopfli"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
dependencies = [
"bumpalo",
"crc32fast",
"log",
"simd-adler32",
]

View file

@ -21,6 +21,9 @@ interprocess = "2.4.2"
ring = "0.17" ring = "0.17"
rusqlite = { version = "0.31", features = ["bundled"] } rusqlite = { version = "0.31", features = ["bundled"] }
base64 = "0.22" base64 = "0.22"
calamine = { version = "=0.26.1", features = ["dates"] }
csv = "=1.3.0"
encoding_rs = "0.8"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
tauri = { version = "=2.10.2", features = ["devtools"] } tauri = { version = "=2.10.2", features = ["devtools"] }
@ -31,6 +34,7 @@ tauri-plugin-dialog = "=2.7.2"
uuid = { version = "1", features = ["v4"] } uuid = { version = "1", features = ["v4"] }
url = "2" url = "2"
reqwest = { version = "0.13.2", default-features = false, features = ["cookies", "form", "json", "rustls", "stream"] } reqwest = { version = "0.13.2", default-features = false, features = ["cookies", "form", "json", "rustls", "stream"] }
rust_xlsxwriter = "=0.64.2"
tokio = { version = "1", features = ["time"] } tokio = { version = "1", features = ["time"] }
futures-util = "0.3" futures-util = "0.3"
minisign-verify = "0.2.5" minisign-verify = "0.2.5"

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -7,6 +7,8 @@ mod code_repo_login;
mod direct_local_broker; mod direct_local_broker;
mod direct_local_session; mod direct_local_session;
mod dynamic_capability_routing; mod dynamic_capability_routing;
mod education_translation;
mod education_workspace;
mod enterprise_work_channel; mod enterprise_work_channel;
mod glp_envelope; mod glp_envelope;
mod gls_bootstrap_compiler; mod gls_bootstrap_compiler;

View file

@ -45,6 +45,13 @@ const PERSONA_CHANNEL_BODY_PACKAGE: &[u8] = include_bytes!(
const PERSONA_CHANNEL_BODY_SIGNATURE: &str = include_str!( const PERSONA_CHANNEL_BODY_SIGNATURE: &str = include_str!(
"../../fixtures/module-packages/HLP-MOD-LOCAL-PERSONA-CHANNEL-BODY-0001-0.1.0.ghmod.sig" "../../fixtures/module-packages/HLP-MOD-LOCAL-PERSONA-CHANNEL-BODY-0001-0.1.0.ghmod.sig"
); );
const EDUCATION_WORKBENCH_NUMBER: &str = "HLP-MOD-OFFICIAL-EDUCATION-WORKBENCH-0001";
const EDUCATION_WORKBENCH_PACKAGE: &[u8] = include_bytes!(
"../../fixtures/module-packages/HLP-MOD-OFFICIAL-EDUCATION-WORKBENCH-0001-0.1.0.ghmod"
);
const EDUCATION_WORKBENCH_SIGNATURE: &str = include_str!(
"../../fixtures/module-packages/HLP-MOD-OFFICIAL-EDUCATION-WORKBENCH-0001-0.1.0.ghmod.sig"
);
struct BundledModuleSource { struct BundledModuleSource {
module_number: &'static str, module_number: &'static str,
@ -68,6 +75,11 @@ const BUNDLED_MODULES: &[BundledModuleSource] = &[
package: PERSONA_CHANNEL_BODY_PACKAGE, package: PERSONA_CHANNEL_BODY_PACKAGE,
signature: PERSONA_CHANNEL_BODY_SIGNATURE, signature: PERSONA_CHANNEL_BODY_SIGNATURE,
}, },
BundledModuleSource {
module_number: EDUCATION_WORKBENCH_NUMBER,
package: EDUCATION_WORKBENCH_PACKAGE,
signature: EDUCATION_WORKBENCH_SIGNATURE,
},
]; ];
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -1513,7 +1525,7 @@ mod tests {
assert_eq!(package.manifest.adapter, "channel-workbench-v1"); assert_eq!(package.manifest.adapter, "channel-workbench-v1");
assert_eq!(package.manifest.permissions.len(), 4); assert_eq!(package.manifest.permissions.len(), 4);
assert!(is_sha256(&digest)); assert!(is_sha256(&digest));
assert_eq!(BUNDLED_MODULES.len(), 3); assert_eq!(BUNDLED_MODULES.len(), 4);
} }
#[test] #[test]
@ -1534,6 +1546,25 @@ mod tests {
assert!(is_sha256(&digest)); assert!(is_sha256(&digest));
} }
#[test]
fn bundled_education_workbench_is_an_official_signed_module() {
let (_, package, digest) = read_verified_package_bytes(
EDUCATION_WORKBENCH_PACKAGE,
EDUCATION_WORKBENCH_SIGNATURE,
RELEASE_TRUST_RAW,
)
.unwrap();
assert_eq!(package.manifest.module_number, EDUCATION_WORKBENCH_NUMBER);
assert_eq!(package.manifest.registration_class, "OFFICIAL_LIGHTHOUSE");
assert_eq!(package.manifest.adapter, "education-workbench-v1");
assert_eq!(package.manifest.permissions.len(), 8);
assert_eq!(
package.payload["adapterConfig"]["importDefaultScope"],
"UNASSIGNED"
);
assert!(is_sha256(&digest));
}
#[cfg(unix)] #[cfg(unix)]
#[test] #[test]
fn symlinked_package_input_is_rejected_before_signature_processing() { fn symlinked_package_input_is_rejected_before_signature_processing() {

View file

@ -53,7 +53,7 @@ fn validate_tree() -> Result<(), String> {
|| tree.record_id != "HLP-UNIFIED-NUMBER-TREE-001" || tree.record_id != "HLP-UNIFIED-NUMBER-TREE-001"
|| tree.state != "MACHINE_COMPILED_STARTUP_ENFORCED" || tree.state != "MACHINE_COMPILED_STARTUP_ENFORCED"
|| tree.root_number != "HLP-NUMBER-WORLD-ROOT-001" || tree.root_number != "HLP-NUMBER-WORLD-ROOT-001"
|| tree.route_count != 106 || tree.route_count != 124
|| tree.routes.len() != tree.route_count || tree.routes.len() != tree.route_count
|| !tree.invariants.number_is_stable_coordinate_not_authority || !tree.invariants.number_is_stable_coordinate_not_authority
|| !tree.invariants.path_is_unique_navigation || !tree.invariants.path_is_unique_navigation

View file

@ -934,7 +934,7 @@ mod tests {
#[test] #[test]
fn registry_is_closed_and_contains_every_migrated_command() { fn registry_is_closed_and_contains_every_migrated_command() {
let registry = load_registry().unwrap(); let registry = load_registry().unwrap();
assert_eq!(registry.operations.len(), 84); assert_eq!(registry.operations.len(), 102);
assert!(!registry.runtime.legacy_direct_commands_allowed); assert!(!registry.runtime.legacy_direct_commands_allowed);
} }

View file

@ -167,6 +167,67 @@ pub(crate) async fn dispatch(
"channel_growth::update_channel_growth_sharing" => { "channel_growth::update_channel_growth_sharing" => {
json(crate::channel_growth::update_channel_growth_sharing(app, input(&payload)?).await?) json(crate::channel_growth::update_channel_growth_sharing(app, input(&payload)?).await?)
} }
"education_workspace::get_education_workspace_snapshot" => {
json(crate::education_workspace::get_education_workspace_snapshot(app).await?)
}
"education_workspace::create_education_document" => json(
crate::education_workspace::create_education_document(app, input(&payload)?).await?,
),
"education_workspace::read_education_document" => {
json(crate::education_workspace::read_education_document(app, input(&payload)?).await?)
}
"education_workspace::save_education_document" => {
json(crate::education_workspace::save_education_document(app, input(&payload)?).await?)
}
"education_workspace::archive_education_document" => json(
crate::education_workspace::archive_education_document(app, input(&payload)?).await?,
),
"education_workspace::create_education_table" => {
json(crate::education_workspace::create_education_table(app, input(&payload)?).await?)
}
"education_workspace::read_education_table" => {
json(crate::education_workspace::read_education_table(app, input(&payload)?).await?)
}
"education_workspace::save_education_table" => {
json(crate::education_workspace::save_education_table(app, input(&payload)?).await?)
}
"education_workspace::archive_education_table" => {
json(crate::education_workspace::archive_education_table(app, input(&payload)?).await?)
}
"education_workspace::assign_imported_table_to_education" => json(
crate::education_workspace::assign_imported_table_to_education(app, input(&payload)?)
.await?,
),
"education_translation::import_education_tables_from_dialog" => {
json(crate::education_translation::import_education_tables_from_dialog(app).await?)
}
"education_translation::export_education_table_to_dialog" => json(
crate::education_translation::export_education_table_to_dialog(app, input(&payload)?)
.await?,
),
"education_translation::get_education_recognition_capability" => {
json(crate::education_translation::get_education_recognition_capability(app)?)
}
"education_workspace::create_education_automation_rule" => json(
crate::education_workspace::create_education_automation_rule(app, input(&payload)?)
.await?,
),
"education_workspace::save_education_automation_rule" => json(
crate::education_workspace::save_education_automation_rule(app, input(&payload)?)
.await?,
),
"education_workspace::archive_education_automation_rule" => json(
crate::education_workspace::archive_education_automation_rule(app, input(&payload)?)
.await?,
),
"education_workspace::preview_education_automation_rule" => json(
crate::education_workspace::preview_education_automation_rule(app, input(&payload)?)
.await?,
),
"education_workspace::execute_education_automation_rule" => json(
crate::education_workspace::execute_education_automation_rule(app, input(&payload)?)
.await?,
),
"local_development_bridge::acquire_development_write_lane" => json( "local_development_bridge::acquire_development_write_lane" => json(
crate::local_development_bridge::acquire_development_write_lane(app, input(&payload)?) crate::local_development_bridge::acquire_development_write_lane(app, input(&payload)?)
.await?, .await?,

View file

@ -7,6 +7,7 @@ import type { ChannelWorkbenchSnapshot } from './modules/channel-workbench'
const ChannelWorkbenchStudio = lazy(() => import('./modules/channel-workbench').then((module) => ({ default: module.ChannelWorkbenchStudio }))) 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 }))) const PersonaChannelBody = lazy(() => import('./modules/persona-channel-body').then((module) => ({ default: module.PersonaChannelBody })))
const EducationWorkspace = lazy(() => import('./modules/education-workspace').then((module) => ({ default: module.EducationWorkspace })))
const TAG_TINTS = ['tag-lavender', 'tag-sky', 'tag-mint', 'tag-amber', 'tag-rose', 'tag-slate'] const TAG_TINTS = ['tag-lavender', 'tag-sky', 'tag-mint', 'tag-amber', 'tag-rose', 'tag-slate']
@ -53,7 +54,7 @@ import './design-tokens.css'
import './styles.css' import './styles.css'
type ThemeId = 'night' | 'dawn' | 'nebula' | 'candle' | 'clear' type ThemeId = 'night' | 'dawn' | 'nebula' | 'candle' | 'clear'
type ViewId = 'overview' | 'knowledge' | 'composition' | 'workbench' | 'persona' | 'code' | 'receipts' | 'system' type ViewId = 'overview' | 'knowledge' | 'composition' | 'workbench' | 'education' | 'persona' | 'code' | 'receipts' | 'system'
type WorldStage = 'domain' | 'heart' | 'heartbeat' | 'lightLake' | 'love' | 'tomorrow' | 'bottle' | 'channel' | 'enterpriseWork' | 'personalNodeGuide' | 'tool' type WorldStage = 'domain' | 'heart' | 'heartbeat' | 'lightLake' | 'love' | 'tomorrow' | 'bottle' | 'channel' | 'enterpriseWork' | 'personalNodeGuide' | 'tool'
type KnowledgeSource = 'native' | 'legacy' type KnowledgeSource = 'native' | 'legacy'
@ -367,7 +368,7 @@ const previewCode: CodeChannelSnapshot = { state: 'UNAVAILABLE', channels: [], a
const themes: Array<{ id: ThemeId; name: string }> = [ const themes: Array<{ id: ThemeId; name: string }> = [
{ id: 'night', name: '夜湖星光' }, { id: 'dawn', name: '晨湖曦光' }, { id: 'nebula', name: '星云紫夜' }, { id: 'candle', name: '烛畔暖湖' }, { id: 'clear', name: '清浅澄湖' }, { id: 'night', name: '夜湖星光' }, { id: 'dawn', name: '晨湖曦光' }, { id: 'nebula', name: '星云紫夜' }, { id: 'candle', name: '烛畔暖湖' }, { id: 'clear', name: '清浅澄湖' },
] ]
const viewLabels: Record<ViewId, string> = { overview: '个人频道', knowledge: '知识空间', composition: '结构组合', workbench: '频道资料工作台', persona: '人格频道本体', code: '人格代码频道', receipts: '运行回执', system: '系统详情' } const viewLabels: Record<ViewId, string> = { overview: '个人频道', knowledge: '知识空间', composition: '结构组合', workbench: '频道资料工作台', education: '教育工作台', persona: '人格频道本体', code: '人格代码频道', receipts: '运行回执', system: '系统详情' }
const domainGates = [ const domainGates = [
{ domain: 'BRANCH_DOMAIN', className: 'd-sub', title: '光湖分域', gate: 'GATE 02 · ONLINE', facts: [ { 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'], ['域标识', 'BRANCH_DOMAIN'], ['责任主体', '花尔 · TCS-GL-0005∞'], ['人格体主体', '爆米花 · PER-BMH001 · AGE'], ['关系支持', '糖星云 · PER-TXY001 · AGE'], ['工作仓库', 'PRIVATE · 1 · LIVE'],
@ -607,6 +608,9 @@ function HoloLakeApp() {
const [personaBodyModule, setPersonaBodyModule] = useState<BundledModuleDescriptor | null>(null) const [personaBodyModule, setPersonaBodyModule] = useState<BundledModuleDescriptor | null>(null)
const [personaBodyBusy, setPersonaBodyBusy] = useState(false) const [personaBodyBusy, setPersonaBodyBusy] = useState(false)
const [personaBodyMessage, setPersonaBodyMessage] = useState('') const [personaBodyMessage, setPersonaBodyMessage] = useState('')
const [educationModule, setEducationModule] = useState<BundledModuleDescriptor | null>(null)
const [educationBusy, setEducationBusy] = useState(false)
const [educationMessage, setEducationMessage] = useState('')
const [expanded, setExpanded] = useState<Set<string>>(() => new Set(['导入'])) const [expanded, setExpanded] = useState<Set<string>>(() => new Set(['导入']))
const [editing, setEditing] = useState(false) const [editing, setEditing] = useState(false)
const [draft, setDraft] = useState('') const [draft, setDraft] = useState('')
@ -1157,6 +1161,27 @@ function HoloLakeApp() {
finally { setPersonaBodyBusy(false) } finally { setPersonaBodyBusy(false) }
} }
const refreshEducationModule = async () => {
try {
const catalog = await invoke<BundledModuleDescriptor[]>('get_bundled_module_catalog')
const module = catalog.find((item) => item.moduleNumber === 'HLP-MOD-OFFICIAL-EDUCATION-WORKBENCH-0001') || null
setEducationModule(module)
return module
} catch (error) { setEducationMessage(humanError(error, 'system')); return null }
}
const openEducation = () => { openWorldTool('education'); void refreshEducationModule() }
const activateEducationModule = async () => {
setEducationBusy(true)
setEducationMessage('正在验证官方签名、登记模块编号、确认八项边界并执行自检……')
try {
await invoke('activate_bundled_module', { input: { moduleNumber: 'HLP-MOD-OFFICIAL-EDUCATION-WORKBENCH-0001', humanConfirmedPermissionExpansion: true } })
const module = await refreshEducationModule()
if (!module || module.installedState !== 'ACTIVE') throw new Error('HOLOLAKE_MODULE_NOT_ACTIVE')
setEducationMessage('教育工作台已通过签名、编号、权限和自检验收。')
} catch (error) { setEducationMessage(humanError(error, 'system')) }
finally { setEducationBusy(false) }
}
const cloneCodeChannel = async (event: React.FormEvent) => { const cloneCodeChannel = async (event: React.FormEvent) => {
event.preventDefault() event.preventDefault()
if (!cloneUrl.trim()) return if (!cloneUrl.trim()) return
@ -1557,6 +1582,20 @@ function HoloLakeApp() {
</section> </section>
) )
const renderEducation = () => (
<section className="full-workbench education-workbench-world">
{educationModule?.installedState === 'ACTIVE'
? <Suspense fallback={<div className="workbench-empty"><p></p></div>}><EducationWorkspace onBack={() => setWorldStage(toolReturnStage)}/></Suspense>
: <div className="workbench-empty">
<span></span><h2></h2>
<p></p>
<code>HLP-MOD-OFFICIAL-EDUCATION-WORKBENCH-0001 · 8 </code>
<button className="primary-button" type="button" disabled={educationBusy} onClick={() => void activateEducationModule()}>{educationBusy ? '正在验收官方模块…' : '确认八项边界并启用'}</button>
{educationMessage && <p>{educationMessage}</p>}
</div>}
</section>
)
const renderCode = () => ( const renderCode = () => (
<section className="full-workbench code-workbench"> <section className="full-workbench code-workbench">
<aside className="code-channels"> <aside className="code-channels">
@ -1826,6 +1865,7 @@ function HoloLakeApp() {
<LakePool className="channel-knowledge" title="知识空间" meta={`${knowledge.uniqueDocumentCount} 唯一页`} onClick={() => openWorldTool('knowledge')}/> <LakePool className="channel-knowledge" title="知识空间" meta={`${knowledge.uniqueDocumentCount} 唯一页`} onClick={() => openWorldTool('knowledge')}/>
<LakePool className="channel-light" title="结构组合" meta="签名模块 · 只读知识投影" onClick={() => { openWorldTool('composition'); void refreshCompositionModule() }}/> <LakePool className="channel-light" title="结构组合" meta="签名模块 · 只读知识投影" onClick={() => { openWorldTool('composition'); void refreshCompositionModule() }}/>
<LakePool className="channel-code" title="资料工作台" meta="签名模块 · 文档与智能表格" onClick={openWorkbench}/> <LakePool className="channel-code" title="资料工作台" meta="签名模块 · 文档与智能表格" onClick={openWorkbench}/>
<LakePool className="channel-education" title="教育工作台" meta="官方编号模块 · 教育文档、表格与自动化" onClick={openEducation}/>
{timeAuthorityModule && <LakePool className="channel-time" title="时间主控" meta={beijingCoordinate ? `光湖历第 ${beijingCoordinate.guanghuEraDay}` : '北京时间正在流动'} onClick={openEraTimeline}/>} {timeAuthorityModule && <LakePool className="channel-time" title="时间主控" meta={beijingCoordinate ? `光湖历第 ${beijingCoordinate.guanghuEraDay}` : '北京时间正在流动'} onClick={openEraTimeline}/>}
<LakePool className="channel-status" title="湖面天气" meta={connectionLabel} onClick={() => openWorldTool('system')}/> <LakePool className="channel-status" title="湖面天气" meta={connectionLabel} onClick={() => openWorldTool('system')}/>
</section>} </section>}
@ -1839,7 +1879,7 @@ function HoloLakeApp() {
</section>} </section>}
{worldStage === 'tool' && <section className={`world-tool world-tool-${view}`}> {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> <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 === '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 === 'persona' ? renderPersonaBody() : view === 'code' ? renderCode() : view === 'receipts' ? renderReceipts() : renderSystem()}</div>
</section>} </section>}
</main> </main>
<footer className="world-footer"><b> · </b><span>GH-AIOS</span></footer> <footer className="world-footer"><b> · </b><span>GH-AIOS</span></footer>

View file

@ -0,0 +1,81 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
export const EDUCATION_EDITABLE_CELL_BUDGET = 240
export type EducationAdaptiveView = 'grid' | 'cards' | 'board' | 'dashboard'
export interface EducationAdaptiveInputs {
rowCount: number
columnCount: number
numericColumnCount: number
groupableColumnCount: number
focusConfidence: 'high' | 'medium' | 'low'
}
export interface EducationModelRenderProposal {
primaryFocus: string
groupColumnIndex?: number
measureColumnIndex?: number
views: EducationAdaptiveView[]
pageSize?: number
}
export interface EducationAdaptiveRenderPlan {
source: 'LOCAL_BASELINE' | 'MODEL_PROPOSAL'
state: 'READY' | 'DEGRADED' | 'WAITING_HUMAN_CONFIRMATION' | 'MODEL_PROPOSAL_REJECTED'
views: EducationAdaptiveView[]
pageSize: number
primaryFocus: string
reasons: string[]
}
export function educationEditablePageSize(columnCount: number): number {
const boundedColumns = Math.max(1, Math.min(30, columnCount))
return Math.max(5, Math.min(20, Math.floor(EDUCATION_EDITABLE_CELL_BUDGET / boundedColumns)))
}
export function buildEducationBaselineRenderPlan(inputs: EducationAdaptiveInputs): EducationAdaptiveRenderPlan {
const reasons: string[] = []
const views: EducationAdaptiveView[] = ['grid', 'cards']
if (inputs.groupableColumnCount > 0) views.push('board')
else reasons.push('没有可靠分类字段,分类看板已降级停用。')
if (inputs.numericColumnCount > 0) views.push('dashboard')
else reasons.push('没有可靠数值字段,数据概览降级为计数摘要。')
if (inputs.focusConfidence === 'low') reasons.push('重点置信度不足,保留人工选择,不自动突出字段。')
if (inputs.rowCount * inputs.columnCount > EDUCATION_EDITABLE_CELL_BUDGET) reasons.push('可编辑单元格超过本机安全预算,已启用动态分页。')
return {
source: 'LOCAL_BASELINE',
state: reasons.length ? 'DEGRADED' : 'READY',
views,
pageSize: educationEditablePageSize(inputs.columnCount),
primaryFocus: inputs.focusConfidence === 'low' ? '重点待选择' : '本地基础算法重点',
reasons,
}
}
export function validateEducationModelRenderProposal(
inputs: EducationAdaptiveInputs,
proposal: EducationModelRenderProposal,
humanConfirmed: boolean,
): EducationAdaptiveRenderPlan {
const baseline = buildEducationBaselineRenderPlan(inputs)
const reasons: string[] = []
const uniqueViews = [...new Set(proposal.views)]
if (!proposal.primaryFocus.trim() || proposal.primaryFocus.length > 120) reasons.push('模型建议的重点标题无效。')
if (!uniqueViews.length || uniqueViews.some((view) => !['grid', 'cards', 'board', 'dashboard'].includes(view))) reasons.push('模型建议包含未登记视图。')
if (proposal.groupColumnIndex !== undefined && (proposal.groupColumnIndex < 0 || proposal.groupColumnIndex >= inputs.columnCount)) reasons.push('模型建议的分类字段不存在。')
if (proposal.measureColumnIndex !== undefined && (proposal.measureColumnIndex < 0 || proposal.measureColumnIndex >= inputs.columnCount)) reasons.push('模型建议的统计字段不存在。')
const requestedPageSize = proposal.pageSize ?? baseline.pageSize
const maximumSafePageSize = educationEditablePageSize(inputs.columnCount)
if (!Number.isInteger(requestedPageSize) || requestedPageSize < 5 || requestedPageSize > maximumSafePageSize) reasons.push('模型建议的页面密度超过本机安全预算。')
if (reasons.length) return { ...baseline, state: 'MODEL_PROPOSAL_REJECTED', reasons: [...reasons, '已优雅退回本地基础算法。'] }
if (!humanConfirmed) return { ...baseline, state: 'WAITING_HUMAN_CONFIRMATION', reasons: ['模型建议已通过本地校验,等待用户确认后启用。'] }
return {
source: 'MODEL_PROPOSAL',
state: 'READY',
views: uniqueViews,
pageSize: requestedPageSize,
primaryFocus: proposal.primaryFocus.trim(),
reasons: ['模型建议已通过本地结构校验、资源上限与用户确认。'],
}
}

View file

@ -0,0 +1,75 @@
export interface EducationDataColumn { columnId: string; title: string }
export interface EducationDataRow { rowId: string; cells: string[] }
export interface EducationDataTable { columns: EducationDataColumn[]; rows: EducationDataRow[] }
export const EDUCATION_CLEANUP_CHANGE_PREVIEW_LIMIT = 12
export type EducationDataChangeKind = 'TRIM_CELL' | 'REMOVE_EMPTY_ROW' | 'REMOVE_DUPLICATE_ROW'
export interface EducationDataChangePreview {
kind: EducationDataChangeKind
rowNumber: number
columnTitle: string
before: string
after: string
}
export interface EducationDataAnalysis {
originalRows: number
cleanedRows: EducationDataRow[]
emptyRows: number
duplicateRows: number
trimmedCells: number
changeCount: number
changePreview: EducationDataChangePreview[]
}
function previewCell(value: string): string {
const singleLine = value.replace(/\t/g, '⇥').replace(/\r?\n/g, '↵').replace(/ /g, '·')
return singleLine.length > 120 ? `${singleLine.slice(0, 117)}` : singleLine
}
export function analyzeEducationTableData(table: EducationDataTable): EducationDataAnalysis {
let emptyRows = 0
let duplicateRows = 0
let trimmedCells = 0
let changeCount = 0
const seen = new Set<string>()
const cleanedRows: EducationDataRow[] = []
const changePreview: EducationDataChangePreview[] = []
const addPreview = (change: EducationDataChangePreview) => {
changeCount += 1
if (changePreview.length < EDUCATION_CLEANUP_CHANGE_PREVIEW_LIMIT) changePreview.push(change)
}
for (const [rowIndex, row] of table.rows.entries()) {
const cells = table.columns.map((_, index) => {
const original = row.cells[index] || ''
const cleaned = original.trim()
if (cleaned !== original) {
trimmedCells += 1
addPreview({
kind: 'TRIM_CELL',
rowNumber: rowIndex + 1,
columnTitle: table.columns[index]?.title || `字段 ${index + 1}`,
before: previewCell(original),
after: previewCell(cleaned),
})
}
return cleaned
})
if (cells.every((cell) => !cell)) {
emptyRows += 1
addPreview({ kind: 'REMOVE_EMPTY_ROW', rowNumber: rowIndex + 1, columnTitle: '整行', before: '完整空行', after: '移除' })
continue
}
const signature = JSON.stringify(cells)
if (seen.has(signature)) {
duplicateRows += 1
addPreview({ kind: 'REMOVE_DUPLICATE_ROW', rowNumber: rowIndex + 1, columnTitle: '整行', before: '与前文完全重复', after: '移除' })
continue
}
seen.add(signature)
cleanedRows.push({ ...row, cells })
}
return { originalRows: table.rows.length, cleanedRows, emptyRows, duplicateRows, trimmedCells, changeCount, changePreview }
}

View file

@ -0,0 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import ChannelDocumentEngine from '../channel-workbench/document-engine'
export default function EducationDocumentEngine({ body, onChange }: { body: string; onChange: (body: string) => void }) {
return <ChannelDocumentEngine body={body} onChange={onChange} />
}

View file

@ -0,0 +1,169 @@
import { lazy, Suspense, useEffect, useMemo, useState } from 'react'
import { numberedInvoke as invoke } from '../numbered-ipc'
import { analyzeEducationTableData, type EducationDataAnalysis } from './education-data'
import './styles.css'
const EducationDocumentEngine = lazy(() => import('./education-document-engine'))
const ChannelSpreadsheetEngine = lazy(() => import('../channel-workbench/spreadsheet-engine'))
export const EDUCATION_MODULE_NUMBER = 'HLP-MOD-OFFICIAL-EDUCATION-WORKBENCH-0001'
interface EducationColumn { columnId: string; title: string }
interface EducationRow { rowId: string; cells: string[] }
interface DocumentSummary { documentId: string; title: string; revision: number; updatedAtUnixMs: number }
interface TableSummary { tableId: string; title: string; revision: number; columnCount: number; rowCount: number; updatedAtUnixMs: number }
interface UnassignedTableSummary { tableId: string; title: string; columnCount: number; rowCount: number; importedAtUnixMs: number }
interface EducationDocument extends DocumentSummary { body: string; createdAtUnixMs: number }
interface EducationTable extends TableSummary { columns: EducationColumn[]; rows: EducationRow[]; createdAtUnixMs: number }
interface AutomationRule {
ruleId: string; title: string; tableId: string; conditionColumnId: string; operator: string; conditionValue: string
actionColumnId: string; actionValue: string; enabled: boolean; revision: number
}
interface AutomationPreview { state: string; ruleId: string; tableId: string; ruleRevision: number; tableRevision: number; matchedRows: number; changedCells: number; previewToken: string }
interface EducationSnapshot {
schema: string; state: string; documents: DocumentSummary[]; tables: TableSummary[]; automationRules: AutomationRule[]
recentAutomationRuns: Array<{ runId: string; state: string; changedCells: number }>
recentImports: Array<{ importId: string; sourceFilename: string; sourceFormat: string; tableCount: number }>
unassignedTables: UnassignedTableSummary[]; documentCount: number; tableCount: number; automationRuleCount: number; unassignedCount: number
}
interface RecognitionCapability { state: string; modelApiState: string; fileTransferDefault: string; deterministicAdapterIds: string[] }
type Mode = 'overview' | 'documents' | 'tables' | 'cleanup' | 'automation' | 'import'
function cleanError(error: unknown) { return String(error).replace(/^Error:\s*/, '') }
function localTime(value: number) { return new Intl.DateTimeFormat('zh-CN', { dateStyle: 'medium', timeStyle: 'short' }).format(new Date(value)) }
export function EducationWorkspace({ onBack }: { onBack: () => void }) {
const [snapshot, setSnapshot] = useState<EducationSnapshot | null>(null)
const [capability, setCapability] = useState<RecognitionCapability | null>(null)
const [mode, setMode] = useState<Mode>('overview')
const [busy, setBusy] = useState(false)
const [message, setMessage] = useState('')
const [document, setDocument] = useState<EducationDocument | null>(null)
const [documentTitle, setDocumentTitle] = useState('')
const [documentBody, setDocumentBody] = useState('')
const [table, setTable] = useState<EducationTable | null>(null)
const [tableTitle, setTableTitle] = useState('')
const [tableValue, setTableValue] = useState<{ columns: EducationColumn[]; rows: EducationRow[] }>({ columns: [], rows: [] })
const [cleanup, setCleanup] = useState<EducationDataAnalysis | null>(null)
const [rule, setRule] = useState<AutomationRule | null>(null)
const [preview, setPreview] = useState<AutomationPreview | null>(null)
const refresh = async () => {
const [nextSnapshot, nextCapability] = await Promise.all([
invoke<EducationSnapshot>('get_education_workspace_snapshot'),
invoke<RecognitionCapability>('get_education_recognition_capability'),
])
setSnapshot(nextSnapshot)
setCapability(nextCapability)
return nextSnapshot
}
useEffect(() => { void refresh().catch((error) => setMessage(cleanError(error))) }, [])
const run = async (action: () => Promise<void>) => {
setBusy(true); setMessage('')
try { await action() } catch (error) { setMessage(cleanError(error)) } finally { setBusy(false) }
}
const openDocument = (documentId: string) => run(async () => {
const next = await invoke<EducationDocument>('read_education_document', { input: { documentId } })
setDocument(next); setDocumentTitle(next.title); setDocumentBody(next.body); setMode('documents')
})
const createDocument = () => run(async () => {
const next = await invoke<EducationDocument>('create_education_document', { input: { title: '冰朔教育迁移验收' } })
setDocument(next); setDocumentTitle(next.title); setDocumentBody(next.body); setMode('documents'); await refresh()
})
const saveDocument = () => document && run(async () => {
const next = await invoke<EducationDocument>('save_education_document', { input: { documentId: document.documentId, title: documentTitle, body: documentBody, expectedRevision: document.revision } })
setDocument(next); setDocumentTitle(next.title); setDocumentBody(next.body); setMessage(`文档修订 ${next.revision} 已写入当前账号。`); await refresh()
})
const archiveDocument = () => document && run(async () => {
await invoke('archive_education_document', { input: { documentId: document.documentId } }); setDocument(null); await refresh(); setMessage('文档已进入可恢复归档。')
})
const openTable = (tableId: string, nextMode: Mode = 'tables') => run(async () => {
const next = await invoke<EducationTable>('read_education_table', { input: { tableId } })
setTable(next); setTableTitle(next.title); setTableValue({ columns: next.columns, rows: next.rows }); setCleanup(null); setPreview(null); setMode(nextMode)
})
const createTable = () => run(async () => {
const next = await invoke<EducationTable>('create_education_table', { input: { title: '冰朔教育迁移验收表' } })
setTable(next); setTableTitle(next.title); setTableValue({ columns: next.columns, rows: next.rows }); setMode('tables'); await refresh()
})
const addAcceptanceRow = () => {
if (!tableValue.columns.length) return
setTableValue((value) => ({ ...value, rows: [...value.rows, { rowId: `ROW-${crypto.randomUUID()}`, cells: value.columns.map((_, index) => index === 0 ? '冰朔验收' : index === value.columns.length - 1 ? '已迁移' : '教育工作台') }] }))
}
const saveTableValue = (rows = tableValue.rows, success = '表格已保存。') => table && run(async () => {
const next = await invoke<EducationTable>('save_education_table', { input: { tableId: table.tableId, title: tableTitle, columns: tableValue.columns, rows, expectedRevision: table.revision } })
setTable(next); setTableTitle(next.title); setTableValue({ columns: next.columns, rows: next.rows }); setCleanup(null); setPreview(null); setMessage(`${success} 当前修订 ${next.revision}`); await refresh()
})
const archiveTable = () => table && run(async () => {
await invoke('archive_education_table', { input: { tableId: table.tableId } }); setTable(null); await refresh(); setMessage('表格已进入可恢复归档。')
})
const analyzeCleanup = () => setCleanup(analyzeEducationTableData(tableValue))
const applyCleanup = () => cleanup && saveTableValue(cleanup.cleanedRows, '清理结果经人工确认后已保存。')
const createRule = () => table && table.columns.length > 0 && run(async () => {
const next = await invoke<AutomationRule>('create_education_automation_rule', { input: {
title: '验收状态自动化', tableId: table.tableId, conditionColumnId: table.columns[0].columnId, operator: 'CONTAINS', conditionValue: '冰朔',
actionColumnId: table.columns[table.columns.length - 1].columnId, actionValue: '自动化已验收',
} })
setRule(next); setPreview(null); setMode('automation'); await refresh(); setMessage('规则已建立,但尚未执行。')
})
const chooseRule = async (nextRule: AutomationRule) => {
setRule(nextRule); setPreview(null)
if (!table || table.tableId !== nextRule.tableId) await openTable(nextRule.tableId, 'automation')
else setMode('automation')
}
const previewRule = () => rule && table && run(async () => {
const next = await invoke<AutomationPreview>('preview_education_automation_rule', { input: { ruleId: rule.ruleId, expectedRuleRevision: rule.revision, expectedTableRevision: table.revision } })
setPreview(next); setMessage(`只读预览:命中 ${next.matchedRows} 行,将改变 ${next.changedCells} 个单元格。`)
})
const executeRule = () => rule && table && preview && run(async () => {
const receipt = await invoke<{ state: string; changedCells: number; toTableRevision: number }>('execute_education_automation_rule', { input: {
ruleId: rule.ruleId, expectedRuleRevision: preview.ruleRevision, expectedTableRevision: preview.tableRevision, previewToken: preview.previewToken,
} })
setPreview(null); await openTable(table.tableId, 'automation'); await refresh(); setMessage(`自动化 ${receipt.state},改变 ${receipt.changedCells} 个单元格,修订 ${receipt.toTableRevision}`)
})
const importTables = () => run(async () => {
const outcome = await invoke<{ state: string } | null>('import_education_tables_from_dialog')
await refresh(); setMode('import'); setMessage(outcome ? `导入结果:${outcome.state}。数据仍在未归属区,需人工确认。` : '已取消导入。')
})
const assignTable = (tableId: string) => run(async () => {
await invoke('assign_imported_table_to_education', { input: { tableId } }); await refresh(); setMessage('已由人工确认归入教育频道。')
})
const exportTable = (format: string) => table && run(async () => {
const result = await invoke<{ state: string; targetFilename: string } | null>('export_education_table_to_dialog', { input: { table: { tableId: table.tableId, title: tableTitle, columns: tableValue.columns, rows: tableValue.rows, revision: table.revision }, format } })
setMessage(result ? `${result.targetFilename} 已导出。` : '已取消导出。')
})
const analysis = useMemo(() => table ? analyzeEducationTableData(tableValue) : null, [table, tableValue])
if (!snapshot) return <section className="education-world"><button type="button" onClick={onBack}> </button><p>{message || '正在读取当前账号的教育工作台……'}</p></section>
return <section className="education-world">
<header className="education-hero"><button type="button" onClick={onBack}> </button><div><span>OFFICIAL NUMBERED MODULE</span><h1></h1><p>沿</p></div><aside><b>{snapshot.state}</b><small>{EDUCATION_MODULE_NUMBER}</small></aside></header>
<nav className="education-tabs" aria-label="教育工作台功能">{(['overview','documents','tables','cleanup','automation','import'] as Mode[]).map((item) => <button type="button" key={item} className={mode === item ? 'active' : ''} onClick={() => setMode(item)}>{{ overview:'总览', documents:'文档', tables:'表格', cleanup:'数据清理', automation:'自动化', import:'导入与识别' }[item]}</button>)}</nav>
{mode === 'overview' && <div className="education-overview">
<article><span></span><strong>{snapshot.documentCount}</strong><button type="button" disabled={busy} onClick={createDocument}></button></article>
<article><span></span><strong>{snapshot.tableCount}</strong><button type="button" disabled={busy} onClick={createTable}></button></article>
<article><span></span><strong>{snapshot.unassignedCount}</strong><button type="button" disabled={busy} onClick={importTables}></button></article>
<article><span></span><strong>{snapshot.automationRuleCount}</strong><small></small></article>
<section><h2></h2>{snapshot.documents.map((item) => <button type="button" key={item.documentId} onClick={() => void openDocument(item.documentId)}><b>{item.title}</b><small> {item.revision} · {localTime(item.updatedAtUnixMs)}</small></button>)}</section>
<section><h2></h2>{snapshot.tables.map((item) => <button type="button" key={item.tableId} onClick={() => void openTable(item.tableId)}><b>{item.title}</b><small>{item.rowCount} × {item.columnCount} · {item.revision}</small></button>)}</section>
</div>}
{mode === 'documents' && <div className="education-editor-shell"><aside><button type="button" onClick={createDocument}> </button>{snapshot.documents.map((item) => <button type="button" className={document?.documentId === item.documentId ? 'active' : ''} key={item.documentId} onClick={() => void openDocument(item.documentId)}>{item.title}<small> {item.revision}</small></button>)}</aside><main>{document ? <><header><input aria-label="教育文档标题" value={documentTitle} onChange={(event) => setDocumentTitle(event.target.value)}/><div><button type="button" className="danger" onClick={archiveDocument}></button><button type="button" disabled={busy} onClick={saveDocument}></button></div></header><Suspense fallback={<p></p>}><EducationDocumentEngine key={`${document.documentId}-${document.revision}`} body={documentBody} onChange={setDocumentBody}/></Suspense></> : <p></p>}</main></div>}
{mode === 'tables' && <div className="education-editor-shell"><aside><button type="button" onClick={createTable}> </button>{snapshot.tables.map((item) => <button type="button" className={table?.tableId === item.tableId ? 'active' : ''} key={item.tableId} onClick={() => void openTable(item.tableId)}>{item.title}<small>{item.rowCount} · {item.revision}</small></button>)}</aside><main>{table ? <><header><input aria-label="教育表格标题" value={tableTitle} onChange={(event) => setTableTitle(event.target.value)}/><div><button type="button" onClick={addAcceptanceRow}></button><button type="button" onClick={() => void exportTable('XLSX')}> XLSX</button><button type="button" className="danger" onClick={archiveTable}></button><button type="button" disabled={busy} onClick={() => void saveTableValue()}></button></div></header><Suspense fallback={<p></p>}><ChannelSpreadsheetEngine key={`${table.tableId}-${table.revision}`} tableId={table.tableId} title={tableTitle} columns={tableValue.columns} rows={tableValue.rows} onChange={setTableValue}/></Suspense></> : <p></p>}</main></div>}
{mode === 'cleanup' && <div className="education-action-layout"><aside><h2></h2>{snapshot.tables.map((item) => <button type="button" key={item.tableId} onClick={() => void openTable(item.tableId, 'cleanup')}>{item.title}</button>)}</aside><main><h2></h2>{table && analysis ? <><p>{table.title} · {analysis.originalRows} </p><div className="education-metrics"><b>{analysis.trimmedCells}<small></small></b><b>{analysis.emptyRows}<small></small></b><b>{analysis.duplicateRows}<small></small></b></div><button type="button" onClick={analyzeCleanup}></button>{cleanup && <section className="education-preview"><h3> · {cleanup.changeCount} </h3>{cleanup.changePreview.map((item, index) => <p key={`${item.kind}-${index}`}><b>{item.kind}</b> {item.rowNumber} / {item.columnTitle}{item.before} {item.after}</p>)}<button type="button" disabled={busy} onClick={applyCleanup}></button></section>}</> : <p></p>}</main></div>}
{mode === 'automation' && <div className="education-action-layout"><aside><h2></h2>{snapshot.automationRules.map((item) => <button type="button" className={rule?.ruleId === item.ruleId ? 'active' : ''} key={item.ruleId} onClick={() => void chooseRule(item)}>{item.title}<small> {item.revision}</small></button>)}</aside><main><h2></h2>{table && <button type="button" onClick={createRule}></button>}{rule && table ? <section className="education-rule"><p><b>{rule.title}</b></p><p>{rule.operator} {rule.conditionValue} · {rule.actionValue}</p><button type="button" disabled={busy} onClick={previewRule}></button>{preview && <div className="education-preview"><p> {preview.matchedRows} {preview.changedCells} </p><code>{preview.previewToken.slice(0, 20)}</code><button type="button" disabled={busy} onClick={executeRule}></button></div>}</section> : <p></p>}</main></div>}
{mode === 'import' && <div className="education-import"><header><div><h2></h2><p></p></div><button type="button" disabled={busy} onClick={importTables}> XLSX / CSV / TSV / ODS</button></header><dl><div><dt></dt><dd>{capability?.deterministicAdapterIds.join(' · ')}</dd></div><div><dt></dt><dd>{capability?.modelApiState}</dd></div><div><dt></dt><dd>{capability?.fileTransferDefault}</dd></div></dl><section><h3></h3>{snapshot.unassignedTables.length ? snapshot.unassignedTables.map((item) => <article key={item.tableId}><div><b>{item.title}</b><small>{item.rowCount} × {item.columnCount} </small></div><button type="button" onClick={() => void assignTable(item.tableId)}></button></article>) : <p></p>}</section></div>}
{message && <p className="education-message" role="status">{message}</p>}
</section>
}

View file

@ -0,0 +1,8 @@
.education-world{min-height:100%;padding:28px;color:#eaf3f3;background:radial-gradient(circle at 75% 0,rgba(84,210,190,.13),transparent 36%),linear-gradient(150deg,#0b1216,#111c21 58%,#0b1318)}
.education-world button,.education-world input{font:inherit}.education-hero{display:grid;grid-template-columns:auto 1fr auto;gap:22px;align-items:start}.education-hero>button{background:transparent;border:0;color:#91aaa9;padding:10px}.education-hero span{font-size:11px;letter-spacing:.18em;color:#74d6c5}.education-hero h1{font-size:38px;margin:5px 0}.education-hero p{margin:0;color:#9db1b2}.education-hero aside{display:flex;flex-direction:column;align-items:flex-end;gap:5px}.education-hero aside b{color:#79d9c7}.education-hero aside small{color:#718588;max-width:260px;text-align:right}
.education-tabs{display:flex;gap:8px;margin:24px 0;border-bottom:1px solid #263a3d}.education-tabs button{border:0;background:transparent;color:#8da3a4;padding:12px 15px;border-bottom:2px solid transparent}.education-tabs button.active{color:#eafffb;border-color:#63d5c2}
.education-overview{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}.education-overview>article,.education-overview>section,.education-import,.education-action-layout>aside,.education-action-layout>main{background:#121f24;border:1px solid #26393d;border-radius:16px;padding:18px}.education-overview>article{display:flex;flex-direction:column;gap:8px}.education-overview>article strong{font-size:30px}.education-overview button,.education-editor-shell button,.education-action-layout button,.education-import button{border:1px solid #345155;background:#172b30;color:#dffbf6;border-radius:9px;padding:9px 12px}.education-overview>section{grid-column:span 2}.education-overview>section>button{width:100%;display:flex;justify-content:space-between;margin-top:8px;text-align:left}.education-overview small{color:#8ba0a2}
.education-editor-shell{display:grid;grid-template-columns:230px minmax(0,1fr);gap:14px;min-height:650px}.education-editor-shell>aside{display:flex;flex-direction:column;gap:7px;background:#0e191d;border:1px solid #26393d;border-radius:14px;padding:12px}.education-editor-shell>aside button{text-align:left;display:flex;flex-direction:column}.education-editor-shell button.active{border-color:#62d3c0;background:#1b3537}.education-editor-shell>main{min-width:0;background:#f7faf9;color:#172326;border-radius:14px;overflow:hidden}.education-editor-shell>main>header{display:flex;justify-content:space-between;gap:12px;padding:12px;background:#e8f0ee;border-bottom:1px solid #ccd9d6}.education-editor-shell>main>header input{flex:1;border:0;background:transparent;font-size:19px;font-weight:700;outline:0}.education-editor-shell>main>header>div{display:flex;gap:7px}.education-editor-shell button.danger{color:#ffc4bd;border-color:#734841;background:#33211f}
.education-action-layout{display:grid;grid-template-columns:250px 1fr;gap:14px}.education-action-layout aside{display:flex;flex-direction:column;gap:8px}.education-action-layout main{min-height:420px}.education-metrics{display:flex;gap:12px;margin:18px 0}.education-metrics b{font-size:26px;background:#0d191d;border-radius:12px;padding:14px 22px}.education-metrics small{font-size:11px;display:block;color:#91a6a7}.education-preview{margin-top:15px;padding:16px;border:1px solid #40726d;background:#102725;border-radius:12px}.education-preview p{color:#c9d7d5}.education-preview code{display:block;color:#78dbc9;margin:10px 0}.education-rule{margin-top:18px}
.education-import>header{display:flex;justify-content:space-between;align-items:center}.education-import dl{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}.education-import dl div{background:#0d181c;border-radius:10px;padding:12px}.education-import dt{color:#789092;font-size:12px}.education-import dd{margin:5px 0 0;overflow-wrap:anywhere}.education-import section article{display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-top:1px solid #273b3e}.education-import section article div{display:flex;flex-direction:column}.education-import small{color:#89a0a1}.education-message{position:sticky;bottom:14px;background:#17302f;border:1px solid #4e8b83;color:#d8fff8;border-radius:12px;padding:12px 16px;margin:16px 0 0}
@media(max-width:900px){.education-overview{grid-template-columns:repeat(2,1fr)}.education-editor-shell,.education-action-layout{grid-template-columns:1fr}.education-editor-shell>aside{max-height:180px;overflow:auto}.education-hero{grid-template-columns:1fr}.education-hero>button{justify-self:start}.education-import dl{grid-template-columns:1fr}}

View file

@ -672,6 +672,150 @@ const ROUTES = {
"moduleNumber": "HLP-NIPC-MOD-0023", "moduleNumber": "HLP-NIPC-MOD-0023",
"operationNumber": "HLP-NIPC-OP-0084", "operationNumber": "HLP-NIPC-OP-0084",
"targetNumber": "HLP-NIPC-TGT-0023" "targetNumber": "HLP-NIPC-TGT-0023"
},
"get_education_workspace_snapshot": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0024",
"operationNumber": "HLP-NIPC-OP-0085",
"targetNumber": "HLP-NIPC-TGT-0024"
},
"create_education_document": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0024",
"operationNumber": "HLP-NIPC-OP-0086",
"targetNumber": "HLP-NIPC-TGT-0024"
},
"read_education_document": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0024",
"operationNumber": "HLP-NIPC-OP-0087",
"targetNumber": "HLP-NIPC-TGT-0024"
},
"save_education_document": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0024",
"operationNumber": "HLP-NIPC-OP-0088",
"targetNumber": "HLP-NIPC-TGT-0024"
},
"archive_education_document": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0024",
"operationNumber": "HLP-NIPC-OP-0089",
"targetNumber": "HLP-NIPC-TGT-0024"
},
"create_education_table": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0024",
"operationNumber": "HLP-NIPC-OP-0090",
"targetNumber": "HLP-NIPC-TGT-0024"
},
"read_education_table": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0024",
"operationNumber": "HLP-NIPC-OP-0091",
"targetNumber": "HLP-NIPC-TGT-0024"
},
"save_education_table": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0024",
"operationNumber": "HLP-NIPC-OP-0092",
"targetNumber": "HLP-NIPC-TGT-0024"
},
"archive_education_table": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0024",
"operationNumber": "HLP-NIPC-OP-0093",
"targetNumber": "HLP-NIPC-TGT-0024"
},
"assign_imported_table_to_education": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0024",
"operationNumber": "HLP-NIPC-OP-0094",
"targetNumber": "HLP-NIPC-TGT-0024"
},
"import_education_tables_from_dialog": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0024",
"operationNumber": "HLP-NIPC-OP-0095",
"targetNumber": "HLP-NIPC-TGT-0024"
},
"export_education_table_to_dialog": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0024",
"operationNumber": "HLP-NIPC-OP-0096",
"targetNumber": "HLP-NIPC-TGT-0024"
},
"get_education_recognition_capability": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0024",
"operationNumber": "HLP-NIPC-OP-0097",
"targetNumber": "HLP-NIPC-TGT-0024"
},
"create_education_automation_rule": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0024",
"operationNumber": "HLP-NIPC-OP-0098",
"targetNumber": "HLP-NIPC-TGT-0024"
},
"save_education_automation_rule": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0024",
"operationNumber": "HLP-NIPC-OP-0099",
"targetNumber": "HLP-NIPC-TGT-0024"
},
"archive_education_automation_rule": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0024",
"operationNumber": "HLP-NIPC-OP-0100",
"targetNumber": "HLP-NIPC-TGT-0024"
},
"preview_education_automation_rule": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0024",
"operationNumber": "HLP-NIPC-OP-0101",
"targetNumber": "HLP-NIPC-TGT-0024"
},
"execute_education_automation_rule": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0024",
"operationNumber": "HLP-NIPC-OP-0102",
"targetNumber": "HLP-NIPC-TGT-0024"
} }
} as const } as const